#include "egg/util/eggException.h"

#include "egg/core/eggController.h"
#include "egg/core/eggHeap.h"
#include "egg/core/eggSystem.h"
#include "egg/core/eggVideo.h"
#include "nw4r/db/db_directPrint.h"
#include "nw4r/db/db_exception.h"
#include "nw4r/db/db_mapFile.h"

#include "rvl/KPAD.h" // IWYU pragma: export
#include "rvl/OS.h"   // IWYU pragma: export

namespace EGG {

Exception *Exception::sException;
nw4r::db::ConsoleHandle Exception::sConsoleHandle;
void *Exception::sMapFileWorks;
u32 Exception::sMapFileNumMax;
u32 Exception::sCurrentMapFileNum;
void (*Exception::sPreException)();
void (*Exception::sUserCallbackFunc)(CoreStatus *status);
volatile bool Exception::sUserCallbackMode;
s16 Exception::sExceptionDisplayInfo;

void Exception::ExceptionWaitTime(u32 time) {
    u32 tick = OSGetTick();
    u32 tick2;
    do {
        tick2 = OSGetTick();
    } while ((tick2 - tick) / (OS_BUS_CLOCK_SPEED / 4 / 1000) < time);
}

bool Exception::ExceptionCallback(nw4r::db::ConsoleHandle handle, void *data) {
    if (sPreException != nullptr) {
        (*sPreException)();
    }
    nw4r::db::Exception_SetDisplayInfo(sExceptionDisplayInfo);
    nw4r::db::Exception_SetUserCallback(ExceptionCallback_, data);
    return true;
}

bool Exception::ExceptionCallback_(nw4r::db::ConsoleHandle console, void *data) {
    EGG::CoreStatus status;
    if (console == nullptr) {
        OSReport("No Console\n");
        return 0;
    }
    OSEnableInterrupts();
    if (data != nullptr) {
        // Require a specific button sequence to show the exception handler
        int u4 = 0;
        while (((u16 *)data)[u4] != 0) {
            KPADRead(0, &status, 1);
            ExceptionWaitTime(50);
            if (status.trig) {
                // Reset the sequence on wrong button press, advance on right button press
                u4 = status.trig & (((u16 *)data)[u4]) ? u4 + 1 : 0;
            }
        }
    }
    int line, start, end;
    start = nw4r::db::Console_GetBufferHeadLine(console);
    line = start;
    nw4r::db::Console_SetVisible(console, true);
    end = nw4r::db::Console_GetTotalLines(console);
    for (; line < end; line++) {
        nw4r::db::Console_SetViewBaseLine(console, line);
        nw4r::db::Console_DrawDirect(console);
        ExceptionWaitTime(250);
    }

    int prevY, newY;
    int newX, prevX;

    newY = start;

    nw4r::db::Console_SetViewBaseLine(console, start);
    nw4r::db::Console_DrawDirect(console);
    while (true) {
        KPADRead(0, &status, 1);
        ExceptionWaitTime(100);
        if (status.trig & cCORE_BUTTON_HOME) {
            sUserCallbackMode = !sUserCallbackMode;
        }
        if (sUserCallbackMode && sUserCallbackFunc != nullptr) {
            (*sUserCallbackFunc)(&status);
            continue;
        }

        prevX = newX = nw4r::db::Console_GetPositionX(console);
        ;
        prevY = newY;

        if (status.hold & cCORE_BUTTON_DOWN) {
            newY += 1;
            if (newY > end) {
                newY = end;
            }
        } else if (status.hold & cCORE_BUTTON_UP) {
            newY -= 1;
            if (newY < start) {
                newY = start;
            }
        } else if (status.hold & cCORE_BUTTON_RIGHT) {
            newX -= 5;
            if (newX < -150) {
                newX = -150;
            }
        } else if (status.hold & cCORE_BUTTON_LEFT) {
            newX += 5;
            if (newX > 10) {
                newX = 10;
            }
        }

        if (newY != prevY || newX != prevX) {
            nw4r::db::Console_SetPosition(console, newX, nw4r::db::Console_GetPositionY(console));
            nw4r::db::Console_SetViewBaseLine(console, newY);
            nw4r::db::Console_DrawDirect(console);
        }
    }
}

Exception *Exception::create(u16 width, u16 height, u16 attr, EGG::Heap *heap, int mapFileNum) {
    if (sException == nullptr) {
        nw4r::db::Exception_Init();
        nw4r::db::DirectPrint_Init();
        if (heap == nullptr) {
            heap = Heap::sCurrentHeap;
        }
        sException = new (heap) Exception(width, height, attr, heap, mapFileNum);
    }

    return sException;
}
void Exception::SetCallbackArgs(void *data) {
    nw4r::db::Exception_SetUserCallback(ExceptionCallback_, data);
    nw4r::db::Exception_SetUserCallback(ExceptionCallback, data);
    sExceptionDisplayInfo = nw4r::db::Exception_SetDisplayInfo(0);
}

void Exception::SetPreExceptionCallback(void (*cb)()) {
    sPreException = cb;
}

void Exception::SetUserCallback(void (*cb)(CoreStatus *status)) {
    sUserCallbackFunc = cb;
}

void Exception::SetCallbackMode(bool arg) {
    sUserCallbackMode = arg;
}

nw4r::db::ConsoleHandle Exception::GetConsoleHandle() {
    return sConsoleHandle;
}

Exception::~Exception() {
    nw4r::db::ConsoleHandle h = sConsoleHandle;
    if (h != nullptr) {
        nw4r::db::ConsoleHandle handle = nw4r::db::Console_Destroy(h);
        Heap::free(handle, nullptr);
        sConsoleHandle = nullptr;
    }
    nw4r::db::MapFile_UnregistAll();
    if (sMapFileWorks != nullptr) {
        Heap::free(sMapFileWorks, nullptr);
        sMapFileWorks = nullptr;
        sMapFileNumMax = 0;
        sCurrentMapFileNum = 0;
    }
}

Exception::Exception(u16 width, u16 height, u16 attr, EGG::Heap *heap, int mapFileNum) {
    void *data = Heap::alloc((width + 1) * height + 0x2C, 4, heap);
    nw4r::db::ConsoleHandle handle = nw4r::db::Console_Create(data, width, height, 18, 0, attr);
    sConsoleHandle = handle;
    if (handle == nullptr) {
        return;
    }

    const GXRenderModeObj *o = EGG::BaseSystem::getVideo()->getCurrentOrFallbackRenderModeObj();
    // Fakematch? Everything before Exception_SetConsole matches if it's volatile, but things after
    // it break
    nw4r::db::Exception_SetConsole(*(nw4r::db::ConsoleHandle *volatile)&sConsoleHandle, o);

    nw4r::db::Console_SetPosition(sConsoleHandle, 0, 30);
    nw4r::db::Console_SetVisible(sConsoleHandle, false);

    sMapFileNumMax = mapFileNum;
    sCurrentMapFileNum = 0;
    sMapFileWorks = Heap::alloc(mapFileNum * 0x14, 4, heap);
    SetCallbackArgs(nullptr);
}

} // namespace EGG
