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

/*
 * Channels: 4, 8
 * Patterns: [1...255]
 * Rows    : 64
 * Orders  : [1...255]
 * Samples : [1...255]
 * Instr.  : 0
 * Tempo   : 50 frames per seconds.
 * Speed   : [1...31], S frames per row.
 * Notes   : [C-0...C-4...B-7]
 * Pitch   : Uses 16.16 fraction of mixing frequency (1/48 s), i.e. *2 each octave up.
 *           Slides in x*16 or x*64 fraction units per frame.
 * Volume  : [255...0], logarithmic.
 * Panning : [0...F].
 * Sampling: 8363 Hz.
 *
 * Notes:
 * Effects have no memory. So effect value 0 as usually no effect.
 * Slides are applied every frames, not on non-0 frames.
 *
 * Samples are in Archimedes logarithmic format with amplitude divided by 2 each 32 steps
 * and sign in lowest bit.
 *
 * Volume uses a logarithmic scale with 0 being loudest and 255 quietest,
 * with amplitude is divided by 2 each 32 steps (I found this because it is substracted
 * directly from the sample in the CocoPlayer module).
 *
 * Implementation issues:
 * As we already use internally at some point a representation of the pitch as a .16 fixed
 * point fraction of the base sample frequency, we will force the sample frequency
 * to 1/48us (20833 Hz) and use a relative tone to bring it back to 8363 Hz to have
 * the appropriate fraction as pitch.
 */

#define NoteDelta (Note_Central-12*4)

static const uint8_t Typestr_Coco[] = "Coconizer";

typedef struct
{
	uint8_t     Channels;
	uint8_t     Name[20]; // LF terminated
	uint8_t     Samples;
	uint8_t     Orders;
	uint8_t     Patterns;
	uint32_t    SequenceOffset;
	uint32_t    PatternsOffset;
} LHdr;

