#include "jaudio_NES/seqsetup.h"

#include "jaudio_NES/jammain_2.h"
#include "jaudio_NES/noteon.h"
#include "jaudio_NES/fat.h"
#include "jaudio_NES/playercall.h"
#include "jaudio_NES/jamosc.h"
#include "jaudio_NES/driverinterface.h"
#include "jaudio_NES/oneshot.h"
#include "jaudio_NES/fat.h"

#include "dolphin/os/OSInterrupt.h"

#define SEQ_SIZE             (256)
#define ROOT_OUTER_SIZE      (16)
#define ROOTSEQ_SIZE         (16)
#define FREE_SEQP_QUEUE_SIZE (256)

static seqp_ seq[SEQ_SIZE];
static OuterParam_ ROOT_OUTER[ROOT_OUTER_SIZE];
static seqp_* rootseq[ROOTSEQ_SIZE];
static seqp_* FREE_SEQP_QUEUE[FREE_SEQP_QUEUE_SIZE];

static u32 BACK_P;
static u32 GET_P;
static u32 SEQ_REMAIN;

/*
 * --INFO--
 * Address:	80013DA0
 * Size:	0000A0
 */
void Jaq_Reset(void)
{
	int i;

	for (i = 0; i < SEQ_SIZE; ++i) {
		seq[i].trackState    = 0;
		seq[i].childMuteMask = 0;
		seq[i].isMuted       = 0;
		FREE_SEQP_QUEUE[i]   = &seq[i];
	}

	BACK_P     = 0;
	GET_P      = 0;
	SEQ_REMAIN = FREE_SEQP_QUEUE_SIZE;

	for (i = 0; i < ROOTSEQ_SIZE; ++i) {
		rootseq[i] = NULL;
	}

	Jam_InitRegistTrack();
}

/*
 * --INFO--
 * Address:	........
 * Size:	000008
 */
void Jaq_GetRemainFreeTracks(void)
{
	// UNUSED FUNCTION
}

/*
 * --INFO--
 * Address:	80013E40
 * Size:	000088
 */
static BOOL BackTrack(seqp_* track)
{
	seqp_** REF_track;

	REF_track         = &track;
	track->trackState = 0;
	if (track->_3E4 == 1) {
		if (SEQ_REMAIN == FREE_SEQP_QUEUE_SIZE) {
			return FALSE;
		}

		FREE_SEQP_QUEUE[BACK_P] = track;
		++SEQ_REMAIN;
		++BACK_P;

		if (BACK_P == FREE_SEQP_QUEUE_SIZE) {
			BACK_P = 0;
		}
		return TRUE;
	}
	return FALSE;
}

/*
 * --INFO--
 * Address:	80013EE0
 * Size:	000064
 */
static seqp_* GetNewTrack()
{
	seqp_* track;

	if (SEQ_REMAIN == 0) {
		return NULL;
	}

	track = FREE_SEQP_QUEUE[GET_P];
	--SEQ_REMAIN;
	++GET_P;

	if (GET_P == FREE_SEQP_QUEUE_SIZE) {
		GET_P = 0;
	}

	track->trackState = 2;
	track->_3E4       = 1;
	return track;
}

/*
 * --INFO--
 * Address:	80013F60
 * Size:	000048
 */
int AllocNewRoot(seqp_* track)
{
	int i;

	for (i = 0; i < ROOTSEQ_SIZE; ++i) {
		if (!rootseq[i]) {
			rootseq[i] = track;
			return i;
		}
	}
	return -1;
}

/*
 * --INFO--
 * Address:	80013FC0
 * Size:	00004C
 */
int DeAllocRoot(seqp_* track)
{
	int i;

	for (i = 0; i < ROOTSEQ_SIZE; ++i) {
		if (rootseq[i] == track) {
			rootseq[i] = NULL;
			return i;
		}
	}
	return -1;
}

/*
 * --INFO--
 * Address:	80014020
 * Size:	000018
 */
seqp_* Jaq_HandleToSeq(u32 handle)
{
	return rootseq[handle];
}

/*
 * --INFO--
 * Address:	80014040
 * Size:	000368
 */
