/* Scream Tracker 2 STM loader
**
** Note: Data sanitation is done in the last stage
** of module loading, so you don't need to do that here.
*/

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include "../ft2_header.h"
#include "../ft2_module_loader.h"
#include "../ft2_sample_ed.h"
#include "../ft2_tables.h"
#include "../ft2_sysreqs.h"

#ifdef _MSC_VER // please don't mess with these structs!
#pragma pack(push)
#pragma pack(1)
#endif
typedef struct stmSmpHdr_t
{
	char name[12];
	uint8_t nul, junk1;
	uint16_t junk2, length, loopStart, loopEnd;
	uint8_t volume, junk3;
	uint16_t midCFreq;
	int32_t junk4;
	uint16_t paraLen;
}
#ifdef __GNUC__
__attribute__ ((packed))
#endif
stmSmpHdr_t;

typedef struct stmHdr_t
{
	char name[20], sig[8];
	uint8_t x1A, type;
	uint8_t verMajor, verMinor;
	uint8_t tempo, numPatterns, masterVol, reserved[13];
	stmSmpHdr_t smp[31];
}
#ifdef __GNUC__
__attribute__ ((packed))
#endif
stmHdr_t;
#ifdef _MSC_VER
#pragma pack(pop)
#endif

static const uint8_t stmEfx[16] = { 0, 0, 11, 0, 10, 2, 1, 3, 4, 7, 0, 5, 6, 0, 0, 0 };

static uint16_t stmTempoToBPM(uint8_t tempo);

bool loadSTM(FILE *f, uint32_t filesize)
{
	stmHdr_t header;

	tmpLinearPeriodsFlag = false; // use Amiga periods

	if (filesize < sizeof (header))
	{
		loaderMsgBox("Error: This file is either not a module, or is not supported.");
		return false;
	}

	if (fread(&header, 1, sizeof (header), f) != sizeof (header))
	{
		loaderMsgBox("Error: This file is either not a module, or is not supported.");
		return false;
	}

	if (header.type != 2)
	{
		loaderMsgBox("Error loading STM: Incompatible module!");
		return false;
	}

	songTmp.numChannels = 4;

	uint8_t maxOrders = (header.verMinor == 0) ? 64 : 128;
	fread(songTmp.orders, 1, maxOrders, f);

	// count number of orders (song length)
	songTmp.songLength = 0;
	for (int32_t i = 0; i < maxOrders; i++)
	{
		if (songTmp.orders[i] >= 99)
			break;

		songTmp.songLength++;
	}

	if (songTmp.songLength == 0)
		songTmp.songLength = 1;

	// clear unused orders
	memset(&songTmp.orders[songTmp.songLength], 0, 256 - songTmp.songLength);

	memcpy(songTmp.name, header.name, 20);

	uint8_t tempo = header.tempo;
	if (header.verMinor < 21)
		tempo = ((tempo / 10) << 4) + (tempo % 10);

	if (tempo == 0)
		tempo = 96;

	songTmp.BPM = stmTempoToBPM(tempo);
	songTmp.speed = header.tempo >> 4;

	// patterns

	for (int32_t i = 0; i < header.numPatterns; i++)
	{
		if (!allocateTmpPatt(i, 64))
		{
			loaderMsgBox("Not enough memory!");
			return false;
		}

		fread(tmpBuffer, 1, 64 * 4 * 4, f);
		uint8_t *pattPtr = tmpBuffer;

		for (int32_t row = 0; row < 64; row++)
		{
			for (int32_t ch = 0; ch < 4; ch++, pattPtr += 4)
			{
				note_t *p = &patternTmp[i][(row * MAX_CHANNELS) + ch];
				
				if (pattPtr[0] == 254)
				{
					p->note = NOTE_OFF;
				}
				else if (pattPtr[0] < 96)
				{
					p->note = (12 * (pattPtr[0] >> 4)) + (25 + (pattPtr[0] & 0x0F));
					if (p->note > 96)
						p->note = 0;
				}
				else
				{
					p->note = 0;
				}

				p->instr = pattPtr[1] >> 3;

				uint8_t vol = (pattPtr[1] & 7) + ((pattPtr[2] & 0xF0) >> 1);
				if (vol <= 64)
					p->vol = 0x10 + vol;

				p->efxData = pattPtr[3];

				uint8_t efx = pattPtr[2] & 0x0F;
				if (efx == 1) // set speed
				{
					p->efx = 15;

					if (header.verMinor < 21)
						p->efxData = ((p->efxData / 10) << 4) + (p->efxData % 10);

					p->efxData >>= 4;
					if (p->efxData == 0)
						p->efx = 0;
				}
				else if (efx == 3) // pattern break
				{
					p->efx = 13;
					p->efxData = 0;
				}
				else if (efx == 2 || (efx >= 4 && efx <= 12))
				{
					p->efx = stmEfx[efx]; // convert to XM effect

					if (p->efx == 0xA) // volume slide
					{
						if (p->efxData & 0x0F)
							p->efxData &= 0x0F;
						else
							p->efxData &= 0xF0;
					}
				}
				else
				{
					p->efxData = 0;
				}
			}
		}
	}

	// samples

	stmSmpHdr_t *srcSmp = header.smp;
	for (int32_t i = 0; i < 31; i++, srcSmp++)
	{
		memcpy(&songTmp.instrName[1+i], srcSmp->name, 12);

		if (srcSmp->length > 0)
		{
			if (!allocateTmpInstr(1 + i))
			{
				loaderMsgBox("Not enough memory!");
				return false;
			}
			setNoEnvelope(instrTmp[i]);
			sample_t *s = &instrTmp[1+i]->smp[0];

			memcpy(s->name, srcSmp->name, 12);

			// non-FT2: fixes "acidlamb.stm" and other broken STMs
			const uint32_t offsetInFile = (uint32_t)ftell(f);
			if (offsetInFile+srcSmp->length > filesize)
				srcSmp->length = (uint16_t)(filesize - offsetInFile);

			if (!allocateSmpData(s, srcSmp->length, false))
			{
				loaderMsgBox("Not enough memory!");
				return false;
			}

			s->length = srcSmp->length;
			s->volume = srcSmp->volume;
			s->loopStart = srcSmp->loopStart;
			s->loopLength = srcSmp->loopEnd - srcSmp->loopStart;
			setSampleC4Hz(s, srcSmp->midCFreq);

			if (s->loopStart < s->length && srcSmp->loopEnd > s->loopStart && srcSmp->loopEnd != 0xFFFF)
			{
				if (s->loopStart+s->loopLength > s->length)
					s->loopLength = s->length - s->loopStart;

				s->flags |= LOOP_FORWARD;
			}
			else
			{
				s->loopStart = s->loopLength = 0;
			}

			if (offsetInFile < filesize)
			{
				if (fread(s->dataPtr, s->length, 1, f) != 1)
				{
					loaderMsgBox("General I/O error during loading! Possibly corrupt module?");
					return false;
				}
			}
		}
	}

	return true;
}

static uint16_t stmTempoToBPM(uint8_t tempo) // ported from original ST2.3 replayer code
{
	const uint8_t slowdowns[16] = { 140, 50, 25, 15, 10, 7, 6, 4, 3, 3, 2, 2, 2, 2, 1, 1 };
	uint16_t hz = 50;

	hz -= ((slowdowns[tempo >> 4] * (tempo & 15)) >> 4); // can and will underflow

	const uint16_t bpm = (hz << 1) + (hz >> 1); // BPM = hz * 2.5
	return bpm; // result can be slightly off, but close enough...
}
