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

/**
 * Channels: 8
 * Patterns: [1...128]
 * Rows    : [1...64] (even if pattern is always stored as block of 64 rows)
 * Orders  : [1...128]
 * Samples : [1...64]
 * Instr.  : 0
 * Tempo   : Fixed at 31 frames/second.
 * Speed   : [1...15].
 *           Meaning of value 0 used by UNIS 669 is unknown.
 * Notes   : [C-0...C-2...B-4]
 * Pitch   : Uses frequency fraction: 100 is C-1, 200 C-2, 400 C-3, ...
 *           Slides in x/1 units per frame.
 *           See remark below on pitch limits.
 * Volume  : [0...F], linear.
 * Panning : [0...F].
 * Sampling: 8300 Hz
 *
 * Doc errors:
 * - SBPro channels 1,3,5,7 are left 2,4,6,8 are right. When listening it is actually the reverse.
 *   There are also controls for altering the card's left and right levels and they work as should,
 *   so it's not an inversion by DOSBox.
 * - Sample loops if loop end < sample size. In reality it is <=.
 *
 * Doc clarification, arrays of tempo and pattern breaks:
 *   Edition in Composer669 shows tempo and pattern break (=number of rows)
 *   in the order window while they are associated to the pattern in the file.
 *   Changing these values changes all the values in the order window refering
 *   to the same pattern, confirming that these values are stored in the pattern
 *   not in the order sequence.
 *
 * The Composer669 is somewhat deviant in its behaviour compared to other trackers
 *   in that the effects continue to work in the rows that follow till it is cancelled
 *   by a new note, another command (a - e) or by an effect with a value of 0.
 *
 * Since the tracker has no looping/branching effects, we will rewrite the patterns
 *   based on the song sequence (1 pattern per sequence entry) and memorize
 *   the last effect used in each column.
 *
 * Not much is described about the values of the effects:
 *   - Are the applied once per row, every frame?
 *   - What is their correspondance in to other trackers: the same, twice finer, ...?
 *   - Effect d seems to correspond to a fine tune, but is it a signed value?
 *
 * So, first, what is a 669 tempo? It's range is [1-15] + an ultra fast value 0 for UNIS669.
 *   While using Composer 669 I noticed that:
 *   - using pitch slides, the time taken to slide from a given pitch to another
 *     is independant of the tempo.
 *   - doubling the tempo halves the number of rows consumed in a given time.
 *   This means that the 669 tempo acts as a speed. But what is the tempo base?
 *   Actually, the hint is given by the Farandole Composer doc which states that it uses
 *   the same tempo as Composer 669, and specify that the tempo gives the duration of the row
 *   as tempo / 32 Hz. The reality is somewhat different thought, after timing several songs
 *   I have to assume that it is not 32 but 31 Hz i.e. it corresponds to MOD tempo 77.5.
 *   Also from mesure, sample base frequency is 8300.
 *
 *   Note speed = 669 tempo cannot work for UNIS669's tempo 0, but no test could be performed
 *   to determine its meaning. So we will assume that "super fast" tempo 0 means higher than 15 value
 *   not play faster.
 *   => Implementation: define tempo 0 as tempo 0x10.
 *
 * Pitch mode, units for pitch slides?
 *   Slide b1 tests made by ear, gives about 390 frames to slide from C4 to C3, 195 from C3 to C-2
 *   and 100 from C2 to C1. It confirms that 669 works with a variable which is either a frequency
 *   or a fraction with which to multiply the base frequency. It should be noted that slides
 *   are applied even on frame 0, as it works even on tempo 1. Now for the step, my guess is that
 *   100 represents C-1, 200 C-2 (central note), 400 C-3, etc.
 *   => Implementation: We will use the fraction mode: central note is 1<<16 and slides unit is
 *      16/(1<<16) so we have to multiply 669 slides by (1<<16)/16/200 ~= 21 to slide by the same.
 *      amount Comparative waves of a slide up, shows that rounded up value of 21 is about right.
 *
 * Pitch limits:
 *   Testing slide up shows only that after a while sound becomes garbage and then slides up again
 *   from the original pitch. It is as if the slide is retained as a counter which is added
 *   to the base tone and that this counter overflows.
 *   Testing slide down shows that the sound quickly becomes garbage and then slides up (not down
 *   as expected). This can be heard on "IBM goes Heavy" or "InfoHit!". It is as if the slide is
 *   retained as a counter which is substracted from the base value and that at some point
 *   the result becomes negative and we enter the domain of negative frequencies.
 *   => Implementation: disable limits. The player was modified to cope this negative frequencies,
 *      but the overflow vehaviour (which is never used in songs anyway) will not be reproduced
 *      since the module will clip frequency to maximal (absolute) value.
 *
 * Effect d, acts like a finetune by offsetting the base frequency, except that it can be modified
 *   at any time, that it always add to the frequency (no negative values) and that
 *   it works in twice the units of slides.
 *
 * Vibrato, since there is no speed variable, how does it work?
 *   From the sound, it's somewhat like an arpeggio which switches between 2 very distinct freqs,
 *   cleary between the normal frequency and an higher one.
 *   => Implementation: Vibrato with halfsquare waveform, speed 128 to alternate between +max, 0
 *      every frame and a depth 16 times deeper than a slide.
 */