static void Init_Track(seqp_* track, u32 dataAddress, seqp_* parent)
{
	int i;

	if (!parent) {
		track->baseData         = (u8*)dataAddress;
		track->programCounter   = 0;
		track->tempo            = 120;
		track->timeBase         = AUDIO_TATUMS_PER_BEAT;
		track->timeRelationMode = 1;
		track->isPaused         = FALSE;
		track->pauseStatus      = 10;
		track->childMuteMask    = 0;
		track->isMuted          = 0;
	} else {
		track->baseData         = parent->baseData;
		track->programCounter   = dataAddress;
		track->fileHandle       = parent->fileHandle;
		track->tempo            = parent->tempo;
		track->doChangeTempo    = FALSE;
		track->tempoFactor      = parent->tempoFactor;
		track->timeBase         = parent->timeBase;
		track->timeRelationMode = parent->timeRelationMode;
		track->isPaused         = parent->isPaused;
		track->pauseStatus      = parent->pauseStatus;
		track->childMuteMask    = 0;
	}
	track->callStackDepth  = 0;
	track->_D0             = 0;
	track->waitTimer       = 0;
	track->trackState      = 1;
	track->parent          = parent;
	track->interruptEnable = 0;
	track->interruptActive = 0;
	track->timer           = 0;

	// Initialize all MoveParams with default values.
	for (i = 0; i < 18; ++i) {
		track->timedParam.move[i].duration     = 0.0f;
		track->timedParam.move[i].currentValue = 1.0f;
		track->timedParam.move[i].targetValue  = 1.0f;
	}

	track->timedParam.inner.pitch.currentValue = 0.0f;
	track->timedParam.inner.pitch.targetValue  = 0.0f;
	track->timedParam.inner.pitch.currentValue = 0.0f; // Just to be sure.
	track->timedParam.inner.pitch.targetValue  = 0.0f; // Just to be sure.
	track->timedParam.inner.pan.currentValue   = 0.5f;
	track->timedParam.inner.pan.targetValue    = 0.5f;

	track->timedParam.inner._100.currentValue = 0.5f;
	track->timedParam.inner._100.targetValue  = 0.5f;
	track->timedParam.inner._110.currentValue = 0.0f;
	track->timedParam.inner._110.targetValue  = 0.0f;

	track->timedParam.inner.fxmix.currentValue = 0.0f;
	track->timedParam.inner.fxmix.targetValue  = 0.0f;
	track->timedParam.inner.dolby.currentValue = 0.0f;
	track->timedParam.inner.dolby.targetValue  = 0.0f;

	// Initialize IIRs (skipping the first one)
	for (i = 1; i < 4; ++i) {
		track->timedParam.inner.IIRs[i].currentValue = 0.0f;
		track->timedParam.inner.IIRs[i].targetValue  = 0.0f;
	}

	track->timedParam.inner.distFilter.currentValue = 0.0f;
	track->timedParam.inner.distFilter.targetValue  = 0.0f;

	for (i = 0; i < 32; ++i) {
		track->regParam.reg[i] = 0;
	}

	if ((track->flags & 2) || !track->parent) {
		track->regParam.param.arguments[0] = 0;
		track->regParam.param.arguments[1] = 1;
		track->regParam.param.arguments[2] = 1;
		track->regParam.param.arguments[3] = 0x7fff;
		track->regParam.param.arguments[4] = 0x4000;
		for (i = 0; i < 3; ++i) {
			track->panCalcTypes[i]                  = 2;
			track->parentPanCalcTypes[i]            = 2;
			track->parentController.panCalcTypes[i] = 26;
		}
		track->regParam.param.bankNumber   = 0xf0;
		track->regParam.param.pitchScale   = 0x0c;
		track->regParam.param.basePriority = 0x40;
	} else {
		for (i = 0; i < 5; ++i) {
			track->regParam.param.arguments[i] = track->parent->regParam.param.arguments[i];
		}
		track->regParam.param.bankNumber   = track->parent->regParam.param.bankNumber;
		track->regParam.param.pitchScale   = track->parent->regParam.param.pitchScale;
		track->regParam.param.basePriority = track->parent->regParam.param.basePriority;
		for (i = 0; i < 3; ++i) {
			track->panCalcTypes[i]                  = track->parent->panCalcTypes[i];
			track->parentPanCalcTypes[i]            = track->parent->parentPanCalcTypes[i];
			track->parentController.panCalcTypes[i] = track->parent->parentController.panCalcTypes[i];
		}
	}

	for (i = 0; i < 16; ++i) {
		track->childOuterParams[i] = NULL;
		track->children[i]         = 0;
	}

	for (i = 0; i < 8; ++i) {
		track->_94[i]            = -1;
		track->channels[i]       = NULL;
		track->activeSoundIds[i] = 0;
	}
	track->_D4 = 0;
	track->_D5 = 0;
	track->_90 = 0;
	track->_D6 = FALSE;
	Osc_Init_Env(track);
	track->transpose      = 0;
	track->finalTranspose = 0;
	track->tickCounter    = -1;

	for (i = 0; i < 16; ++i) {
		track->trackPort[i].importFlag = 0;
		track->trackPort[i].exportFlag = 0;
	}

	track->isRegistered = 0;
}

