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

/*
 * Channels: 4, 8
 * Patterns: [1...256]
 * Rows    : 64
 * Orders  : [1...256? 65535?]
 * Samples : 61
 * Instr.  : 0 // But we map samples as instruments as they have a volume envelope
 * Tempo   : [32...255], T frames per 2.5 seconds.
 * Speed   : [1...31], S frames per row.
 * Notes   : [C-0...C-3...B-4]
 * 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 : None
 * Sampling: 8363 Hz.
 *
 * Notes: For DOC see AONPLAY/SSI/AON_INT.S
 * Effects have no memory except for portamento and vibrato.
 * This means that effect value 0 as usually no effect.
 *
 * Synthetic samples and related effects are not supported.
 * Arpeggio tables are not supported.
 */

#define NoteDelta (Note_Central-12*3)

static const uint8_t Tag_AON4[] = "AON4";
static const uint8_t Tag_AON8[] = "AON8";
static const uint8_t Tag_NAME[] = "NAME";
static const uint8_t Tag_AUTH[] = "AUTH";
static const uint8_t Tag_DATE[] = "DATE";
static const uint8_t Tag_RMRK[] = "RMRK";
static const uint8_t Tag_INFO[] = "INFO";
static const uint8_t Tag_ARPG[] = "ARPG";
static const uint8_t Tag_PLST[] = "PLST";
static const uint8_t Tag_PATT[] = "PATT";
static const uint8_t Tag_INST[] = "INST";
static const uint8_t Tag_INAM[] = "INAM";
static const uint8_t Tag_WLEN[] = "WLEN";
static const uint8_t Tag_WAVE[] = "WAVE";
static const uint8_t Typestr_AON[] = "Art of Noise";
static const char Invalid_Block[] = "Invalid block";
#ifdef USELOG
static const char Unexpected_Block[] = "Block not expected in that position";
#endif

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

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

	switch(effect)
	{
		// MOD like effects
		case 0: // Pitch: Arpeggio, No memory
		case 1: // Pitch: Slide up, No memory
		case 2: // Pitch: Slide down, No memory
		case 3: // Pitch: Portamento, Memory
		case 4: // Pitch: Vibrato, Memory
		case 5: // Pitch: Portamento 0 - Volume: Slide, No memory
		case 6: // Pitch: Vibrato 0 - Volume: Slide, No memory
		case 7: // Volume: Vibrato (alias Tremolo), Memory
		case 9: // Note: Set sample offset
		case 0xa: // Volume: Slide (up has priority), No memory
		case 0xb: // Global: Jump
		case 0xc: // Volume: Set, invalid values are rounded to max
		case 0xd: // Global: Break
		case 0xe: // Special effects
		case 0xf: // Global: Set speed/tempo
		{
			convert_effect_MOD(pSong, channel, effect, value, note);
		}
		break;
		// AON specific tags
		case 0x10: // Gxy, Volume: Set with delay: 4*x + 4, y = delay
		{
			if (value & 0xf)
				SysLog_SkipEffect(pSong, 0, oeffect, ovalue);
			value >>= 4;
			convert_effect_MOD(pSong, channel, 0xc, 4*value+4, note);
		}
		break;
		case 0x11: // H, if Note, Synth Control Bits: #0 no vol env. setup, #4 no WAVE setup
		case 0x12: // I, Wave Speed Control
		case 0x13: // J, Set Arpeggio Speed  y only (and <> 0)
		{
			SysLog_SkipEffect(pSong, 0, oeffect, ovalue);
		}
		break;
		case 0x14: // K, Pitch: Vibrato 0 - Volume: Set
		{
			convert_effect_MOD(pSong, channel, 0x4, 0, note);
			convert_effect_MOD(pSong, channel, 0xc, value, note);
		}
		break;
		case 0x15: // L, Pitch: Slide up y, No memory - Volume: Up/Down x (signed), No memory
		{
			convert_effect_MOD(pSong, channel, 0x1, value & 0xf, note);
			value >>= 4;
			if (value < 8)
				convert_effect_MOD(pSong, channel, 0xe, 0xa0 + value, note);
			else
				convert_effect_MOD(pSong, channel, 0xe, 0xb0 + (16 - value), note);
		}
		break;
		case 0x16: // M, Pitch: Slide down y, No memory - Volume: Up/Down x (signed), No memory
		{
			convert_effect_MOD(pSong, channel, 0x2, value & 0xf, note);
			value >>= 4;
			if (value < 8)
				convert_effect_MOD(pSong, channel, 0xe, 0xa0 + value, note);
			else
				convert_effect_MOD(pSong, channel, 0xe, 0xb0 + (16 - value), note);
		}
		break;
		case 0x17: // N, Avoid noise ( wave < 512 bytes), 0 = off
		case 0x18: // O, Play samples > 128K, 0 = off
		{
			// Ignore
		}
		break;
		case 0x19: // P, Pitch: Vibrato 0 - Volume: Up/Down, No memory
		{
			convert_effect_MOD(pSong, channel, 0x4, 0, note);
			if (value & 0xf0)
				convert_effect_MOD(pSong, channel, 0xe, 0xa0 + (value >> 4), note);
			else
				convert_effect_MOD(pSong, channel, 0xe, 0xb0 + (value & 0xf), note);
		}
		break;
		case 0x1a: // Q, Pitch: Slide down x<<3, No memory - Volume: Slide down y, No memory
		{
			convert_effect_MOD(pSong, channel, 0x2, (value & 0xf0) >> 1, note);
			convert_effect_MOD(pSong, channel, 0xa, (value & 0xf), note);
		}
		break;
		case 0x1b: // R, Pitch: Portamento 0 - Volume: Set
		{
			convert_effect_MOD(pSong, channel, 0x3, 0, note);
			convert_effect_MOD(pSong, channel, 0xc, value, note);
		}
		break;
		case 0x1c: // S, Pitch: Portamento 0 - Volume: Up/Down, No memory
		{
			convert_effect_MOD(pSong, channel, 0x3, 0, note);
			if (value & 0xf0)
				convert_effect_MOD(pSong, channel, 0xe, 0xa0 + (value >> 4), note);
			else
				convert_effect_MOD(pSong, channel, 0xe, 0xb0 + (value & 0xf), note);
		}
		break;
		case 0x1d: // T, Channel Volume: Set, invalid values are rounded to max
		{
			if (value <= 64)
				Pattern_AddEffect_ChannelVolume(pSong, cmd_set, value << 2);
			else
				Pattern_AddEffect_ChannelVolume(pSong, cmd_set, 256);
		}
		break;
		case 0x1e: // U, Synth Wave, x = stop, y = count
		{
			SysLog_SkipEffect(pSong, 0, oeffect, ovalue);
		}
		break;
		case 0x21: // X, Synchro Event 1
		case 0x22: // Y, Synchro Event 2
		case 0x23: // Z, Synchro Event 3
		{
			// Ignore
		}
		break;
		default:
		{
			SysLog_BadEffect(pSong, 0, oeffect, ovalue);
		}
	}
}

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;
}

