#include "SND_command.h"

#include "mmap.h"
#include "registers.h"

#include "MI_memory.h"
#include "OS_message.h"
#include "OS_system.h"
#include "PXI_fifo.h"
#include "SND_alarm.h"
#include "SND_capture.h"
#include "SND_channel.h"
#include "SND_exChannel.h"
#include "SND_global.h"
#include "SND_main.h"
#include "SND_seq.h"
#include "SND_work.h"

#include "code32.h"

#define SND_MSG_ARRAY_SIZE 8

static OSMessage sMsgArray[SND_MSG_ARRAY_SIZE];
static struct OSMessageQueue sMsgQueue;

static void InitPXI(void);
static void StartTimer(u32 channelMask, u32 captureMask, u32 alarmMask, s32 unused);
static void StopTimer(u32 channelMask, u32 captureMask, u32 alarmMask, s32 hold);
static void SetChannelTimer(u32 channelMask, int timer);
static void SetChannelVolume(u32 channelMask, int vol, int shift);
static void SetChannelPan(u32 channelMask, int pan);
static void ReadDriverInfo(struct SNDDriverInfo *driverInfo);

void SND_CommandInit(void)
{
    OS_InitMessageQueue(&sMsgQueue, sMsgArray, SND_MSG_ARRAY_SIZE);
    InitPXI();
    SNDi_SharedWork = NULL;
}