typedef struct
{
	uint16_t    TAG;
	uint8_t     Comments[108];
	uint8_t     Samples;
	uint8_t     Patterns;
	uint8_t     RestartPos;
	uint8_t     Seq[128];
	uint8_t     Tempo[128];
	uint8_t     Row[128];
} LHdr;

typedef struct
{
	uint8_t     Dummy[3]; // for alignment
	uint8_t     Filename[13];
	uint32_t    Size;
	uint32_t    LoopStart;
	uint32_t    LoopEnd; // 0 for no loop
} LSmp;

#define NoteDelta   (Note_Central-24)

static const uint8_t Tag_if[] = "if";
static const uint8_t Tag_JN[] = "JN"; // Note used if UNIS 669 extensions are present in song
static const uint8_t Typestr_669[] = "Composer 669";
static const uint8_t Typestr_U669[] = "UNIS 669";
#define TAG(x) (*(const uint16_t*) Tag_##x)

static uint8_t convert_effect(SongHdr* pSong, uint32_t effect, uint32_t value)
{
	uint8_t save = 0xff; // Need to memorize continuous effect?

	switch(effect)
	{
		case 0: // a Pitch: Slide up (till reset), No memory
		{
			if (value) save = (effect << 4) + value;
			Pattern_AddEffect_Pitch(pSong, cmd_pitch_auto_up+flag_cmd_slide_frame0N0, value * 21);
		}
		break;
		case 1: // b Pitch: Slide down (till reset), No memory
		{
			if (value) save = (effect << 4) + value;
			Pattern_AddEffect_Pitch(pSong, cmd_pitch_auto_down+flag_cmd_slide_frame0N0, value * 21);
		}
		break;
		case 2: // c Pitch: Portamento (till reset), No memory
		{
			if (value) save = (effect << 4) + value;
			Pattern_AddEffect_Pitch(pSong, cmd_pitch_auto_portamento+flag_cmd_slide_frame0N0, value * 21);
		}
		break;
		case 3: // d Note: Add to base pitch (kind like a finetune)
		{
			if (value) save = (effect << 4) + value;
			Pattern_AddEffect_Pitch(pSong, cmd_pitch_setoffset, value * 21 * 2);
		}
		break;
		case 4: // e Pitch: Vibrato, No memory, sounds like an arpeggio with only 2 values
		{
			if (value) save = (effect << 4) + value;
			value *= 21;
			Pattern_AddEffect_Pitch(pSong, cmd_pitch_auto_vibrato | flag_cmd_vibrato_frame0N0, value);
		}
		break;
		case 5: // f Global: Set speed
		{
			if ((pSong->pType == Typestr_U669) || value)
			{
				if (!value) value = 0x10;
				Pattern_AddGlbEffect_Frames(pSong, gcmd_frames_set, value);
			}
		}
		break;
		case 6: // g subcommands
		{
			if (pSong->pType == Typestr_U669)
			{
				switch (value)
				{
					case 0: // Panning: Slide left by fixed amount
					{
						effect = cmd_down + flag_cmd_slide_frame0;
						Pattern_AddEffect_Panning(pSong, effect, 0xe);
					}
					break;
					case 1: // Panning: Slide right by fixed amount
					{
						effect = cmd_up + flag_cmd_slide_frame0;
						Pattern_AddEffect_Panning(pSong, effect, 0xe);
					}
					break;
					default:
					{
						// Unknown effect
						SysLog_BadEffect(pSong, 0, effect, value);
					}
				}
			}
			else
			{
				// Unknown effect
				SysLog_BadEffect(pSong, 0, effect, value);
			}
		}
		break;
		case 7: // Slot retrig
		{
			if (pSong->pType == Typestr_U669)
			{
				SysLog_SkipEffect(pSong, 0, effect, value);
			}
			else
			{
				// Unknown effect
				SysLog_BadEffect(pSong, 0, effect, value);
			}
		}
		break;
		default:
		{
			// Unknown effect
			SysLog_BadEffect(pSong, 0, effect, value);
		}
	}

	return save;
}

