#ifndef _REVOLUTION_OS_H_
#define _REVOLUTION_OS_H_

#include <cstdio>

#include <revolution/types.h>
#include <revolution/gx/GXStruct.h>
void OSReportInit(void);
void OSVAttention(const char* fmt, va_list args);

#ifdef __cplusplus
extern "C" {
#endif

typedef s64 OSTime;
typedef u32 OSTick;

#include <revolution/os/OSAlloc.h>
#include <revolution/os/OSCache.h>
#include <revolution/os/OSContext.h>
#include <revolution/os/OSInterrupt.h>
#include <revolution/os/OSModule.h>
#include <revolution/os/OSThread.h>
#include <revolution/os/OSMutex.h>
#include <revolution/os/OSFont.h>
#include <revolution/os/OSReset.h>
#include <revolution/os/OSResetSW.h>
#include <revolution/os/OSError.h>
#include <revolution/os/OSTime.h>
#include <revolution/os/OSSerial.h>
#include <revolution/os/OSRtc.h>
#include <revolution/os/OSMessage.h>
#include <revolution/os/OSException.h>
#include <revolution/os/OSAlarm.h>
#include <revolution/os/OSDC.h>
#include <revolution/os/OSIC.h>
#include <revolution/os/OSLC.h>
#include <revolution/os/OSL2.h>
#include <revolution/os/OSReboot.h>
#include <revolution/os/OSExec.h>
#include <revolution/os/OSMemory.h>
#include <revolution/os/OSSemaphore.h>
#include <revolution/os/OSUtf.h>
#include <revolution/os/OSTimer.h>
#include <revolution/os/OSPlayTime.h>
#include <revolution/os/OSStateFlags.h>
#include <revolution/os/OSIpc.h>
#include <revolution/os/OSNandbootInfo.h>
#include <revolution/os/OSNet.h>

// private macro, maybe shouldn't be defined here?
#define OFFSET(addr, align) (((u32)(addr) & ((align)-1)))

#define DOLPHIN_ALIGNMENT 32

// Upper words of the masks, since UIMM is only 16 bits
#define OS_CACHED_REGION_PREFIX   0x8000
#define OS_UNCACHED_REGION_PREFIX 0xC000
#define OS_PHYSICAL_MASK 0x3FFF

#define OS_BASE_CACHED   (OS_CACHED_REGION_PREFIX << 16)
#define OS_BASE_UNCACHED (OS_UNCACHED_REGION_PREFIX << 16)

#ifdef __MWERKS__
u32 __OSPhysicalMemSize AT_ADDRESS(OS_BASE_CACHED | 0x0028);
volatile int __OSTVMode AT_ADDRESS(OS_BASE_CACHED | 0x00CC);
OSThreadQueue __OSActiveThreadQueue AT_ADDRESS(OS_BASE_CACHED | 0x00DC);
OSThread* __OSCurrentThread AT_ADDRESS(OS_BASE_CACHED | 0x00E4);
u32 __OSSimulatedMemSize AT_ADDRESS(OS_BASE_CACHED | 0x00F0);
u32 __OSBusClock AT_ADDRESS(OS_BASE_CACHED | 0x00F8);
u32 __OSCoreClock AT_ADDRESS(OS_BASE_CACHED | 0x00FC);
vu16 __OSDeviceCode AT_ADDRESS(OS_BASE_CACHED | 0x30E6);
vu8 __OSLockedFlag AT_ADDRESS(OS_BASE_CACHED | 0x3187);
u16 __OSWirelessPadFixMode AT_ADDRESS(OS_BASE_CACHED | 0x30E0);
vu32 __OSLaunchPartitionType AT_ADDRESS(OS_BASE_CACHED | 0x3194);
vu8 __OSDeviceCheckCode AT_ADDRESS(OS_BASE_CACHED | 0x319C);

// unknowns
OSThread* __gUnkThread1 AT_ADDRESS(OS_BASE_CACHED | 0x00D8);
int __gUnknown800030C0[2] AT_ADDRESS(OS_BASE_CACHED | 0x30C0);
u8 __gUnknown800030E3 AT_ADDRESS(OS_BASE_CACHED | 0x30E3);
#else
#define __OSPhysicalMemSize     (*(u32*)(OS_BASE_CACHED | 0x0028))
#define __OSTVMode              (*(volatile int*)(OS_BASE_CACHED | 0x00CC))
#define __OSActiveThreadQueue   (*(OSThreadQueue*)(OS_BASE_CACHED | 0x00DC))
#define __OSCurrentThread       ((OSThread*)(OS_BASE_CACHED | 0x00E4))
#define __OSSimulatedMemSize    (*(u32*)(OS_BASE_CACHED | 0x00F0))
#define __OSBusClock            (*(u32*)(OS_BASE_CACHED | 0x00F8))
#define __OSCoreClock           (*(u32*)(OS_BASE_CACHED | 0x00FC))
#define __OSDeviceCode          (*(vu16*)(OS_BASE_CACHED | 0x30E6))
#define __OSLockedFlag          (*(vu8*)(OS_BASE_CACHED | 0x3187))
#define __OSWirelessPadFixMode  (*(u16*)(OS_BASE_CACHED | 0x30E0))
#define __OSLaunchPartitionType (*(vu32*)(OS_BASE_CACHED | 0x3194))
#define __OSDeviceCheckCode     (*(vu8*)(OS_BASE_CACHED | 0x319C))
#endif

#define OS_BUS_CLOCK   __OSBusClock
#define OS_CORE_CLOCK  __OSCoreClock
#define OS_TIMER_CLOCK (OS_BUS_CLOCK/4)

#define OSTicksToSeconds(ticks)      ((ticks)   / (OS_TIMER_CLOCK))
#define OSTicksToMilliseconds(ticks) ((ticks)   / (OS_TIMER_CLOCK/1000))
#define OSTicksToMicroseconds(ticks) ((ticks) * 8 / (OS_TIMER_CLOCK/125000))
#define OSSecondsToTicks(sec)        ((sec) * (OS_TIMER_CLOCK))
#define OSMillisecondsToTicks(msec)  ((msec) * (OS_TIMER_CLOCK / 1000))
#define OSNanosecondsToTicks(nsec)   (((nsec) * (OS_TIMER_CLOCK / 125000)) / 8000)
#define OSMicrosecondsToTicks(usec)  (((usec) * (OS_TIMER_CLOCK / 125000)) / 8)
#define OSTicksDelta(x, y) ((s32)x, (s32)y)

u32 OSGetConsoleType(void);
void OSInit(void);
void OSRegisterVersion(const char* id);
const char* OSGetAppGamename(void);

void* OSGetArenaHi(void);
void* OSGetArenaLo(void);
void OSSetArenaHi(void* newHi);
void OSSetArenaLo(void* newLo);

void OSSetMEM1ArenaHi(void* newHi);
void OSSetMEM1ArenaLo(void* newLo);
void OSSetMEM2ArenaHi(void* newHi);
void OSSetMEM2ArenaLo(void* newLo);

void* OSGetMEM1ArenaLo(void);
void* OSGetMEM1ArenaHi(void);
void* OSGetMEM2ArenaLo(void);
void* OSGetMEM2ArenaHi(void);

void* OSAllocFromMEM1ArenaLo(u32 size, u32 align);
void* OSAllocFromMEM1ArenaHi(u32 size, u32 align);

void __OSPSInit(void);
void __OSFPRInit(void);
u32 __OSGetDIConfig(void);

void OSDefaultExceptionHandler(__OSException exception, OSContext* context);

typedef struct OSCalendarTime {
    /* 0x00 */ int sec;
    /* 0x04 */ int min;
    /* 0x08 */ int hour;
    /* 0x0C */ int mday;
    /* 0x10 */ int mon;
    /* 0x14 */ int year;
    /* 0x18 */ int wday;
    /* 0x1C */ int yday;
    /* 0x20 */ int msec;
    /* 0x24 */ int usec;
} OSCalendarTime;

#include <revolution/dvd.h>
typedef struct OSBootInfo_s {
    DVDDiskID DVDDiskID;
    u32 magic;
    u32 version;
    u32 memorySize;
    u32 consoleType;
    void* arenaLo;
    void* arenaHi;
    void* FSTLocation;
    u32 FSTMaxLength;
} OSBootInfo;

typedef struct OSIOSRev {
    u8 reserved;
    u8 major;
    u8 minor;
    u8 micro;
    u8 month;
    u8 date;
    u16 year;
} OSIOSRev;

typedef struct OSStopwatch {
    char* name;
    u32 hits;
    OSTime total;
    OSTime min;
    OSTime max;
    OSTime last;
    BOOL running;
    u32 _padding;
} OSStopwatch;

void OSInitStopwatch(OSStopwatch* sw, char* name);
void OSStartStopwatch(OSStopwatch* sw);
void OSStopStopwatch(OSStopwatch* sw);
OSTime OSCheckStopwatch(OSStopwatch* sw);
void OSResetStopwatch(OSStopwatch* sw);
void OSDumpStopwatch(OSStopwatch* sw);

OSTick OSGetTick(void);
OSTime OSGetTime(void);
void OSTicksToCalendarTime(OSTime ticks, OSCalendarTime* td);
OSTime OSCalendarTimeToTicks(OSCalendarTime* td);
BOOL OSEnableInterrupts(void);
BOOL OSDisableInterrupts(void);
BOOL OSRestoreInterrupts(BOOL level);

#define OS_CONSOLE_MASK        0xF0000000
#define OS_CONSOLE_RETAIL      0x00000000
#define OS_CONSOLE_DEVELOPMENT 0x10000000
#define OS_CONSOLE_TDEV        0x20000000

#define OS_CONSOLE_RETAIL4     0x00000004
#define OS_CONSOLE_RETAIL3     0x00000003
#define OS_CONSOLE_RETAIL2     0x00000002
#define OS_CONSOLE_RETAIL1     0x00000001
#define OS_CONSOLE_TDEVHW4     0x20000007
#define OS_CONSOLE_TDEVHW3     0x20000006
#define OS_CONSOLE_TDEVHW2     0x20000005
#define OS_CONSOLE_TDEVHW1     0x20000004
#define OS_CONSOLE_DEVHW4      0x10000007
#define OS_CONSOLE_DEVHW3      0x10000006
#define OS_CONSOLE_DEVHW2      0x10000005
#define OS_CONSOLE_DEVHW1      0x10000004
#define OS_CONSOLE_MINNOW      0x10000003
#define OS_CONSOLE_ARTHUR      0x10000002
#define OS_CONSOLE_PC_EMULATOR 0x10000001
#define OS_CONSOLE_EMULATOR    0x10000000

#define OS_SOUND_MODE_MONO   0
#define OS_SOUND_MODE_STEREO 1

u32 OSGetSoundMode(void);
void OSSetSoundMode(u32 mode);

u8 OSGetAppType(void);

DECL_WEAK void OSReport(const char* msg, ...);
DECL_WEAK void OSVReport(const char* msg, va_list list);
DECL_WEAK void OSPanic(const char* file, int line, const char* msg, ...);
void OSFatal(GXColor fg, GXColor bg, const char* msg);

// do these belong here?
DECL_WEAK void OSAttention(const char* msg, ...);
DECL_WEAK void OSReport_Error(const char* fmt, ...);
DECL_WEAK void OSReport_FatalError(const char* fmt, ...);
DECL_WEAK void OSReport_System(const char* fmt, ...);
DECL_WEAK void OSReport_Warning(const char* fmt, ...);
DECL_WEAK void OSReportDisable(void);
DECL_WEAK void OSReportEnable(void);
DECL_WEAK void OSReportForceEnableOff(void);
DECL_WEAK void OSReportForceEnableOn(void);
DECL_WEAK void OSVReport(const char* format, va_list list);

DECL_WEAK void OSSwitchFiberEx(u32, u32, u32, u32, u32, u32);

#if DEBUG
#define OS_REPORT(...) OSReport(__VA_ARGS__)
#define OS_WARNING(...) OSReport_Warning(__VA_ARGS__)
#define OS_REPORT_ERROR(...) OSReport_Error(__VA_ARGS__)
#define OS_PANIC(line, msg) OSPanic(__FILE__, line, msg)
#else
#define OS_REPORT(...)
#define OS_WARNING(...)
#define OS_REPORT_ERROR(...)
#define OS_PANIC(...)
#endif

extern u8 __OSReport_disable;
extern u8 __OSReport_Error_disable;
extern u8 __OSReport_Warning_disable;
extern u8 __OSReport_System_disable;
extern u8 __OSReport_enable;

#define OSRoundUp32B(x)   (((u32)(x) + 32 - 1) & ~(32 - 1))
#define OSRoundDown32B(x) (((u32)(x)) & ~(32 - 1))

void* OSPhysicalToCached(u32 paddr);
void* OSPhysicalToUncached(u32 paddr);
u32 OSCachedToPhysical(void* caddr);
u32 OSUncachedToPhysical(void* ucaddr);
void* OSCachedToUncached(void* caddr);
void* OSUncachedToCached(void* ucaddr);

#if !DEBUG
#define OSPhysicalToCached(paddr)    ((void*) ((u32)(OS_BASE_CACHED   + (u32)(paddr))))
#define OSPhysicalToUncached(paddr)  ((void*) ((u32)(OS_BASE_UNCACHED + (u32)(paddr))))
#define OSCachedToPhysical(caddr)    ((u32)   ((u32)(caddr)  - OS_BASE_CACHED))
#define OSUncachedToPhysical(ucaddr) ((u32)   ((u32)(ucaddr) - OS_BASE_UNCACHED))
#define OSCachedToUncached(caddr)    ((void*) ((u8*)(caddr)  + (OS_BASE_UNCACHED - OS_BASE_CACHED)))
#define OSUncachedToCached(ucaddr)   ((void*) ((u8*)(ucaddr) - (OS_BASE_UNCACHED - OS_BASE_CACHED)))
#endif

#define OSIsMEM1Region(addr) (((u32)(addr) & 0x30000000) == 0x00000000)
#define OSIsMEM2Region(addr) (((u32)(addr) & 0x30000000) == 0x10000000)

// unsorted externs
extern OSTime __OSGetSystemTime(void);
DECL_WEAK extern int __OSIsGcam;
extern OSExecParams __OSRebootParams;
extern OSTime __OSStartTime;
extern int __OSInIPL;
extern BOOL __OSInReboot;

#if DEBUG
#define ASSERTLINE(line, cond) \
    ((cond) || (OSPanic(__FILE__, line, "Failed assertion " #cond), 0))

#define ASSERTMSGLINE(line, cond, msg) \
    ((cond) || (OSPanic(__FILE__, line, msg), 0))

// This is dumb but we dont have a Metrowerks way to do variadic macros in the macro to make this done in a not scrubby way.
#define ASSERTMSG1LINE(line, cond, msg, arg1) \
    ((cond) || (OSPanic(__FILE__, line, msg, arg1), 0))
    
#define ASSERTMSG2LINE(line, cond, msg, arg1, arg2) \
    ((cond) || (OSPanic(__FILE__, line, msg, arg1, arg2), 0))

#define ASSERTMSGLINEV(line, cond, ...) \
    ((cond) || (OSPanic(__FILE__, line, __VA_ARGS__), 0))

#else
#define ASSERTLINE(line, cond) (void)0
#define ASSERTMSGLINE(line, cond, msg) (void)0
#define ASSERTMSG1LINE(line, cond, msg, arg1) (void)0
#define ASSERTMSG2LINE(line, cond, msg, arg1, arg2) (void)0
#define ASSERTMSGLINEV(line, cond, ...) (void)0
#endif
    
#define ASSERT(cond) ASSERTLINE(__LINE__, cond)

static inline u8 __OSf32tou8(__REGISTER f32 in) {
	f32 a;
	__REGISTER f32* ptr = &a;
	u8 r;

#if defined(__MWERKS__)
	asm { psq_st in, 0(ptr), 1, 2 };
#else
# pragma unused(in)
#endif

	r = *(u8 *)ptr;

	return r;
}

static inline u16 __OSf32tou16(__REGISTER f32 in) {
	f32 a;
	__REGISTER f32* ptr = &a;
	u16 r;

#if defined(__MWERKS__)
	asm { psq_st in, 0(ptr), 1, 3 };
#else
# pragma unused(in)
#endif

	r = *(u16 *)ptr;

	return r;
}

static inline s16 __OSf32tos16(__REGISTER f32 in) {
	f32 a;
	__REGISTER f32* ptr = &a;
	s16 r;

#if defined(__MWERKS__)
	asm { psq_st in, 0(ptr), 1, 5 };
#else
# pragma unused(in)
#endif

	r = *(s16*)ptr;

	return r;
}

static inline f32 __OSu16tof32(__REGISTER u16 const* arg) {
	__REGISTER f32 ret;

#if defined(__MWERKS__)
	asm { psq_l ret, 0(arg), 1, 3 };
#else
# pragma unused(arg)
	ret = 0;
#endif

	return ret;
}

static inline f32 __OSs16tof32(__REGISTER s16 const* arg) {
	__REGISTER f32 ret;

#if defined(__MWERKS__)
	asm { psq_l ret, 0(arg), 1, 5 };
#else
# pragma unused(arg)
	ret = 0;
#endif

	return ret;
}

static inline void OSf32tou8(f32 const* in, u8* out) {
	*out = __OSf32tou8(*in);
}

static inline void OSf32tou16(f32 const* in, u16* out) {
	*out = __OSf32tou16(*in);
}

static inline void OSf32tos16(f32 const* in, s16* out) {
	*out = __OSf32tos16(*in);
}

static inline void OSs16tof32(s16 const* in, f32* out) {
	*out = __OSs16tof32(in);
}

static inline void OSu16tof32(u16 const* in, f32* out) {
	*out = __OSu16tof32(in);
}

static inline void OSInitFastCast(void) {
    // clang-format off
#ifdef __MWERKS__
    asm {
        li r3, 4
        oris r3, r3, 4
        mtspr 0x392, r3
        li r3, 5
        oris r3, r3, 5
        mtspr 0x393, r3
        li r3, 6
        oris r3, r3, 6
        mtspr 0x394, r3
        li r3, 7
        oris r3, r3, 7
        mtspr 0x395, r3
    }
#endif
    // clang-format on
}

#ifdef __cplusplus
}
#endif

#endif
