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

/*
 * Channels: [1...8]
 * Patterns: [1...255]
 * 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-1...B-2] or [C-0...C-2...B-4]
 * Pitch   : Uses Amiga periods, base is 428 units, period is divided by 2 each octave up.
 *           Slides in x/1 units per frame.
 * Volume  : [0...64], linear.
 * Panning : None.
 * Sampling: 8363 Hz.
 */

#define NoteDelta (Note_Central-12*2)

static const uint8_t Tag_DIGI[20] = "DIGI Booster module";

static const uint8_t Typestr_Digi[] = "DIGI Booster";

typedef struct
{
	uint8_t     Type[20];
	uint8_t     TVers[4];
	uint8_t     Vers;
	uint8_t     Channels; // [1-8]
	uint8_t     Packing;
	uint8_t     Dummy[19];
	uint8_t     Patterns; // highest pattern
	uint8_t     Orders; // highest order
	uint8_t     Seqs[128];
	uint8_t     SmpSizes[31][4];
	uint8_t     SmpLStarts[31][4];
	uint8_t     SmpLLens[31][4];
	uint8_t     SmpVolumes[31];
	uint8_t     SmpFineTunes[31];
	uint8_t     SongName[32];
	uint8_t     SmpNames[31][30];
} LHdr;

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

	switch(effect)
	{
		case 8: // Robot "Metallic" effect
		{
			SysLog_SkipEffect(pSong, 0, oeffect, ovalue);
		}
		break;
		case 0xe: // Special effects
		{
			effect = value >> 4;
			value &= 0xf;
			switch(effect)
			{
				case 3: // e3 play backwards (0 backward, 1 backward + forward)
				{
					// only if a note is defined at the same time
					if (note) Pattern_AddEffect_Instrument(pSong, cmd_sample_reverse_play, 0);
					else SysLog_SkipEffect(pSong, 0, oeffect, ovalue);
				}
				break;
				case 4: // e40 stop playing sample
				{
					if (!value) Pattern_AddEffect_Instrument(pSong, cmd_sample_note_action_cut, 0);
					else SysLog_BadEffectValue(pSong, 0, oeffect, ovalue);
				}
				break;
				case 5: // turn on/off channel
				{
					if (!value) Pattern_AddEffect_ChannelVolume(pSong, cmd_set, 0);
					else if (value == 1) Pattern_AddEffect_ChannelVolume(pSong, cmd_set, 256);
					else SysLog_BadEffectValue(pSong, 0, oeffect, ovalue);
				}
				break;
				case 7:
				{
					SysLog_BadEffect(pSong, 0, oeffect, ovalue);
				}
				break;
				case 8: // e8, Note: Set sample offset
				{
					// set offset byte 2
					Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, 0x200 | value);

					// only if a note is defined at the same time
					if (note) Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, 0xf00);
				}
				break;
				default:
				{
					convert_effect_MOD(pSong, channel, oeffect, ovalue, note);
				}
			}
		}
		break;
		default:
		{
			convert_effect_MOD(pSong, channel, oeffect, ovalue, note);
		}
	}
}

