#include "global.h"
#include "memmap.h"
#include "memory.h"
#include "util.h"

/**
 * Based on the context of this variable's usage, it appears to be intended to
 * be non-0 whenever the expansion RAM is in, however it is never initialized
 * properly. It seems to serve as whereever the new start of memory will be
 * located whenever plugged in, but its only functional with the debug profiler.
 * It will, however, allocate a much bigger pool if set to non-0.
 */
u32 gExpansionRAMStart = FALSE;

extern MainPool** gMainPool; // gMainPool

void func_80003860(void);
s32 func_80007A58(void);

/**
 * Convert any valid address to its virtual (KSEG0) counterpart.
 */
uintptr_t Util_ConvertAddrToVirtAddr(uintptr_t addr) {
    uintptr_t retaddr = NULL; // any invalid cases are treated as NULL return.

    // convert physical (in installed memory range) to virtual.
    if (addr < osMemSize) {
        retaddr = addr | 0x80000000;
        // convert segmented to virtual.
    } else if (addr < 0x10000000U) {
        retaddr = Memmap_GetSegmentVaddr(addr);
        // convert a fragment pre-relocated address to a post-relocated virtual address.
    } else if ((addr >= 0x81000000U) &&
               (addr < 0x90000000U)) { // is the address in fragment space? convert it to its post-relocated address.
        retaddr = Memmap_GetFragmentVaddr(addr);
        // pass-through addresses that are already virtual (in installed memory range)
    } else if ((addr >= 0x80000000U) && (addr < (uintptr_t)(osMemSize + 0x80000000U))) {
        retaddr = addr;
    }

    return retaddr;
}

/**
 * Copy memory from one address to the other.
 */
void Util_Memcpy(u32* dest, u32* src, int size) {
    while (size-- > 0) {
        *(dest++) = *(src++);
    }
}

/**
 * Initialize the global memory pools and set the main pool after the
 * global pool.
 */
void Util_InitMainPools(void) {
    /**
     * wat? mem sizes are only ever 0x400000 or 0x800000. This check makes no sense
     * in normal contexts. However, since osGetMemSize checks each MB at a time, if
     * the 7MB check fails for whatever reason it can theoretically set osMemSize to
     * 0x600000. This is not known to occur in practical contexts, but there may be
     * some other unknown reason for this.
     */
    if ((gExpansionRAMStart > 0) && (osMemSize > 0x600000U)) {
        main_pool_init(&gPool, POOL_END_6MB);
    } else {
        main_pool_init(&gPool, POOL_END_4MB);
        gExpansionRAMStart = 0;
    }
    func_80003860();
    gMainPool = mem_pool_try_init(0x10000, 0);
}

/**
 * Allocate memory from the main pool.
 */
void* Util_Malloc(s32 size) {
    return mem_pool_alloc(gMainPool, size);
}

/**
 * Free a pointer being used in the main pool.
 */
void Util_Free(void* ptr) {
    mem_pool_free(gMainPool, ptr);
}

/**
 * Draws a profiler rectangle with given coordinates. Solely used by the next function
 * which facilitates the tacked on memory profiler.
 */
void Util_DrawRect(Gfx** dlist, s32 ulx, s32 lrx, u16 color) {
    s32 uly = 15;
    s32 lry = 17;
    Gfx* gfx = *dlist;

    if (func_80007A58() != 0) {
        ulx <<= 1;
        lrx <<= 1;
        uly <<= 1;
        lry <<= 1;
    }

    gDPPipeSync(gfx++);
    gDPSetFillColor(gfx++, color << 16 | color);
    gDPFillRectangle(gfx++, ulx, uly, lrx, lry);
    *dlist = gfx;
}

/**
 * Render the memory profiler bar and print the MEM display. For some reason, this
 * is not within profiler.c itself but added in this "util" file. This seems to be
 * tacked onto the profiler by HAL instead of EAD.
 */
