#include "PR/rdb.h"
#include "PR/ultratypes.h"
#include "PR/os.h"
#include "PR/ultraerror.h"
#include "PR/ultralog.h"
#include "PR/sptask.h"
#include "../os/osint.h"
#include "macros.h"
#include "osint_debug.h"

#ifndef _FINALROM

OSTimer __osProfTimer;
OSMesg __osProfTimerMsg;

OSMesgQueue __osProfFlushMQ ALIGNED(8);
OSMesg __osProfFlushMesg;

OSMesgQueue __osProfAckMQ ALIGNED(8);
OSMesg __osProfAckMesg;

u32 __osProfTimerPeriod;

u32 __osProfNumSections;

static u32 __osProfileActive = FALSE;
static u32 __osProfileIOActive = FALSE;

unsigned char __osProfileIOStack[2400] ALIGNED(16);

static OSThread __osProfileIOThread;

void osProfSendWord(u32 word);

void __osProfileIO(void* arg) {
    s32 totalBytes;
    u32 bytesThisBlock;
    u32 ct;
    u8* sendPtr;
    OSProf* t;

    while (TRUE) {
        osRecvMesg(&__osProfFlushMQ, NULL, OS_MESG_BLOCK);
        osProfSendWord(__osProfNumSections);
        osProfSendWord(__osProfTimerPeriod);
        osProfSendWord(__osProfileOverflowBin);

        for (t = __osProfileList; t < __osProfileListEnd; t++) {
            osProfSendWord(t->text_start);
            osProfSendWord(t->histo_size);
            osRecvMesg(&__osProfAckMQ, NULL, OS_MESG_BLOCK);

            totalBytes = t->histo_size * 2;
            sendPtr = t->histo_base;
            while (totalBytes > 0) {
                bytesThisBlock = (totalBytes < 0x800U) ? totalBytes : 0x800U;

                ct = 0;
                while (ct < bytesThisBlock) {
                    ct += __osRdbSend(sendPtr + ct, bytesThisBlock - ct, RDB_TYPE_GtoH_PROF_DATA);
                }

                sendPtr += bytesThisBlock;
                totalBytes -= bytesThisBlock;
                osRecvMesg(&__osProfAckMQ, NULL, OS_MESG_BLOCK);
            }
        }
    }
}

void osProfSendWord(u32 word) {
    u32 ct = 0;
    u8* sendPtr = &word;

    while (ct < sizeof(word)) {
        ct += __osRdbSend(sendPtr + ct, sizeof(word) - ct, RDB_TYPE_GtoH_PROF_DATA);
    }
}

void osProfileFlush(void) {
    osSendMesg(&__osProfFlushMQ, NULL, OS_MESG_BLOCK);
}

void osProfileInit(OSProf* profp, u32 profcnt) {
    u32 i;
    OSProf* t;

#if !defined(NDEBUG) && BUILD_VERSION >= VERSION_K
    if (__osProfileActive) {
        __osError(ERR_OSPROFILEINIT_STR, 0);
        return;
    }

    if (profcnt == 0) {
        __osError(ERR_OSPROFILEINIT_CNT, 1, profcnt);
        return;
    }
#endif

    for (t = profp; t < profp + profcnt; t++) {
#ifndef NDEBUG
        if ((u32)t->histo_base & 1) {
            __osError(ERR_OSPROFILEINIT_ALN, 1, t->histo_base);
            return;
        }

        if (t->text_start >= t->text_end) {
            __osError(ERR_OSPROFILEINIT_ORD, 2, t->text_start, t->text_end);
            return;
        }

        if (((u32)(t->text_end - t->text_start) / 4) > t->histo_size) {
            __osError(ERR_OSPROFILEINIT_SIZ, 1, t->histo_size);
            return;
        }
#endif

        for (i = 0; i < t->histo_size; i++) {
            t->histo_base[i] = 0;
        }
    }

    __osProfileActive = FALSE;
    __osProfileOverflowBin = 0;
    __osProfileList = profp;
    __osProfileListEnd = profp + profcnt;
    __osProfNumSections = profcnt;
    if (!__osProfileIOActive) {
        osCreateMesgQueue(&__osProfFlushMQ, &__osProfFlushMesg, 1);
        osSetEventMesg(OS_EVENT_RDB_FLUSH_PROF, &__osProfFlushMQ, 0);
        osCreateMesgQueue(&__osProfAckMQ, &__osProfAckMesg, 1);
        osSetEventMesg(OS_EVENT_RDB_ACK_PROF, &__osProfAckMQ, 0);
        osCreateThread(&__osProfileIOThread, 0, __osProfileIO, NULL, &__osProfileIOStack[2400], 0x81);
        osStartThread(&__osProfileIOThread);
        __osProfileIOActive = TRUE;
    }
}

void osProfileStart(u32 microseconds) {
#ifndef NDEBUG
    if (microseconds < 50) {
        __osError(ERR_OSPROFILESTART_TIME, 1, microseconds);
        return;
    }

    if (__osProfileActive) {
        __osError(ERR_OSPROFILESTART_FLAG, 0);
        return;
    }
#endif

    osCreateMesgQueue(&__osProfTimerQ, &__osProfTimerMsg, 1);
    osSetTimer(&__osProfTimer, 0, OS_USEC_TO_CYCLES(microseconds), &__osProfTimerQ, NULL);
    __osProfTimerPeriod = microseconds;
    __osProfileActive = TRUE;
}

void osProfileStop(void) {
#ifndef NDEBUG
    if (!__osProfileActive) {
        __osError(ERR_OSPROFILESTOP_FLAG, 0);
        return;
    }
#endif

    if (osStopTimer(&__osProfTimer) < 0) {
#ifndef NDEBUG
        __osError(ERR_OSPROFILESTOP_TIMER, 0);
        return;
#endif
    }

    __osProfileActive = FALSE;
}

#endif
