#include "egg/prim/eggAssert.h"

#include <cstdarg>

#include "egg/core/eggSystem.h"
#include "egg/core/eggXfbManager.h"
#include "nw4r/db/db_directPrint.h"
#include "nw4r/db/db_mapFile.h"
#include "string.h"

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

namespace EGG {

namespace Assert {

AssertCallback sAssertCallback;
AssertCallback sAssertCallback2;
u32 sPtrOverride;
bool sInstantHalt;
bool sAssertOccurred;

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

void system_vreport(const char *str, va_list list) {
    OSVReport(str, list);
}
void system_report(const char *str, ...) {
    va_list l;
    va_start(l, str);
    system_vreport(str, l);
    va_end(l);
}
s32 getPeriodPos(const char *str) {
    char *b = strchr(str, '.');
    s32 len;
    if (b == nullptr) {
        len = strlen(str);
    } else {
        len = b - str;
    }

    if (len > 0xff) {
        return 0xff;
    }
    return len;
}

char buf[260];

const char *getMapSymbol(void *arg) {
    bool success = nw4r::db::MapFile_QuerySymbol((u32)arg, (u8 *)buf, sizeof(buf));
    return success ? buf : nullptr;
}

bool isOutsideMEM1(u32 addr) {
    if (!(0x80000000 <= addr) || !(addr <= 0x83000000 - 1)) {
        return true;
    }

    return false;
}
AssertCallback setAssertCallback(AssertCallback cb) {
    AssertCallback old = sAssertCallback;
    sAssertCallback = cb;
    return old;
}

void system_halt(const char *file, u32 line, const char *msg, va_list list) {
    if (sAssertOccurred) {
#line 152
        OSError("Recursive assertioned");
    }

    sAssertOccurred = true;
    if (sAssertCallback != nullptr) {
        sAssertCallback();
    }
    VISetPreRetraceCallback(nullptr);
    VISetPostRetraceCallback(nullptr);
    system_report("\n---------- HALT -------------\n");
    s32 pos = getPeriodPos(file);
    system_report("%.*s(%d):", pos, file, line);
    va_list args;
    va_copy(args, list);
    system_vreport(msg, args);
    system_report("\n");
    u32 *stackp = (u32 *)OSGetStackPointer();
    if (sPtrOverride != 0) {
        stackp = (u32 *)((u32 *)sPtrOverride)[1];
    }
    u32 *stack = stackp;

    s32 num = 0;
    for (s32 num = 0; num < 0x1e; num++) {
        if (isOutsideMEM1((u32)stack)) {
            break;
        }
        const char *sym = getMapSymbol((void *)stack[1]);
        if (sym != nullptr) {
            system_report("%d: %s\n", num, sym);
        } else {
            system_report("%d: %p\n", num, stack[1]);
        }
        stack = (u32 *)stack[0];
        if (isOutsideMEM1((u32)stack)) {
            break;
        }
    }

    if (sInstantHalt == 0) {
        VISetBlack(false);
        VIFlush();
        nw4r::db::DirectPrint_Init();
        nw4r::db::detail::DirectPrint_SetupFB(0);
        Xfb *fb = BaseSystem::mConfigData->getXfbMgr()->mToShowXfb;
        if (fb == nullptr) {
            fb = BaseSystem::mConfigData->getXfbMgr()->mNextXfb;
        }

        if (fb == nullptr) {
#line 239
            OSError("Program Halt");
        }

        // Random float conversion here for no reason
        f32 wid = fb->mWidth;
        nw4r::db::DirectPrint_EraseXfb(0, 0, fb->mWidth, fb->mHeight);
        nw4r::db::DirectPrint_StoreCache();
        OSDisableInterrupts();
        OSDisableScheduler();
        OSEnableInterrupts();
        while (true) {
            nw4r::db::DirectPrint_EraseXfb(0, 0, fb->mWidth, fb->mHeight);
            nw4r::db::DirectPrint_StoreCache();
            nw4r::db::DirectPrint_Printf(2, 2, "%.*s:%d", pos, file, line);
            va_list args;
            va_copy(args, list);
            nw4r::db::DirectPrint_printfsub(2, 0xe, msg, args);
            nw4r::db::DirectPrint_StoreCache();

            s32 line;
            u32 *stack = stackp;
            s32 counter;

            for (counter = 0, line = 0x1a; counter < 0x10; counter++) {
                if (isOutsideMEM1((u32)stack)) {
                    break;
                }
                const char *sym = getMapSymbol((void *)stack[1]);
                if (sym != nullptr) {
                    nw4r::db::DirectPrint_Printf(2, line, "%d:%s\n", counter, sym);
                } else {
                    nw4r::db::DirectPrint_Printf(2, line, "LR Save[%d]:%p\n", counter, (void *)stack[1]);
                }
                nw4r::db::DirectPrint_StoreCache();
                stack = (u32 *)*stack;
                if (isOutsideMEM1((u32)stack)) {
                    break;
                }
                line += 0xc;
            }
            nw4r::db::DirectPrint_StoreCache();
            wait(4000);
            if (sAssertCallback2 == nullptr) {
                goto end;
            }
            nw4r::db::DirectPrint_EraseXfb(0, 0, fb->mWidth, fb->mHeight);
            (sAssertCallback2)();
            nw4r::db::DirectPrint_StoreCache();
            wait(4000);
        }
    }
end:
#line 315
    OSError("Program Halt");
    return;
}
void assert(const char *file, u32 line, const char *msg, ...) {
    va_list l;
    va_start(l, msg);
    system_halt(file, line, msg, l);
    va_end(l);
}

} // namespace Assert

} // namespace EGG
