#include <stdio.h>
#include "FChannel.h"
#include "FNotes.h"
#include "FTables.h"
#include "Effects.h"
#include "GlobHdr.h"

/**
 * When new instrument nr is specified, volume/panning are taken from it.
 */
void Channel_SetInstrument(Channel* pChannel, bool bFromNewInst)
{
	const Instrument* pInst;
	const Sample* pSample;
	const Note* pNote = pChannel->pnote;

	if (bFromNewInst || !pNote)
	{
		pInst = pChannel->pinstrument;
		pSample = pChannel->psample;
	}
	else
	{
		pInst = pNote->pinstrument;
		pSample = pNote->psample;
	}

	//
	// Undefined samples are ignored to mimic S3M/IT behaviour
	//
	if (!pSample)
		return;

	//
	// Reset Instrument Parameters
	//
	if (pInst)
	{
		// set panning ?
		if (pInst->Flags & Inst_Flag_Set_Panning)
		{
			pChannel->panning.value.final =
			pChannel->panning.value.start = pInst->Panning;
		}
	}

	//
	// Reset Sample Parameters
	//
	if (pSample)
	{
		// set panning ?
		if (pSample->Type & Smp_Type_Set_Panning)
		{
			pChannel->panning.value.final =
			pChannel->panning.value.start = pSample->Panning;
		}

		// set volume
		pChannel->note_volume.value.final =
		pChannel->note_volume.value.start = pSample->DefaultVolume;
	}
}

/**
 * Fixes channel and note information when a new instrument is specified
 * but no new note is triggered.
 * For some formats, the existing note will use the new instruments,
 * while for other some parameters to be taken over from the new sample
 * and others are reset.
 */
void Channel_FixNoteInstrument(Channel* pChannel, bool bSwapSamples, bool bFromNewInst)
{
	Note* pNote = pChannel->pnote;
	const Instrument* pInst;
	const Sample* pSample;

	//
	// Case 1, do nothing if no note is played.
	// Actually, should not be so for non IT songs.
	//

	if (!pNote || (pNote->stream.pos == -1))
		return;

	if (bFromNewInst)
	{
		pInst = pChannel->pinstrument;
		pSample = pChannel->psample;
	}
	else
	{
		pInst = pNote->pinstrument;
		pSample = pNote->psample;
	}

	//
	// Undefined samples are ignored to mimic S3M/IT behaviour
	//
	if (!pSample)
		return;

	if (bSwapSamples && (pChannel->inst_id != pNote->inst_id))
	{
		//
		// Case 2, Replace with new instrument
		//

		// cut note if sample is not defined
		if (!pSample || !pSample->Size)
		{
			Note_ClearNote(pNote);
			return;
		}

		// copy channel info to note
		pNote->pinstrument = pInst;
		pNote->psample = pSample;
		pNote->inst_id = pChannel->inst_id;
		pNote->smp_id = pChannel->smp_id;
		Note_SetStreamInfo(pNote);
	}
	else
	{
		//
		// Case 3, Keep old instrument/sample.
		//
		// reset fadeout and sustain
		pNote->inst_flags |= note_inst_flag_sustain;
		pNote->inst_flags &= ~note_inst_flag_fadeout;
		pNote->fadeout_volume = 0x10000;
	}

	//
	// Reset all the controller's vibrato positions.
	//
	if (!(pChannel->channel_volume.vibrato_type & vibrato_type_noretrig))
		pChannel->channel_volume.vibrato_pos = 0;
	if (!(pChannel->panning.vibrato_type & vibrato_type_noretrig))
		pChannel->panning.vibrato_pos = 0;
	if (!(pChannel->note_volume.vibrato_type & vibrato_type_noretrig))
		pChannel->note_volume.vibrato_pos = 0;
	if (!(pChannel->note_pitch.vibrato_type & vibrato_type_noretrig))
		pChannel->note_pitch.vibrato_pos = 0;

	//
	// Reset instrument parameters from new instrument
	//
	if (pInst)
	{
		uint32_t flags = 0;
		// reset envelopes info
		if ((pInst->VolumeEnvelope.Flags & Envelope_Flag_On)
		&&  pInst->VolumeEnvelope.Points)
			flags |= note_envelope_flag_vol;
		if ((pInst->PanningEnvelope.Flags & Envelope_Flag_On)
		&&  pInst->PanningEnvelope.Points)
			flags |= note_envelope_flag_pan;
		if ((pInst->PitchEnvelope.Flags & Envelope_Flag_On)
		&&  pInst->PitchEnvelope.Points)
			flags |= note_envelope_flag_pitch;
		if ((pInst->FilterEnvelope.Flags & Envelope_Flag_On)
		&&  pInst->FilterEnvelope.Points)
			flags |= note_envelope_flag_filter;
		pNote->envelope_flags = flags;

		pNote->vol_envelope_pos = 0;
		pNote->pan_envelope_pos = 0;
		pNote->pitch_envelope_pos = 0;
		pNote->filter_envelope_pos = 0;
	}
}

