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

/*
 * Channels: [1...16?]
 * Patterns: [1...255]
 * Rows    : 64
 * Orders  : [1...128]
 * 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   : Uses Amiga periods, 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...128], values above 128 = surround.
 * Sampling: 8363 Hz (frequency is turned into a relative tone).
 *
 * Notes:
 * Effects have no memory except for portamento and vibrato.
 * This means that effect value 0 as usually no effect.
 */

typedef struct
{
	uint8_t     TAG[3];
	uint8_t     Version;
	uint8_t     Name[32];
	uint8_t     _1A;
	uint8_t     Channels;
	uint8_t     Orders;
	uint8_t     Samples;
	uint8_t     Patterns;
	uint8_t     Speed;
	uint8_t     Tempo;
	uint8_t     Volume;
	uint16_t    Reserved[2];
	uint8_t     Seq[128];
} LHdr;

typedef struct
{
	uint8_t     TAG[3];
	uint8_t     Version;
	uint8_t     Name[32];
	uint16_t    Offset; // <<4
	uint16_t    Size;
	uint16_t    LoopStart;
	uint16_t    LoopEnd; // 0 for no loop
	uint16_t    Frequency;
	uint8_t     Volume; // [0-64]
	uint8_t     Reserved[17];
} LSmp;

#define NoteDelta   (Note_Central-24)

static const uint8_t TAG_DSM[] = "DSM";
static const uint8_t Typestr_DSM[] = "DigiSound";

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 = Panning_Surround;
			else value <<= 1;
			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;
		default:
		{
			convert_effect_MOD(pSong, channel, effect, value, note);
		}
	}
}

