#include "Song.h"
#include "Effects.h"
#include "Instrument.h"
#include "Loaders.h"
#include "FSysLog.h"

/*
 * Channels: 4
 * Patterns: [1...99]
 * Rows    : 64
 * Orders  : [1...128]
 * Samples : 31
 * Instr.  : 0
 * Tempo   : [32...255], T frames per 2.5 seconds.
 * Speed   : [1...31], S frames per row.
 * Notes   : [C-0...C-2...B-4]
 * Pitch   : Amiga periods mode, base is 428 units, period is divided by 2 each octave up.
 *           Slides as x/1 units per frame.
 * Volume  : [0...64] , linear.
 * Panning : None.
 * Sampling: 8363 Hz (frequency is turned into a relative tone).
 *
 */

typedef struct
{
	uint8_t   Name[12];
	uint8_t   Dummy[2];
	uint16_t  Offset;
	uint16_t  Length;
	uint16_t  LoopStart;
	uint16_t  LoopEnd;
	uint8_t   Volume;
	uint8_t   Dummy2;
	uint16_t  Frequency;
	uint8_t   Dummy3[6];
} LSmp;

typedef struct
{
	uint8_t   Name[20];
	uint8_t   Tracker[8];
	uint8_t   _1A;
	uint8_t   Type;
	uint8_t   Version[2];
	uint8_t   Period;
	uint8_t   Patterns;
	uint8_t   GlobalVolume;
	uint8_t   Dummy[13];
	LSmp      Samples[31];
	uint8_t   Orders[128];
} LHdr;

#define NoteDelta   (Note_Central-12*2)
#define PatternSize (4*4*64)

static const uint8_t Tag_SCREAM[] = "!Scream!";
static const uint8_t Tag_BMOD2STM[] = "BMOD2STM";
static const uint8_t Typestr_STM[] = "ScreamTracker";

typedef struct
{
	uint32_t	Channel;
	uint32_t	Note;
	uint32_t	LastEffect;
	uint32_t	LastValue;
} ChannelInfo;

