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

/*
 * Channels: [1...16]
 * Patterns: [1...65535]
 * 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-4...B-7]
 * 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], &a4 = Surround.
 * Sampling: 8363 Hz (frequency is turned into a relative tone).
 *
 * Information is taken from AUDIO.H and IMPORT.C in the DSIK sources.
 * Period range is [28...428...6848], i.e. B-7...C-4...C-0.
 * Effects are those of MOD, but not to sure about panning (cf. surround).
 * Order of blocks may vary: SONG, PATT(s), INST(s) or SONG, , INST(s), PATT(s).
 */

#define NoteDelta (Note_Central-12*4)

static const uint8_t Tag_RIFF[] = "RIFF";
static const uint8_t Tag_DSMF[] = "DSMF";
static const uint8_t Tag_SONG[] = "SONG";
static const uint8_t Tag_PATT[] = "PATT";
static const uint8_t Tag_INST[] = "INST";
static const uint8_t Typestr_DSMF[] = "Digital Sound Interface Kit";
static const char Invalid_Block[] = "Invalid block";
static const char Unexpected_Block[] = "Block not expected in that position";

#define TAG(x) (*(const uint32_t*) Tag_##x)

typedef struct
{
	uint8_t     Name[28];
	uint16_t    Version;
	uint16_t    Flags;
	uint32_t    Padding;
	uint16_t    Orders;
	uint16_t    Samples;
	uint16_t    Patterns;
	uint16_t    Channels;
	uint8_t     GlobalVolume;
	uint8_t     MixingVolume;
	uint8_t     Speed;
	uint8_t     Tempo;
	uint8_t     Panning[16];
	uint8_t     Seq[128];
} LHdr;

typedef struct
{
	uint8_t     Filename[13];
	uint8_t     Flags[2];
	uint8_t     Volume;
	uint32_t    Size;
	uint32_t    LoopStart;
	uint32_t    LoopEnd;
	uint32_t	Dummy; // Used internally to point to sample data
	uint16_t	Frequency;
	uint16_t	Period;
	uint8_t		Name[28];
} LSmp;

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

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

	switch(effect)
	{
		case 8: // Panning: Set [0, 128] + &A4 for surround
		{
			if (value == 0xA4)
				Pattern_AddEffect_Panning(pSong, cmd_set, Panning_Surround);
			else if (value <= 128)
				Pattern_AddEffect_Panning(pSong, cmd_set, value << 1);
			else
			{
				// Bad values are ignored
				SysLog_BadEffectValue(pSong, 0, oeffect, ovalue);
			}
		}
		break;
		case 0xe: // Extended commands
		{
			switch(value >> 4)
			{
				case 8: //e8 Panning: Set
				{
					value &= 0xf;
					value += value << 4;
					if (value >= 128) value++;
					Pattern_AddEffect_Panning(pSong, cmd_set, value);
				}
				break;
				default:
				{
					convert_effect_MOD(pSong, pChInfo->Channel, effect, value, pChInfo->Note);
				}
			}
		}
		break;
		default: convert_effect_MOD(pSong, pChInfo->Channel, oeffect, ovalue, pChInfo->Note);
	}
}