static const _kernel_oserror* Pattern_669
	( SongHdr* pSong
	, uint32_t i
	, uint32_t tempo
	, Pattern* pPattern
	, const uint8_t* pPos
	, uint8_t* effects
	)
{
	const _kernel_oserror*    err = NULL;
	int                 j, k;
	uint32_t            Note, Inst, Vol;

	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 (j == 0) convert_effect(pSong, 5, tempo & 0xf);

		for (k = 0; k < pSong->Channels; k++, pPos += 3)
		{
			// Read note, instrument and Volume
			if (pPos[0] < 0xfe)
			{
				Note = (pPos[0] >> 2) + NoteDelta;
				Inst = (pPos[0] & 0x3) << 4;
				Inst |= pPos[1] >> 4;
				Inst++;
				// reset old effect
				effects[k] = 0xff;
			}
			else
			{
				Note = 0;
				Inst = 0;
			}

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

			if (pPos[0] < 0xff)
			{
				Vol = pPos[1] & 0xf;
				Vol |= Vol << 4;
				Pattern_AddEffect_NoteVolume(pSong, cmd_set, Vol);
			}

			if (pPos[2] != 0xff)
			{
				// cancel old effect, unless its the same type
				if ((effects[k] != 0xff) && ((pPos[2] >> 4) != (effects[k] >> 4)))
					convert_effect(pSong, effects[k] >> 4, 0);

				effects[k] = pPos[2];
				effects[k] = convert_effect(pSong, effects[k] >> 4, effects[k] & 0xf);
			}

			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_669(SongHdr* pSong)
{
	const _kernel_oserror* err = NULL;
	LHdr*           pLHdr = NULL;
	LSmp*           pLSmp = NULL;
	uint8_t*        pLTrack = NULL;
	SubSong*        pSubSong = Song_GetSubSong(pSong, 0);
	Pattern*        pPattern;
	Sample*         pSmp;
	int             i, j;
	uint8_t         effects[8];
	uint8_t*        pc;

	// Reject file to short to contain header
	if (FileLoad_GetSize(pSong) < 0x1f1)
		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, 0x1f1);
	if (err) goto loader_err;

	// check if really 669 Module?
	if (((pLHdr->TAG != TAG(if)) && (pLHdr->TAG != TAG(JN)))
	||  !pLHdr->Samples
	||  (pLHdr->Samples > 64)
	||  !pLHdr->Patterns
	||  (pLHdr->Patterns > 128)
	||  (pLHdr->RestartPos > 127)
	||  (pLHdr->Seq[127] != 0xff))
	{
		err = Loaders_NotThisType;
		goto loader_err;
	}

	// set version
	if (pLHdr->TAG == TAG(if))
		pSong->pType = Typestr_669;
	else
		pSong->pType = Typestr_U669;
	pSong->Version = 100;

	pSong->Flags = Song_Flag_PitchAsFraction;
	pSong->MinPitch = 0; // Ignore limits
	pSong->MaxPitch = 0; // Ignore limits
	pSong->TempoBase = Tempo_Base_20Hz;
	pSong->MinTempo
		= pSong->MaxTempo
		= pSubSong->Defaults.Tempo = 620;
	pSubSong->Defaults.Speed = 4;
	pSubSong->Defaults.VibratoType = vibrato_type_halfsquare | vibrato_type_inverted;
	pSubSong->Defaults.VibratoSpeed = 128;
	pSubSong->Defaults.VibratoStrength = 16;
	pSubSong->RestartPos = pLHdr->RestartPos;

	pSong->Samples  = pLHdr->Samples;
	pSong->Channels = 8;

	pSong->CommentsLen = sizeof(pLHdr->Comments) + 3;
	err = Loaders_AllocString(pSong, (void**) &pSong->pComments, pSong->CommentsLen);
	if (err) goto loader_err;

	pc = pSong->pComments;
	for (i = 0, j = 0; i < sizeof(pLHdr->Comments); i++, j++)
	{
		if (j == 36)
		{
			*pc++ = '\n';
			j = 0;
		}
		*pc = pLHdr->Comments[i];
		if (*pc < ' ') *pc = ' ';
		pc++;
	}
	*pc++ = '\n';
	*pc = 0;

	pSubSong->SeqLen = 128;
	err = Loaders_AllocSeq(pSong, pSubSong);
	if (err) goto loader_err;

	// fill the pattern sequence
	pSubSong->SeqLen = 0;
	for (i = 0; i < 128; i++)
	{
		if (pLHdr->Seq[i] < pLHdr->Patterns)
			pSubSong->SeqLen = i + 1;
		pSubSong->pSeqs[i] = pLHdr->Seq[i];
	}

	if (!pSubSong->SeqLen)
	{
		err = Loaders_InvalidSong(pSong, 0x71, &pSubSong->SeqLen, pSubSong->SeqLen);
		goto loader_err;
	}

	// set panning
	pSubSong->Defaults.ChDef[0].Panning
	= pSubSong->Defaults.ChDef[2].Panning
	= pSubSong->Defaults.ChDef[4].Panning
	= pSubSong->Defaults.ChDef[6].Panning
	= 256;
	pSubSong->Defaults.ChDef[1].Panning
	= pSubSong->Defaults.ChDef[3].Panning
	= pSubSong->Defaults.ChDef[5].Panning
	= pSubSong->Defaults.ChDef[7].Panning
	= 0;

	// read samples info
	for (i = 0, pSmp = pSong->pSamples; i < pSong->Samples; i++, pSmp++)
	{
		err = FileLoad_Read(pSong, &pLSmp->Filename[0], sizeof(*pLSmp) - 3);
		if (err) goto loader_err;

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

		// read sizes
		pSmp->Size      = pLSmp->Size;
		pSmp->LoopStart = pLSmp->LoopStart;
		pSmp->LoopEnd   = pLSmp->LoopEnd;
		pSmp->Type      = Smp_Type_Unsigned;
		pSmp->Frequency = 8300;

		if (pSmp->LoopEnd <= pLSmp->Size)
			pSmp->Type |= Smp_Type_Loop;
	}

	// Fill the patterns
	// Due to effects cancelling previous effects,
	// we will rewite the sequence and patterns.
	err = Loaders_Alloc(pSong, (void**) &pLTrack, 0x600*pLHdr->Patterns);
	if (err) goto loader_err;
	err = FileLoad_Read(pSong, pLTrack, 0x600*pLHdr->Patterns);
	if (err) goto loader_err;

    for (i = 0; i < pSong->Channels; i++)
    	effects[i] = 0xff;

	pSong->Patterns = 0;
	pPattern = pSong->pPatterns;
	for (i = 0; i < pSubSong->SeqLen; i++)
	{
		j = pSubSong->pSeqs[i];
		if (j < pLHdr->Patterns)
		{
			pPattern->Rows = pLHdr->Row[j] + 1;

			err = Pattern_669(pSong, pSong->Patterns, pLHdr->Tempo[j], pPattern, pLTrack+j*0x600, effects);
			if (err) goto loader_err;

			pSubSong->pSeqs[i] = pSong->Patterns;
			pSong->Patterns++;
			pPattern++;
		}
		else pSubSong->pSeqs[i] = -1;
	}

	Loaders_Free(pSong, pLTrack);
	pLTrack = NULL;

	// read samples data
	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;
}