static const _kernel_oserror* Pattern_Digi
	( SongHdr* pSong
	, uint32_t i
	, Pattern* pPattern
	, const uint8_t* pMask
	, const uint32_t* pPos
	, const uint32_t* pPosEnd
	)
{
	const _kernel_oserror* err = NULL;
	int             j, k, mask;
	uint32_t        Note, Inst, Cmd, Value;

	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;

		mask = pMask[j];

		for (k = 0; k < pSong->Channels; k++)
		{
			if ((mask & (0x80 >> k)) && (pPos < pPosEnd - 3))
			{
				Value = *pPos++;
				Note = scan_period_MOD(((Value & 0xf) << 8) + ((Value & 0x0000ff00) >> 8));

				if (Note && ((Note < pSong->MinPitch) || (Note > pSong->MaxPitch)))
				{
					pSong->MinPitch = NoteDelta;
					pSong->MaxPitch = NoteDelta + 12 * 5 - 1;
				}

				Inst = (Value & 0x000000f0) + ((Value & 0x00f00000) >> 20);
				Cmd = (Value & 0x000f0000) >> 16;
				Value = Value >> 24;

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

				convert_effect(pSong, k, Cmd, Value, Note);
				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_Digi(SongHdr* pSong)
{
	const _kernel_oserror* err = NULL;
	SubSong*    pSubSong = Song_GetSubSong(pSong, 0);
    LHdr*       pLHdr = NULL;
	Pattern*    pPattern;
	Sample*     pSmp;
	uint32_t    val;
	int         i, j;
	uint32_t*   pLTrack = NULL;
	uint8_t*    pLMask = NULL;

	// First read all information required for memory allocation
	err = Loaders_Alloc(pSong, (void**) &pLHdr, sizeof(*pLHdr));
	if (err) goto loader_err;
	err = Loaders_Alloc(pSong, (void**) &pLMask, 64);
	if (err) goto loader_err;
	err = FileLoad_Read(pSong, pLHdr, 1572);
	if (err
	||  Loaders_strncmp(&pLHdr->Type[0], Tag_DIGI, sizeof(pLHdr->Type)))
	{
		err = Loaders_NotThisType;
		goto loader_err;
	}

	pSong->pType = Typestr_Digi;
	pSong->Flags = Song_Flag_InstNoNote_Swap;
	pSong->MinPitch = NoteDelta + 12 * 1;
	pSong->MaxPitch = NoteDelta + 12 * 4 - 1;

	pSong->Samples = 31;
	pSong->Version = (pLHdr->Vers >> 4) * 100 + (pLHdr->Vers & 0xf) * 10;
	pSong->Channels = pLHdr->Channels;
	if (!pSong->Channels
	||  (pSong->Channels > 8))
	{
		err = Loaders_InvalidSong(pSong, 25, &pSong->Channels, pSong->Channels);
		goto loader_err;
	}
	pSong->Patterns = pLHdr->Patterns + 1;
	if (pSong->Patterns > song_max_patterns)
	{
		err = Loaders_InvalidSong(pSong, 46, &pSong->Patterns, pSong->Patterns);
		goto loader_err;
	}
	pSubSong->SeqLen = pLHdr->Orders + 1;
	if (pSubSong->SeqLen > 128)
	{
		err = Loaders_InvalidSong(pSong, 47, &pSubSong->SeqLen, pSubSong->SeqLen);
		goto loader_err;
	}

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

	for (i = 0; i < pSubSong->SeqLen; i++)
		pSubSong->pSeqs[i] = pLHdr->Seqs[i];

	// Song name
	err = Loaders_String(pSong, &pSong->pName, &pLHdr->SongName[0], sizeof(pLHdr->SongName), false);
	if (err) return err;

	//------------------
	// read samples info
	for(i = 0, pSmp = pSong->pSamples; i < pSong->Samples; i++, pSmp++)
	{
		pSmp->Type = 0;

		// read sample name
		err = Loaders_String(pSong, &pSmp->pName, &pLHdr->SmpNames[i][0], 30, false);
		if (err) return err;
		pSmp->Size = pLHdr->SmpSizes[i][3]
		           + (pLHdr->SmpSizes[i][2] << 8)
		           + (pLHdr->SmpSizes[i][1] << 16)
		           + (pLHdr->SmpSizes[i][0] << 24);
		pSmp->LoopStart = pLHdr->SmpLStarts[i][3]
		                + (pLHdr->SmpLStarts[i][2] << 8)
		                + (pLHdr->SmpLStarts[i][1] << 16)
		                + (pLHdr->SmpLStarts[i][0] << 24);
		pSmp->LoopEnd = pLHdr->SmpLLens[i][3]
		              + (pLHdr->SmpLLens[i][2] << 8)
		              + (pLHdr->SmpLLens[i][1] << 16)
		              + (pLHdr->SmpLLens[i][0] << 24);
		if (pSmp->LoopEnd)
		{
			pSmp->LoopEnd += pSmp->LoopStart;
			pSmp->Type |= Smp_Type_Loop;
		}

		// read fine tune
		pSmp->FineTune = (pLHdr->SmpFineTunes[i] & 0xf) << 28;

		// read volume (max 64)
		val = pLHdr->SmpVolumes[i];
		if (val > 64)
			pSmp->DefaultVolume = 256;
		else
			pSmp->DefaultVolume = pLHdr->SmpVolumes[i] << 2;
	}

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

		if (pLHdr->Packing)
		{
			// Pattern size
			err = FileLoad_ReadReverseInt(pSong, &val, 2);
			if (err) goto loader_err;

			val -= 64;
			err = FileLoad_Read(pSong, pLMask, 64);
			if (err) goto loader_err;
		}
		else
		{
			for (j = 0; j < 64; j++)
				pLMask[j] = 0xff;
			val = 4*64*pSong->Channels;
		}

		err = Loaders_Alloc(pSong, (void**) &pLTrack, val);
		if (err) goto loader_err;

		err = FileLoad_Read(pSong, pLTrack, val);
		if (err) goto loader_err;

		err = Pattern_Digi(pSong, i, pPattern, pLMask, pLTrack, pLTrack + (val >> 2));
		if (err) goto loader_err;

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

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

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

	return err;
}
