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

/*
 * Channels: [1...32]
 * Patterns: [1...256]
 * Rows    : [1...255?]
 * Orders  : [1...65535]
 * 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   : 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 or logarithmic.
 * Panning : [0...255].
 * 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.
 */

#define NoteDelta (Note_Central-12*4)

static const uint8_t Tag_PSM[] = "PSM";
static const uint8_t Typestr_PSM[] = "Protracker Studio";

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

typedef struct
{
	uint32_t    Tag;
	uint8_t     Name[60];
	uint8_t     Type;
	uint8_t     Version;
	uint8_t     PattVersion;
	uint8_t     Speed;
	uint8_t     Tempo;
	uint8_t     MasterVolume;
	uint16_t    DummyOrders; // Copy of Orders
	uint16_t    Orders;
	uint16_t    Patterns;
	uint16_t    Samples;
	uint16_t    DummyChannels; // Real playing channels
	uint16_t    Channels; // Total stored channels (ex: 669 converted to 8+1)
} LHdr;

typedef struct
{
	uint32_t    OffsetOrders;
	uint32_t    OffsetPannings;
	uint32_t    OffsetPatterns;
	uint32_t    OffsetSamples;
	uint32_t    OffsetComments;
	uint32_t    PatternsSize;
} LHdr2;

typedef struct
{
	uint8_t     Filename[13];
	uint8_t     Name[24];
	uint8_t     Offset[4];
	uint8_t     Mem[4];
	uint8_t     Num[2];
	uint8_t     Type;
	uint32_t    Length;
	uint32_t    LoopStart;
	uint32_t    LoopEnd;
	uint8_t     FineTune;
	uint8_t     Volume;
	uint16_t    Frequency;
} LSmp;

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