/***
 * Prepare for the triggering of a new note, that is to say mark the notes
 * that will be cut so that they can be volume ramped to 0.
 *
 * Pre-requisites:
 *  inst_note, inst_id, smp_note, smp_id, pintrument and psample
 *  already refer to the new note to be played.
 */
void Channel_PreTriggerNote(Channel* pChannel, NoteHandler* pHNotes)
{
	Note* pNote = pChannel->pnote;
	const Instrument* pInst = pChannel->pinstrument;
	const Sample* pSample = pChannel->psample;

	// Undefined samples are ignored to mimic S3M/IT behaviour
	if (!pSample)
		return;

	// Prepare for the moving of previous note to the virtual pool.
	if (pNote) Note_PreApplyNoteAction(pNote, pNote->newnoteaction);

	// Prepare for the removal of duplicate notes from the virtual pool.
	if (pInst)
	{
		Notes_PreActOnDuplicateNotes
			( pHNotes
			, pInst->DuplicateNoteAction
			, pChannel
			, pChannel->inst_note
			, pChannel->inst_id
			, pChannel->smp_id
			, pInst->DuplicateCheckType
			);
	}
}

/***
 * Triggers a new note on the channel.
 * Beforehands it will:
 * - move the old note to the virtual pool or cut the note.
 * - cut notes "duplicates" of the new one from the virtual pool.
 * that will be cut so that they can be volume ramped to 0.
 *
 * Pre-requisites:
 *  inst_note, inst_id, smp_note, smp_id, pintrument and psample
 *  already refer to the new note to be played.
 */
