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

/*
 * Channels: [1...32]
 * Patterns: [1...256] single channel patterns
 * Rows    : [1...255]
 * Orders  : [1...256]
 * Samples : [1...255]
 * 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 : [0...255].
 * Sampling: 8363 Hz.
 *
 * Notes:
 * Effects have no memory except for portamento and vibrato.
 * This means that effect value 0 as usually no effect.
 *
 * Sequence position is n channels wide and patterns are 1 channel wide.
 */

typedef struct
{
	uint8_t     TAG[3];             // "MTM"
	uint8_t     Version;            // (major << 4) + minor
	uint8_t     Name[20];
	uint16_t    Tracks;
	uint8_t     HighestPatterns;    // number of patterns saved - 1
	uint8_t     HighestOrders;      // number of last order - 1
	uint16_t    CommentsLength;
	uint8_t     Samples;
	uint8_t     Flags;
	uint8_t     TrackLength;
	uint8_t     Channels;
	uint8_t     Panning[32];
}
LHdr;

typedef struct
{
	uint16_t    Dummy; // align words for loading
	uint8_t     Name[22];
	uint32_t    Length;
	uint32_t    LoopStart;
	uint32_t    LoopEnd;
	uint8_t     FineTune;
	uint8_t     Volume;
	uint8_t     Flags;
} LSmp;

#define Size_LHdr   66
#define Size_LSmp   37

#define NoteDelta (Note_Central+1-12*2) // +1 compared to what is expected

static const uint8_t Tag_MTM[] = "MTM";
static const uint8_t Typestr_MTM[] = "MultiTracker";

static void convert_effect(SongHdr* pSong, uint32_t channel, uint32_t effect, uint32_t value, uint32_t note)
{
	switch(effect)
	{
		case 8: // Panning: Set
		{
			if (value >= 128) value++;
			Pattern_AddEffect_Panning(pSong, cmd_set, value);
		}
		break;
		case 0xe: // Special effects
		{
			switch(value >> 4)
			{
				case 8: //e8 Panning: Set
				{
					value &= 0xf;
					value += value << 4;
					convert_effect(pSong, channel, 8, value, note);
				}
				break;
				case 0:
				case 0xf:
				{
					SysLog_BadEffect(pSong, 0, effect, value);
				}
				break;
				default:
				{
					convert_effect_MOD(pSong, channel, effect, value, note);
				}
			}
		}
		break;
		case 0xf: // Global: Set speed/tempo
		{
			if (value)
			{
				if (value <= pSong->MaxSpeed)
				{
					// effect_f_test_speed
					Pattern_AddGlbEffect_Frames(pSong, gcmd_frames_set, value);
				}
				else
				{
					// effect_f_tempo
					Pattern_AddGlbEffect_Tempo(pSong, cmd_set, value);
				}
			}
		}
		break;
		default:
		{
			convert_effect_MOD(pSong, channel, effect, value, note);
		}
	}
}