/*
 * --INFO--
 * Address:	800143C0
 * Size:	0000A0
 */
BOOL Jaq_StopSeq(s32 index)
{
	seqp_* track;

	if (index == -1) {
		return FALSE;
	}
	track = rootseq[index];
	if (!track) {
		return FALSE;
	}

	switch (track->trackState) {
	case 0:
		break;
	case 2:
		BackTrack(track);
		DeAllocRoot(track);
		break;
	default:
		track->trackState = 3;
		break;
	}
	return TRUE;
}

/*
 * --INFO--
 * Address:	80014460
 * Size:	000054
 */
static void __StopSeq(seqp_* track)
{
	SeqUpdate(track, 0);
	Jaq_CloseTrack(track);
	DeAllocRoot(track);
	if (track->dataSourceMode == 1) {
		FAT_FreeMemory(track->fileHandle);
	}
}

/*
 * --INFO--
 * Address:	800144C0
 * Size:	000024
 */
s32 Jaq_SetSeqData(seqp_* param_1, u8* param_2, u32 param_3, u32 param_4)
{
	return Jaq_SetSeqData_Limit(param_1, param_2, param_3, param_4, 0);
}

/*
 * --INFO--
 * Address:	80014500
 * Size:	000170
 */
s32 Jaq_SetSeqData_Limit(seqp_* track, u8* param_2, u32 param_3, u32 param_4, u8 param_5)
{
	s32 root;
	BOOL level;
	u8* puVar2;

	if (!track) {
		level = OSDisableInterrupts();
		track = GetNewTrack();
		OSRestoreInterrupts(level);
		if (!track) {
			return -1;
		}
	} else {
		track->_3E4 = 0;
	}

	root = AllocNewRoot(track);
	if (root == -1) {
		return -1;
	}

	track->dataSourceMode = param_4;
	switch (param_4) {
	case 0:
		puVar2 = param_2;
		break;
	case 1:
		track->fileHandle = FAT_AllocateMemory(param_3);
		if (track->fileHandle == 0xffff) { // Isn't this literally impossible?
			return -1;
		}
		FAT_StoreBlock(param_2, track->fileHandle, 0, param_3);
		puVar2 = NULL;
		break;
	case 2:
		track->fileHandle = (u8)param_2;
		puVar2            = NULL;
		break;
	}
	track->trackId = root;
	track->flags   = 3;
	Init_Track(track, (u32)puVar2, NULL);
	Jam_InitExtBuffer(&ROOT_OUTER[root]);
	Jam_AssignExtBuffer(track, &ROOT_OUTER[root]);
	Init_1shot(&track->parentController, param_5);
	track->tempoAccumulator = 0.0f;
	track->tempoFactor      = 1.0f;
	Jam_UpdateTrackAll(track);
	track->trackState = 2;
	return root;
}

/*
 * --INFO--
 * Address:	80014680
 * Size:	00002C
 */
BOOL Jaq_SetBankNumber(seqp_* track, u8 bankNum)
{
	u8 lo;

	// Let's get ahead of ourselves here.
	lo = track->regParam.param.bankNumber & 0xFF;
	if (!track) {
		return FALSE;
	}

	track->regParam.param.bankNumber = (bankNum << 8) | lo;
	return TRUE;
}

static s32 Jaq_RootCallback(void* track);

/*
 * --INFO--
 * Address:	800146C0
 * Size:	0000B4
 */
BOOL Jaq_StartSeq(u32 param_1)
{
	seqp_* track;
	u8* lbzu;

	if (param_1 == -1) {
		return FALSE;
	}

	track = rootseq[param_1];
	if (!track) {
		return FALSE;
	};

	// This feels like a fakematch, but oh well.
	switch (*(lbzu = &track->trackState)) {
	case 0:
		return FALSE;
	case 1:
		return FALSE;
	case 3:
		return FALSE;
	case 2:
		*lbzu = 1;
	}
	Jac_RegisterDspPlayerCallback(&Jaq_RootCallback, rootseq[param_1]);
	return TRUE;
}

/* Flags bit layout (u32):
 * -----------------------
 * Bits 0-3   : Index Selector (0 to 15)
 * Bit 5      : Direct Register Read Flag
 *               - If set, index is read from hardware register
 *               - Forces mode to 4
 * Bits 6-7   : Mode Flags (0 to 3)
 */

/*
 * --INFO--
 * Address:	80014780
 * Size:	00014C
 */