void Channel_TriggerNote(Channel* pChannel, NoteHandler* pHNotes)
{
	Note* pNote = pChannel->pnote;
	int32_t pos_vol, pos_pan, pos_pitch, pos_filter;
	const Sample* pSample = pChannel->psample;

	// Undefined samples are ignored to mimic S3M/IT behaviour
	if (!pSample)
		return;

	//
	// 1. Stop/alter existing notes and permanant effects.
	//

	//
	// 1.1 Channel autoslides effects are disabled by the new note.
	//
	pChannel->note_volume.autoslide =
	pChannel->note_pitch.autoslide = cmd_up;

	//
	// 1.2 Move the old note to the virtual pool or cut it.
	//
	if (pNote)
	{
		Notes_MoveNoteToVirtualPool(pHNotes, pNote);
		pChannel->pnote = 0;
	}

	//
	// 1.3 Act on duplicate notes of new note in virtual pool
	//
	const Instrument* pInst = pChannel->pinstrument; // The new instrument

	Notes_ActOnDuplicateNotes
		( pHNotes
		, pInst ? pInst->DuplicateNoteAction : note_action_cut
		, pChannel
		, pChannel->inst_note
		, pChannel->inst_id
		, pChannel->smp_id
		, pInst ? pInst->DuplicateCheckType : Inst_DCT_all
		);

	//
	// 1.4 Save old note information that could be reused by the new note.
	//

	// If the new note will use the same instrument as the old note,
	// note down the current envelopes positions of the old note
	// so that they can be reused on the new note (see envelopes carry flag).
	// This is performed after NNA.
	if (pNote
	&&  (pNote->stream.pos != -1)
	&&  (pChannel->pinstrument == pNote->pinstrument))
	{
		pos_vol = pNote->vol_envelope_pos;
		pos_pan = pNote->pan_envelope_pos;
		pos_pitch = pNote->pitch_envelope_pos;
		pos_filter = pNote->filter_envelope_pos;
	}
	else
	{
		pos_vol =
		pos_pan =
		pos_pitch =
		pos_filter = 0;
	}
	pNote = 0;

	//
	// 2. New note
	//

	// Reset the positions of some retrig effects.
	pChannel->retrig_pos = pChannel->last_retrig; // bottom byte is counter
	pChannel->retrig_pos_XM = 0;
	// Resets note silencing (tremor)
	pChannel->flags &= ~ch_flags_zero_volume;

	// If no new sample is defined don't do anything.
	pSample = pChannel->psample;
	if (!pSample || !pSample->Size) return;

	//
	// 2.1 Create a new primary note.
	//
	pNote = Notes_GetNewNote(pHNotes, pChannel);
	if (!pNote) return;
	// Copy note/instrument/sample info from channel.
	pNote->pchannel = pChannel; // Important when note moves to virtual pool
	pNote->pinstrument = pInst;
	pNote->psample = pSample;
	pNote->inst_note = pChannel->inst_note;
	pNote->inst_id = pChannel->inst_id;
	pNote->smp_id = pChannel->smp_id;
	// Note down the trigger of the note (affects volume ramping, retrigs, ...)
	pChannel->flags |= ch_flags_triggernote;


	//
	// Reset all the controller's vibrato positions.
	//
	if (!(pChannel->channel_volume.vibrato_type & vibrato_type_noretrig))
		pChannel->channel_volume.vibrato_pos = 0;
	if (!(pChannel->panning.vibrato_type & vibrato_type_noretrig))
		pChannel->panning.vibrato_pos = 0;
	if (!(pChannel->note_volume.vibrato_type & vibrato_type_noretrig))
		pChannel->note_volume.vibrato_pos = 0;
	if (!(pChannel->note_pitch.vibrato_type & vibrato_type_noretrig))
		pChannel->note_pitch.vibrato_pos = 0;

	// Reset all the controller's offsets.
	pChannel->channel_volume.value.offset = 0;
	pChannel->note_volume.value.offset = 0;
	pChannel->panning.value.offset = 0;
	pChannel->note_pitch.value.offset = 0;

	// Reset the new note action
	pNote->newnoteaction = (pInst) ? pInst->NewNoteAction : note_action_cut;

	// Preset note volume and panning from instrument/sample.
	if (pChannel->new_inst_id)
	{
		// Set panning ?

		// - from sample ?
		if (pSample->Type & Smp_Type_Set_Panning)
			pChannel->panning.value.start = pSample->Panning;
		// - from instrument ?
		else if (pInst && (pInst->Flags & Inst_Flag_Set_Panning))
			pChannel->panning.value.start = pInst->Panning;

		// Set volume
		pChannel->note_volume.value.start = pSample->DefaultVolume;
	}

	if (pInst)
	{
		//
		// 2.2 Copy saved information from the old note to the new one.
		//

		pNote->vol_envelope_pos = (pInst->VolumeEnvelope.Flags & Envelope_Flag_Carry)
		                        ? pos_vol : 0;
		pNote->pan_envelope_pos = (pInst->PanningEnvelope.Flags & Envelope_Flag_Carry)
		                        ? pos_pan : 0;
		pNote->pitch_envelope_pos = (pInst->PitchEnvelope.Flags & Envelope_Flag_Carry)
		                          ? pos_pitch : 0;
		pNote->filter_envelope_pos = (pInst->FilterEnvelope.Flags & Envelope_Flag_Carry)
		                           ? pos_filter : 0;

		//
		// 2.3 Initialise variables from the instrument/sample defaults.
		//

		// Reset envelopes state.
		{
			uint32_t flags = 0;

			if ((pInst->VolumeEnvelope.Flags & Envelope_Flag_On)
			&&  pInst->VolumeEnvelope.Points)
				flags |= note_envelope_flag_vol;
			if ((pInst->PanningEnvelope.Flags & Envelope_Flag_On)
			&&  pInst->PanningEnvelope.Points)
				flags |= note_envelope_flag_pan;
			if ((pInst->PitchEnvelope.Flags & Envelope_Flag_On)
			&&  pInst->PitchEnvelope.Points)
				flags |= note_envelope_flag_pitch;
			if ((pInst->FilterEnvelope.Flags & Envelope_Flag_On)
			&&  pInst->FilterEnvelope.Points)
				flags |= note_envelope_flag_filter;
			pNote->envelope_flags = flags;
		}

// Should it not be only if new_inst_id is specified?
		// Preset filter from the instrument definition.
		if (pInst->FilterCutoff & 0x80)
			pNote->cutoff = pInst->FilterCutoff & 0x7f;
		if (pInst->FilterResonance & 0x80)
			pNote->resonance = pInst->FilterResonance & 0x7f;

		// Volume swing.
		pNote->inst_volume = pInst->pNotesMap[pNote->inst_note].Volume + 1;
		if (pInst->VolumeSwing)
		{
			// Build weighted signed random value.
			int32_t value = CRandom(Glb);
			value = ((value << 24) >> 24);
			value *= pInst->VolumeSwing;
			// Nudge volume.
			value = pNote->inst_volume
			      + ((value * pNote->inst_volume) >> 16);
			if (value < 0)
				pNote->inst_volume = 0;
			else if (value > 256)
				pNote->inst_volume = 256;
			else
				pNote->inst_volume = value;
		}

		// Panning swing, but not in case of surround.
		if ((pInst->PanningSwing)
		&&  (pChannel->panning.value.start <= 256))
		{
			// Build weighted signed random value.
			int32_t value = CRandom(Glb);
			value = ((value << 24) >> 24);
			value *= pInst->PanningSwing;
			value >>= 8;
			// Nudge panning by that value.
			value += pChannel->panning.value.start;
			if (value < 0)
				pChannel->panning.value.start = 0;
			else if (value > 256)
				pChannel->panning.value.start = 256;
			else
				pChannel->panning.value.start = value;
		}

		// Pitch-pan seperation.
		{
			int32_t value = ((pInst->PitchPanSep << 24) >> 24);

			// Not in case of surround.
			if ((value)
			&&  (pChannel->panning.value.start <= 256))
			{
				// Build value: sep * (note - center).
				value *= (pChannel->inst_note - pInst->PitchPanCenter);
				value >>= 5;
				// Nudge panning.
				value += pChannel->panning.value.start;
				if (value < 0)
					pChannel->panning.value.start = 0;
				else if (value > 256)
					pChannel->panning.value.start = 256;
				else
					pChannel->panning.value.start = value;
			}
		}
	}
	else
		pNote->inst_volume = 256;

	// Set values that will be moved from new to old for ramping.
	pChannel->note_volume.value.final = pChannel->note_volume.value.start;
	pChannel->panning.value.final = pChannel->panning.value.start;

	// Reset finetune
	pNote->finetune = pSample->FineTune >> 23;
	Note_SetStreamInfo(pNote);
}

void Channels_ClearNotes(Channel* pChannels, uint32_t count)
{
	while (count--)
	{
		pChannels->pnote = 0;
		pChannels++;
	}
}
