#include <stdlib.h>
#include <string.h>
#include "Song.h"
#include "Effects.h"
#include "Instrument.h"
#include "Loaders.h"
#include "FSysLog.h"

/*
 * Channels: 16
 * Patterns: [1...256]
 * Rows    : [1...256]
 * Orders  : [1...256]
 * Samples : 64
 * Instr.  : 0
 * Tempo   : [1...15], Row duration is 32/T seconds, but has finer slide values.
 * Speed   : Autocalculated? All effects values are given for a complete row.
 * Notes   : [C-0...C-1...B-5]
 * Pitch   : Uses frequency, base is 236 units, thus multiplied by 2 each octave up.
 *           Slides as x*4 units per row.
 * Volume  : [0...F], linear.
 * Panning : [0...F].
 * Sampling: 8363 Hz.
 *
 * Notes:
 * Effects have no memory. So effect value 0 as usually no effect.
 *
 * Tempo works in the same way as 669, it sets row duration as 32/tempo
 * or more precisely as 128 / (4*tempo + tempo offset).
 *
 * Implementation:
 * We will use an internal speed of 16 to smooth out slides.
 * This should also cope relatively well with x retrigs per row.
 */

typedef struct
{
	uint8_t   Name[32];
	uint32_t  Length;
	uint8_t   FineTune; // Not supported
	uint8_t   Volume;
	uint8_t   LoopStart[4];
	uint8_t   LoopEnd[4];
	uint8_t   Type;
	uint8_t   Loop;
} LSmp;

typedef __packed struct
{
	uint32_t  Tag;
	uint8_t   Name[40];
	uint8_t   _13;
	uint8_t   _10;
	uint8_t   _26;
	uint16_t  HdrLen;
	uint8_t   Version;
	uint8_t   OnOff[16];
	uint8_t   Edit1[9];
	uint8_t   Tempo;
	uint8_t   Panning[16];
	uint8_t   Edit2[4];
	uint16_t  MsgLength;
} LHdr;

typedef struct
{
	uint8_t   Order[256];
	uint8_t   Patterns;
	uint8_t   Orders;
	uint8_t   RestartPos;
	uint8_t   PatternSize[512];
} LHdr2;

#define NoteDelta   (Note_Central-12*1)
#define CentralPitch 236

static const uint8_t Tag_FAR[] = "FAR\xfe";
static const uint8_t Typestr_FAR[] = "Farandole Composer";

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

typedef struct
{
	int32_t     note_base;
	int32_t     note_delta;
	int32_t     volume_base;
	int32_t     volume_delta;
	uint8_t     note_x;
	uint8_t     note_count;
	uint8_t     volume_x;
	uint8_t     volume_count;
} ChInfo;

typedef struct
{
	uint32_t    tempo_base;
	int32_t     tempo_delta;
	uint32_t    tempo_mode;
	uint32_t    note_min;
	uint32_t    note_max;
	ChInfo      channel[16];
} FarInfo;

