#include "ultra64.h"
#include "ultra64/bcp.h"

typedef struct __osExceptionVector {
    u32 inst1; // lui     $k0, %hi(__osException)
    u32 inst2; // addiu   $k0, $k0, %lo(__osException)
    u32 inst3; // jr      $k0
    u32 inst4; //  nop
} __osExceptionVector;

extern __osExceptionVector __osExceptionPreamble;

extern OSPiHandle __Dom1SpeedParam;
extern OSPiHandle __Dom2SpeedParam;

u64 osClockRate = OS_CLOCK_RATE;
s32 osViClock = VI_NTSC_CLOCK;
u32 __osShutdown = false;
OSIntMask __OSGlobalIntMask = OS_IM_ALL;

u32 __osFinalrom;

#if LIBULTRA_VERSION < LIBULTRA_VERSION_K

#define OSINITIALIZE_FUNC osInitialize
#define SPEED_PARAM_FUNC createSpeedParam

#if (LIBULTRA_VERSION == LIBULTRA_VERSION_I && LIBULTRA_PATCH == 1) || (LIBULTRA_VERSION == LIBULTRA_VERSION_J)
static void SPEED_PARAM_FUNC(void);
#endif

#else

#define OSINITIALIZE_FUNC __osInitialize_common
#define SPEED_PARAM_FUNC __createSpeedParam

void SPEED_PARAM_FUNC(void) {
    __Dom1SpeedParam.type = DEVICE_TYPE_INIT;
    __Dom1SpeedParam.latency = IO_READ(PI_BSD_DOM1_LAT_REG);
    __Dom1SpeedParam.pulse = IO_READ(PI_BSD_DOM1_PWD_REG);
    __Dom1SpeedParam.pageSize = IO_READ(PI_BSD_DOM1_PGS_REG);
    __Dom1SpeedParam.relDuration = IO_READ(PI_BSD_DOM1_RLS_REG);

    __Dom2SpeedParam.type = DEVICE_TYPE_INIT;
    __Dom2SpeedParam.latency = IO_READ(PI_BSD_DOM2_LAT_REG);
    __Dom2SpeedParam.pulse = IO_READ(PI_BSD_DOM2_PWD_REG);
    __Dom2SpeedParam.pageSize = IO_READ(PI_BSD_DOM2_PGS_REG);
    __Dom2SpeedParam.relDuration = IO_READ(PI_BSD_DOM2_RLS_REG);
}

#endif