static void convert_effect(SongHdr* pSong, const ChannelInfo* pChInfo, uint32_t effect, uint32_t value)
{
#ifdef USELOG
	uint32_t oeffect = effect;
	uint32_t ovalue = value;
#endif

	switch(effect)
	{
		case 1: // Volume: Up, No memory
		{
			effect = cmd_up | flag_cmd_slide_frame0;
			Pattern_AddEffect_NoteVolume(pSong, effect, value << 2);
		}
		break;
		case 2: // Volume: Slide up, No memory
		{
			effect = cmd_up | flag_cmd_slide_frameN0;
			Pattern_AddEffect_NoteVolume(pSong, effect, value << 2);
		}
		break;
		case 3: // Volume: Down, No memory
		{
			effect = cmd_down | flag_cmd_slide_frame0;
			Pattern_AddEffect_NoteVolume(pSong, effect, value << 2);
		}
		break;
		case 4: // Volume: Slide down, No memory
		{
			effect = cmd_down | flag_cmd_slide_frameN0;
			Pattern_AddEffect_NoteVolume(pSong, effect, value << 2);
		}
		break;
		case 10: // Pitch: Up, No memory
		{
			effect = cmd_up | flag_cmd_slide_frame0;
			Pattern_AddEffect_Pitch(pSong, effect, value << 2);
		}
		break;
		case 11: // Pitch: Slide Up, No memory
		{
			effect = cmd_up | flag_cmd_slide_frameN0;
			Pattern_AddEffect_Pitch(pSong, effect, value << 2);
		}
		break;
		case 12: // Pitch: Down, No memory
		{
			effect = cmd_down | flag_cmd_slide_frame0;
			Pattern_AddEffect_Pitch(pSong, effect, value << 2);
		}
		break;
		case 13: // Pitch: Slide down, No memory
		{
			effect = cmd_down | flag_cmd_slide_frameN0;
			Pattern_AddEffect_Pitch(pSong, effect, value << 2);
		}
		break;
		case 14: // Pitch: Portamento, Memory
		{
			Pattern_AddEffect_Pitch(pSong, cmd_portamento + flag_cmd_slide_frameN0, value << 2);
		}
		break;
		case 15: // Pitch: Glissando control
		{
			if (value < 2)
				Pattern_AddEffect_Pitch(pSong, cmd_pitch_glissando, value);
			else
				SysLog_BadEffectValue(pSong, 0, oeffect, ovalue);
	        }
		break;
		case 16: // Pitch: Portamento, Volume: Slide up
		{
			convert_effect(pSong, pChInfo, 14, 0);
			convert_effect(pSong, pChInfo, 2, value);
		}
		break;
		case 17: // Pitch: Portamento, Volume: Slide down
		{
			convert_effect(pSong, pChInfo, 14, 0);
			convert_effect(pSong, pChInfo, 4, value);
		}
		break;
		case 20: // Pitch: Vibrato, Memory
		{
			uint32_t speed = value >> 4;
			value &= 0xf;
			// Vibrate between [-2*y,+2*y]
			value <<= 3; // << 2 for range conversion and << 1 for depth
			if (speed) Pattern_AddEffect_Pitch(pSong, cmd_vibrato_speed, speed << 2);
			Pattern_AddEffect_Pitch(pSong, cmd_vibrato_depth | flag_cmd_vibrato_frameN0, value);
		}
		break;
		case 21: // Pitch: Vibrato type
		{
			effect = value;

			switch(effect & 3)
			{
				case 1: value = vibrato_type_ramp; break;
				case 2: value = vibrato_type_square; break;
				default: value = vibrato_type_sin; break;
			}

			if (effect & 4)
				value |= vibrato_type_noretrig;

			Pattern_AddEffect_Pitch(pSong, cmd_vibrato_type, value);
		}
		break;
		case 22: // Pitch: Vibrato, Volume: Slide up
		{
			convert_effect(pSong, pChInfo, 20, 0);
			convert_effect(pSong, pChInfo, 2, value);
		}
		break;
		case 23: // Pitch: Vibrato, Volume: Slide down
		{
			convert_effect(pSong, pChInfo, 20, 0);
			convert_effect(pSong, pChInfo, 4, value);
		}
		break;
		case 30: // Volume: Vibrato (alias Tremolo), Memory
		{
			uint32_t speed = value >> 4;
			value &= 0xf;
			// Vibrate between [-4*y,+4*y]
			value <<= 4; // << 2 for range conversion and << 2 for depth
			if (speed) Pattern_AddEffect_NoteVolume(pSong, cmd_vibrato_speed, speed << 2);
			Pattern_AddEffect_NoteVolume(pSong, cmd_vibrato_depth | flag_cmd_vibrato_frameN0, value);
		}
		break;
		case 31: // Volume: Vibrato type
		{
			effect = value;

			switch(effect & 3)
			{
				case 1: value = vibrato_type_ramp; break;
				case 2: value = vibrato_type_square; break;
				default: value = vibrato_type_sin; break;
			}

			if (effect & 4)
				value |= vibrato_type_noretrig;

			Pattern_AddEffect_NoteVolume(pSong, cmd_vibrato_type, value);
		}
		break;
		case 40: // Note: Set sample offset
		{
			if (value)
			{
				// define offset
				// clear byte 0
				Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, (0<<8));
				// set byte 1
				Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, (1<<8) | (value & 0xff));
				// clear byte 2
				Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, (2<<8));
			}

			// only if a note is defined at the same time
			if (pChInfo->Note) Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, 0xf00);
		}
		break;
		case 41: // Note: Retrig note, Memory
		{
			Pattern_AddEffect_RetrigCounter(pSong, cmd_retrig_count_type_MOD, value);
			Pattern_AddEffect_Instrument(pSong, cmd_sample_note_retrig, 8);
		}
		break;
		case 42: // Note: Cut note after x ticks
		{
			Pattern_AddEffect_NoteVolume(pSong, cmd_volume_cut, value);
		}
		break;
		case 43: // Note: Delay for x ticks
		{
			Pattern_AddEffect_Instrument(pSong, cmd_sample_note_delay, value);
		}
		break;
		case 50: // Global: Jump
		{
			Pattern_AddGlbEffect_Position(pSong, gcmd_pos_jump, value);
		}
		break;
		case 51: // Global: Break
		{
			Pattern_AddGlbEffect_Position(pSong, gcmd_pos_break, value);
		}
		break;
		case 52: // Global: Loop in column
		{
			Pattern_AddGlbEffect_Position(pSong, gcmd_pos_loop | value, pChInfo->Channel);
		}
		break;
		case 53: // Global: Delay row for x rows
		{
			Pattern_AddGlbEffect_Frames(pSong, gcmd_frames_repeat, value);
		}
		break;
		case 60: // Global: Set speed
		{
			if (value) Pattern_AddGlbEffect_Frames(pSong, gcmd_frames_set, value);
		}
		break;
		case 61: // Global: Set tempo, No prev InfoByte
		{
			if (value >= 0x20)
			{
				Pattern_AddGlbEffect_Tempo(pSong, cmd_set, value);
			}
			else
			{
				SysLog_BadEffectValue(pSong, 0, oeffect, ovalue);
			}
		}
		break;
		case 70: // Pitch: Arpeggio, No memory
		{
			if (value)
			{
				uint32_t val;

				val = value >> 4;
				value &= 0xf;
				Pattern_AddEffect_Arpeggio(pSong, value, val, cmd_arpeggio_type_NHL);
			}
		}
		break;
		case 71: // Note: Set fine tune in 1/8 semitone
		{
			int32_t svalue = value << 28;

			// only if a note is defined at the same time
			if (pChInfo->Note) Pattern_AddEffect_Instrument(pSong, cmd_sample_set_finetune, svalue >> 23);
		}
		break;
		case 72: // Panning: Set
		{
			value = value & 0xf;
			value += value << 4;
			if (value >= 128) value++;
			Pattern_AddEffect_Panning(pSong, cmd_set, value);
		}
		break;
		default:
		{
			SysLog_BadEffect(pSong, 0, oeffect, ovalue);
		}
	}
}