static void convert_effect(SongHdr* pSong, ChannelInfo* pChInfo, uint32_t effect, uint32_t value)
{
	uint32_t oeffect = effect;
#ifdef USELOG
	uint32_t ovalue = value;
#endif

	switch(effect)
	{
		case 0: // no cmd
		break;
		case 1: // A, Global: Set speed, No prev InfoByte
		{
			if (value >> 4) Pattern_AddGlbEffect_Frames(pSong, gcmd_frames_set, value >> 4);
		}
		break;
		case 2: // B, Global: Jump, No prev InfoByte
		{
			Pattern_AddGlbEffect_Position(pSong, gcmd_pos_jump, value);
		}
		break;
		case 3: // C, Global: Break, No prev InfoByte
		{
			uint32_t ten = value >> 4;
			value &= 0xf;
			if ((value > 9) || (ten > 9))
				SysLog_BadEffectValue(pSong, 0, oeffect, ovalue);
			else
			{
				value += ten * 10;
				Pattern_AddGlbEffect_Position(pSong, gcmd_pos_break, value);
			}
		}
		break;
		case 4: // D, Volume: Slide, Prev InfoByte
		{
			if ((value == 0) && (pChInfo->LastEffect != oeffect))
			{
				value = pChInfo->LastValue;
				// SysLog_WatchEffect(pSong, 0, oeffect, ovalue);
			}

			if (value == 0)
			{
				// D00 repeat last slide, in case no previous infobyte in this pattern
				Pattern_AddEffect_NoteVolume(pSong, cmd_last_slide, 0);
			}
			else if (!(value & 0x0f))
			{
				// Dx0 slide up
				effect = cmd_upmem1 | flag_cmd_slide_frameN0;
				value &= 0xf0;
				Pattern_AddEffect_NoteVolume(pSong, effect, value >> 2);
			}
			else if (!(value & 0xf0))
			{
				// D0y slide down
				effect = cmd_downmem1 | flag_cmd_slide_frameN0;
				value &= 0x0f;
				Pattern_AddEffect_NoteVolume(pSong, effect, value << 2);
			}
			else if ((value & 0x0f) == 0x0f)
			{
				// DxF up
				effect = cmd_upmem1 | flag_cmd_slide_frame0;
				value &= 0xf0;
				Pattern_AddEffect_NoteVolume(pSong, effect, value >> 2);
			}
			else if ((value & 0xf0) == 0xf0)
			{
				// DFy down
				effect = cmd_downmem1 | flag_cmd_slide_frame0;
				value &= 0x0f;
				Pattern_AddEffect_NoteVolume(pSong, effect, value << 2);
			}
			else
			{
				// Bad values behave as D0y slide down
				effect = cmd_downmem1 | flag_cmd_slide_frameN0;
				value &= 0x0f;
				Pattern_AddEffect_NoteVolume(pSong, effect, value << 2);
			}
		}
		break;
		case 5: // E, Pitch: Slide down, Prev InfoByte
		{
			effect = cmd_downmem1;

			if ((value == 0) && (pChInfo->LastEffect != oeffect))
			{
				value = pChInfo->LastValue;
				// SysLog_WatchEffect(pSong, 0, oeffect, ovalue);
			}

			if (value >= 0xf0)
			{
				// EFx fine down
				value = (value & 0x0f) << 2;
				// EF0 must force memory to 0, not read it
				if (!value) effect = cmd_pitch_memslide;
				effect |= flag_cmd_slide_frame0;
			}
			else if (value >= 0xe0)
			{
				// EEx extra fine down
				value &= 0x0f;
				// EE0 must force memory to 0, not read it
				if (!value) effect = cmd_pitch_memslide;
				effect |=flag_cmd_slide_frame0;
			}
			else if (value)
			{
				// Exx slide down
				value <<= 2;
				effect |= flag_cmd_slide_frameN0;
			}
				// E00 slide at last slide type and value

			Pattern_AddEffect_Pitch(pSong, effect, value);
		}
		break;
		case 6: // F, Pitch: Slide up, Prev InfoByte
		{
			effect = cmd_upmem1;

			if ((value == 0) && (pChInfo->LastEffect != oeffect))
			{
				value = pChInfo->LastValue;
				// SysLog_WatchEffect(pSong, 0, oeffect, ovalue);
			}

			if (value >= 0xf0)
			{
				// FFx fine up
				value = (value & 0x0f) << 2;
				// FF0 must force memory to 0, not read it
				if (!value) effect = cmd_pitch_memslide;
				effect |= flag_cmd_slide_frame0;
			}
			else if (value >= 0xe0)
			{
				// FEx extra fine up
				value &= 0x0f;
				// FE0 must force memory to 0, not read it
				if (!value) effect = cmd_pitch_memslide;
				effect |= flag_cmd_slide_frame0;
			}
			else if (value)
			{
				// Fxx slide up
				value <<= 2;
				effect |= flag_cmd_slide_frameN0;
			}
				// F00 slide at last slide type and value

			Pattern_AddEffect_Pitch(pSong, effect, value);
		}
		break;
		case 7: // G, Pitch: Portamento, Memory
		{
			Pattern_AddEffect_Pitch(pSong, cmd_portamento + flag_cmd_slide_frameN0, value << 2);
		}
		break;
		case 8: // H, Pitch: Vibrato, Memory
		{
			uint32_t speed = value >> 4;
			value &= 0xf;
			value <<= 3; // << 2 for range conversion and << 1 for depth
			if (speed) Pattern_AddEffect_Pitch(pSong, cmd_vibrato_speed, speed << 2);
			Pattern_AddEffect_Pitch(pSong, cmd_vibrato_depth | flag_cmd_vibrato_frameN0, value);
		}
		break;
		case 9: // I, Volume: Tremor, Prev InfoByte
		{
			uint32_t val;

			if ((value == 0) && (pChInfo->LastEffect != oeffect))
			{
				value = pChInfo->LastValue;
				// SysLog_WatchEffect(pSong, 0, oeffect, ovalue);
			}

			val = value >> 4;
			value &= 0xf;
			Pattern_AddEffect_NoteVolume(pSong, cmd_volume_tremor_S3M, Effect_Tremor(value, val));
		}
		break;
		case 0xa: // J, Pitch: Arpeggio, Prev InfoByte
		{
			uint32_t val;

			if ((value == 0) && (pChInfo->LastEffect != oeffect))
			{
				value = pChInfo->LastValue;
				// SysLog_WatchEffect(pSong, 0, oeffect, ovalue);
			}

			val = value >> 4;
			value &= 0xf;
			Pattern_AddEffect_Arpeggio(pSong, value, val, cmd_arpeggio_type_NHL);
		}
		break;
		case 0xb: // K, Pitch: Vibrato 0 - Volume: Slide, Prev InfoByte
		{
			convert_effect(pSong, pChInfo, 0x8, 0);
			convert_effect(pSong, pChInfo, 0x4, value);
		}
		break;
		case 0xc: // L, Pitch: Portamento 0 - Volume: Slide, Prev InfoByte
		{
			convert_effect(pSong, pChInfo, 0x7, 0);
			convert_effect(pSong, pChInfo, 0x4, value);
		}
		break;
		default:
		{
			SysLog_BadEffect(pSong, 0, oeffect, ovalue);
		}
	}
}