static const _kernel_oserror* Read_TagInfo(SongHdr* pSong, uint32_t* pTag, uint32_t* pTagSize)
{
	const _kernel_oserror* err = NULL;

	err = FileLoad_ReadInt(pSong, pTag, 4);
	if (err) return err;
	err = FileLoad_ReadInt(pSong, pTagSize, 4);

	return err;
}

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

	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;

		lastchannel = -1;
		while (pPos < pEnd)
		{
			// read channel and flags
			val = *pPos++;
			// next row mark?
			if (!val) break;
			channel = val & 0x0f;
			// read flags
			// will we remain within pattern limits?
			pPos2 = pPos;
			if (val & 0x80) pPos2++;
			if (val & 0x40) pPos2++;
			if (val & 0x20) pPos2++;
			if (val & 0x10) pPos2 += 2;
			if (pPos2 > pEnd)
			{
				SysLog_FileCorrupted(SysLog_High, pSong, -1, "Pattern truncated");
				pPos = pEnd;
				break;
			}
			// valid channel ?
			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);
				pPos = pPos2;
				continue;
			}
			lastchannel = channel;

			// Read note
			if (val & 0x80)
			{
				Note = *pPos++;
				if (Note && (Note <= 12*8))
				{
					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;

			err = Loaders_StartChannel(pSong, channel, Note, Inst);
			if (err) return err;

			pChInfo->Note = Note;
			pChInfo->Channel = channel;

			// 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 effect and value
			if (val & 0x10)
			{
				convert_effect(pSong, pChInfo, pPos[0], pPos[1]);
				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_DSMF(SongHdr* pSong)
{
	const _kernel_oserror* err = NULL;
	SubSong*        pSubSong = Song_GetSubSong(pSong, 0);
	LHdr*           pLHdr = NULL;
	LSmp*           pLSmp = NULL;
	uint8_t*        pLTrack = NULL;
	int             i;
	ChannelDefault* pDef;
	Pattern*        pPattern;
	Sample*         pSmp;
	uint32_t        Tag, TagSize;
	uint32_t        FileOffset;
	uint32_t        FileSize = FileLoad_GetSize(pSong);
	uint32_t        curPattern = 0;
	uint32_t        curSmp = 0;

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

	// is it a DSMF file?
	err = FileLoad_SetPos(pSong, 0);
	if (err) goto loader_err;
	err = Read_TagInfo(pSong, &Tag, &FileSize);
	if (err) goto loader_err;

	if (Tag != TAG(RIFF))
	{
		err = Loaders_NotThisType;
		goto loader_err;
	}
	FileSize += 8;

	err = FileLoad_ReadInt(pSong, &Tag, 4);
	if (err) goto loader_err;

	if (Tag != TAG(DSMF))
	{
		err = Loaders_NotThisType;
		goto loader_err;
	}

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

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

	// Read TAGS.
	// Rule: assume that when tags require information from another tag, this tag is stored
	// earlier in the tag sequence, but don't assume any fixed tag order.
	// We will thus reject files which break this rule as too badly corrupted.
	// For missing tags which don't impact others (pattern, sequence),
	// we will let the global inegrity check catch these cases.
	while (FileLoad_GetPos(pSong) < FileSize)
	{
		// Read TAG id & size
		err = Read_TagInfo(pSong, &Tag, &TagSize);
		if (err) goto loader_err;
		FileOffset = FileLoad_GetPos(pSong);

		if (Tag == TAG(SONG))
		{
			if (pSong->Channels > 0)
			{
				err = Loaders_Error(pSong, FileOffset - 8, Unexpected_Block);
				goto loader_err;
			}

			if (TagSize < sizeof(*pLHdr))
			{
				err = Loaders_Error(pSong, FileOffset - 8, Invalid_Block);
				goto loader_err;
			}

			err = FileLoad_Read(pSong, pLHdr, sizeof(*pLHdr));
			if (err) goto loader_err;
			// Title
			err = Loaders_String(pSong, &pSong->pName, &pLHdr->Name[0], sizeof(pLHdr->Name), false);
			if (err) goto loader_err;
			pSubSong->SeqLen = pLHdr->Orders;
			pSong->Samples  = pLHdr->Samples;
			pSong->Patterns = pLHdr->Patterns;
			pSong->Channels = pLHdr->Channels;

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

			if (!pSubSong->SeqLen
			|| (pSubSong->SeqLen > 128))
			{
				err = Loaders_InvalidSong(pSong, FileOffset + 36, &pSubSong->SeqLen, pSubSong->SeqLen);
				goto loader_err;
			}
			if (!pSong->Samples
			|| (pSong->Samples > 255))
			{
				err = Loaders_InvalidSong(pSong, FileOffset + 38, &pSong->Samples, pSong->Samples);
				goto loader_err;
			}
			if (!pSong->Patterns
			|| (pSong->Patterns > song_max_patterns))
			{
				err = Loaders_InvalidSong(pSong, FileOffset + 40, &pSong->Patterns, pSong->Patterns);
				goto loader_err;
			}
			// Nr of channels
			if (!pSong->Channels
			|| (pSong->Channels > 16))
			{
				err = Loaders_InvalidSong(pSong, FileOffset + 42, &pSong->Channels, pSong->Channels);
				goto loader_err;
			}

			// Read channels settings
			for (i = 0, pDef = pSubSong->Defaults.ChDef; i < 16; i++, pDef++)
			{
				if (pLHdr->Panning[i] <= 128)
					pDef->Panning = pLHdr->Panning[i] << 1;
				else
					pDef->Panning = Panning_Surround;
			}

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

			// Read sequence
			for (i = 0; i < pSubSong->SeqLen; i++)
				pSubSong->pSeqs[i] = pLHdr->Seq[i];
		}
		else if (Tag == TAG(PATT))
		{
			uint32_t size;

			if (curPattern >= pSong->Patterns)
			{
				err = Loaders_Error(pSong, FileOffset - 8, Unexpected_Block);
				goto loader_err;
			}

			pPattern = &pSong->pPatterns[curPattern];

			pPattern->Rows = 64;

			// Read data length
			err = FileLoad_ReadInt(pSong, &size, 2);
			if (err) goto loader_err;

			// Is Tag size correct ?
			if (TagSize < size)
			{
				err = Loaders_Error(pSong, FileOffset, Invalid_Block);
				goto loader_err;
			}
			size -= 2;

			if (size)
			{
				err = Loaders_Alloc(pSong, (void**) &pLTrack, size);
				if (err) goto loader_err;
				err = FileLoad_Read(pSong, pLTrack, size);
				if (err) goto loader_err;
			}

			err = Pattern_DSMF(pSong, curPattern, pPattern, pLTrack, pLTrack + size);
			if (err) goto loader_err;
			curPattern++;

			Loaders_Free(pSong, pLTrack);
			pLTrack = NULL;
		}
		else if (Tag == TAG(INST))
		{
			if (curSmp >= pSong->Samples)
			{
				err = Loaders_Error(pSong, FileOffset - 8, Unexpected_Block);
				goto loader_err;
			}

			pSmp = &pSong->pSamples[curSmp];

			if (TagSize < sizeof(*pLSmp))
			{
				err = Loaders_Error(pSong, FileOffset - 8, Invalid_Block);
				goto smp_err;
			}

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

			err = Loaders_String(pSong, &pSmp->pFilename, &pLSmp->Filename[0], sizeof(pLSmp->Filename), false);
			if (err) goto loader_err;
			err = Loaders_String(pSong, &pSmp->pName, &pLSmp->Name[0], sizeof(pLSmp->Name), false);
			if (err) goto loader_err;
			pSmp->Size      = pLSmp->Size;
			pSmp->LoopStart = pLSmp->LoopStart;
			pSmp->LoopEnd   = pLSmp->LoopEnd;
			pSmp->Frequency = 8363;
			if (pLSmp->Frequency)
				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->Flags[0] & 1) pSmp->Type |= Smp_Type_Loop;
			if (!(pLSmp->Flags[0] & 2)) pSmp->Type |= Smp_Type_Unsigned;
			if (pLSmp->Flags[0] & 4) pSmp->Type |= Smp_Type_16bit;

			err = FileLoad_ReadSample(pSong, pSmp);
			if (err) goto loader_err;

smp_err:
			// Accept files truncated within the samples
			if (err)
				SysLog_FileCorrupted(SysLog_Medium, pSong, 0, "Sample %d: invalid data offset", 1 + (pSmp - pSong->pSamples));
			err = NULL;

			curSmp++;
		}
		else
		{
			SysLog_FileCorrupted(SysLog_Medium, pSong, FileOffset - 8, Unexpected_Block);
		}

		// to prevent overflows
		if ((TagSize >= FileSize)
		||  (FileOffset + TagSize >= FileSize))
			break;

		FileLoad_SetPos(pSong, FileOffset + TagSize);
	}

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

	return err;
}