static const _kernel_oserror* Pattern_DSM(SongHdr* pSong, uint32_t i, Pattern* pPattern, const uint8_t* pStart, const uint8_t* pEnd)
{
	const _kernel_oserror*    err = NULL;
	uint32_t            val;
	int                 j;
	const uint8_t*      pPos = pStart;
	const uint8_t*      pPos2;
	int                 channel, lastchannel;
	uint32_t            Note, Inst;

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

	// For exclusive or between value and position
	pStart -= 2;

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

		lastchannel = -1;
		while (pPos < pEnd)
		{
			// read channel nr and flags
			val = *pPos++;
			val ^= (pPos - pStart);
			val &= 0xff;
			// next row mark?
			if (!val) break;
			// will we remain within pattern limits?
			// valid channel ?
			channel = val & 0x0f;
			if ((channel <= lastchannel)
			||  (channel >= pSong->Channels))
			{
				if (channel <= lastchannel)
					SysLog_FileCorrupted(SysLog_High, pSong, - (pEnd - pPos + 1), "Invalid channel %d order in pattern", channel);
				else
					SysLog_FileCorrupted(SysLog_Low, pSong, - (pEnd - pPos + 1), "Invalid channel %d in pattern", channel);
				// Bad channel, ignore info
				if (val & 0x10) pPos+=2;
				if (val & 0x20) pPos++;
				if (val & 0x40) pPos++;
				if (val & 0x80) pPos++;
			}
			else
			{
				lastchannel = channel;
				// will we remain within pattern limits while reading channel info?
				pPos2 = pPos;
				if (val & 0x10) pPos2+=2;
				if (val & 0x20) pPos2++;
				if (val & 0x40) pPos2++;
				if (val & 0x80) pPos2++;
				if (pPos2 > pEnd) break;

				// Read note
				if (val & 0x80)
				{
					Note = *pPos++;
					if (Note && (Note <= 12*5))
					{
						Note += NoteDelta - 1;
					}
					else
					{
						SysLog_BadNote(pSong, FileLoad_GetPos(pSong) - (pEnd - pPos), channel, Note);
						Note = 0;
					}
				}
				else
					Note = 0;
				// Read instrument
				if (val & 0x40)
					Inst = *pPos++;
				else
					Inst = 0;

				// Read note & instrument
				err = Loaders_StartChannel(pSong, channel, Note, Inst);
				if (err) return err;

				// Read Volume column
				if (val & 0x20)
				{
					if (*pPos <= 64)
						Pattern_AddEffect_NoteVolume(pSong, cmd_set, (*pPos) << 2);
					else
						SysLog_BadEffectValue(pSong, 0, 256, *pPos);
					pPos++;
				}

				// Read efffect and value
				if (val & 0x10)
				{
					convert_effect(pSong, channel, pPos[0], pPos[1], Note);
					pPos += 2;
				}

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

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

	return Loaders_EndPattern(pSong, pPattern);
}

const _kernel_oserror* Loader_DSM(SongHdr* pSong)
{
	const _kernel_oserror* err = NULL;
	SubSong*    pSubSong = Song_GetSubSong(pSong, 0);
	LHdr*       pLHdr = NULL;
	LSmp*       pLSmp = NULL;
	uint8_t*    pLTrack = NULL;
	uint16_t*   pOffsetTable = NULL;
	uint16_t*   pOffset;
	Pattern*    pPattern;
	Sample*     pSmp;
	uint32_t    Size;
	uint32_t    val;
	int         i;

	// Allocate memory
	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;

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

	// check if really DSM Module?
	if ((pLHdr->TAG[0] != TAG_DSM[0])
	||  (pLHdr->TAG[1] != TAG_DSM[1])
	||  (pLHdr->TAG[2] != TAG_DSM[2]))
	{
		err = Loaders_NotThisType;
		goto loader_err;
	}

	// some sanity checks
	if (!pLHdr->Channels || (pLHdr->Channels > song_max_channels))
	{
		err = Loaders_InvalidSong
				( pSong, ((char*) &pLHdr->Channels) - ((char*) pLHdr)
				, &pSong->Channels, pLHdr->Channels
				);
		goto loader_err;
	}
	if (!pLHdr->Orders || (pLHdr->Orders > 128))
	{
		err = Loaders_InvalidSong
				( pSong, ((char*) &pLHdr->Orders) - ((char*) pLHdr)
				, &pSubSong->SeqLen, pLHdr->Orders
				);
		goto loader_err;
	}
	if (!pLHdr->Patterns || (pLHdr->Patterns > song_max_patterns))
	{
		err = Loaders_InvalidSong
				( pSong, ((char*) &pLHdr->Patterns) - ((char*) pLHdr)
				, &pSong->Patterns, pLHdr->Patterns);
		goto loader_err;
	}
	if (!pLHdr->Samples)
	{
		err = Loaders_InvalidSong
				( pSong, ((char*) &pLHdr->Samples) - ((char*) pLHdr)
				, &pSong->Samples, pLHdr->Samples);
		goto loader_err;
	}

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

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

	// 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_DSM;

	pSubSong->SeqLen = pLHdr->Orders;
	pSong->Patterns = pLHdr->Patterns;
	pSong->Samples  = pLHdr->Samples;
	pSong->Channels = pLHdr->Channels;

	if (pLHdr->Volume <= 64)
		pSubSong->Defaults.Volume = pLHdr->Volume << 2;
	else
		pSubSong->Defaults.Volume = 256;
	if (pLHdr->Speed) pSubSong->Defaults.Speed = pLHdr->Speed;
	if (pLHdr->Tempo) pSubSong->Defaults.Tempo = pLHdr->Tempo;

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

	// fill the pattern sequence
	for (i = 0; i < pSubSong->SeqLen; i++)
	{
		pSubSong->pSeqs[i] = pLHdr->Seq[i];
	}

	// read offset tables
	val = (pSong->Samples + pSong->Patterns) << 1;
	err = Loaders_Alloc(pSong, (void**) &pOffsetTable, val);
	if (err) goto loader_err;
	err = FileLoad_Read(pSong, pOffsetTable, val);
	if (err) goto loader_err;

	pOffset = pOffsetTable;

	// read samples info
	for (i = 0, pSmp = pSong->pSamples; i < pSong->Samples; i++, pSmp++)
	{
		// move to sample header start
		err = FileLoad_SetPos(pSong, (*pOffset++) << 4);
		if (err) goto loader_err;

		err = FileLoad_Read(pSong, pLSmp, sizeof(*pLSmp));
		if (err) goto loader_err;

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

		// read sizes
		pSmp->Size         = pLSmp->Size;
		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;

		// read sample type
		pSmp->Type = 0;
		if (pLSmp->LoopEnd) pSmp->Type |= Smp_Type_Loop;

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

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

		if (!*pOffset)
		{
			// empty pattern of 64 rows
			pPattern->Rows = 64;
			err = Loaders_EmptyPattern(pSong, pPattern);
			if (err) goto loader_err;
		}
		else
		{
			// Move to pattern description
			err = FileLoad_SetPos(pSong, (*pOffset) << 4);
			if (err) goto loader_err;
			// Read pattern length
			err = FileLoad_ReadInt(pSong, &Size, 2);
			if (err) goto loader_err;

			if (Size > 2)
			{
				Size -= 2;
				err = Loaders_Alloc(pSong, (void**) &pLTrack, Size);
				if (err) goto loader_err;
				err = FileLoad_Read(pSong, pLTrack, Size);
				if (err) goto loader_err;
			}
			else Size = 0;

			err = Pattern_DSM(pSong, i, pPattern, pLTrack, pLTrack + Size);
			if (err) goto loader_err;

			if (pLTrack)
			{
				Loaders_Free(pSong, pLTrack);
				pLTrack = NULL;
			}
		}
	}

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

	return err;
}
