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

/*
 * Channels: [1...32?]
 * Patterns: [1...256]
 * Rows    : 64
 * Orders  : [1...256]
 * Samples : [1...255]
 * Instr.  : 0
 * Tempo   : [48...255], T frames per 2.5 seconds.
 * Speed   : [1...47], S frames per row.
 * Notes   : [C-0...C-2...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...255] , linear or logarithmic?.
 * Panning : [0...F].
 * Sampling: 8363 Hz (frequency is turned into a relative tone).
 *
 * Notes:
 * It is not in the documentation but if you ask for help on effects in the tracker itself
 * you notice that most effects have memory and that Portamento is an auto portamento
 * (its effect continue in the following lines till a Portamento of 0 is specified
 * or the target pitch is reached or a new note is played).
 *
 * Special effect 5 01 has no effect if no note is started on the same line.
 * Special effect 5 02 has no effect if no note is started on the same line.
 * Special effect 5 0C has effect ONLY if no note is started on the same line.
 *
 * ULT filters from old versions effects that were not supported to prevent compatibility
 * issues, so it's best if we do the same.
 */

typedef struct
{
	uint8_t     TAG[14];
	uint8_t     Version;
	uint8_t     Name[32];
	uint8_t     CommentsLines;
} LHdr;

typedef struct
{
	uint8_t     Name[32];
	uint8_t     Filename[12];
	uint32_t    LoopStart;
	uint32_t    LoopEnd;
	uint32_t    SizeStart;
	uint32_t    SizeEnd;
	uint8_t     Volume; // 0-255, (vers < 1.4) ? log : lin
	uint8_t     Flags;
	uint16_t    Frequency;
} LSmp;

typedef struct
{
	LSmp        smp;
	uint16_t    FineTune;
} LSmpNew; // 1.6 info

#define LSmp_Flags_16bit    4
#define LSmp_Flags_Loop     8
#define LSmp_Flags_BidiLoop 16

#define NoteDelta (Note_Central-12*2)