static const _kernel_oserror* Pattern_PSM254
	( 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, Effect, Value;
	ChannelInfo     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;

		if (pPos < pEnd)
		{
			lastchannel = -1;
			while (pPos < pEnd)
			{
				// read flags and channel
				val = *pPos++;
				// end of row?
				if (!val) break;

				channel = val & 0x1f;
				// will we remain within pattern limits?
				pPos2 = pPos;
				if (val & 0x80) pPos2+=2;
				if (val & 0x40) pPos2++;
				if (val & 0x20) pPos2+=2;
				if (pPos2 > pEnd)
				{
					SysLog_FileCorrupted(SysLog_Medium, pSong, -1, "Pattern row 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)
							, "Extra channel %d in pattern"
							, channel
							);
					pPos = pPos2;
					continue;
				}
				lastchannel = channel;

				// Read note & instrument
				if (val & 0x80)
				{
					Note = *pPos++;
					Inst = *pPos++;
					// 0 is a valid note
					if ((pSong->MinPitch + Note) <= pSong->MaxPitch)
						Note += pSong->MinPitch;
					else
					{
						SysLog_BadNote(pSong, FileLoad_GetPos(pSong) - (pEnd - pPos), channel, Note);
						Note = 0;
					}
				}
				else
				{
					Note = 0;
					Inst = 0;
				}

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

				// Read Volume column
				if (val & 0x40)
				{
					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 & 0x20)
				{
					ChInfo.Note = (Note < Note_CmdMin) ? Note : 0;
					Effect = *pPos++;
					Value = *pPos++;
					convert_effect(pSong, &ChInfo, Effect, Value);
				}

				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_PSM254(SongHdr* pSong)
{
	const _kernel_oserror* err = NULL;
	SubSong*        pSubSong = Song_GetSubSong(pSong, 0);
	LHdr*           pLHdr = NULL;
	LHdr2*          pLHdr2 = NULL;
	LSmp*           pLSmp = NULL;
	uint8_t*        pLTrack = NULL;
	uint32_t        val;
	ChannelDefault* pDef;
	Pattern*        pPattern;
	Sample*         pSmp;
	int             i;

	// Reject file to short to contain header
	if (FileLoad_GetSize(pSong) < 160)
		return Loaders_NotThisType;

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

	// is it a PSM file?
	err = FileLoad_SetPos(pSong, 0);
	if (err) goto loader_err;
	err = FileLoad_Read(pSong, pLHdr, 82);
	if (err) goto loader_err;
	err = FileLoad_Read(pSong, pLHdr2, sizeof(LHdr2));
	if (err) goto loader_err;

	if ((pLHdr->Tag != TAG(PSM))
	||  (pLHdr->Type & 0x1))
	{
		err = Loaders_NotThisType;
		goto loader_err;
	}

	pSong->Flags = 0;
	if (pLHdr->Type & 0x2)
	{
		pSong->MinPitch = Note_Central - 24;
		pSong->MaxPitch = Note_Central + 11;
	}
	else
	{
		pSong->MinPitch = Note_Central - 24;
		pSong->MaxPitch = Note_Central + 35;
	}

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

	pSubSong->SeqLen   = pLHdr->Orders;
	if (!pSubSong->SeqLen)
	{
		err = Loaders_InvalidSong(pSong, 0x48, &pSubSong->SeqLen, pSubSong->SeqLen);
		goto loader_err;
	}
	pSong->Patterns = pLHdr->Patterns;
	if (!pSong->Patterns || (pSong->Patterns > 256))
	{
		err = Loaders_InvalidSong(pSong, 0x4a, &pSong->Patterns, pSong->Patterns);
		goto loader_err;
	}
	pSong->Samples = 0;
	pSong->Channels = pLHdr->Channels;
	if (!pSong->Channels || (pSong->Channels > 32))
	{
		err = Loaders_InvalidSong(pSong, 0x50, &pSong->Channels, pSong->Channels);
		goto loader_err;
	}

	pSubSong->Defaults.Speed = pLHdr->Speed;
	pSubSong->Defaults.Tempo = pLHdr->Tempo;

	err = Loaders_AllocSeq(pSong, pSubSong);
	if (!err) err = FileLoad_SetPos(pSong, pLHdr2->OffsetOrders);
	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;
	}

	// read panning settings
	err = FileLoad_SetPos(pSong, pLHdr2->OffsetPannings);
	if (err) goto loader_err;

	for (i = 0, pDef = pSubSong->Defaults.ChDef; i < pSong->Channels; i++, pDef++)
	{
		err = FileLoad_ReadByte(pSong, &val);
		if (err) goto loader_err;
		pDef->Panning = val + (val << 4);
		if (pDef->Panning > 128) pDef->Panning++;
	}

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

	for (i = 0, pPattern = pSong->pPatterns; i < pSong->Patterns; i++, pPattern++)
	{
		uint32_t size, channels;

		err = FileLoad_ReadInt(pSong, &size, 2);
		if (!err) err = FileLoad_ReadByte(pSong, &pPattern->Rows);
		if (!err) err = FileLoad_ReadByte(pSong, &channels);
		if (err) goto loader_err;

		if ((size < 4)
		||  !pPattern->Rows
		||  !channels
		||  (channels > pSong->Channels))
		{
			err = Loaders_Error(pSong, -4, "Invalid pattern");
			goto loader_err;
		}

		size -= 4;
		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_PSM254(pSong, i, pPattern, pLTrack, pLTrack + size);
		if (err) goto loader_err;

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


	// read samples info
	err = FileLoad_SetPos(pSong, pLHdr2->OffsetSamples);
	if (err) goto loader_err;

	for (i = 0; i < pLHdr->Samples; i++)
	{
		err = FileLoad_Read(pSong, pLSmp, sizeof(*pLSmp));
		if (err) goto loader_err;

		val = pLSmp->Num[0] + (pLSmp->Num[1] << 8);
		if (!val || (val > 255))
		{
			err = Loaders_InvalidSong(pSong, 0x4c, &pSong->Samples, val);
			goto loader_err;
		}
		if (val > pSong->Samples)
			pSong->Samples = val;
		pSmp = pSong->pSamples + (val - 1);

		// read filename
		err = Loaders_String(pSong, &pSmp->pFilename, pLSmp->Filename, sizeof(pLSmp->Filename), false);
		if (err) goto loader_err;

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

		// read offset of sample, use SustainStart as temp storage
		pSmp->SustainStart = pLSmp->Offset[0]
		                   + (pLSmp->Offset[1] << 8)
		                   + (pLSmp->Offset[2] << 16)
		                   + (pLSmp->Offset[3] << 24);

		// read type
		if (pLSmp->Type & 0x04) pSmp->Type |= Smp_Type_16bit;
		if (pLSmp->Type & 0x08) pSmp->Type |= Smp_Type_Unsigned;
		if (!(pLSmp->Type & 0x10)) pSmp->Type |= Smp_Type_Delta;
		if (pLSmp->Type & 0x20) pSmp->Type |= Smp_Type_Loop_Bidi;
		if (pLSmp->Type & 0x80) pSmp->Type |= Smp_Type_Loop;

		// read size, loop start, end
		pSmp->Size = pLSmp->Length;
		pSmp->LoopStart = pLSmp->LoopStart;
		pSmp->LoopEnd = pLSmp->LoopEnd;

		// read fine tune
		pSmp->FineTune = (pLSmp->FineTune & 0xf) << 28;

		// read volume (max 64)
		if (pLSmp->Volume > 64)
			pSmp->DefaultVolume = 256;
		else
			pSmp->DefaultVolume = pLSmp->Volume << 2;

		// read freq
		pSmp->Frequency = 8363;
		pSmp->RelTone = Convert_RelTone(pSmp->Frequency, pLSmp->Frequency);
	}

	// read samples info
	for (i = 0, pSmp = pSong->pSamples; i < pSong->Samples; i++, pSmp++)
	{
		// read data
		if (pSmp->SustainStart)
		{
			err = FileLoad_SetPos(pSong, pSmp->SustainStart);
			pSmp->SustainStart = 0;
			if (err)
			{
				SysLog_FileCorrupted(SysLog_Medium, pSong, 0, "Sample %d: invalid data offset", 1 + (pSmp - pSong->pSamples));
				pSmp->Size = 0;
			}
			else
			{
				err = FileLoad_ReadSample(pSong, pSmp);
				if (err) goto loader_err;
			}
		}
	}

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

	return err;
}