void OSINITIALIZE_FUNC(void) {
    u32 pifdata;
#if LIBULTRA_VERSION < LIBULTRA_VERSION_K
    u32 clock = 0;
#endif

    __osFinalrom = true;
    __osSetSR(__osGetSR() | SR_CU1);
    __osSetFpcCsr(FPCSR_FS | FPCSR_EV | FPCSR_RM_RN);
#if LIBULTRA_VERSION >= LIBULTRA_VERSION_K
    __osSetWatchLo(0x04900000);
#endif

#ifdef BBPLAYER
    {
        u32 x, y;

        // Check for iQue Player hardware by enabling and disabling FLASH and IDE interrupts and checking if the
        // register gives the correct response.
        IO_WRITE(MI_EX_INTR_MASK_REG, MI_EX_INTR_MASK_SET_FLASH | MI_EX_INTR_MASK_SET_IDE);
        x = IO_READ(MI_EX_INTR_MASK_REG);
        IO_WRITE(MI_EX_INTR_MASK_REG, MI_EX_INTR_MASK_CLR_FLASH | MI_EX_INTR_MASK_CLR_IDE);
        y = IO_READ(MI_EX_INTR_MASK_REG);

        __osBbIsBb =
            ((x & (MI_EX_INTR_MASK_FLASH | MI_EX_INTR_MASK_IDE)) == (MI_EX_INTR_MASK_FLASH | MI_EX_INTR_MASK_IDE)) &&
            ((y & (MI_EX_INTR_MASK_FLASH | MI_EX_INTR_MASK_IDE)) == 0);
    }

    //! @bug Most games do not have permission to use GPIO, so they often cannot correctly tell if they are running on
    //! HW V1 or V2.
    if (__osBbIsBb && PI_GPIO_IS_HW_V2(IO_READ(PI_GPIO_REG))) {
        __osBbIsBb = 2;
    }

    if (__osBbIsBb) {
        // Set IPL boot parameters
        osTvType = OS_TV_NTSC;
        osRomType = 0;
        osResetType = 0;
        osVersion = 1;
        osMemSize = 0x400000;
    }

    if (!__osBbIsBb) {
        // The PIF doesn't exist on iQue, no need to enable NMI from PIF
#endif
        while (__osSiRawReadIo((void*)(PIF_RAM_END - 3), &pifdata)) {
            ;
        }
        while (__osSiRawWriteIo((void*)(PIF_RAM_END - 3), pifdata | 8)) {
            ;
        }
#ifdef BBPLAYER
    }
#endif

    *(__osExceptionVector*)UT_VEC = __osExceptionPreamble;  // TLB miss
    *(__osExceptionVector*)XUT_VEC = __osExceptionPreamble; // XTLB miss
    *(__osExceptionVector*)ECC_VEC = __osExceptionPreamble; // cache errors
    *(__osExceptionVector*)E_VEC = __osExceptionPreamble;   // general exceptions

    osWritebackDCache((void*)K0BASE, E_VEC - K0BASE + sizeof(__osExceptionVector));
    osInvalICache((void*)K0BASE, E_VEC - K0BASE + sizeof(__osExceptionVector));
#if (LIBULTRA_VERSION == LIBULTRA_VERSION_I && LIBULTRA_PATCH == 1) || (LIBULTRA_VERSION > LIBULTRA_VERSION_I)
    SPEED_PARAM_FUNC();
    osUnmapTLBAll();
    osMapTLBRdb();
#endif

    osClockRate = osClockRate * 3 / 4;

    if (!osResetType) {
        bzero(osAppNMIBuffer, sizeof(osAppNMIBuffer));
    }

    if (osTvType == OS_TV_PAL) {
        osViClock = VI_PAL_CLOCK;
    } else if (osTvType == OS_TV_MPAL) {
        osViClock = VI_MPAL_CLOCK;
    } else {
        osViClock = VI_NTSC_CLOCK;
    }

#if (LIBULTRA_VERSION == LIBULTRA_VERSION_I && LIBULTRA_PATCH == 1) || (LIBULTRA_VERSION >= LIBULTRA_VERSION_J)
    // If PreNMI is pending, loop until reset
    if (__osGetCause() & CAUSE_IP5) {
        while (true) {
            ;
        }
    }
#endif

#ifdef BBPLAYER
    if (!__osBbIsBb) {
        // In a real iQue Player environment (that is, real hardware + app launched from the system menu)
        // these are set on app launch by the system menu based on the contents of the game's associated
        // ticket. Set some dummy values if not running on iQue Player hardware.
        __osBbEepromSize = 0x200;
        __osBbPakSize = 0x8000;
        __osBbFlashSize = 0x20000;
        __osBbEepromAddress = 0x80400000 - 0x200;
        __osBbPakAddress[0] = 0x80400000 - 0x8200;
        __osBbPakAddress[1] = 0;
        __osBbPakAddress[2] = 0;
        __osBbPakAddress[3] = 0;
        __osBbFlashAddress = 0x80400000 - 0x20000;
        __osBbSramSize = __osBbFlashSize;
        __osBbSramAddress = __osBbFlashAddress;
    }
    if (__osBbIsBb) {
        IO_WRITE(PI_64_REG, IO_READ(PI_64_REG) & ~0x80000000);
        IO_WRITE(MI_EX_INTR_MASK_REG, MI_EX_INTR_MASK_SET_IDE);
        IO_WRITE(SI_0C_REG, 0);
        IO_WRITE(SI_1C_REG, (IO_READ(SI_1C_REG) & ~0x7F000000) | 0x2F400000);
    }
#endif

    IO_WRITE(AI_CONTROL_REG, AI_CONTROL_DMA_ON);
    IO_WRITE(AI_DACRATE_REG, AI_MAX_DAC_RATE - 1);
    IO_WRITE(AI_BITRATE_REG, AI_MAX_BIT_RATE - 1);
}

#if (LIBULTRA_VERSION == LIBULTRA_VERSION_I && LIBULTRA_PATCH == 1) || (LIBULTRA_VERSION == LIBULTRA_VERSION_J)
static void SPEED_PARAM_FUNC(void) {
    __Dom1SpeedParam.type = DEVICE_TYPE_INIT;
    __Dom1SpeedParam.latency = IO_READ(PI_BSD_DOM1_LAT_REG);
    __Dom1SpeedParam.pulse = IO_READ(PI_BSD_DOM1_PWD_REG);
    __Dom1SpeedParam.pageSize = IO_READ(PI_BSD_DOM1_PGS_REG);
    __Dom1SpeedParam.relDuration = IO_READ(PI_BSD_DOM1_RLS_REG);

    __Dom2SpeedParam.type = DEVICE_TYPE_INIT;
    __Dom2SpeedParam.latency = IO_READ(PI_BSD_DOM2_LAT_REG);
    __Dom2SpeedParam.pulse = IO_READ(PI_BSD_DOM2_PWD_REG);
    __Dom2SpeedParam.pageSize = IO_READ(PI_BSD_DOM2_PGS_REG);
    __Dom2SpeedParam.relDuration = IO_READ(PI_BSD_DOM2_RLS_REG);
}
#else
void __osInitialize_autodetect(void) {
}
#endif