s32 Jaq_OpenTrack(seqp_* track, u32 flags, u32 source)
{

	seqp_* oldChildTrack;
	seqp_* newChildTrack;
	u8 childIndex;
	u8 trackFlags;

	childIndex = (flags & 0b00001111);
	trackFlags = (flags & 0b11000000) >> 6;
	if ((flags & 0b00100000)) {
		trackFlags = 4;
	}

	if ((u8)(flags & 0b00100000)) { // This u8 cast is a repeating pattern across JAudio... Mysterious.
		childIndex = Jam_ReadRegDirect(track, childIndex);
	}

	if (childIndex >= 16) {
		return -1;
	}

	oldChildTrack = track->children[childIndex];
	if (oldChildTrack) {
		Jaq_CloseTrack(oldChildTrack);
	}

	newChildTrack = GetNewTrack();
	if (!newChildTrack) {
		return -1;
	}

	track->children[childIndex] = newChildTrack;

    u32 trackId = track->trackId;
	newChildTrack->trackId        = ((trackId << 4 | childIndex) & 0xFFFFFFF) | ((trackId & 0xF0000000) + 0x10000000);
	newChildTrack->connectionId   = 0;
	newChildTrack->dataSourceMode = track->dataSourceMode;
	newChildTrack->flags          = trackFlags;

	Init_Track(newChildTrack, source, track);

	// Dev rolls "worst bit extraction method", asked to leave Nintendo EAD.
	newChildTrack->isMuted = newChildTrack->parent->isMuted | ((newChildTrack->parent->childMuteMask & (1 << childIndex)) >> childIndex);
	newChildTrack->outerParams = newChildTrack->parent->childOuterParams[childIndex];
	if (newChildTrack->outerParams) {
		++newChildTrack->outerParams->refCount;
	}

	Init_1shot(&newChildTrack->parentController, 0);
	Jam_UpdateTrackAll(newChildTrack);
	return 0;
}

/*
 * --INFO--
 * Address:	800148E0
 * Size:	0000B4
 */
void __AllNoteOff(seqp_* track)
{
	u32 i;

	if (!track->parent) {
		for (i = 0; i < 8; ++i) {
			NoteOFF_R(track, i, 10);
			track->_94[i]      = -1;
			track->channels[i] = NULL;
		}
	} else {
		for (i = 0; i < 8; ++i) {
			NoteOFF(track, i);
			track->_94[i]      = -1;
			track->channels[i] = NULL;
		}
	}
}

/*
 * --INFO--
 * Address:	800149A0
 * Size:	000120
 */
u32 Jaq_CloseTrack(seqp_* track)
{
	size_t i;

	// Specifically two separate conditional blocks, because why not.
	if (!track) {
		return 0;
	}
	if (track->trackState == 0) {
		return 0;
	}

	__AllNoteOff(track);
	BackTrack(track);

	for (i = 0; i < 16; ++i) {
		if (track->children[i]) {
			Jaq_CloseTrack(track->children[i]);
			track->children[i] = NULL;
		}
	}

	if (track->outerParams) {
		// This smells like refcounting.
		track->outerParams->refCount -= 1;
		track->outerParams = NULL;
	}

	for (i = 0; i < 16; ++i) {
		if (track->childOuterParams[i]) {
			track->childOuterParams[i]->isAssigned = FALSE;
			track->childOuterParams[i]             = NULL;
		}
	}
	track->isMuted       = 0;
	track->childMuteMask = 0;
	if (track->parent) {
		FixMoveChannelAll(&track->parentController, &track->parent->parentController);
	} else {
		FixReleaseChannelAll(&track->parentController);
	}
	Jam_UnRegistTrack(track);
	return 0;
}

/*
 * --INFO--
 * Address:	80014AC0
 * Size:	0000E8
 *
 * Note: While this function accepts `void*`, it really expects `seqp_*`.
 */
static s32 Jaq_RootCallback(void* VOID_track)
{
	seqp_* track;

	track = (seqp_*)VOID_track;
	if (track && track->trackState != 0) {
		if (track->trackState == 3) {
			__StopSeq(track);
			return -1;
		}

		track->tempoAccumulator += track->tempoFactor;
		if (track->tempoAccumulator < 1.0f) {
			SeqUpdate(track, 0);
		} else {
			while (track->tempoAccumulator >= 1.0f) {
				track->tempoAccumulator -= 1.0f;
				if (Jam_SeqmainNote(track, 0) == -1) {
					__StopSeq(track);
					return -1;
				}
			}

			SeqUpdate(track, 0);
		}
	} else {
		return -1;
	}

	return 0;
}
