/**
 * @file audio_thread_manager.c
 *
 * This file implements basic thread features for the audio driver. It manages updating the driver on vertical retrace
 * and sending the audio rsp tasks generated by the driver to the task scheduler.
 */

#include "global.h"

void AudioMgr_NotifyTaskDone(AudioMgr* audioMgr) {
    AudioTask* task = audioMgr->rspTask;

    // If the audio rsp task has a message queue to receive task done notifications, post a message to it.
    if (audioMgr->rspTask->msgQueue != NULL) {
        osSendMesg(task->msgQueue, NULL, OS_MESG_BLOCK);
    }
}

/**
 * Handle retrace event.
 * Update the audio driver and schedule audio rsp tasks.
 */
void AudioMgr_HandleRetrace(AudioMgr* audioMgr) {
    AudioTask* rspTask;

    if (R_AUDIOMGR_DEBUG_LEVEL > AUDIOMGR_DEBUG_LEVEL_NONE) {
        // Inhibit audio rsp task processing
        audioMgr->rspTask = NULL;
    }

    if (audioMgr->rspTask != NULL) {
        // Got an rsp task to process, build the OSScTask and forward it to the scheduler to run

        audioMgr->audioTask.next = NULL;
        audioMgr->audioTask.flags = OS_SC_NEEDS_RSP;
        audioMgr->audioTask.framebuffer = NULL;

        audioMgr->audioTask.list = audioMgr->rspTask->task;
        audioMgr->audioTask.msgQueue = &audioMgr->taskDoneQueue;

        audioMgr->audioTask.msg = NULL;
        osSendMesg(&audioMgr->sched->cmdQueue, (OSMesg)&audioMgr->audioTask, OS_MESG_BLOCK);
        Sched_Notify(audioMgr->sched);
    }

    // Update the audio driver

    gAudioThreadUpdateTimeStart = osGetTime();

    if (R_AUDIOMGR_DEBUG_LEVEL >= AUDIOMGR_DEBUG_LEVEL_NO_UPDATE) {
        // Skip update, no rsp task produced
        rspTask = NULL;
    } else {
        rspTask = AudioThread_Update();
    }

    gAudioThreadUpdateTimeAcc += osGetTime() - gAudioThreadUpdateTimeStart;
    gAudioThreadUpdateTimeStart = 0;

    if (audioMgr->rspTask != NULL) {
        // Wait for the audio rsp task scheduled on the previous retrace to complete. This looks like it should wait
        // for the task scheduled on the current retrace, earlier in this function, but since the queue is initially
        // filled in AudioMgr_Init this osRecvMesg call doesn't wait for the task scheduler to post a message for the
        // most recent task as there is already a message waiting.
        osRecvMesg(&audioMgr->taskDoneQueue, NULL, OS_MESG_BLOCK);
        // Report task done
        //! @bug As the above osRecvMesg is waiting for the previous task to complete rather than the current task,
        //! the task done notification is sent to the task done queue for the current task as soon as the previous task
        //! is completed, without waiting for the current task.
        //! In practice, task done notifications are not used by the audio driver so this is inconsequential.
        AudioMgr_NotifyTaskDone(audioMgr);
    }
    // Update rsp task to be scheduled on next retrace
    audioMgr->rspTask = rspTask;
}

/**
 * Handle Pre-NMI event.
 * Implemented by the audio driver.
 *
 * @see Audio_PreNMI
 */
void AudioMgr_HandlePreNMI(AudioMgr* audioMgr) {
    PRINTF(
        T("オーディオマネージャが OS_SC_PRE_NMI_MSG を受け取りました\n", "Audio manager received OS_SC_PRE_NMI_MSG\n"));
    Audio_PreNMI();
}

void AudioMgr_ThreadEntry(void* arg) {
    AudioMgr* audioMgr = (AudioMgr*)arg;
    IrqMgrClient irqClient;
    s16* msg = NULL;

    PRINTF(T("オーディオマネージャスレッド実行開始\n", "Start running audio manager thread\n"));

    // Initialize audio driver
    Audio_Init();
    AudioLoad_SetDmaHandler(DmaMgr_AudioDmaHandler);
    Audio_InitSound();

    // Fill init queue to signal that the audio driver is initialized
    osSendMesg(&audioMgr->initQueue, NULL, OS_MESG_BLOCK);

    IrqMgr_AddClient(audioMgr->irqMgr, &irqClient, &audioMgr->interruptQueue);

    // Spin waiting for events
    for (;;) {
        osRecvMesg(&audioMgr->interruptQueue, (OSMesg*)&msg, OS_MESG_BLOCK);

        switch (*msg) {
            case OS_SC_RETRACE_MSG:
                AudioMgr_HandleRetrace(audioMgr);

                // Empty the interrupt queue
                while (!MQ_IS_EMPTY(&audioMgr->interruptQueue)) {
                    osRecvMesg(&audioMgr->interruptQueue, (OSMesg*)&msg, OS_MESG_BLOCK);

                    switch (*msg) {
                        case OS_SC_RETRACE_MSG:
                            // Don't process a retrace more than once in quick succession
                            break;

                        case OS_SC_PRE_NMI_MSG:
                            // Always handle Pre-NMI
                            AudioMgr_HandlePreNMI(audioMgr);
                            break;
                    }
                }
                break;

            case OS_SC_PRE_NMI_MSG:
                AudioMgr_HandlePreNMI(audioMgr);
                break;
        }
    }
}

/**
 * Stalls the current thread until the audio thread is sufficiently initialized.
 *
 * Note this function only works once. After the first call the message that the audio thread posted to the init queue
 * will have been removed, subsequent calls to this function will block indefinitely as the audio thread does not refill
 * the queue.
 */
void AudioMgr_WaitForInit(AudioMgr* audioMgr) {
    osRecvMesg(&audioMgr->initQueue, NULL, OS_MESG_BLOCK);
}

void AudioMgr_Init(AudioMgr* audioMgr, void* stack, OSPri pri, OSId id, Scheduler* sched, IrqMgr* irqMgr) {
    bzero(audioMgr, sizeof(AudioMgr));

    audioMgr->sched = sched;
    audioMgr->irqMgr = irqMgr;
    audioMgr->rspTask = NULL;

#if PLATFORM_N64
    R_AUDIOMGR_DEBUG_LEVEL = AUDIOMGR_DEBUG_LEVEL_NO_RSP;
#endif

    osCreateMesgQueue(&audioMgr->taskDoneQueue, &audioMgr->taskDoneMsg, 1);
    osCreateMesgQueue(&audioMgr->interruptQueue, audioMgr->interruptMsgBuf, ARRAY_COUNT(audioMgr->interruptMsgBuf));
    osCreateMesgQueue(&audioMgr->initQueue, &audioMgr->initMsg, 1);

    // Send a message to the task done queue so it is initially full
    osSendMesg(&audioMgr->taskDoneQueue, NULL, OS_MESG_BLOCK);

    osCreateThread(&audioMgr->thread, id, AudioMgr_ThreadEntry, audioMgr, stack, pri);
    osStartThread(&audioMgr->thread);
}