#pragma once
#include "../jabyengine_defines.hpp"

/*
R0	    zr	    Constant Zero	
R1	    at	    Reserved for the assembler	
R2-R3	v0-v1	Values for results and expression evaluation	
R4-R7	a0-a3	Arguments	
R8-R15	t0-t7	Temporaries (not preserved across call)	
R16-R23	s0-s7	Saved (preserved across call)	
R24-R25	t8-t9	More temporaries (not preserved across call)	
R26-R27	k0-k1	Reserved for OS Kernel	
R28	    gp	    Global Pointer	
R29	    sp	    Stack Pointer	
R30	    fp	    Frame Pointer	
R31 	ra	    Return address (set by function call)
*/

namespace JabyEngine {
    namespace BIOS {
        struct Version {
            enum Type {
                Unkown,
                Devboard,
                PS1,
                PS2,
                PS3,
                PSCompatible, // internal usage only
                No$psx,
                XEBRA
            };

            struct {
                uint8_t  day;
                uint8_t  month;
                uint16_t year;
            } date;
            Type        type;
            const char* kernel_maker;
            const char* version_str;
            const char* gui_version;
            const char* copyright;
        };

        extern const Version version;
    }

    struct TCB {
        uint32_t status;
        uint32_t unused;
        uint32_t reg[32];
        uint32_t epc;
        uint32_t hi;
        uint32_t lo;
        uint32_t sr;
        uint32_t cause;
        uint32_t unused2[9];
    };

    struct PCB {
        TCB* current_tcb;
    };

    struct ToT {
        using ExCB = void;
        using EvCB = void;
        using FCB  = void;

        ExCB*    exception_chain;
        uint32_t exception_chain_size;
        PCB*     processes;
        uint32_t processes_size;
        TCB*     threads;
        uint32_t threads_size;
        uint32_t reserved_0;
        uint32_t reserved_1;
        EvCB*    events;
        uint32_t events_size;
        uint32_t reserved_2;
        uint32_t reserved_3;
        uint32_t reserved_4;
        uint32_t reserved_5;
        uint32_t reserved_6;
        uint32_t reserved_7;
        FCB*     files;
        uint32_t files_size;
        uint32_t reserved_8;
        uint32_t reserved_9;
    };

    extern ToT table_of_tables;

    namespace SysCall {
        static constexpr const uint32_t Table_A = 0xA0;
        static constexpr const uint32_t Table_B = 0xB0;
        static constexpr const uint32_t Table_C = 0xC0;

        enum struct Priority {
            CdromDmaIrq      = 0, 
            CdromIoIrq       = 0, 
            SyscallException = 0,

            CardSpecificIrq = 1,
            VblankIrq       = 1,
            Timer2Irq       = 1,
            Timer1Irq       = 1,
            Timer0Irq       = 1,

            PadCardIrq = 2,

            DefInt = 3
        };

        enum InterruptVerifierResult {
            SkipHandler    = 0,
            ExecuteHandler = 1
        };

        typedef InterruptVerifierResult (*InterruptVerifier)();
        typedef void (*InterruptHandler)(uint32_t);
        using   ThreadHandle = uint32_t;

        #pragma pack(push, 1)
        struct InterruptCallback {
            struct InterruptCallback* next;
            InterruptHandler          handler_function;
            InterruptVerifier         verifier_function;
            uint32_t                  notUsed;

            static constexpr InterruptCallback from(InterruptVerifier verifier, InterruptHandler handler) {
                return InterruptCallback{nullptr, handler, verifier, 0};
            }
        };
        #pragma pack(pop)

        #define __syscall_function_cast(table, ...) reinterpret_cast<__VA_ARGS__>(table)

        static __always_inline uint32_t* get_gp() {
            uint32_t* gp;
            __asm__("sw $gp, %0" : "=m"(gp));
            return gp;
        }

        static __always_inline void DequeueCdIntr() {
            register uint32_t FuncID asm("t1") = 0xa3;
            __asm__ volatile("" : "=r"(FuncID) : "r"(FuncID));
    
            __syscall_function_cast(Table_A, void(*)())();
        }

        static __always_inline void FlushCache() {
            register uint32_t FuncID asm("t1") = 0x44;
            __asm__ volatile("" : "=r"(FuncID) : "r"(FuncID));
    
            __syscall_function_cast(Table_A, void(*)())();
        }

        static __always_inline void* memcpy(void *dst, const void *src, size_t len) {
            register uint32_t FuncID asm("t1") = 0x2A;
            __asm__ volatile("" : "=r"(FuncID) : "r"(FuncID));

            return __syscall_function_cast(Table_A, void*(*)(void*, const void*, size_t))(dst, src, len);
        }

        static __always_inline ThreadHandle OpenThread(void (*thread_func)(), uint32_t* stack_ptr, uint32_t* gp) {
            register uint32_t FuncID asm("t1") = 0x0E;
            __asm__ volatile("" : "=r"(FuncID) : "r"(FuncID));

            return __syscall_function_cast(Table_B, ThreadHandle(*)(void(*)(), uint32_t*, uint32_t*))(thread_func, stack_ptr, gp);
        }

        static __always_inline void [[noreturn]] ChangeThread(ThreadHandle handle) {
            register uint32_t FuncID asm("t1") = 0x10;
            __asm__ volatile("" : "=r"(FuncID) : "r"(FuncID));

            __syscall_function_cast(Table_B, void(*)(ThreadHandle))(handle);
        }