static int convert_tempo(FarInfo* pInfo)
{
	int val = 128 / pInfo->tempo_base;

	// curious limits
	if (val + pInfo->tempo_delta > 100)
		pInfo->tempo_delta = 100;
	if (val + pInfo->tempo_delta <= 0)
		pInfo->tempo_delta = 0;

	if (pInfo->tempo_mode)
		val += pInfo->tempo_delta;
	else
		val += pInfo->tempo_delta << 1;

	if (val < 1) val = 1;

	return 5*16*val;
}

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

	switch(effect)
	{
		case 0: // global cmd
		{
			if ((value == 4) || (value == 5))
				pInfo->tempo_mode = value - 4;
			else if (value)
				SysLog_BadEffectValue(pSong, 0, oeffect, ovalue);
		}
		break;
		case 1: // Pitch: Up, value is for whole row, No memory
		{
			value = (value << 14) / CentralPitch;
			pChInfo->note_base += value;
			if (pChInfo->note_base > pInfo->note_max) pChInfo->note_base = pInfo->note_max;
			if (value >> 4)
				Pattern_AddEffect_Pitch(pSong, cmd_up+flag_cmd_slide_frame0N0, value >> 4);
			if (value & 0xf)
				Pattern_AddEffect_Pitch(pSong, cmd_up+flag_cmd_slide_frame0, value & 0xf);
		}
		break;
		case 2: // Pitch: Down, value is for whole row, No memory
		{
			value = (value << 14) / CentralPitch;
			pChInfo->note_base -= value;
			if (pChInfo->note_base < pInfo->note_min) pChInfo->note_base = pInfo->note_min;
			if (value >> 4)
				Pattern_AddEffect_Pitch(pSong, cmd_down+flag_cmd_slide_frame0N0, value >> 4);
			if (value & 0xf)
				Pattern_AddEffect_Pitch(pSong, cmd_down+flag_cmd_slide_frame0, value & 0xf);
		}
		break;
		case 3: // pitch - portamento to note in x rows, 0 is same as 1
		{
			pChInfo->note_x = value ? value : 1;
			pChInfo->note_count = 0;
			// Applied below (and in the following rows)
		}
		break;
		case 4: // retrig note, x times per row instead of every x beat
		{
			if (value)
			{
				value = 16 / value;
				Pattern_AddEffect_RetrigCounter(pSong, cmd_retrig_count_type_MOD, value);
				Pattern_AddEffect_Instrument(pSong, cmd_sample_note_retrig, 8);
			}
		}
		break;
		case 5: // pitch - vibrato depth
		{
			value = (value << 12) / CentralPitch;
			if (value > 0xff) value = 0xff;
			Pattern_AddEffect_Pitch(pSong, cmd_vibrato_depth | flag_cmd_vibrato_frameN0, value);
		}
		break;
		case 6: // pitch - vibrato control, x*6
		{
			if (value)
			{
				value = (value*6)/8;
				if (!value) value = 1;
				Pattern_AddEffect_Pitch(pSong, cmd_vibrato_speed, value << 2);
			}
			Pattern_AddEffect_Pitch(pSong, cmd_vibrato_depth | flag_cmd_vibrato_frameN0, 0);
		}
		break;
		case 7: // Volume: Up, value is for whole row, No memory
		{
			value <<= 4;
			pChInfo->volume_base += value;
			if (pChInfo->volume_base > 256) pChInfo->volume_base = 256;
			if (value >> 4)
				Pattern_AddEffect_NoteVolume(pSong, cmd_up + flag_cmd_slide_frame0N0, value >> 4);
			if (value & 0xf)
				Pattern_AddEffect_NoteVolume(pSong, cmd_up + flag_cmd_slide_frame0, value & 0xf);
		}
		break;
		case 8: // Volume: Down, value is for whole row, No memory
		{
			value <<= 4;
			pChInfo->volume_base -= value;
			if (pChInfo->volume_base < 0) pChInfo->volume_base = 0;
			if (value >> 4)
				Pattern_AddEffect_NoteVolume(pSong, cmd_down + flag_cmd_slide_frame0N0, value >> 4);
			if (value & 0xf)
				Pattern_AddEffect_NoteVolume(pSong, cmd_down + flag_cmd_slide_frame0, value & 0xf);
		}
		break;
		case 9: // pitch - vibrato control, x*6, permanent
		{
			if (value)
			{
				value = (value*6)/8;
				if (!value) value = 1;
				Pattern_AddEffect_Pitch(pSong, cmd_vibrato_speed, value << 2);
			}
			Pattern_AddEffect_Pitch(pSong, cmd_vibrato_depth | flag_cmd_vibrato_frameN0, 0);
		}
		break;
		case 0xa: // volume - portamento to volume in x rows, 0 is same as 1
		{
			pChInfo->volume_x = value ? value : 0;
			pChInfo->volume_count = 0;
			// Applied below (and in the following rows)
		}
		break;
		case 0xb: // Panning: Set
		{
			value |= (value << 4);
			if (value >= 128) value++;
			Pattern_AddEffect_Panning(pSong, cmd_set, value);
		}
		break;
		case 0xc: // Note: Delay note x ticks
		{
			if (value) Pattern_AddEffect_Instrument(pSong, cmd_sample_note_delay, 16 / value);
		}
		break;
		case 0xd: // fine tempo down, 0 for reset
		{
			if (value)
				pInfo->tempo_delta -= value;
			else
				pInfo->tempo_delta = 0;
			value = convert_tempo(pInfo);
			if (value > 0xfff)
				Pattern_AddGlbEffect_Tempo(pSong, gcmd_tempo_setlarge, value >> 4);
			else
				Pattern_AddGlbEffect_Tempo(pSong, cmd_set, value);
		}
		break;
		case 0xe: // fine tempo up, 0 for reset
		{
			if (value)
				pInfo->tempo_delta += value;
			else
				pInfo->tempo_delta = 0;
			value = convert_tempo(pInfo);
			if (value > 0xfff)
				Pattern_AddGlbEffect_Tempo(pSong, gcmd_tempo_setlarge, value >> 4);
			else
				Pattern_AddGlbEffect_Tempo(pSong, cmd_set, value);
		}
		break;
		case 0xf: // Global: Tempo
		{
			if (value)
			{
				pInfo->tempo_base = value;
				value = convert_tempo(pInfo);
				if (value > 0xfff)
					Pattern_AddGlbEffect_Tempo(pSong, gcmd_tempo_setlarge, value >> 4);
				else
					Pattern_AddGlbEffect_Tempo(pSong, cmd_set, value);
			}
		}
		break;
		default:
		{
			SysLog_BadEffect(pSong, 0, oeffect, ovalue);
		}
	}

	// Portamento to note in x rows
	if (pChInfo->note_x > pChInfo->note_count)
	{
		int delta = pChInfo->note_delta / pChInfo->note_x;
		if (!pChInfo->note_count)
			delta += pChInfo->note_delta % pChInfo->note_x;
		if (delta >> 4)
			Pattern_AddEffect_Pitch(pSong, cmd_portamento+flag_cmd_slide_frame0N0, delta >> 4);
		if (delta & 0xf)
			Pattern_AddEffect_Pitch(pSong, cmd_portamento+flag_cmd_slide_frame0, delta & 0xf);
		pChInfo->note_count++;
	}

	// Portamento to volume in x rows
	if (pChInfo->volume_x > pChInfo->volume_count)
	{
		int delta = pChInfo->volume_delta / pChInfo->volume_x;
		if (!pChInfo->volume_count)
			delta += pChInfo->volume_delta % pChInfo->volume_x;
		if (delta >> 4)
			Pattern_AddEffect_NoteVolume(pSong, cmd_portamento+flag_cmd_slide_frame0N0, delta >> 4);
		if (delta & 0xf)
			Pattern_AddEffect_NoteVolume(pSong, cmd_portamento+flag_cmd_slide_frame0, delta & 0xf);
		pChInfo->volume_count++;
	}
}