const _kernel_oserror* Loader_MTM(SongHdr* pSong)
{
	const _kernel_oserror* err = NULL;
	SubSong*    pSubSong = Song_GetSubSong(pSong, 0);
	LHdr*       pLHdr = NULL;
	LSmp*       pLSmp = NULL;
	uint8_t*    pLTracks = NULL;
	uint32_t    val, val2;
	int         i, j, k;
	Pattern*    pPattern;
	Sample*     pSmp;
	uint8_t*    pc;
	ChannelDefault* pDef;
	uint8_t*    Tracks[32];

	// First read all information required for memory allocation
	err = Loaders_Alloc(pSong, (void**) &pLHdr, sizeof(LHdr));
	if (err) goto loader_err;
	err = Loaders_Alloc(pSong, (void**) &pLSmp, sizeof(LSmp));
	if (err) goto loader_err;

	pSong->Flags = 0;
	pSong->MinPitch = NoteDelta;
	pSong->MaxPitch = NoteDelta + 12 * 5 - 1;

	// load MTM header
	err = FileLoad_SetPos(pSong, 0);
	if (err) goto loader_err;
	err = FileLoad_Read(pSong, pLHdr, Size_LHdr);
	if (err) goto loader_err;

	// is MTM ?
	if ((pLHdr->TAG[0] != Tag_MTM[0])
	||  (pLHdr->TAG[1] != Tag_MTM[1])
	||  (pLHdr->TAG[2] != Tag_MTM[2]))
	{
		err = Loaders_NotThisType;
		goto loader_err;
	}

	pSong->Version = (10 * (pLHdr->Version >> 4)) + (pLHdr->Version & 0xf);
	pSong->Version *= 10;

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

	// ... file type ptr
	pSong->pType = Typestr_MTM;

	pSong->Patterns = pLHdr->HighestPatterns + 1;
	pSubSong->SeqLen = pLHdr->HighestOrders + 1;
	pSong->Samples = pLHdr->Samples;
	if (!pSong->Samples)
	{
		err = Loaders_InvalidSong(pSong, 31, &pSong->Samples, pSong->Samples);
		goto loader_err;
	}
	pSong->Channels = pLHdr->Channels;
	if (!pSong->Channels || (pSong->Channels > 32))
	{
		err = Loaders_InvalidSong(pSong, 34, &pSong->Channels, pSong->Channels);
		goto loader_err;
	}

	// set initial panning
	for (i = 0, pDef = pSubSong->Defaults.ChDef; i < pSong->Channels; i++, pDef++)
	{
		val = pLHdr->Panning[i] & 0xf;
		val += val << 4;
		if (val >= 128) val++;
		pDef->Panning = val;
	}

	// read samples info
	for (i = 0, pSmp = pSong->pSamples; i < pSong->Samples; i++, pSmp++)
	{
		// read sample header
		err = FileLoad_Read(pSong, &pLSmp->Name[0], Size_LSmp);
		if (err) goto loader_err;

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

		pSmp->Type = Smp_Type_Unsigned;
		// size, loop start and end
		pSmp->Size = pLSmp->Length;
		pSmp->LoopStart = pLSmp->LoopStart;
		pSmp->LoopEnd = pLSmp->LoopEnd;
		if ((pLSmp->LoopEnd - pLSmp->LoopStart) > 2)
			pSmp->Type |= Smp_Type_Loop;
		pSmp->FineTune = (pLSmp->FineTune & 0xf) << 28;
		val = pLSmp->Volume << 2;
		if (val > 256) pSmp->DefaultVolume = 256;
		else pSmp->DefaultVolume = val;
		if (pLSmp->Flags & 1) pSmp->Type |= Smp_Type_16bit;
	}

	err = Loaders_AllocSeq(pSong, pSubSong);
	if (err) goto loader_err;

	// fill the pattern sequence
	for (i = 0; i < pSubSong->SeqLen; i++)
	{
		err = FileLoad_ReadByte(pSong, &val);
		if (err) goto loader_err;
		pSubSong->pSeqs[i] = val;
	}

	// fill the patterns
	// Move to patterns start
	err = FileLoad_SetPos(pSong, FileLoad_GetPos(pSong) + 128 - pSubSong->SeqLen);
	if (err) goto loader_err;

	// Read the whole patterns in one go :-(
	val = pLHdr->Tracks*pLHdr->TrackLength*3;
	err = Loaders_Alloc(pSong, (void**) &pLTracks, val);
	if (err) goto loader_err;
	err = FileLoad_Read(pSong, pLTracks, val);
	if (err) goto loader_err;

	for (i = 0, pPattern = pSong->pPatterns; i < pSong->Patterns; i++, pPattern++)
	{
		uint32_t Note, Inst, cmd, effect;
		pPattern->Rows = pLHdr->TrackLength;
		err = Loaders_StartPattern(pSong, i);
		if (err) goto loader_err;

		// Read channels/tracks mapping
		val2 = pLHdr->TrackLength*3;
		// Convert channel/track mappings to offsets
		for (k = 0; k  < 32; k++)
		{
			err = FileLoad_ReadInt(pSong, &val, 2);
			if (err) goto loader_err;
			if (val)
			{
				if (val > pLHdr->Tracks)
				{
					err = Loaders_Error(pSong, -2, "Invalid channel/track mapping");
					goto loader_err;
				}
				else
					Tracks[k] = pLTracks + (val-1)*val2;
			}
			else Tracks[k] = NULL;
		}

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

			for (k = 0; k  < pSong->Channels; k++)
			{
				if (Tracks[k])
				{
					pc = Tracks[k] + j*3;
					Note = pc[0] >> 2;
					Inst = ((pc[0] & 3) << 4) + (pc[1] >> 4);
					cmd = pc[1] & 0xf;
					effect = pc[2];

					if (Note)
					{
						if (Note <= 12*5)
							Note += NoteDelta - 1;
						else
						{
							SysLog_BadNote(pSong, 0, k, Note);
							Note = 0;
						}
					}

					err = Loaders_StartChannel(pSong, k, Note, Inst);
					if (err) goto loader_err;
					convert_effect(pSong, k, cmd, effect, Note);
					err = Loaders_EndChannel(pSong);
					if (err) goto loader_err;
				}
			}

			err = Loaders_EndRow(pSong);
			if (err) goto loader_err;
		}

		err = Loaders_EndPattern(pSong, pPattern);
		if (err) goto loader_err;
	}

	//; read comments which are in rows of 40 chars
	if (pLHdr->CommentsLength)
	{
		val = pLHdr->CommentsLength + 39;
		pSong->CommentsLen = pLHdr->CommentsLength + (val / 40);
		err = Loaders_AllocString(pSong, (void**) &pSong->pComments, pSong->CommentsLen);
		if (err) goto loader_err;
		pc = pSong->pComments;
		for (i = 0; i < pLHdr->CommentsLength; i += 40)
		{
			err = FileLoad_Read(pSong, pc, 40);
			if (err) goto loader_err;
			for(j = 0; j < 40; j++, pc++)
			{
				if (*pc < ' ') *pc = ' ';
			}
			*pc++ = '\n';
		}
		*pc = 0;
	}

	// fill the samples
	for(i = 0, pSmp = pSong->pSamples; i < pSong->Samples; i++, pSmp++)
	{
		err = FileLoad_ReadSample(pSong, pSmp);
		if (err) goto loader_err;
	}

loader_err:
	// release patterns buffer
	Loaders_Free(pSong, pLTracks);
	Loaders_Free(pSong, pLSmp);
	Loaders_Free(pSong, pLHdr);

	return err;
}