void SND_CommandProc(void)
{
    struct SNDCommand cmd;
    struct SNDCommand *cmd_ptr;
    OSMessage msg;

    while (OS_ReceiveMessage(&sMsgQueue, &msg, 0))
    {
        // casting it directly below doesn't appear to match
        cmd_ptr = (struct SNDCommand *)msg;

        while (cmd_ptr)
        {
            cmd = *cmd_ptr;

            switch (cmd.id)
            {
                case SND_CMD_START_SEQ:
                    SND_StartSeq((int)cmd.arg[0],
                        (const void *)cmd.arg[1],
                        cmd.arg[2],
                        (struct SNDBankData *)cmd.arg[3]);
                    break;
                case SND_CMD_STOP_SEQ:
                    SND_StopSeq((int)cmd.arg[0]);
                    break;
                case SND_CMD_PREPARE_SEQ:
                    SND_PrepareSeq((int)cmd.arg[0],
                        (const void *)cmd.arg[1],
                        cmd.arg[2],
                        (struct SNDBankData *)cmd.arg[3]);
                    break;
                case SND_CMD_START_PREPARED_SEQ:
                    SND_StartPreparedSeq((int)cmd.arg[0]);
                    break;
                case SND_CMD_PAUSE_SEQ:
                    SND_PauseSeq((int)cmd.arg[0], (BOOL)cmd.arg[1]);
                    break;
                case SND_CMD_SKIP_SEQ:
                    SND_SkipSeq((int)cmd.arg[0], cmd.arg[1]);
                    break;
                case SND_CMD_PLAYER_PARAM:
                    SNDi_SetPlayerParam((int)cmd.arg[0], cmd.arg[1], cmd.arg[2], (int)cmd.arg[3]);
                    break;
                case SND_CMD_TRACK_PARAM:
                    SNDi_SetTrackParam((int)cmd.arg[0] & 0xFFFFFF,
                        cmd.arg[1],
                        cmd.arg[2],
                        cmd.arg[3],
                        (u8)(cmd.arg[0] >> 24));
                    break;
                case SND_CMD_MUTE_TRACK:
                    SND_SetTrackMute((int)cmd.arg[0], cmd.arg[1], (BOOL)cmd.arg[2]);
                    break;
                case SND_CMD_ALLOCATABLE_CHANNEL:
                    SND_SetTrackAllocatableChannel((int)cmd.arg[0], cmd.arg[1], cmd.arg[2]);
                    break;
                case SND_CMD_PLAYER_LOCAL_VAR:
                    SND_SetPlayerLocalVariable((int)cmd.arg[0], (int)cmd.arg[1], (s16)cmd.arg[2]);
                    break;
                case SND_CMD_PLAYER_GLOBAL_VAR:
                    SND_SetPlayerGlobalVariable((int)cmd.arg[0], (s16)cmd.arg[1]);
                    break;
                case SND_CMD_START_TIMER:
                    StartTimer(cmd.arg[0], cmd.arg[1], cmd.arg[2], (int)cmd.arg[3]);
                    break;
                case SND_CMD_STOP_TIMER:
                    StopTimer(cmd.arg[0], cmd.arg[1], cmd.arg[2], (int)cmd.arg[3]);
                    break;
                case SND_CMD_SETUP_CAPTURE:
                    SND_SetupCapture((int)(cmd.arg[2] >> 31u) & 1,
                        (int)(cmd.arg[2] >> 30u) & 1,
                        (void *)cmd.arg[0],
                        (int)cmd.arg[1],
                        (BOOL)(cmd.arg[2] >> 29u) & 1,
                        (int)(cmd.arg[2] >> 28u) & 1,
                        (int)(cmd.arg[2] >> 27u) & 1);
                    break;
                case SND_CMD_SETUP_ALARM:
                    SND_SetupAlarm((int)cmd.arg[0], cmd.arg[1], cmd.arg[2], cmd.arg[3]);
                    break;
                case SND_CMD_CHANNEL_TIMER:
                    SetChannelTimer(cmd.arg[0], (int)cmd.arg[1]);
                    break;
                case SND_CMD_CHANNEL_VOLUME:
                    SetChannelVolume(cmd.arg[0], (int)cmd.arg[1], (int)cmd.arg[2]);
                    break;
                case SND_CMD_CHANNEL_PAN:
                    SetChannelPan(cmd.arg[0], (int)cmd.arg[1]);
                    break;
                case SND_CMD_SETUP_CHANNEL_PCM:
                    SND_SetupChannelPcm((int)cmd.arg[0] & 0xFFFF,
                        (void *)(cmd.arg[1] & 0x7FFFFFFu),
                        (int)(cmd.arg[3] >> 24u) & 0x3,
                        (int)(cmd.arg[3] >> 26u) & 0x3,
                        (int)cmd.arg[3] & 0xFFFF,
                        (int)cmd.arg[2] & 0x3FFFFF,
                        (int)(cmd.arg[2] >> 24u) & 0x7F,
                        (int)(cmd.arg[2] >> 22u) & 0x3,
                        (int)(cmd.arg[0] >> 16u) & 0xFFFF,
                        (int)(cmd.arg[3] >> 16u) & 0x7F);
                    break;
                case SND_CMD_SETUP_CHANNEL_PSG:
                    SND_SetupChannelPsg((int)cmd.arg[0],
                        (int)cmd.arg[3],
                        (int)cmd.arg[1] & 0x7F,
                        (int)(cmd.arg[1] >> 8u) & 0x3,
                        (int)(cmd.arg[2] >> 8u) & 0xFFFF,
                        (int)cmd.arg[2] & 0x7F);
                    break;
                case SND_CMD_SETUP_CHANNEL_NOISE:
                    SND_SetupChannelNoise((int)cmd.arg[0],
                        (int)cmd.arg[1] & 0x7F,
                        (int)(cmd.arg[1] >> 8u) & 0x3,
                        (int)(cmd.arg[2] >> 8u) & 0xFFFF,
                        (int)cmd.arg[2] & 0x7F);
                    break;
                case SND_CMD_SURROUND_DECAY:
                    SNDi_SetSurroundDecay((int)cmd.arg[0]);
                    break;
                case SND_CMD_MASTER_VOLUME:
                    SND_SetMasterVolume((int)cmd.arg[0]);
                    break;
                case SND_CMD_MASTER_PAN:
                    SND_SetMasterPan((int)cmd.arg[0]);
                    break;
                case SND_CMD_OUTPUT_SELECTOR:
                    SND_SetOutputSelector(
                        (int)cmd.arg[0], (int)cmd.arg[1], (int)cmd.arg[2], (int)cmd.arg[3]);
                    break;
                case SND_CMD_LOCK_CHANNEL:
                    SND_LockChannel(cmd.arg[0], cmd.arg[1]);
                    break;
                case SND_CMD_UNLOCK_CHANNEL:
                    SND_UnlockChannel(cmd.arg[0], cmd.arg[1]);
                    break;
                case SND_CMD_STOP_UNLOCKED_CHANNEL:
                    SND_StopUnlockedChannel(cmd.arg[0], cmd.arg[1]);
                    break;
                case SND_CMD_INVALIDATE_SEQ:
                    SND_InvalidateSeq((void *)cmd.arg[0], (void *)cmd.arg[1]);
                    break;
                case SND_CMD_INVALIDATE_BANK:
                    SND_InvalidateBank((void *)cmd.arg[0], (void *)cmd.arg[1]);
                    break;
                case SND_CMD_INVALIDATE_WAVE:
                    SND_InvalidateWave((void *)cmd.arg[0], (void *)cmd.arg[1]);
                    break;
                case SND_CMD_SET_SHARED_WORK:
                    SNDi_SharedWork = (struct SNDSharedWork *)cmd.arg[0];
                    break;
                case SND_CMD_READ_DRIVER_INFO:
                    ReadDriverInfo((struct SNDDriverInfo *)cmd.arg[0]);
                    break;
            } // end switch

            cmd_ptr = cmd.llNext;
        } // end loop over command linked list

        SNDi_SharedWork->finishedCommandTag++;
    } // end loop over receive message
}