const _kernel_oserror* Loader_FAR(SongHdr* pSong)
{
	const _kernel_oserror* err = NULL;
	SubSong*        pSubSong = Song_GetSubSong(pSong, 0);
	LHdr*           pLHdr = NULL;
	LHdr2*          pLHdr2 = NULL;
	LSmp*           pLSmp = NULL;
	uint8_t*        pc;
	uint32_t        val;
	ChannelDefault* pDef;
	Pattern*        pPattern;
	Pattern*        pLPattern;
	Pattern*        pLPatterns;
	Sample*         pSmp;
	int             i, j, k;
	FarInfo         Info;

	// Allocate memory
	err = Loaders_Alloc(pSong, (void**) &pLHdr, sizeof(LHdr));
	if (err) goto loader_err;
	err = Loaders_Alloc(pSong, (void**) &pLHdr2, sizeof(LHdr2));
	if (err) goto loader_err;
	err = Loaders_Alloc(pSong, (void**) &pLSmp, sizeof(LSmp));
	if (err) goto loader_err;
	err = Loaders_Alloc(pSong, (void**) &pLPatterns, sizeof(*pLPatterns)*256);
	if (err) goto loader_err;
	memset(pLPatterns, 0, sizeof(*pLPatterns)*256);

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

	// check if really Farandole Module?
	if ((pLHdr->Tag != TAG(FAR))
	||  (pLHdr->_13 != 13)
	||  (pLHdr->_10 != 10)
	||  (pLHdr->_26 != 26)
	||  (pLHdr->HdrLen < ((sizeof(*pLHdr) + 771 + pLHdr->MsgLength))))
	{
		err = Loaders_NotThisType;
		goto loader_err;
	}

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

	// set version
	pSong->Version = ((pLHdr->Version & 0xf0) >> 4) * 100
	               + (pLHdr->Version & 0x0f);
	// Convert to fine FAR tempo
	if (!pLHdr->Tempo) pLHdr->Tempo = 4;
	else if (pLHdr->Tempo > 15) pLHdr->Tempo = 15;

	Info.tempo_base = pLHdr->Tempo;
	Info.tempo_delta = 0;
	Info.tempo_mode = 1;
	for (i = 0; i < 16; i++)
	{
		Info.channel[i].note_base = Convert_ToneToFrac(Note_Central << 8);
		Info.channel[i].note_delta = 0;
		Info.channel[i].note_x = 0;
		Info.channel[i].note_count = 0;
		Info.channel[i].volume_base = 0xf0;
		Info.channel[i].volume_delta = 0;
		Info.channel[i].volume_x = 0;
		Info.channel[i].volume_count = 0;
	}

	pSong->Flags = Song_Flag_PitchAsFraction;
	pSong->Samples  = 64;
	pSong->Channels = 16;
	pSong->MinPitch = NoteDelta;
	pSong->MaxPitch = NoteDelta + 12 * 6 - 1;
	pSong->MinTempo = 0x1;
	pSong->MaxTempo = 0xffff;
	pSong->TempoBase = Tempo_Base_20Hz;
	pSubSong->Defaults.Tempo = convert_tempo(&Info);
	pSubSong->Defaults.Speed = 16;
	Info.note_min = Convert_ToneToFrac(pSong->MinPitch << 8);
	Info.note_max = Convert_ToneToFrac(pSong->MaxPitch << 8);

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

	for (i = 0, pDef = pSubSong->Defaults.ChDef; i < 16; i++, pDef++)
	{
		val = pLHdr->Panning[i];

		val &= 0xf;
		val += val << 4;
		if (val >= 128) val++;
		pDef->Panning = val;
	}

	// fill comments
	if (pLHdr->MsgLength)
	{
		val = pLHdr->MsgLength + MsgLineLength - 1;
		pSong->CommentsLen = pLHdr->MsgLength + (val / MsgLineLength);
		err = Loaders_AllocString(pSong, (void**) &pSong->pComments, pSong->CommentsLen);
		if (err) goto loader_err;
		uint8_t* pc = pSong->pComments;
		for (i = 0; i < pLHdr->MsgLength; i += MsgLineLength)
		{
			uint32_t len = pLHdr->MsgLength - i;
			if (len > MsgLineLength) len = MsgLineLength;
			err = FileLoad_Read(pSong, pc, len);
			if (err) goto loader_err;
			for(j = 0; j < len; j++, pc++)
			{
				if (*pc < ' ') *pc = ' ';
			}
			*pc++ = '\n';
		}
		pc[-1] = 0;
	}

	// read second part of header
	err = FileLoad_Read(pSong, (void*) pLHdr2, 771);
	if (err) goto loader_err;

	pSubSong->SeqLen = pLHdr2->Orders;
	if (!pSubSong->SeqLen)
	{
		err = Loaders_InvalidSong(pSong, -258, &pSubSong->SeqLen, pSubSong->SeqLen);
		goto loader_err;
	}
	err = Loaders_AllocSeq(pSong, pSubSong);
	if (err) goto loader_err;

	// pLHdr2->Patterns is unreliable
	pSong->Patterns = 0;

	// check pattern sizes
	for (i = 0, pc = pLHdr2->PatternSize, pLPattern = pLPatterns; i < 256; i++, pc += 2, pLPattern++)
	{
		uint32_t len = pc[0] + (pc[1] << 8);

		if (len && (((len - 2) % (4*16)) || ((len - 2) > (4*16*256))))
		{
			err = Loaders_Error(pSong, -2*(256-i), "Bad pattern size %d", len);
			goto loader_err;
		}

		// pLHdr2->Patterns is unreliable
		if (len) pSong->Patterns = i + 1;

		pLPattern->Size = len;
	}

	// fill the pattern sequence
	for (i = 0; i < pSubSong->SeqLen; i++)
	{
		pSubSong->pSeqs[i] = pLHdr2->Order[i];
		if (pSubSong->pSeqs[i] >= pSong->Patterns)
			pSong->Patterns = pSubSong->pSeqs[i] + 1;
	}
	pSubSong->RestartPos = pLHdr2->RestartPos;

	// fill the patterns
	err = FileLoad_SetPos(pSong, pLHdr->HdrLen);
	if (err) goto loader_err;

	for (i = 0, pLPattern = pLPatterns; i < pSong->Patterns; i++, pLPattern++)
	{
		err = Loaders_Alloc(pSong, (void**) &pLPattern->Ptr, pLPattern->Size + 1);
		if (err) goto loader_err;

		if (pLPattern->Size)
		{
			// Read pattern
			err = FileLoad_Read(pSong, pLPattern->Ptr, pLPattern->Size);
			if (err) goto loader_err;

			pLPattern->Rows = (pLPattern->Size - 2) / (16*4);
			if (pLPattern->Rows != (pLPattern->Ptr[0] + 2))
			{
				SysLog_Log(SysLog_Low, "Pattern %d: sizes differ %d vs %d"
					, i, pLPattern->Rows, pLPattern->Ptr[0] + 2);
				if (pLPattern->Rows > (pLPattern->Ptr[0] + 2))
					pLPattern->Rows = pLPattern->Ptr[0] + 2;
			}
		}
		else
			pLPattern->Rows = 64;
	}

	// Rewrite patterns from sequence
	pSong->Patterns = pSubSong->SeqLen;
	for (i = 0, pPattern = pSong->pPatterns; i < pSubSong->SeqLen; i++, pPattern++)
	{
		uint8_t* p;
		pLPattern = &pLPatterns[pSubSong->pSeqs[i]];
		pSubSong->pSeqs[i] = i;

		if (pLPattern->Size)
		{
			p = pLPattern->Ptr + 2;
			pPattern->Rows = pLPattern->Rows;

			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; k < pSong->Channels; k++)
				{
					uint32_t Vol, Note, Inst;

					Note = p[0];
					if (Note)
					{
						if (Note <= 12*6)
						{
							Note += NoteDelta - 1;
							Inst = p[1] + 1;
							if ((p[3] & 0xf0) == 0x30) // Portamento to Note
							{
								int delta = Convert_ToneToFrac(Note << 8)
								          - Info.channel[k].note_base;
								delta = abs(delta) >> 4;
								Info.channel[k].note_delta = delta;
							}
							else
								Info.channel[k].note_base = Convert_ToneToFrac(Note << 8);
						}
						else
						{
							SysLog_BadNote(pSong, 0, k, Note);
							Note = 0;
							Inst = 0;
						}
					}
					else Inst = 0;

					Loaders_StartChannel(pSong, k, Note, Inst);

					if (p[2])
					{
						Vol = (p[2] - 1);
						if (Vol > 15)
						{
							SysLog_BadEffectValue(pSong, 0, 256, p[2]);
							Vol = 15;
						}
						Vol <<= 4;
						if ((p[3] & 0xf0) == 0xA0) // Portamento to Volume
						{
							int delta = (int) Vol;
							delta -= (int)Info.channel[k].volume_base;
							Info.channel[k].volume_delta = abs(delta);
							// New note sets sample, so overwride
							Pattern_AddEffect_NoteVolume(pSong, cmd_set, Info.channel[k].volume_base);
							Pattern_AddEffect_NoteVolume(pSong, cmd_setfinal, Vol);
						}
						else
						{
							Info.channel[k].volume_base = Vol;
							Pattern_AddEffect_NoteVolume(pSong, cmd_set, Vol);
						}
					}

					convert_effect(pSong, &Info, &Info.channel[k], (p[3] & 0xf0) >> 4, p[3] & 0x0f);

					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;
		}
		else
		{
			// empty pattern of 64 rows
			pPattern->Rows = 64;
			err = Loaders_EmptyPattern(pSong, pPattern);
			if (err) goto loader_err;
		}
	}

	// read sample map
	err = FileLoad_ReadInt(pSong, &val, 4);
	if (err) return err;
	for (i = 0, pSmp = pSong->pSamples; i < 32; i++, pSmp++)
	{
		if (val & (1<<i))
			pSmp->Size = 1;
	}
	err = FileLoad_ReadInt(pSong, &val, 4);
	if (err) return err;
	for (i = 0, pSmp = pSong->pSamples + 32; i < 32; i++, pSmp++)
	{
		if (val & (1<<i))
			pSmp->Size = 1;
	}

	// read samples info
	for (i = 0, pSmp = pSong->pSamples; i < pSong->Samples; i++, pSmp++)
	{
		if (pSmp->Size)
		{
			pSmp->Size = 0;
			err = FileLoad_Read(pSong, pLSmp, sizeof(*pLSmp));
			if (err)
			{
				SysLog_FileCorrupted(SysLog_Medium, pSong, -1, "Sample %d: failed to read header", 1 + (pSmp - pSong->pSamples));
				pSmp->Size = 0;
				break;
			}

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

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

			// read sizes
			pSmp->Size = pLSmp->Length;
			pSmp->LoopStart = pLSmp->LoopStart[0] + (pLSmp->LoopStart[1] << 8)
			                + (pLSmp->LoopStart[2] << 16) + (pLSmp->LoopStart[3] << 24);
			pSmp->LoopEnd = pLSmp->LoopEnd[0] + (pLSmp->LoopEnd[1] << 8)
			              + (pLSmp->LoopEnd[2] << 16) + (pLSmp->LoopEnd[3] << 24);
			if ((pLSmp->Loop & 8) && (pSmp->LoopEnd > 4))
				pSmp->Type |= Smp_Type_Loop;
			if (pLSmp->Type & 1)
			{
				pSmp->Type |= Smp_Type_16bit;
				pSmp->Size >>= 1;
				pSmp->LoopStart >>= 1;
				pSmp->LoopEnd >>= 1;
			}
			pSmp->Frequency = 8363;

			// read data
			err = FileLoad_ReadSample(pSong, pSmp);
			if (err) goto loader_err;
		}
	}

loader_err:
	Loaders_Free(pSong, pLSmp);
	Loaders_Free(pSong, (void*) pLHdr2);
	Loaders_Free(pSong, (void*) pLHdr);
	for (i = 0, pLPattern = pLPatterns; i < 256; i++, pLPattern++)
		Loaders_Free(pSong, pLPattern->Ptr);
	Loaders_Free(pSong, pLPatterns);

	return err;
}
