#include "SND_bank.h"
#include "OS_mutex.h"
#include "nitro/types.h"
#include "code32.h"

void DC_StoreRange(const void *, u32);

/*
 * WaveArc linking seems to work like the following:
 *
 * Each SNDBankData can have up to 4 WaveArcs assigned.
 * To avoid loading the same WaveArc by multiple banks,
 * a linked list using WaveArcLinks is chained through all banks
 * which use a WaveArc.
 *
 * That WaveArc has a head pointer, which points to the first
 * WaveArcLink in that chain of banks.
 */

/*
 * Set bank's wavearc link at index to specified wavearc
 */
void SND_AssignWaveArc(struct SNDBankData *bankData, s32 index, struct SNDWaveArc *waveArc) {
    SNDi_LockMutex();
    struct SNDWaveArc *selectedWaveArc = bankData->waveArcLinks[index].waveArc;
    if (selectedWaveArc != NULL) {
        if (waveArc == selectedWaveArc) {
            SNDi_UnlockMutex();
            return;
        }

        if (&bankData->waveArcLinks[index] == selectedWaveArc->waveArcLLHead) {
            selectedWaveArc->waveArcLLHead = bankData->waveArcLinks[index].waveArcLLnext;
            DC_StoreRange(bankData->waveArcLinks[index].waveArc, sizeof(struct SNDWaveArc));
        } else {
            struct SNDWaveArcLink *cur;
            for (cur = selectedWaveArc->waveArcLLHead; cur != NULL; cur = cur->waveArcLLnext) {
                if (&bankData->waveArcLinks[index] == cur->waveArcLLnext)
                    break;
            }
            cur->waveArcLLnext = bankData->waveArcLinks[index].waveArcLLnext;
            DC_StoreRange(cur, sizeof(*cur));
        }
    }
    struct SNDWaveArcLink *oldHead = waveArc->waveArcLLHead;
    waveArc->waveArcLLHead = &bankData->waveArcLinks[index];
    bankData->waveArcLinks[index].waveArcLLnext = oldHead;
    bankData->waveArcLinks[index].waveArc = waveArc;
    // BUG: Shouldn't the mutex unlock be after writing to cache?
    SNDi_UnlockMutex();
    DC_StoreRange(bankData, sizeof(*bankData));
    DC_StoreRange(waveArc, sizeof(*waveArc));
}

void SND_DestroyBank(struct SNDBankData *bankData) {
    SNDi_LockMutex();

    for (s32 i = 0; i < SND_BANK_MAX_WAVEARC; i++) {
        struct SNDWaveArcLink *curWaveArcLink = &bankData->waveArcLinks[i];
        struct SNDWaveArc *curWaveArc = bankData->waveArcLinks[i].waveArc;

        if (curWaveArc == NULL)
            continue;

        if (curWaveArcLink == curWaveArc->waveArcLLHead) {
            curWaveArc->waveArcLLHead = bankData->waveArcLinks[i].waveArcLLnext;
            DC_StoreRange(curWaveArc, sizeof(*curWaveArc));
        } else {
            struct SNDWaveArcLink *cur;
            for (cur = curWaveArc->waveArcLLHead; cur != NULL; cur = cur->waveArcLLnext) {
                if (&bankData->waveArcLinks[i] == cur->waveArcLLnext)
                    break;
            }
            cur->waveArcLLnext = bankData->waveArcLinks[i].waveArcLLnext;
            DC_StoreRange(cur, sizeof(*cur));
        }
    }

    SNDi_UnlockMutex();
}

void SND_DestroyWaveArc(struct SNDWaveArc *waveArc) {
    SNDi_LockMutex();
    struct SNDWaveArcLink *cur = waveArc->waveArcLLHead;

    while (cur != NULL) {
        struct SNDWaveArcLink *newCur = cur->waveArcLLnext;
        cur->waveArc = NULL;
        cur->waveArcLLnext = NULL;
        DC_StoreRange(cur, sizeof(*cur));
        cur = newCur;
    }

    SNDi_UnlockMutex();
}

struct SNDInstPos SND_GetFirstInstDataPos(const struct SNDBankData *bankData) {
#pragma unused (bankData)
    struct SNDInstPos retval;
    retval.program = 0;
    retval.index = 0;
    return retval;
}

static inline struct SNDDrumSet *test(const struct SNDBankData *bank, u32 off) {
    return (struct SNDDrumSet *)((u8 *)bank + (off >> 8));
}

BOOL SND_GetNextInstData(const struct SNDBankData *bankData, struct SNDInstData *instData, struct SNDInstPos *instPos) {
    while (instPos->program < bankData->instCount) {
        struct SNDDrumSet *drums;
        struct SNDKeySplit *keySplit;

        u32 instOffset = bankData->instOffsets[instPos->program];
        instData->type = SND_INST_OFFSET_TYPE(instOffset);

        switch (instData->type) {
        case SND_INST_PCM:
        case SND_INST_PSG:
        case SND_INST_NOISE:
        case SND_INST_DIRECTPCM:
        case SND_INST_DUMMY:
            instData->param = *SND_INST_OFFSET_NORMAL(bankData, instOffset);
            instPos->program++;
            return TRUE;
        case SND_INST_DRUM_TABLE:
            drums = SND_INST_OFFSET_DRUMS(bankData, instOffset);
            // silly programming 101: put a loop in a place that never loops
            for (; instPos->index < drums->maxKey - drums->minKey + 1;) {
                *instData = drums->instruments[instPos->index];
                instPos->index++;
                return TRUE;
            }
            break;
        case SND_INST_KEY_SPLIT:
            keySplit = SND_INST_OFFSET_KEYSPL(bankData, instOffset);
            for (; instPos->index < SND_INST_MAX_KEYSPLIT;) {
                if (keySplit->key[instPos->index] == 0)
                    break;
                *instData = keySplit->instruments[instPos->index];
                instPos->index++;
                return TRUE;
            }
            break;
        }

        instPos->program++;
        instPos->index = 0;
    }
    return FALSE;
}

u32 SND_GetWaveDataCount(const struct SNDWaveArc *waveArc) {
    return waveArc->waveCount;
}

void SND_SetWaveDataAddress(struct SNDWaveArc *waveArc, s32 index, const struct SNDWaveData *waveData) {
    SNDi_LockMutex();
    waveArc->waveOffsets[index] = (u32)waveData;
    DC_StoreRange(&waveArc->waveOffsets[index], sizeof(u32));
    SNDi_UnlockMutex();
}

const struct SNDWaveData *SND_GetWaveDataAddress(const struct SNDWaveArc *waveArc, s32 index) {
    SNDi_LockMutex();
    u32 retval = waveArc->waveOffsets[index];
    if (retval != 0) {
        // < 0x2000000 aka, not a pointer to main RAM
        if (retval < 0x2000000)
            retval = (u32)&((u8 *)waveArc)[retval];
    } else {
        retval = 0;
    }
    SNDi_UnlockMutex();
    return (struct SNDWaveData *)retval;
}