static void PxiFifoCallback(PXIFifoTag tag, u32 data, BOOL error)
{
    (void)tag;
    (void)error;

    OSIntrMode intrMode = OS_DisableInterrupts();

    if (data >= HW_MAIN_MEM)
    {
        (void)OS_SendMessage(&sMsgQueue, (OSMessage)data, 0);
    }
    else
    {
        if (data == 0)
            SND_SendWakeupMessage();
    }

    (void)OS_RestoreInterrupts(intrMode);
}

static void InitPXI(void)
{
    PXI_SetFifoRecvCallback(PXI_FIFO_TAG_SOUND, PxiFifoCallback);
}

static void SetChannelTimer(u32 channelMask, int timer)
{
    for (int i = 0; i < SND_CHANNEL_COUNT && channelMask != 0; i++, channelMask >>= 1)
    {
        if (channelMask & 1)
            SND_SetChannelTimer(i, timer);
    }
}

static void SetChannelVolume(u32 channelMask, int vol, int shift)
{
    for (int i = 0; i < SND_CHANNEL_COUNT && channelMask != 0; i++, channelMask >>= 1)
    {
        if (channelMask & 1)
            SND_SetChannelVolume(i, vol, shift);
    }
}

static void SetChannelPan(u32 channelMask, int pan)
{
    for (int i = 0; i < SND_CHANNEL_COUNT && channelMask != 0; i++, channelMask >>= 1)
    {
        if (channelMask & 1)
            SND_SetChannelPan(i, pan);
    }
}

static void StartTimer(u32 channelMask, u32 captureMask, u32 alarmMask, s32 unused)
{
    (void)unused;

    OSIntrMode intrMode = OS_DisableInterrupts();

    for (int i = 0; i < SND_CHANNEL_COUNT && channelMask != 0; i++, channelMask >>= 1)
    {
        if (channelMask & 1)
            reg_SOUNDxCNT_STAT(i) |= 0x80;
    }

    if (captureMask & 1)
    {
        if (captureMask & 2)
        {
            *(vu16 *)&reg_SNDCAPxCNT(0) |= 0x8080;
        }
        else
        {
            reg_SNDCAPxCNT(0) |= 0x80;
        }
    }
    else if (captureMask & 2)
    {
        reg_SNDCAPxCNT(1) |= 0x80;
    }

    for (int i = 0; i < SND_ALARM_COUNT && alarmMask != 0; i++, alarmMask >>= 1)
    {
        if (alarmMask & 1)
            SND_StartAlarm(i);
    }

    (void)OS_RestoreInterrupts(intrMode);
    SND_UpdateSharedWork();
}

static void StopTimer(u32 channelMask, u32 captureMask, u32 alarmMask, s32 hold)
{
    OSIntrMode intrMode = OS_DisableInterrupts();

    for (int i = 0; i < SND_ALARM_COUNT && alarmMask != 0; i++, alarmMask >>= 1)
    {
        if (alarmMask & 1)
            SND_StopAlarm(i);
    }

    for (int i = 0; i < SND_CHANNEL_COUNT && channelMask != 0; i++, channelMask >>= 1)
    {
        if (channelMask & 1)
            SND_StopChannel(i, hold);
    }

    if (captureMask & 1)
        reg_SNDCAPxCNT(0) = 0;
    if (captureMask & 2)
        reg_SNDCAPxCNT(1) = 0;

    (void)OS_RestoreInterrupts(intrMode);
    SND_UpdateSharedWork();
}

static void ReadDriverInfo(struct SNDDriverInfo *driverInfo)
{
    MI_CpuCopy32(&SNDi_Work, driverInfo, sizeof(SNDi_Work));

    driverInfo->workPtr = &SNDi_Work;

    for (int i = 0; i < SND_CHANNEL_COUNT; i++)
    {
        driverInfo->channelControls[i] = SND_GetChannelControl(i);
    }

    driverInfo->lockedChannels = SND_GetLockedChannel(0);
}
