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

/*
 * Channels: [4...8]
 * Patterns: [1...255]
 * Rows    : [1...65535]
 * Orders  : [1...128]
 * Samples : 36
 * 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]
 * 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.
 * Panning : [0...127].
 * Samples : 8363 Hz.
 *
 */

#define NoteDelta (Note_Central-12*1)

static const uint8_t Tag_OKTASONG[] = "OKTASONG";
static const uint8_t Tag_CMOD[] = "CMOD";
static const uint8_t Tag_SAMP[] = "SAMP";
static const uint8_t Tag_SPEE[] = "SPEE";
static const uint8_t Tag_SLEN[] = "SLEN";
static const uint8_t Tag_PLEN[] = "PLEN";
static const uint8_t Tag_PATT[] = "PATT";
static const uint8_t Tag_PBOD[] = "PBOD";
static const uint8_t Tag_SBOD[] = "SBOD";
static const uint8_t Typestr_Oktalyzer[] = "Oktalyzer";
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)

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

	switch(effect)
	{
		case 1: // Pitch: Slide up, No memory
		{
			if (value) Pattern_AddEffect_Pitch(pSong, cmd_up+flag_cmd_slide_frameN0, value << 2);
		}
		break;
		case 2: // Pitch: Slide down, No memory
		{
			if (value) Pattern_AddEffect_Pitch(pSong, cmd_down+flag_cmd_slide_frameN0, value << 2);
		}
		break;
		case 10: // Pitch: Arpeggio 3, No memory
		{
			if (value)
			{
				uint32_t val;

				val = value >> 4;
				value &= 0xf;
				Pattern_AddEffect_Arpeggio(pSong, value, val, cmd_arpeggio_type_mLNH);
			}
		}
		break;
		case 11: // Pitch: Arpeggio 4, No memory
		{
			if (value)
			{
				uint32_t val;

				val = value >> 4;
				value &= 0xf;
				Pattern_AddEffect_Arpeggio(pSong, value, val, cmd_arpeggio_type_NHNmL);
			}
		}
		break;
		case 12: // Pitch: Arpeggio 5, No memory
		{
			if (value)
			{
				uint32_t val;

				val = value >> 4;
				value &= 0xf;
				Pattern_AddEffect_Arpeggio(pSong, value, val, cmd_arpeggio_type_LLN);
			}
		}
		break;
		case 13: // Pitch: Note slide down (unit semitone)
		{
			if (value)
			{
				if (value > 0x3f) value = 0x3f;
				Pattern_AddEffect_Pitch(pSong, cmd_pitch_semitonedown, (flag_cmd_slide_frameN0 << 4) | value);
			}
		}
		break;
		case 15: // Filter on/off
		{
			// ignore
		}
		break;
		case 17: // Pitch: Note slide up (unit semitone)
		{
			if (value)
			{
				if (value > 0x3f) value = 0x3f;
				Pattern_AddEffect_Pitch(pSong, cmd_pitch_semitoneup, (flag_cmd_slide_frameN0 << 4) | value);
			}
		}
		break;
		case 21: // Pitch: Note down (unit semitone)
		{
			if (value)
			{
				if (value > 0x3f) value = 0x3f;
				Pattern_AddEffect_Pitch(pSong, cmd_pitch_semitonedown, (flag_cmd_slide_frame0 << 4) | value);
			}
		}
		break;
		case 25: // Global: Jump
		{
			Pattern_AddGlbEffect_Position(pSong, gcmd_pos_jump, value);
		}
		break;
		case 27: // Note: sustain off
		{
			Pattern_AddEffect_Instrument(pSong, cmd_sample_note_action_sustainoff, 0);
		}
		break;
		case 28: // Global: Set speed
		{
			// if 0 stop (restart) song, test removed cf multiple F on same row
			if (value) Pattern_AddGlbEffect_Frames(pSong, gcmd_frames_set, value);
		}
		break;
		case 30: // Pitch: Note up (unit semitone)
		{
			if (value)
			{
				if (value > 0x3f) value = 0x3f;
				Pattern_AddEffect_Pitch(pSong, cmd_pitch_semitoneup, (flag_cmd_slide_frame0 << 4) | value);
			}
		}
		break;
		case 31: // Volume: Set, or slide
		{
			if (value <= 0x40)
				Pattern_AddEffect_NoteVolume(pSong, cmd_set, value << 2);
			else if (value <= 0x50)
			{
				value = (value - 0x40) << 2;
				Pattern_AddEffect_NoteVolume(pSong, cmd_down | flag_cmd_slide_frameN0, value);
			}
			else if (value <= 0x60)
			{
				value = (value - 0x50) << 2;
				Pattern_AddEffect_NoteVolume(pSong, cmd_up | flag_cmd_slide_frameN0, value);
			}
			else if (value <= 0x70)
			{
				value = (value - 0x60) << 2;
				Pattern_AddEffect_NoteVolume(pSong, cmd_down | flag_cmd_slide_frame0, value);
			}
			else if (value <= 0x80)
			{
				value = (value - 0x70) << 2;
				Pattern_AddEffect_NoteVolume(pSong, cmd_up | flag_cmd_slide_frame0, value);
			}
			else
			{
				SysLog_BadEffectValue(pSong, 0, oeffect, ovalue);
			}
		}
		break;
		default:
		{
			if (effect)
			{
				// Unknown effect
				SysLog_BadEffect(pSong, 0, oeffect, ovalue);
			}
		}
	}
}

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