const _kernel_oserror* Loader_STM(SongHdr* pSong)
{
	const _kernel_oserror* err = NULL;
	SubSong*        pSubSong = Song_GetSubSong(pSong, 0);
	LHdr*           pLHdr = NULL;
	LSmp*           pLSmp = NULL; // Points in LHdr, no memory allocation
	uint8_t*        pLTrack = NULL;
	Pattern*        pPattern;
	Sample*         pSmp;
	ChannelInfo     ChInfo[4]; // There is a single shared memory per channel
	ChannelInfo*    pChInfo;
	int             i, j, k;

	// Reject file to short to contain header
	if (FileLoad_GetSize(pSong) < sizeof(LHdr))
		return Loaders_NotThisType;

	// Allocate memory
	err = Loaders_Alloc(pSong, (void**) &pLHdr, sizeof(LHdr));
	if (err) goto loader_err;
	err = Loaders_Alloc(pSong, (void**) &pLTrack, PatternSize);
	if (err) goto loader_err;

	// load header
	err = FileLoad_SetPos(pSong, 0);
	if (err) goto loader_err;
	err = FileLoad_Read(pSong, pLHdr, sizeof(LHdr));
	if (err)
	{
		// Header can be larger than some files of other type
		err = Loaders_NotThisType;
		goto loader_err;
	}

	// check if really STM Module?
	if ((pLHdr->Type != 2)
	||  (pLHdr->_1A != 0x1a)
	||  (pLHdr->Patterns >= 99)
	||  (   Loaders_strncmp(pLHdr->Tracker, Tag_SCREAM, 8)
	     && Loaders_strncmp(pLHdr->Tracker, Tag_BMOD2STM, 8)))
	{
		err = Loaders_NotThisType;
		goto loader_err;
	}

	// set version
	pSong->Version = (pLHdr->Version[0] & 0x0f) * 100
	               + ((pLHdr->Version[1] & 0xf0) >> 4) * 10
	               + (pLHdr->Version[1] & 0x0f);

	pSong->Flags = 0;
	pSong->MinPitch = NoteDelta;
	pSong->MaxPitch = NoteDelta + 12 * 5 - 1;
	pSong->Samples  = 31;
	pSong->Channels = 4;
	pSubSong->Defaults.TremorOn = true;

	// fill the info
	err = Loaders_String(pSong, &pSong->pName, &pLHdr->Name[0], sizeof(pLHdr->Name), false);
	if (err) goto loader_err;

	// fill file type ptr
	pSong->pType = Typestr_STM;

	pSong->Patterns = pLHdr->Patterns;
	if (!pSong->Patterns || (pSong->Patterns > 99)) // cf. sequence
	{
		err = Loaders_InvalidSong(pSong, 33, &pSong->Patterns, pSong->Patterns);
		goto loader_err;
	}

	if (pLHdr->GlobalVolume <= 64)
		pSubSong->Defaults.Volume = pLHdr->GlobalVolume << 2;
	else
		pSubSong->Defaults.Volume = 256;
	pSubSong->Defaults.Speed = pLHdr->Period >> 4;
	if (pSubSong->Defaults.Speed < 1) pSubSong->Defaults.Speed = 1;

	// read samples info
	for (i = 0, pSmp = pSong->pSamples; i < pSong->Samples; i++, pSmp++)
	{
		pLSmp = &pLHdr->Samples[i];

		// read name
		err = Loaders_String(pSong, &pSmp->pName, &pLSmp->Name[0], sizeof(pLSmp->Name), false);
		if (err) goto loader_err;

		// read sample type
		pSmp->Type = 0;

		// read sizes
		if (pLSmp->Length < 4)
			pSmp->Size = 0;
		else
			pSmp->Size = pLSmp->Length;
		if ((pLSmp->LoopStart < pLSmp->LoopEnd) && (pLSmp->LoopEnd != 0xffff))
		{
			pSmp->Type |= Smp_Type_Loop;
			pSmp->LoopStart = pLSmp->LoopStart;
			pSmp->LoopEnd = pLSmp->LoopEnd;
		}
		pSmp->Frequency = 8363;
		pSmp->RelTone = Convert_RelTone(pSmp->Frequency, pLSmp->Frequency);

		// read volume
		if (pLSmp->Volume > 64) pSmp->DefaultVolume = 256;
		else pSmp->DefaultVolume = pLSmp->Volume << 2;
	}

	pSubSong->SeqLen = 128;
	err = Loaders_AllocSeq(pSong, pSubSong);
	if (err) goto loader_err;

	// fill the pattern sequence, value 99 marks end of sequence
	for (i = 0; i < pSubSong->SeqLen; i++)
	{
		if (pLHdr->Orders[i] < 99)
			pSubSong->pSeqs[i] = pLHdr->Orders[i];
		else
			pSubSong->SeqLen = i;
	}

	// fill the patterns
	// pOffset still up to date
	for (i = 0, pPattern = pSong->pPatterns; i < pSong->Patterns; i++, pPattern++)
	{
		uint8_t* p = pLTrack;

		for (j = 0, pChInfo = ChInfo; j < pSong->Channels; j++, pChInfo++)
		{
			pChInfo->Channel = j;
			pChInfo->Note = 0;
			pChInfo->LastEffect = 0;
			pChInfo->LastValue = 0;
		}

		pPattern->Rows = 64;

		// Read pattern
		err = FileLoad_Read(pSong, pLTrack, PatternSize);
		if (err) goto loader_err;

		err = Loaders_StartPattern(pSong, i);
		if (err) return err;

		for (j = 0; j < pPattern->Rows; j++)
		{
			err = Loaders_StartRow(pSong, j);
			if (err) return err;

			for (k = 0, pChInfo = ChInfo; k < pSong->Channels; k++, pChInfo++)
			{
				uint32_t Vol, Note, Inst;

				Note = p[0];
				if ((Note == 254) || (Note == 252)) Note = Note_Cut;
				else if (((Note & 0xf0) <= 0x40)
				     &&  ((Note & 0x0f) <= 0x0b))
				{
					Note = ((Note & 0xf0) >> 4)*12 + (Note & 0x0f);
					Note += NoteDelta;
				}
				else if (Note)
				{
					if (Note != 0xff)
					{
						SysLog_BadNote
							( pSong, FileLoad_GetPos(pSong) - PatternSize + (p - pLTrack)
							, k, Note
							);
					}
					Note = 0;
				}
				Inst = p[1] >> 3;

				Loaders_StartChannel(pSong, k, Note, Inst);

				Vol = (p[1] & 0x07) | ((p[2] & 0xf0) >> 1);
				if (Vol <= 64)
					Pattern_AddEffect_NoteVolume(pSong, cmd_set, Vol << 2);
				else if (Vol != 65)
					SysLog_BadEffectValue(pSong, 0, 256, Vol);

				pChInfo->Note = Note;
				if (p[3] != 0)
				{
					pChInfo->LastEffect = p[2] & 0x0f;
					pChInfo->LastValue = p[3];
				}
				convert_effect(pSong, pChInfo, p[2] & 0x0f, p[3]);

				err = Loaders_EndChannel(pSong);
				if (err) return err;

				p += 4;
			}

			err = Loaders_EndRow(pSong);
			if (err) return err;
		}

		err = Loaders_EndPattern(pSong, pPattern);
		if (err) return err;
	}

	// read samples info
	for (i = 0, pSmp = pSong->pSamples; i < pSong->Samples; i++, pSmp++)
	{
		pLSmp = &pLHdr->Samples[i];

		if (pSmp->Size > 0)
		{
			// read data
			err = FileLoad_SetPos(pSong, pLSmp->Offset << 4);
			if (err)
			{
				SysLog_FileCorrupted(SysLog_Medium, pSong, 0, "Sample %d: invalid data offset", 1 + (pSmp - pSong->pSamples));
				pSmp->Size = 0;
			}
			else
			{
				err = FileLoad_ReadSample(pSong, pSmp);
				if (err) goto loader_err;
			}
		}
	}

loader_err:
	Loaders_Free(pSong, pLHdr);
	Loaders_Free(pSong, pLTrack);

	return err;
}
