#include "global.h"

vs32 gIrqMgrResetStatus = 0;
volatile OSTime sIrqMgrResetTime = 0;
volatile OSTime sIrqMgrRetraceTime = 0;
s32 sIrqMgrRetraceCount = 0;

void IrqMgr_AddClient(IrqMgr* irqmgr, IrqMgrClient* param_2, OSMesgQueue* param_3) {
    u32 saveMask;

    saveMask = osSetIntMask(1);

    param_2->queue = param_3;
    param_2->next = irqmgr->callbacks;
    irqmgr->callbacks = param_2;

    osSetIntMask(saveMask);

    if (irqmgr->prenmiStage > 0) {
        osSendMesg(param_2->queue, &irqmgr->prenmiMsg.type, 0);
    }
    if (irqmgr->prenmiStage > 1) {
        osSendMesg(param_2->queue, &irqmgr->nmiMsg.type, 0);
    }
}

void IrqMgr_RemoveClient(IrqMgr* irqmgr, IrqMgrClient* remove) {
    IrqMgrClient* iter;
    IrqMgrClient* last;
    u32 saveMask;

    iter = irqmgr->callbacks;
    last = NULL;

    saveMask = osSetIntMask(1);

    while (iter != NULL) {
        if (iter == remove) {
            if (last != NULL) {
                last->next = remove->next;
            } else {
                irqmgr->callbacks = remove->next;
            }
            break;
        }
        last = iter;
        iter = iter->next;
    }

    osSetIntMask(saveMask);
}

void IrqMgr_SendMesgForClient(IrqMgr* irqmgr, OSMesg msg) {
    IrqMgrClient* iter = irqmgr->callbacks;

    while (iter != NULL) {
        osSendMesg(iter->queue, msg, 0);
        iter = iter->next;
    }
}

void IrqMgr_JamMesgForClient(IrqMgr* irqmgr, OSMesg msg) {
    IrqMgrClient* iter = irqmgr->callbacks;

    while (iter != NULL) {
        if (iter->queue->validCount < iter->queue->msgCount) {
            osSendMesg(iter->queue, msg, 0);
        }
        iter = iter->next;
    }
}

void IrqMgr_HandlePreNMI(IrqMgr* irqmgr) {
    gIrqMgrResetStatus = 1;
    irqmgr->prenmiStage = 1;

    sIrqMgrResetTime = irqmgr->lastPrenmiTime = osGetTime();

    // Wait .45 seconds then generate a stage 2 prenmi interrupt
    osSetTimer(&irqmgr->prenmiTimer, OS_USEC_TO_CYCLES(450000), 0, &irqmgr->irqQueue, (OSMesg)0x29F);

    IrqMgr_JamMesgForClient(irqmgr, &irqmgr->prenmiMsg.type);
}

void IrqMgr_CheckStack(void) {
    StackCheck_Check(NULL);
}

void IrqMgr_HandlePRENMI450(IrqMgr* irqmgr) {
    gIrqMgrResetStatus = 2;
    irqmgr->prenmiStage = 2;

    // Wait .03 seconds then generate a stage 3 prenmi interrupt
    osSetTimer(&irqmgr->prenmiTimer, OS_USEC_TO_CYCLES(30000), 0, &irqmgr->irqQueue, (OSMesg)0x2A0);

    IrqMgr_SendMesgForClient(irqmgr, &irqmgr->nmiMsg.type);
}

void IrqMgr_HandlePRENMI480(IrqMgr* irqmgr) {
    // Wait .52 seconds. After this we will have waited an entire second
    osSetTimer(&irqmgr->prenmiTimer, OS_USEC_TO_CYCLES(520000), 0, &irqmgr->irqQueue, (OSMesg)0x2A1);

    osAfterPreNMI();
}

void IrqMgr_HandlePRENMI500(IrqMgr* irqmgr) {
    IrqMgr_CheckStack();
}
void IrqMgr_HandleRetrace(IrqMgr* irqmgr) {
    if (sIrqMgrRetraceTime == 0) {
        if (irqmgr->lastFrameTime == 0) {
            irqmgr->lastFrameTime = osGetTime();
        } else {
            sIrqMgrRetraceTime = osGetTime() - irqmgr->lastFrameTime;
        }
    }

    sIrqMgrRetraceCount += 1;
    IrqMgr_SendMesgForClient(irqmgr, irqmgr);
}

void IrqMgr_ThreadEntry(IrqMgr* irqmgr) {
    u32 interrupt;
    u32 stop;

    interrupt = 0;
    stop = 0;
    while (stop == 0) {
        if (stop) {
            ;
        }

        osRecvMesg(&irqmgr->irqQueue, (OSMesg*)&interrupt, 1);
        switch (interrupt) {
            case 0x29A:
                IrqMgr_HandleRetrace(irqmgr);
                break;
            case 0x29D:
                IrqMgr_HandlePreNMI(irqmgr);
                break;
            case 0x29F:
                IrqMgr_HandlePRENMI450(irqmgr);
                break;
            case 0x2A0:
                IrqMgr_HandlePRENMI480(irqmgr);
                break;
            case 0x2A1:
                IrqMgr_HandlePRENMI500(irqmgr);
                break;
        }
    }
}

void IrqMgr_Init(IrqMgr* irqmgr, void* stack, OSPri pri, u8 retraceCount) {
    irqmgr->callbacks = NULL;
    irqmgr->verticalRetraceMesg.type = 1;
    irqmgr->prenmiMsg.type = 4;
    irqmgr->nmiMsg.type = 3;
    irqmgr->prenmiStage = 0;
    irqmgr->lastPrenmiTime = 0;

    osCreateMesgQueue(&irqmgr->irqQueue, (OSMesg*)irqmgr->irqBuffer, 8);
    osSetEventMesg(0xE, &irqmgr->irqQueue, (OSMesg)0x29D);
    osViSetEvent(&irqmgr->irqQueue, (OSMesg)0x29A, retraceCount);

    osCreateThread(&irqmgr->thread, Z_THREAD_ID_IRQMGR, (osCreateThread_func)IrqMgr_ThreadEntry, irqmgr, stack, pri);
    osStartThread(&irqmgr->thread);
}