typedef struct
{
	uint32_t	Offset;
	uint32_t	Size;
} Wave;

const _kernel_oserror* Loader_AON(SongHdr* pSong)
{
	const _kernel_oserror* err = NULL;
	SubSong*     pSubSong = Song_GetSubSong(pSong, 0);
	uint8_t*     pLTrack = NULL;
	uint32_t     val;
	int          i, j, k;
	Pattern*     pPattern;
	Sample*      pSmp;
	Instrument*  pInstr;
	uint8_t*     pc;
	uint32_t     FileSize, Tag, TagSize;
	uint32_t     FileOffset;
	Wave         Waves[64];

	// Allocate enough memory for a pattern
	err = Loaders_Alloc(pSong, (void**) &pLTrack, 8*64*4);
	if (err) goto loader_err;

	FileSize = FileLoad_GetSize(pSong);

	// is it an AON file?
	err = FileLoad_SetPos(pSong, 0);
	if (err) goto loader_err;
	err = FileLoad_ReadInt(pSong, &Tag, 4);
	if (err) return err;

	if ((Tag != TAG(AON4)) && (Tag != TAG(AON8)))
	{
		err = Loaders_NotThisType;
		goto loader_err;
	}

	err = FileLoad_Skip(pSong, 42);
	if (err) goto loader_err;

	pSong->Flags = Song_Flag_Instruments;
	pSong->MinPitch = NoteDelta;
	pSong->MaxPitch = NoteDelta + 12 * 5 - 1;
	pSong->Channels = (Tag == TAG(AON4)) ? 4 : 8;

	pSong->Instruments = pSong->Samples = 61;


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

	// 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(NAME))
		{
			// Title
			err = FileLoad_ReadString(pSong, &pSong->pName, TagSize, false);
			if (err) goto loader_err;
		}
		else if (Tag == TAG(AUTH))
		{
			// Author
			err = FileLoad_ReadString(pSong, &pSong->pAuthor, TagSize, false);
			if (err) goto loader_err;
		}
		else if (Tag == TAG(DATE))
		{
			// Date
		}
		else if (Tag == TAG(RMRK))
		{
			// Comments
			pSong->CommentsLen = TagSize;
			err = FileLoad_ReadString(pSong, &pSong->pComments, TagSize, false);
			if (err) goto loader_err;
		}
		else if (Tag == TAG(INFO))
		{
			// 34, Nb Orders - 1, RestartPos, 0
			if (TagSize != 4)
			{
				err = Loaders_Error(pSong, FileOffset - 4, Invalid_Block);
				goto loader_err;
			}
			err = FileLoad_ReadInt(pSong, &val, 4);
			if (err) goto loader_err;
			if ((val & 0xff) != 0x34)
			{
				err = Loaders_Error(pSong, FileOffset - 4, "Unsupported format version %.1s", (char*) &val);
				goto loader_err;
			}
			// pSubSong->SeqLen = 1 + ((val >> 8) & 0xff);
			pSubSong->RestartPos = (val >> 16) & 0xff;
		}
		else if (Tag == TAG(ARPG))
		{
			// Arpeggio list
		}
		else if (Tag == TAG(PLST))
		{
			// Pattern Sequence
			pSubSong->SeqLen = TagSize;
			if (!pSubSong->SeqLen)
			{
				err = Loaders_InvalidSong(pSong, FileOffset - 4, &pSubSong->SeqLen, pSubSong->SeqLen);
				goto loader_err;
			}
			err = Loaders_AllocSeq(pSong, pSubSong);
			if (err) goto loader_err;
			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(PATT))
		{
			// Is Tag size correct ?
			if (!TagSize || (TagSize % (pSong->Channels*64*4)))
			{
				err = Loaders_Error(pSong, FileOffset - 4, Invalid_Block);
				goto loader_err;
			}
			pSong->Patterns = TagSize / (pSong->Channels*64*4);

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

				pPattern->Rows = 64;

				// Read pattern data
				err = FileLoad_Read(pSong, pLTrack, pSong->Channels*64*4);
				if (err) goto loader_err;

				err = Loaders_StartPattern(pSong, i);
				if (err) goto loader_err;

				for (j = 0, pc = pLTrack; j < pPattern->Rows; j++)
				{
					err = Loaders_StartRow(pSong, j);
					if (err) goto loader_err;

					for (k = 0; k  < pSong->Channels; k++, pc += 4)
					{
						Note = pc[0] & 0x3f; // Ignore top bits
						if (Note)
						{
							if (Note <= 12*5)
								Note += NoteDelta - 1;
							else
							{
								SysLog_BadNote(pSong, 0, k, Note);
								Note = 0;
							}
						}
						err = Loaders_StartChannel(pSong, k, Note, pc[1]);
						if (err) goto loader_err;

						convert_effect(pSong, k, pc[2] & 0x3f, pc[3], Note);

						err = Loaders_EndChannel(pSong);
						if (err) goto loader_err;
					}

					err = Loaders_EndRow(pSong);
					if (err) goto loader_err;
				}

				err = Loaders_EndPattern(pSong, pPattern);
				if (err) goto loader_err;
			}
		}
		else if (Tag == TAG(INST))
		{
			// Read Samples
			uint32_t SmpEnd;

			// Is Tag size correct ?
			if ((TagSize % 0x20) || (TagSize > 61*0x20))
			{
				err = Loaders_Error(pSong, FileOffset - 4, Invalid_Block);
				goto loader_err;
			}

			SmpEnd = FileLoad_GetPos(pSong) + TagSize;

			for( i = 1, pSmp = pSong->pSamples, pInstr = pSong->pInstruments
			   ; FileLoad_GetPos(pSong) < SmpEnd
			   ; i++, pSmp++, pInstr++)
			{
				err = Loaders_AllocMapTable(pSong, &pInstr->pNotesMap);
				if (err) goto loader_err;

				NoteMap* pn;
				for (j = 0, pn = pInstr->pNotesMap; j < Note_Max; j++, pn++)
				{
					pn->Volume = 255;
					pn->SampleNr = i;
					pn->Pitch = j << 8;
				}

				err = FileLoad_ReadInt(pSong, &val, 4);
				if (err) goto loader_err;

				pSmp->SustainEnd = val; // Temp storage for type and wave nr
				pSmp->DefaultVolume = (val & 0xff00) >> 6;
				if (pSmp->DefaultVolume > 256)
					pSmp->DefaultVolume = 256;
				pSmp->FineTune = (val & 0xf0000) << 12;

				if (val & 0xff)
				{
					// Synth
					err = FileLoad_ReadByte(pSong, &pSmp->Size);
					if (!err) err = FileLoad_Skip(pSong, 5);
					if (err) goto loader_err;
					pSmp->Size <<= 1;
					pSmp->LoopStart = 0;
					pSmp->LoopEnd = pSmp->Size;
					pSmp->Type |= Smp_Type_Loop;
					pSmp->SustainStart = 0; // Temp storage for offset
					// Vibrato
					err = FileLoad_ReadByte(pSong, &val);
					if (err) goto loader_err;
					if (val)
					{
						SysLog_FileCorrupted(SysLog_High, pSong, 0
											, "Hybrid sample %d with vibrato", i);
					}
					err = FileLoad_Skip(pSong, 3);
					// WAVElen
					if (!err) err = FileLoad_ReadByte(pSong, &val);
					if (!err) err = FileLoad_Skip(pSong, 13);
					if (err) goto loader_err;
					if (val)
					{
						SysLog_FileCorrupted(SysLog_High, pSong, 0
											, "Hybrid sample %d with WAVE x%d", i, val);
					}
				}
				else
				{
					// Sample
					err = FileLoad_ReadReverseInt(pSong, &pSmp->SustainStart, 4); // Temp storage for offset
					if (!err) err = FileLoad_ReadReverseInt(pSong, &pSmp->Size, 4);
					if (!err) err = FileLoad_ReadReverseInt(pSong, &pSmp->LoopStart, 4);
					if (!err) err = FileLoad_ReadReverseInt(pSong, &pSmp->LoopEnd, 4);
					if (!err) err = FileLoad_Skip(pSong, 8);
					if (err) goto loader_err;
					pSmp->SustainStart <<= 1;
					pSmp->Size <<= 1;
					pSmp->LoopStart <<= 1;
					pSmp->LoopEnd &= 0xffff; // Ignore top 16 bits
					pSmp->LoopEnd <<= 1;

					if (pSmp->LoopEnd)
					{
						pSmp->Type |= Smp_Type_Loop;
						pSmp->LoopEnd += pSmp->LoopStart;
					}
				}

				// Read volume envelope
				Envelope* pEnv = &pInstr->VolumeEnvelope;
				err = Loaders_Alloc(pSong, (void**) &pEnv->pTable, 4*3);
				if (err) goto loader_err;

				uint32_t min, max, step, frames;
				err = FileLoad_ReadByte(pSong, &min); // Start volume [0-127]
				if (err) goto loader_err;
				err = FileLoad_ReadByte(pSong, &step); // Time to max volume
				if (err) goto loader_err;
				if (step)
				{
					max = 256;
					min <<= 1;
					if (min > 256) min = 256;
					pEnv->Flags = Envelope_Flag_On | Envelope_Flag_Sustain;
					pEnv->Points = 2;
					pEnv->pTable[0] = (min << 16) + 0;
					frames = (max + step - 1 - min) / step;
					pEnv->pTable[1] = (max << 16) + frames;
					err = FileLoad_ReadByte(pSong, &min); // End volume [0-127]
					if (err) goto loader_err;
					err = FileLoad_ReadByte(pSong, &step); // Time to end volume
					if (err) goto loader_err;
					if (step)
					{
						min <<= 1;
						if (min > 256) min = 256;
						pEnv->Points = 3;
						frames += (max + step - 1 - min) / step;
						pEnv->pTable[2] = (min << 16) + frames;
					}
					pEnv->SustainStart =
					pEnv->SustainEnd = pEnv->Points - 1;
				}
				else
				{
					// Deactivate envelope and skip the rest
					err = FileLoad_Skip(pSong, 2);
					if (err) goto loader_err;
					pEnv->Points = 0;
				}
			}
		}
		else if (Tag == TAG(INAM))
		{
			// Read Samples names

			// Is Tag size correct ?
			if (TagSize != 61*0x20)
			{
				err = Loaders_Error(pSong, FileOffset - 4, Invalid_Block);
				goto loader_err;
			}

			for (i = 0, pSmp = pSong->pSamples; i < pSong->Samples; i++, pSmp++)
			{
				// Name
				err = FileLoad_ReadString(pSong, &pSmp->pName, 0x20, false);
				if (err) goto loader_err;
			}
		}
		else if (Tag == TAG(WLEN))
		{
			// Read wave sizes

			// Is Tag size correct ?
			if (TagSize != 0x100)
			{
				err = Loaders_Error(pSong, FileOffset - 4, Invalid_Block);
				goto loader_err;
			}

			for (i = 0; i < 64; i++)
			{
				err = FileLoad_ReadReverseInt(pSong, &Waves[i].Size, 4);
				if (err) goto loader_err;
			}
		}
		else if (Tag == TAG(WAVE))
		{
			Waves[0].Offset = FileLoad_GetPos(pSong);
			for (i = 1; i < 64; i++)
				Waves[i].Offset = Waves[i - 1].Offset + Waves[i - 1].Size;

			uint32_t size = Waves[63].Offset + Waves[63].Size - Waves[0].Offset;
			if (size != TagSize)
				SysLog_FileCorrupted(SysLog_High, pSong, FileLoad_GetPos(pSong)
					, "Wave size %d, expected %d", TagSize, size);
			// End here for now
			break;
		}
		else
		{
			SysLog_FileCorrupted(SysLog_Medium, pSong, -8, Unexpected_Block);
		}

		// to prevent overflows
		if ((TagSize >= FileSize)
		||  (FileOffset + TagSize >= FileSize))
			break;

		FileLoad_SetPos(pSong, FileOffset + TagSize);
	}

	// Read Samples
	for (i = 1, pSmp = pSong->pSamples; i <= pSong->Samples; i++, pSmp++)
	{
		int nr = pSmp->SustainEnd >> 24; // Temp storage for wave nr

		if (pSmp->SustainEnd & 0xff) // Temp storage for type
		{
			SysLog_FileCorrupted(SysLog_High, pSong, 0, "Hybrid Sample %d", i);
			Loaders_String(pSong, &pSmp->pName, (uint8_t*) "[Hybrid sample]", 15, false);
		}

		if (nr < 64)
		{
			if (Waves[nr].Size < pSmp->SustainStart + pSmp->Size)
			{
				SysLog_FileCorrupted(SysLog_High, pSong, 0
					, "Wave %d, size %d too short for sample %d (offset %d + size %d)"
					, nr, Waves[nr].Size, i, pSmp->SustainStart, pSmp->Size);
				if (Waves[nr].Size < pSmp->SustainStart)
					pSmp->Size = 0;
				else
					pSmp->Size = Waves[nr].Size - pSmp->SustainStart;
			}

			FileLoad_SetPos(pSong, Waves[nr].Offset + pSmp->SustainStart);
			pSmp->SustainStart = pSmp->SustainEnd = 0;
			err = FileLoad_ReadSample(pSong, pSmp);

			// Continue with next sample if sample tags not correct
			if (err)
			{
				SysLog_FileCorrupted(SysLog_High, pSong, 0, "%s", &err->errmess[0]);
				err = NULL;
			}
		}
		else
		{
			pSmp->Size = 0;
			pSmp->SustainStart = pSmp->SustainEnd = 0;
			SysLog_FileCorrupted(SysLog_High, pSong, 0
				, "Sample %d has invlid wave nr %d", i, nr);
		}
	}

loader_err:
	Loaders_Free(pSong, pLTrack);

	return err;
}