static const uint8_t TAG_ULT[] = "MAS_UTrack_V00";
static const uint8_t Typestr_ULT[] = "Ultra Tracker";

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

	switch(effect)
	{
		case 0: // Pitch: Arpeggio, No memory
		{
			if (value)
			{
				if  (pSong->Version >= 150)
				{
					uint32_t val;

					val = value >> 4;
					value &= 0xf;
					Pattern_AddEffect_Arpeggio(pSong, value, val, cmd_arpeggio_type_NHL);
				}
				else SysLog_BadEffect(pSong, 0, oeffect, ovalue);
			}
		}
		break;
		case 1: // Pitch: Slide up, Memory
		{
			Pattern_AddEffect_Pitch(pSong, cmd_upmem1+flag_cmd_slide_frameN0, value << 2);
		}
		break;
		case 2: // Pitch: Slide down, Memory
		{
			Pattern_AddEffect_Pitch(pSong, cmd_downmem1+flag_cmd_slide_frameN0, value << 2);
		}
		break;
		case 3: // Pitch: Portamento, Memory
		{
			Pattern_AddEffect_Pitch(pSong, cmd_pitch_auto_portamento+flag_cmd_slide_frameN0, value << 2);
		}
		break;
		case 4: // Pitch: Vibrato
		{
			uint32_t speed = value >> 4;
			value &= 0xf;
			// Vibrate between [-2*y,+2*y]
			value <<= 2; // << 2 for range conversion (no << 1 for depth, cf. strength default = 2)
			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 5: // Special effects (high nible and/or low nibble)
		{
			while (value)
			{
				switch(value & 0xf)
				{
					case 0: break; // Nothing
					case 1: // will play without loop
					{
						// only if a note is defined at the same time
						if (note)
							Pattern_AddEffect_Instrument(pSong, cmd_sample_loop_off, 0);
					}
					break;
					case 2: // will play backward
					{
						// only if a note is defined at the same time
						if (note)
							Pattern_AddEffect_Instrument(pSong, cmd_sample_reverse_play, 0);
					}
					break;
					case 0xc: // stop loop, but play to the end
					{
						if  (pSong->Version >= 150)
						{
							// only if NO note is defined at the same time
							if (!note)
								Pattern_AddEffect_Instrument(pSong, cmd_sample_loop_off, 0);
						}
						else SysLog_BadEffect(pSong, 0, oeffect, ovalue);
					}
					break;
					default:
					{
						SysLog_BadEffectValue(pSong, 0, oeffect, ovalue);
					}
				}
				value >>= 4;
			}
		}
		break;
		case 7: // Volume: Vibrato (alias Tremolo)
		{
			if  (pSong->Version >= 160)
			{
				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);
			}
			else SysLog_BadEffect(pSong, 0, oeffect, ovalue);
		}
		break;
		case 9: // 9, Note: Set sample offset
		{
			if  (pSong->Version >= 140)
			{
				if (value)
				{
					uint32_t val = value << 10; // * 1024
					value = val >> 16;
					value |= 0x200; // set byte 2
					Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, value);
					value = (val >> 8) & 0xff;
					value |= 0x100; // set byte 1
					Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, value);
					value = val & 0xff;
					value |= 0x000; // set byte 0
					Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, value);
				}

				// only if a note is defined at the same time
				if (note) Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, 0xf00);
			}
			else SysLog_BadEffect(pSong, 0, oeffect, ovalue);
		}
		break;
		case 0xa: // Volume: Slide, Memory
		{
			if (value == 0)
			{
				// A00 repeat last slide
				Pattern_AddEffect_NoteVolume(pSong, cmd_last_slide, 0);
			}
			else if (!(value & 0x0f))
			{
				// Ax0 slide up
				effect = cmd_upmem1 | flag_cmd_slide_frameN0;
				value &= 0xf0;

				Pattern_AddEffect_NoteVolume(pSong, effect, value >> 2);
			}
			else if (!(value & 0xf0))
			{
				// A0y slide down
				effect = cmd_downmem1 | flag_cmd_slide_frameN0;
				value &= 0x0f;

				Pattern_AddEffect_NoteVolume(pSong, effect, value << 2);
			}
			else
				SysLog_BadEffectValue(pSong, 0, oeffect, ovalue);
		}
		break;
		case 0xb: // B, Panning: Set
		{
			value = value & 0xf;
			value += value << 4;
			if (value >= 128) value++;
			Pattern_AddEffect_Panning(pSong, cmd_set, value);
		}
		break;
		case 0xc: // C, Volume: Set
		{
			Pattern_AddEffect_NoteVolume(pSong, cmd_set, value);
		}
		break;
		case 0xd: // Global: Break
		{
			uint32_t ten = value >> 4;
			value &= 0xf;
			if ((value > 9) || (ten > 8))
				SysLog_BadEffectValue(pSong, 0, oeffect, ovalue);
			else
			{
				value += ten * 10;
				Pattern_AddGlbEffect_Position(pSong, gcmd_pos_break, value);
			}
		}
		break;
		case 0xe:
		{
			effect = value >> 4;
			value &= 0xf;

			switch(effect)
			{
				case 0x0: // e0, Vibrato strength
				{
					if (value) Pattern_AddEffect_Pitch(pSong, cmd_vibrato_strength, value);
				}
				break;
				case 0x1: // e1 Pitch: Up, Memory
				{
					effect = cmd_upmem1;

					if (value) effect |= flag_cmd_slide_frame0;

					Pattern_AddEffect_Pitch(pSong, effect, value << 2);
				}
				break;
				case 0x2: // e2 Pitch: Down, Memory
				{
					effect = cmd_downmem1;

					if (value) effect |= flag_cmd_slide_frame0;

					Pattern_AddEffect_Pitch(pSong, effect, value << 2);
				}
				break;
				case 0x8: // e8, Global: Delay row for x ticks
				{
					if  (pSong->Version >= 160)
					{
						Pattern_AddGlbEffect_Frames(pSong, gcmd_frames_delay, value);
					}
					else SysLog_BadEffect(pSong, 0, oeffect, ovalue);
				}
				break;
				case 0x9: // e9 Note: Retrig note every x ticks
				{
					Pattern_AddEffect_RetrigCounter(pSong, cmd_retrig_count_type_MOD, value);
					Pattern_AddEffect_Instrument(pSong, cmd_sample_note_retrig, 8);
				}
				break;
				case 0xa: // ea Volume: Up, Memory seperate from Axy
				{
					effect = cmd_upmem2;

					if (value) effect |= flag_cmd_slide_frame0;

					Pattern_AddEffect_NoteVolume(pSong, effect, value << 2);
				}
				break;
				case 0xb: // eb Volume: Down, Memory seperate from Axy
				{
					effect = cmd_downmem2; // Should be yet another memory

					if (value) effect |= flag_cmd_slide_frame0;

					Pattern_AddEffect_NoteVolume(pSong, effect, value << 2);
				}
				break;
				case 0xc: // ec Note: Cut note after x ticks
				{
					Pattern_AddEffect_NoteVolume(pSong, cmd_volume_cut, value);
				}
				break;
				case 0xd: // ed Note: Delay note after x ticks
				{
					Pattern_AddEffect_Instrument(pSong, cmd_sample_note_delay, value);
				}
				break;
				default:
				{
					SysLog_BadEffect(pSong, 0, oeffect, ovalue);
				}
			}
		}
		break;
		case 0xf: // Global: Set speed/tempo
		{
			if (value)
			{
				if (value <= pSong->MaxSpeed)
				{
					// Speed
					Pattern_AddGlbEffect_Frames(pSong, gcmd_frames_set, value);
				}
				else
				{
					// Tempo
					Pattern_AddGlbEffect_Tempo(pSong, cmd_set, value);
				}
			}
			else
			{
				// Reset to Speed 6, Tempo 125
				Pattern_AddGlbEffect_Frames(pSong, gcmd_frames_set, 6);
				Pattern_AddGlbEffect_Tempo(pSong, cmd_set, 125);
			}
		}
		break;
		default:
		{
			SysLog_BadEffect(pSong, 0, oeffect, ovalue);
		}
	}
}