        static __always_inline void InitPad(uint8_t *portA, uint32_t portASize, uint8_t *portB, uint32_t portBSize) {
            register uint32_t FuncID asm("t1") = 0x12;
            __asm__ volatile("" : "=r"(FuncID) : "r"(FuncID));

            __syscall_function_cast(Table_B, void(*)(uint8_t*, uint32_t, uint8_t*, uint32_t))(portA, portASize, portB, portBSize);
        }

        static __always_inline void StartPad() {
            register uint32_t FuncID asm("t1") = 0x13;
            __asm__ volatile("" : "=r"(FuncID) : "r"(FuncID));

            __syscall_function_cast(Table_B, void(*)())();
        }

        static __always_inline void StopPad() {
            register uint32_t FuncID asm("t1") = 0x14;
            __asm__ volatile("" : "=r"(FuncID) : "r"(FuncID));

            __syscall_function_cast(Table_B, void(*)())();
        }

        static __always_inline void ChangeClearPad(int32_t _reserved) {
            register uint32_t FuncID asm("t1") = 0x5B;
            __asm__ volatile("" : "=r"(FuncID) : "r"(FuncID));

            __syscall_function_cast(Table_B, void(*)(int32_t))(_reserved);
        }

        static __always_inline void [[noreturn]] ReturnFromException() {
            register uint32_t FuncID asm("t1") = 0x17;
            __asm__ volatile("" : "=r"(FuncID) : "r"(FuncID));

            __syscall_function_cast(Table_B, void(*)())();
        }

        static __always_inline void SetDefaultExitFromException() {
            register uint32_t FuncID asm("t1") = 0x18;
            __asm__ volatile("" : "=r"(FuncID) : "r"(FuncID));
            
            __syscall_function_cast(Table_B, void(*)())();
        }

        static __always_inline int SysEnqIntRP(Priority prio, InterruptCallback* interElm) {
            register uint32_t FuncID asm("t1") = 0x02;
            __asm__ volatile("" : "=r"(FuncID) : "r"(FuncID));

            return __syscall_function_cast(Table_C, int(*)(Priority prio, InterruptCallback *interElm))(prio, interElm);
        }

        static __always_inline int SysDeqIntRP(Priority prio, InterruptCallback *interElm) {
            register uint32_t FuncID asm("t1") = 0x03;
            __asm__ volatile("" : "=r"(FuncID) : "r"(FuncID));

            return __syscall_function_cast(Table_C, int(*)(Priority prio, InterruptCallback *interElm))(prio, interElm);
        }

        static __always_inline uint32_t EnterCriticalSection() {
            register uint32_t FuncID      asm("a0") = 0x01;
            register uint32_t returnValue asm("v0");

            __asm__ volatile("syscall" : "=r"(returnValue) : "r"(FuncID) : "memory");
            return returnValue;
        }

        static __always_inline void ExitCriticalSection() {
            register uint32_t FuncID asm("a0") = 0x02;

            __asm__ volatile("syscall" :: "r"(FuncID) : "memory");
        }

        static __always_inline void DeliverEvent(uint32_t classId, uint32_t spec) {
            register uint32_t FuncID asm("t1") = 0x07;
            
            __asm__ volatile("" : "=r"(FuncID) : "r"(FuncID));
            __syscall_function_cast(Table_B, void (*)(uint32_t, uint32_t))(classId, spec);
        }

        static __always_inline uint32_t OpenEvent(uint32_t classId, uint32_t spec, uint32_t mode, void (*handler)()) {
            register uint32_t FuncID asm("t1") = 0x08;
            
            __asm__ volatile("" : "=r"(FuncID) : "r"(FuncID));
            return __syscall_function_cast(Table_B, uint32_t(*)(uint32_t, uint32_t, uint32_t, void(*)()))(classId, spec, mode, handler);
        }

        static __always_inline int CloseEvent(uint32_t event) {
            register uint32_t FuncID asm("t1") = 0x09;
            
            __asm__ volatile("" : "=r"(FuncID) : "r"(FuncID));
            return __syscall_function_cast(Table_B, uint32_t(*)(uint32_t))(event);
        }

        static __always_inline int32_t TestEvent(uint32_t event) {
            register uint32_t FuncID asm("t1") = 0x0B;
            
            __asm__ volatile("" : "=r"(FuncID) : "r"(FuncID));
            return __syscall_function_cast(Table_B, int32_t (*)(uint32_t))(event);
        }

        static __always_inline int32_t EnableEvent(uint32_t event) {
            register uint32_t FuncID asm("t1") = 0x0C;
            
            __asm__ volatile("" : "=r"(FuncID) : "r"(FuncID));
            return __syscall_function_cast(Table_B, int32_t (*)(uint32_t))(event);
        }

        static __always_inline const uint16_t* Krom2RawAdd(uint16_t sjis_code) {
            register uint32_t FuncID asm("t1") = 0x51;

            __asm__ volatile("" : "=r"(FuncID) : "r"(FuncID));
            return __syscall_function_cast(Table_B, const uint16_t* (*)(uint16_t))(sjis_code);
        }

        void printf(const char* txt, ...);
    }
}