#ifndef USELOG
	IGNORE(pEnd);
#endif

	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++)
		{
			// Read note
			Note = *pPos++;
			Inst = *pPos++;
			Cmd = *pPos++;
			Value = *pPos++;
			if (Note && (Note <= 36))
			{
				Note += NoteDelta;
				Inst += 1;
			}
			else if (Note)
			{
				SysLog_BadNote
					( pSong, FileLoad_GetPos(pSong) - (pEnd - pPos)
					, k, Note
					);
				Note = 0;
				Inst = 0;
			}

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

			err = Loaders_EndChannel(pSong);
			if (err) return err;
		}

		err = Loaders_EndRow(pSong);
		if (err) return err;
	}

	return Loaders_EndPattern(pSong, pPattern);
}

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_ReadReverseInt(pSong, pTagSize, 4);

	return err;
}

const _kernel_oserror* Loader_Oktalyzer(SongHdr* pSong)
{
	const _kernel_oserror* err = NULL;
	SubSong*    pSubSong = Song_GetSubSong(pSong, 0);
	uint8_t*    pLTrack = NULL;
	int         i;
	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;
	uint32_t    val;
	uint8_t     head[8];

	// is it an Oktalyzer file?
	err = FileLoad_Read(pSong, &head[0], 8);
	if (err) goto loader_err;
	if (Loaders_strncmp(head, Tag_OKTASONG, 8))
	{
		err = Loaders_NotThisType;
		goto loader_err;
	}

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

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

	// 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(CMOD))
		{
			if (pSong->Channels > 0)
			{
				err = Loaders_Error(pSong, FileOffset - 8, Unexpected_Block);
				goto loader_err;
			}

			if (TagSize != 8)
			{
				err = Loaders_Error(pSong, FileOffset - 8, Invalid_Block);
				goto loader_err;
			}

			pSong->Channels = 4;
			err = FileLoad_ReadReverseInt(pSong, &val, 2);
			if (val) pSong->Channels += 1;
			err = FileLoad_ReadReverseInt(pSong, &val, 2);
			if (val) pSong->Channels += 1;
			err = FileLoad_ReadReverseInt(pSong, &val, 2);
			if (val) pSong->Channels += 1;
			err = FileLoad_ReadReverseInt(pSong, &val, 2);
			if (val) pSong->Channels += 1;
			if (err) goto loader_err;
		}
		else if (Tag == TAG(SAMP))
		{
			if (pSong->Samples > 0)
			{
				err = Loaders_Error(pSong, FileOffset - 8, Unexpected_Block);
				goto loader_err;
			}

			if (TagSize != 0x480)
			{
				err = Loaders_Error(pSong, FileOffset - 8, Invalid_Block);
				goto loader_err;
			}

			pSong->Samples = TagSize / 32;

			// Read samples definitions, note not all samples have a body
			for (i = 0, pSmp = pSong->pSamples; i < pSong->Samples; i++, pSmp++)
			{
				pSmp->Type = 0;
				pSmp->Frequency = 8363;

				err = FileLoad_ReadString(pSong, &pSmp->pName, 20, false);
				if (!err) err = FileLoad_ReadReverseInt(pSong, &pSmp->Size, 4);
				if (!err) err = FileLoad_ReadReverseInt(pSong, &pSmp->SustainStart, 2);
				if (!err) err = FileLoad_ReadReverseInt(pSong, &pSmp->SustainEnd, 2);
				if (!err) err = FileLoad_ReadByte(pSong, &val);
				if (!err) err = FileLoad_ReadByte(pSong, &pSmp->DefaultVolume);
				if (!err) err = FileLoad_ReadReverseInt(pSong, &val, 2);
				if (err) goto loader_err;
				pSmp->Panning = (val != 1) ? 1: 0; // Store 7-bit info

				pSmp->Size &= ~1; // Always rounded down
				if (pSmp->DefaultVolume > 64) pSmp->DefaultVolume = 256;
				else pSmp->DefaultVolume <<= 2;
				if (pSmp->SustainEnd > 0) pSmp->Type |= Smp_Type_Loop;
				pSmp->SustainEnd += pSmp->SustainStart;
			}

			// Move to first sample with body
			for (curSmp = 0, pSmp = pSong->pSamples; curSmp < pSong->Samples; curSmp++, pSmp++)
			{
				if (pSmp->Size) break;
			}
		}
		else if (Tag == TAG(SPEE))
		{
			if (TagSize != 2)
			{
				err = Loaders_Error(pSong, FileOffset - 8, Invalid_Block);
				goto loader_err;
			}

			err = FileLoad_ReadReverseInt(pSong, &pSubSong->Defaults.Speed, 2);
			if (err) goto loader_err;
		}
		else if (Tag == TAG(SLEN))
		{
			if (pSong->Patterns > 0)
			{
				err = Loaders_Error(pSong, FileOffset - 8, Unexpected_Block);
				goto loader_err;
			}

			if (TagSize != 2)
			{
				err = Loaders_Error(pSong, FileOffset - 8, Invalid_Block);
				goto loader_err;
			}

			err = FileLoad_ReadReverseInt(pSong, &pSong->Patterns, 2);
			if (err) goto loader_err;

			if (!pSong->Patterns
			||  (pSong->Patterns > 255))
			{
				err = Loaders_InvalidSong(pSong, FileOffset, &pSong->Patterns, pSong->Patterns);
				goto loader_err;
			}
		}
		else if (Tag == TAG(PLEN))
		{
			if (pSubSong->SeqLen > 0)
			{
				err = Loaders_Error(pSong, FileOffset - 8, Unexpected_Block);
				goto loader_err;
			}

			if (TagSize != 2)
			{
				err = Loaders_Error(pSong, FileOffset - 8, Invalid_Block);
				goto loader_err;
			}

			err = FileLoad_ReadReverseInt(pSong, &pSubSong->SeqLen, 2);
			if (err) goto loader_err;

			if (!pSubSong->SeqLen
			||  (pSubSong->SeqLen > 128))
			{
				err = Loaders_InvalidSong(pSong, FileOffset, &pSubSong->SeqLen, pSubSong->SeqLen);
				goto loader_err;
			}
		}
		else if (Tag == TAG(PATT))
		{
			if (!pSubSong->SeqLen)
			{
				err = Loaders_Error(pSong, FileOffset - 8, Unexpected_Block);
				goto loader_err;
			}

			if (TagSize < pSubSong->SeqLen)
			{
				err = Loaders_Error(pSong, FileOffset - 8, Invalid_Block);
				goto loader_err;
			}

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

			// Read sequence
			for (i = 0; i < pSubSong->SeqLen; i++)
			{
				err = FileLoad_ReadByte(pSong, &val);
				pSubSong->pSeqs[i] = val;
				if (err) goto loader_err;
			}
		}
		else if (Tag == TAG(PBOD))
		{
			int size;

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

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

			pPattern = &pSong->pPatterns[curPattern];

			err = FileLoad_ReadReverseInt(pSong, &pPattern->Rows, 2);
			if (err) goto loader_err;

			size = 4*pPattern->Rows*pSong->Channels;
			if (TagSize < 2 + size)
			{
				err = Loaders_Error(pSong, FileOffset - 8, Invalid_Block);
				goto loader_err;
			}

			if (!pPattern->Rows || (pPattern->Rows > song_max_rows))
			{
				err = Loaders_InvalidSong(pSong, -2, &pPattern->Rows, pPattern->Rows);
				goto loader_err;
			}

			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_Oktalyzer(pSong, curPattern, pPattern, pLTrack, pLTrack + size);
			if (err) goto loader_err;
			curPattern++;

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

			pSmp = &pSong->pSamples[curSmp];

			if (TagSize != pSmp->Size)
			{
				err = Loaders_Error(pSong, FileOffset - 8, Invalid_Block);
				goto loader_err;
			}

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

			if (pSmp->Panning)
			{
				uint8_t* p = pSmp->Ptr;
				uint8_t* pend = p + pSmp->Size;

				for (; p < pend; p++)
					*p <<= 1;
			}

			// Move to next sample with body
			for (curSmp++, pSmp = &pSong->pSamples[curSmp]; curSmp < pSong->Samples; curSmp++, pSmp++)
			{
				if (pSmp->Size) break;
			}
		}
		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);
	}

	if (curPattern != pSong->Patterns)
		err = Loaders_Error(pSong, 0, "Missing %d patterns", pSong->Patterns - curPattern);
	else if (curSmp != pSong->Samples)
		SysLog_FileCorrupted(SysLog_High, pSong, 0, "Missing %d samples", pSong->Samples - curSmp);

loader_err:
	Loaders_Free(pSong, pLTrack);

	return err;
}