typedef struct
{
	uint32_t    Offset;
	uint32_t    Length;
	uint32_t    Volume;    // 0 = loud -> 255 = silent
	uint32_t    LoopStart; // 0 = no loop
	uint32_t    LoopSize;
	uint8_t     Name[12]; // LF terminated
} LSmp;

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 0x00: // 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 0x01: // Pitch: Slide up, No memory
		{
			if (value) Pattern_AddEffect_Pitch(pSong, cmd_up+flag_cmd_slide_frame0N0, value << 2);
		}
		break;
		case 0x02: // Pitch: Slide down, No memory
		{
			if (value) Pattern_AddEffect_Pitch(pSong, cmd_down+flag_cmd_slide_frame0N0, value << 2);
		}
		break;
		case 0x03: // Volume: Up, No memory
		{
			if (value) Pattern_AddEffect_NoteVolume(pSong, cmd_up+flag_cmd_slide_frame0, value);
		}
		break;
		case 0x04: // Volume: Down, No memory
		{
			if (value) Pattern_AddEffect_NoteVolume(pSong, cmd_down+flag_cmd_slide_frame0, value);
		}
		break;
		case 0x05: // Pitch: Slide up fine, No memory
		{
			if (value) Pattern_AddEffect_Pitch(pSong, cmd_up+flag_cmd_slide_frame0N0, value);
		}
		break;
		case 0x06: // Pitch: Slide down fine, No memory
		{
			if (value) Pattern_AddEffect_Pitch(pSong, cmd_down+flag_cmd_slide_frame0N0, value);
		}
		break;
		case 0x07: // Panning: Set
		{
			switch(value)
			{
				case 1: value = 0; break;
				case 2: value = 54; break;
				case 3: value = 96; break;
				case 4: value = 128; break;
				case 5: value = 160; break;
				case 6: value = 202; break;
				case 7: value = 256; break;
				default:
					value = 512;
					SysLog_BadEffectValue(pSong, 0, oeffect, ovalue);
			}
			if (value <= 256) Pattern_AddEffect_Panning(pSong, cmd_set, value);
		}
		break;
		case 0x08: // Volume: Auto up, 0 = stop
		{
			Pattern_AddEffect_NoteVolume(pSong, cmd_volume_auto_up+flag_cmd_slide_frame0, value);
		}
		break;
		case 0x09: // Volume: Auto down, 0 = stop
		{
			Pattern_AddEffect_NoteVolume(pSong, cmd_volume_auto_down+flag_cmd_slide_frame0, value);
		}
		break;
		case 0x0a: // Pitch: Auto slide up fine, 0 = stop
		{
			Pattern_AddEffect_Pitch(pSong, cmd_pitch_auto_up+flag_cmd_slide_frame0N0, value);
		}
		break;
		case 0x0b: // Pitch: Auto slide down fine, 0 = stop
		{
			Pattern_AddEffect_Pitch(pSong, cmd_pitch_auto_down+flag_cmd_slide_frame0N0, value);
		}
		break;
		case 0x0c: // Volume: Set, log value
		{
			Pattern_AddEffect_NoteVolume(pSong, cmd_set, 255-value);
		}
		break;
		case 0x0d: // Global: Break, value ignored
		{
			Pattern_AddGlbEffect_Position(pSong, gcmd_pos_break, 0);
		}
		break;
		case 0x0e: // Global: Jump
		{
			Pattern_AddGlbEffect_Position(pSong, gcmd_pos_jump, value);
		}
		break;
		case 0x0f: // Global: Set speed
		{
			if (value) Pattern_AddGlbEffect_Frames(pSong, gcmd_frames_set, value);
		}
		break;
		case 0x11: // Pitch: Up, No memory
		{
			if (value) Pattern_AddEffect_Pitch(pSong, cmd_up+flag_cmd_slide_frame0, value << 2);
		}
		break;
		case 0x12: // Pitch: Down, No memory
		{
			if (value) Pattern_AddEffect_Pitch(pSong, cmd_down+flag_cmd_slide_frame0, value << 2);
		}
		break;
		case 0x13: // Volume: Slide up, No memory
		{
			if (value) Pattern_AddEffect_NoteVolume(pSong, cmd_up+flag_cmd_slide_frame0N0, value);
		}
		break;
		case 0x14: // Volume: Slide down, No memory
		{
			if (value) Pattern_AddEffect_NoteVolume(pSong, cmd_down+flag_cmd_slide_frame0N0, value);
		}
		break;
		default:
		{
			SysLog_BadEffect(pSong, 0, oeffect, ovalue);
		}
	}
}

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

	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++)
		{
			Value = *pPos++;
			Note = Value >> 24;
			if (Note)
			{
				if (Note <= 12*8)
					Note += NoteDelta - 1;
				else
				{
					SysLog_BadNote(pSong, 0, k, Note);
					Note = 0;
				}
			}
			Inst = (Value >> 16) & 0xff;
			Cmd = (Value >> 8) & 0xff;
			Value &= 0xff;

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

const _kernel_oserror* Loader_Coconizer(SongHdr* pSong)
{
	const _kernel_oserror* err = NULL;
	SubSong*        pSubSong = Song_GetSubSong(pSong, 0);
	LHdr*           pLHdr = NULL;
	LSmp*           pLSmp = NULL;
	uint32_t*       pLTrack = NULL;
	Pattern*        pPattern;
	Sample*         pSmp;
	uint32_t        val;
	int             i, j;
	uint8_t*        pc;
	const uint8_t*  SmpLogToLin = Table_ArcSampleLogToLin();
	const int       ReplayFreq = 20833;
	const int       RelTone = -Convert_RelTone(8363, ReplayFreq);

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

	// Allocate memory
	err = Loaders_Alloc(pSong, (void**) &pLHdr, sizeof(LHdr));
	if (err) goto loader_err;
	err = Loaders_Alloc(pSong, (void**) &pLSmp, sizeof(LSmp));
	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 Coconizer?
	// First byte is channels count (4 or 8)
	// + 0x80 if samples are present
	if (((pLHdr->Channels != 0x84) && (pLHdr->Channels != 0x88))
	||  !pLHdr->Samples
	||  !pLHdr->Orders
	||  !pLHdr->Patterns
	||  (pLHdr->SequenceOffset != (sizeof(LHdr) + sizeof(LSmp)*pLHdr->Samples))
	||  (pLHdr->PatternsOffset < pLHdr->SequenceOffset + pLHdr->Orders)
	||  (pLHdr->PatternsOffset & 3))
	{
		err = Loaders_NotThisType;
		goto loader_err;
	}

	pSong->Flags = Song_Flag_PitchAsFraction;
	pSong->MinPitch = NoteDelta - 14; // cf Reltone
	pSong->MaxPitch = NoteDelta + 12 * 8 - 1;
	pSong->VolumeTranslation = Table_ArcVolumeLogToLin();

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

	pSong->Channels = pLHdr->Channels & 0xf;
	pSong->Samples  = pLHdr->Samples;
	pSong->Patterns = pLHdr->Patterns;
	pSubSong->SeqLen = pLHdr->Orders;
	pSubSong->Defaults.Tempo = 125;
	pSubSong->Defaults.Speed = 6;
	pSong->MinTempo = 125;
	pSong->MaxTempo = 125;
	pSong->MinSpeed = 1;
	pSong->MaxSpeed = 31;

	if (pSong->Channels == 4)
	{
		pSubSong->Defaults.ChDef[0].Panning = 68;
		pSubSong->Defaults.ChDef[1].Panning = 98;
		pSubSong->Defaults.ChDef[2].Panning = 158;
		pSubSong->Defaults.ChDef[3].Panning = 188;
	}
	else
	{
		pSubSong->Defaults.ChDef[0].Panning = 38;
		pSubSong->Defaults.ChDef[1].Panning = 68;
		pSubSong->Defaults.ChDef[2].Panning = 98;
		pSubSong->Defaults.ChDef[3].Panning = 128;
		pSubSong->Defaults.ChDef[4].Panning = 128;
		pSubSong->Defaults.ChDef[5].Panning = 158;
		pSubSong->Defaults.ChDef[6].Panning = 188;
		pSubSong->Defaults.ChDef[7].Panning = 218;
	}

	// read samples info
	for (i = 0, pSmp = pSong->pSamples; i < pSong->Samples; i++, pSmp++)
	{
		// move to sample header start
		err = FileLoad_SetPos(pSong, sizeof(LHdr) + i*sizeof(LSmp));
		if (err) goto loader_err;

		err = FileLoad_Read(pSong, pLSmp, sizeof(*pLSmp));
		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;

		// read sizes
		if (pLSmp->LoopStart > 0) pSmp->Type |= Smp_Type_Loop;

		pSmp->Size         = pLSmp->Length;
		pSmp->LoopStart    = pLSmp->LoopStart;
		pSmp->LoopEnd      = pLSmp->LoopStart + pLSmp->LoopSize;
		pSmp->DefaultVolume = 255-(pLSmp->Volume & 0xff);
		pSmp->Frequency = ReplayFreq;
		pSmp->RelTone = RelTone;

		// read data
		err = FileLoad_SetPos(pSong, pLSmp->Offset);
		if (err)
		{
			SysLog_FileCorrupted(SysLog_Medium, pSong, sizeof(LHdr) + i*sizeof(LSmp), "Sample %d: invalid data offset", 1 + (pSmp - pSong->pSamples));
			pSmp->Size = 0;
		}
		else
		{
			err = FileLoad_ReadSample(pSong, pSmp);
			if (err) goto loader_err;
			// Convert from Log to Lin
			for (j = 0, pc = pSmp->Ptr; j < pSmp->Size; j++, pc++)
			{
				*pc = SmpLogToLin[*pc];
			}
		}
	}

	// move to sequence start
	err = FileLoad_SetPos(pSong, pLHdr->SequenceOffset);
	if (err) goto loader_err;

	err = Loaders_AllocSeq(pSong, pSubSong);
	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;
	}

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

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

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

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

		err = Pattern_Coco(pSong, i, pPattern, pLTrack);
		if (err) goto loader_err;
	}

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

	return err;
}