static const _kernel_oserror* Pattern_ULT(SongHdr* pSong, uint32_t i, Pattern* pPattern, const uint8_t* pStart)
{
	const _kernel_oserror*    err = NULL;
	uint32_t            val, cmd;
	int                 j, k;
	uint32_t            Note, Inst;
	uint32_t            Size = 5*64*pSong->Patterns;
	const uint8_t*      pPos = pStart;

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

	for (j = 0; j < pPattern->Rows; j++, pStart += 5)
	{
		pPos = pStart;

		err = Loaders_StartRow(pSong, j);
		if (err) return err;

		for (k = 0; k < pSong->Channels; k++)
		{
			// Read note
			Note = pPos[0];
			if (Note)
			{
				if (Note <= 12*8)
					Note += NoteDelta - 1;
				else
				{
					SysLog_BadNote(pSong, 0, k, Note);
					Note = 0;
				}
			}
			Inst = pPos[1];

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

			Note = (Note < Note_CmdMin) ? Note : 0;

			// Treat special cases
			if ((pPos[2] == 0x99) && (pSong->Version >= 140))
			{
				// set fine offset
				cmd = cmd_sample_set_offset;
				val = pPos[3] + (pPos[4] << 8);
				if (val)
				{
					val <<= 2;
					// Set byte 2
					Pattern_AddEffect_Instrument(pSong, cmd, 0x200 + (val >> 16));
					// Set byte 1
					Pattern_AddEffect_Instrument(pSong, cmd, 0x100 + ((val >> 8) & 0xff));
					// Set byte 0
					Pattern_AddEffect_Instrument(pSong, cmd, 0x000 + (val & 0xff));
				}

				// only if a note is defined at the same time
				if (Note) Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, 0xf00);
			}
			else
			{
				// Treat cmd 1
				convert_effect(pSong, pPos[2] & 0xf, pPos[3], Note);
				// Treat cmd 2
				convert_effect(pSong, pPos[2] >> 4, pPos[4], Note);
			}

			pPos += Size;

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

	// Allocate memory
	err = Loaders_Alloc(pSong, (void**) &pLHdr, sizeof(LHdr));
	if (err) goto loader_err;
	err = Loaders_Alloc(pSong, (void**) &pLSmp, sizeof(LSmpNew));
	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 ULT?
	if (Loaders_strncmp(pLHdr->TAG, TAG_ULT, sizeof(pLHdr->TAG)))
	{
		err = Loaders_NotThisType;
		goto loader_err;
	}

	// set version
	pSong->Version = 100 + (pLHdr->Version - 0x2E) * 10;

	pSong->Flags = 0;
	pSong->MinSpeed = 1;
	pSong->MaxSpeed = (pSong->Version >= 140) ? 47 : 255;
	pSong->MinPitch = NoteDelta;
	pSong->MaxPitch = NoteDelta + 12 * 8 - 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_ULT;
	pSubSong->Defaults.VibratoStrength = 2;

	// fill the comments info
	if (pLHdr->CommentsLines)
	{
		pSong->CommentsLen = pLHdr->CommentsLines * 33;
		err = Loaders_AllocString(pSong, (void**) &pSong->pComments, pSong->CommentsLen);
		if (err) goto loader_err;
		pc = pSong->pComments;
		for (i = 0; i < pLHdr->CommentsLines; i ++)
		{
			err = FileLoad_Read(pSong, pc, 32);
			if (err) goto loader_err;
			for(j = 0; j < 32; j++, pc++)
			{
				if (*pc < ' ') *pc = ' ';
			}
			*pc++ = '\n';
		}
		*pc=0;
	}

	// read number of samples
	err = FileLoad_ReadByte(pSong, &pSong->Samples);
	if (err) goto loader_err;
	if (!pSong->pSamples)
	{
		err = Loaders_InvalidSong(pSong, -1, &pSong->Samples, pSong->Samples);
		goto loader_err;
	}

	//------------------
	// read samples

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

		err = FileLoad_Read(pSong, pLSmp, sizeof(LSmp) + ((pSong->Version < 160) ? 0 : 2));
		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;


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

		// read loop info
		pSmp->LoopStart = pLSmp->LoopStart;
		pSmp->LoopEnd = pLSmp->LoopEnd;

		pSmp->Size = pLSmp->SizeEnd - pLSmp->SizeStart;

		// read volume : should test version cf < 1.4 log scale
		pSmp->DefaultVolume = pLSmp->Volume;

		// read sample type
		pSmp->Type = 0;
		if (pLSmp->Flags & LSmp_Flags_16bit)
		{
			pSmp->Type |= Smp_Type_16bit;
			// sample size in nr of samples but loops in nr of bytes !!!!
			pSmp->LoopStart >>= 1;
			pSmp->LoopEnd >>= 1;
		}
		if (pLSmp->Flags & LSmp_Flags_Loop)
			pSmp->Type |= Smp_Type_Loop;
		if (pLSmp->Flags & LSmp_Flags_BidiLoop)
			pSmp->Type |= Smp_Type_Loop_Bidi;

		// read freq or finetune( = linear value added to freq., not MOD finetune)
		pSmp->Frequency = 8363;
		pSmp->FineTune = 0;
		if (pSong->Version < 160)
		{
			finetune = pLSmp->Frequency << 16;
			pLSmp->Frequency = 8363;
		}
		else
			finetune = ((LSmpNew*) pLSmp)->FineTune << 16;
		pLSmp->Frequency = Mul16(pLSmp->Frequency, (1<<16) + (finetune >> 15));
		pSmp->RelTone = Convert_RelTone(pSmp->Frequency, pLSmp->Frequency);
	}

	// fill the pattern sequence
	pSubSong->SeqLen = 256;
	err = Loaders_AllocSeq(pSong, pSubSong);
	if (err) goto loader_err;

	for (i = 0; i < 256; i++)
	{
		err = FileLoad_ReadByte(pSong, &val);
		if (err) goto loader_err;
		if (val == 255)
			pSubSong->pSeqs[i] = -1;
		else
		{
			pSubSong->pSeqs[i] = val;
			pSubSong->SeqLen = i + 1;
		}
	}

	// read number of channels
	err = FileLoad_ReadByte(pSong, &pSong->Channels);
	if (err) goto loader_err;
	pSong->Channels++;
	if (pSong->Channels > song_max_channels)
	{
		err = Loaders_InvalidSong(pSong, -1, &pSong->Channels, pSong->Channels);
		goto loader_err;
	}

	// read number of patterns
	err = FileLoad_ReadByte(pSong, &pSong->Patterns);
	if (err) goto loader_err;
	pSong->Patterns++;
	if (pSong->Patterns > song_max_patterns)
	{
		err = Loaders_InvalidSong(pSong, -1, &pSong->Patterns, pSong->Patterns);
		goto loader_err;
	}

	if (pSong->Version >= 150)
	{
		// set initial panning
		for (i = 0, pDef = pSubSong->Defaults.ChDef; i < pSong->Channels; i++, pDef++)
		{
			err = FileLoad_ReadByte(pSong, &val);
			if (err) goto loader_err;
			val &= 0xf;
			val |= val << 4;
			if (val >= 128) val++;
			pDef->Panning = val;
		}
	}

	err = Loaders_Alloc(pSong, (void**) &pLTrack, 5*64*pSong->Channels*pSong->Patterns);
	if (err) goto loader_err;

	// Uncompress patterns
	// They are stored pat0 column0, pat 1 col0, ..., pat 0 col1, ... !!!!!!
	for (Size = 64*pSong->Channels*pSong->Patterns, Count = 0, pc = pLTrack
		; Size > 0
		; Size--, pc += 5)
	{
		// test repeat count
		if (Count == 0)
		{
			err = FileLoad_ReadByte(pSong, &Note);
			if (err) goto loader_err;
			// is repeat marker?
			if (Note == 0xfc)
			{
				// read repeat count
				err = FileLoad_ReadByte(pSong, &Count);
				if (err) goto loader_err;
				if (Count) Count--;
				// read note
				err = FileLoad_ReadByte(pSong, &Note);
				if (err) goto loader_err;
			}
			pc[0] = Note;
			// read sample + effects
			err = FileLoad_Read(pSong, pc + 1, 4);
			if (err) goto loader_err;
		}
		else
		{
			// duplicate
			pc[0] = pc[-5];
			pc[1] = pc[-4];
			pc[2] = pc[-3];
			pc[3] = pc[-2];
			pc[4] = pc[-1];
			Count--;
		}
	}


	for (i = 0, pPattern = pSong->pPatterns, pc = pLTrack
		; i < pSong->Patterns
		; i++, pPattern++, pc += 5*64)
	{
		pPattern->Rows = 64;

		err = Pattern_ULT(pSong, i, pPattern, pc);
		if (err) goto loader_err;
	}

	//------------------
	// read samples

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

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

	return err;
}