void Util_DrawMemProfiler(Gfx** dlist) {
    MainPool* pool = main_pool_get_pool(); // get pool pointer
    /**
     * Get the available memory offset by gExpansionRAMStart variable. This variable is weird; it
     * seems to be used in reference to osMemSize checks >= 0x600000 which would only be true
     * in Expansion RAM contexts. However, this game does not use the expansion RAM in any
     * other context. This variable is negative when gExpansionRAMStart is too big, and not when
     * it isnt. It is therefore reasonable to assume that this variable would represent how much
     * memory is leftover and if negative would mean this cant fit into 4MB, which would therefore
     * mean that the developers need to trim more memory to get it to fit into 4MB.
     */
    s32 available = main_pool_get_available() - gExpansionRAMStart;

    if (available >= 0) {
        // calculate approximation coordinates based on the current pool addresses in order
        // to display them on the memory profiler.
        s32 baseX = 30;
        s32 startX = ((u32)(K0_TO_PHYS(pool->start)) >> 15) + baseX;
        s32 headLX = ((u32)(K0_TO_PHYS(pool->listHeadL)) >> 15) + baseX;
        s32 headRX = ((u32)(K0_TO_PHYS(pool->listHeadR) - gExpansionRAMStart) >> 15) + baseX;
        s32 endX = ((u32)(K0_TO_PHYS(pool->end) - gExpansionRAMStart) >> 15) + baseX;

        // draw the rects.
        Util_DrawRect(dlist, baseX, startX, GPACK_RGBA5551(248, 120, 40, 1));  // orange
        Util_DrawRect(dlist, startX, headLX, GPACK_RGBA5551(248, 248, 40, 1)); // yellow
        Util_DrawRect(dlist, headLX, headRX, GPACK_RGBA5551(40, 80, 248, 1));  // blue
        Util_DrawRect(dlist, headRX, endX, GPACK_RGBA5551(248, 248, 40, 1));   // yellow

        // how many bytes and kilobytes are available?
        HAL_Printf(baseX, 20, "MEM: +%XH (+%dK)", available, available / 1024);
    } else {
        // same as above.
        s32 baseX = 30;
        s32 startX = ((u32)(K0_TO_PHYS(pool->start)) >> 15) + baseX;
        s32 headLX = ((u32)(K0_TO_PHYS(pool->listHeadL)) >> 15) + baseX;
        s32 headRX = ((u32)(K0_TO_PHYS(pool->listHeadR) - gExpansionRAMStart) >> 15) + baseX;
        s32 endX = ((u32)(K0_TO_PHYS(pool->end) - gExpansionRAMStart) >> 15) + baseX;

        // draw the rects. if we are negative in the memory, we are using red for the backwards
        // allocations to indicate too much memory is being used.
        Util_DrawRect(dlist, baseX, startX, GPACK_RGBA5551(248, 120, 40, 1));  // orange
        Util_DrawRect(dlist, startX, headRX, GPACK_RGBA5551(248, 248, 40, 1)); // yellow
        Util_DrawRect(dlist, headRX, headLX, GPACK_RGBA5551(248, 40, 40, 1));  // red
        Util_DrawRect(dlist, headLX, endX, GPACK_RGBA5551(248, 248, 40, 1));   // yellow

        // how many bytes and kilobytes are available?
        HAL_Printf(baseX, 20, "MEM: -%XH (-%dK)", -available, -available / 1024);
    }
}

/**
 * Clear memory address area.
 */
void Util_Memclear(u64* dest, u32 size) {
    while (size-- > 0) {
        *(dest++) = -1;
    }
}

/**
 * Unused function for checking some 64-bit range (possibly some display list?) for values
 * cooresponding to 0x8040000080400000. This could have been for some memory test however
 * the code for such a test is not present in this ROM, so we can only guess this function's
 * intended usage.
 */
s32 Util_CheckStubMemArea(u64* ptr) {
    s32 ret = 0;

    while (*(ptr++) == 0x8040000080400000) {
        ret++;
    }
    return ret * sizeof(*ptr);
}
