#pragma once
#include "Cafe/HW/MMU/MMU.h"

enum
{
	CPUException_NOTHING,
	CPUException_FPUUNAVAILABLE,
	CPUException_EXTERNAL,
	CPUException_SYSTEMCALL
};

#define PPC_LWARX_RESERVATION_MAX	(4)

union FPR_t
{
	double fpr;
	struct
	{
		double fp0;
		double fp1;
	};
	struct
	{
		uint64 guint; 
	};
	struct
	{
		uint64 fp0int;
		uint64 fp1int;
	};
};

typedef struct  
{
	struct  
	{
		uint32 scr;
		uint32 car;
		//uint32 bcr;
	}sprGlobal;
	uint64 tb;
}PPCInterpreterGlobal_t;

struct PPCInterpreter_t
{
	uint32 instructionPointer;
	uint32 gpr[32];
	FPR_t fpr[32];
	uint32 fpscr;
	uint8 cr[32]; // 0 -> bit not set, 1 -> bit set (upper 7 bits of each byte must always be zero) (cr0 starts at index 0, cr1 at index 4 ..)
	uint8 xer_ca;  // carry from xer
	uint8 LSQE;
	uint8 PSE;
	// thread remaining cycles
	sint32 remainingCycles; // if this value goes below zero, the next thread is scheduled
	sint32 skippedCycles; // number of skipped cycles
	struct  
	{
		uint32 LR;
		uint32 CTR;
		uint32 XER;
		uint32 UPIR;
		uint32 UGQR[8];
	}spr;
	// LWARX and STWCX
	uint32 reservedMemAddr;
	uint32 reservedMemValue;
	// temporary storage for recompiler
	FPR_t temporaryFPR[8];
	uint32 temporaryGPR[4];
	// values below this are not used by Cafe OS usermode
	struct  
	{
		uint32 fpecr; // is this the same register as fpscr ?
		uint32 DEC;
		uint32 srr0;
		uint32 srr1;
		uint32 PVR;
		uint32 msr;
		uint32 sprg[4];
		// DSI/ISI
		uint32 dar;
		uint32 dsisr;
		// DMA
		uint32 dmaU;
		uint32 dmaL;
		// MMU 
		uint32 dbatU[8];
		uint32 dbatL[8];
		uint32 ibatU[8];
		uint32 ibatL[8];
		uint32 sr[16];
		uint32 sdr1;
	}sprExtended;
	// global CPU values
	PPCInterpreterGlobal_t* global;
	// interpreter control
	bool memoryException;
	// core context (starts at 0xFFFFFF00?)
	/* 0xFFFFFFE4 */ uint32 coreInterruptMask;

	// extra variables for recompiler
	void* rspTemp;
};

// parameter access (legacy C style)

static uint32 PPCInterpreter_getCallParamU32(PPCInterpreter_t* hCPU, uint32 index)
{
	if (index >= 8)
		return memory_readU32(hCPU->gpr[1] + 8 + (index - 8) * 4);
	return hCPU->gpr[3 + index];
}

static uint64 PPCInterpreter_getCallParamU64(PPCInterpreter_t* hCPU, uint32 index)
{
	uint64 v = ((uint64)PPCInterpreter_getCallParamU32(hCPU, index)) << 32ULL;
	v |= ((uint64)PPCInterpreter_getCallParamU32(hCPU, index+1));
	return v;
}

#define ppcGetCallParamU32(__index) PPCInterpreter_getCallParamU32(hCPU, __index)
#define ppcGetCallParamU16(__index) ((uint16)(PPCInterpreter_getCallParamU32(hCPU, __index)&0xFFFF))
#define ppcGetCallParamU8(__index) ((uint8)(PPCInterpreter_getCallParamU32(hCPU, __index)&0xFF))
#define ppcGetCallParamStruct(__index, __type) ((__type*)memory_getPointerFromVirtualOffsetAllowNull(PPCInterpreter_getCallParamU32(hCPU, __index)))

// legacy way of accessing parameters
#define ppcDefineParamU32(__name, __index) uint32 __name = PPCInterpreter_getCallParamU32(hCPU, __index)
#define ppcDefineParamU16(__name, __index) uint16 __name = (uint16)PPCInterpreter_getCallParamU32(hCPU, __index)
#define ppcDefineParamU32BEPtr(__name, __index) uint32be* __name = (uint32be*)((uint8*)memory_getPointerFromVirtualOffsetAllowNull(PPCInterpreter_getCallParamU32(hCPU, __index)))
#define ppcDefineParamS32(__name, __index) sint32 __name = (sint32)PPCInterpreter_getCallParamU32(hCPU, __index)
#define ppcDefineParamU64(__name, __index) uint64 __name = PPCInterpreter_getCallParamU64(hCPU, __index)
#define ppcDefineParamMPTR(__name, __index) MPTR __name = (MPTR)PPCInterpreter_getCallParamU32(hCPU, __index)
#define ppcDefineParamMEMPTR(__name, __type, __index) MEMPTR<__type> __name{PPCInterpreter_getCallParamU32(hCPU, __index)}
#define ppcDefineParamU8(__name, __index) uint8 __name = (PPCInterpreter_getCallParamU32(hCPU, __index)&0xFF)
#define ppcDefineParamStructPtr(__name, __type, __index) __type* __name = ((__type*)memory_getPointerFromVirtualOffsetAllowNull(PPCInterpreter_getCallParamU32(hCPU, __index)))
#define ppcDefineParamTypePtr(__name, __type, __index) __type* __name = ((__type*)memory_getPointerFromVirtualOffsetAllowNull(PPCInterpreter_getCallParamU32(hCPU, __index)))
#define ppcDefineParamPtr(__name, __type, __index) __type* __name = ((__type*)memory_getPointerFromVirtualOffsetAllowNull(PPCInterpreter_getCallParamU32(hCPU, __index)))
#define ppcDefineParamStr(__name, __index) char* __name = ((char*)memory_getPointerFromVirtualOffsetAllowNull(PPCInterpreter_getCallParamU32(hCPU, __index)))
#define ppcDefineParamUStr(__name, __index) uint8* __name = ((uint8*)memory_getPointerFromVirtualOffsetAllowNull(PPCInterpreter_getCallParamU32(hCPU, __index)))
#define ppcDefineParamWStr(__name, __index) wchar_t* __name = ((wchar_t*)memory_getPointerFromVirtualOffsetAllowNull(PPCInterpreter_getCallParamU32(hCPU, __index)))
#define ppcDefineParamWStrBE(__name, __index) uint16be* __name = ((uint16be*)memory_getPointerFromVirtualOffsetAllowNull(PPCInterpreter_getCallParamU32(hCPU, __index)))

// GPR constants

#define GPR_SP 1

// interpreter functions

PPCInterpreter_t* PPCInterpreter_createInstance(unsigned int Entrypoint);
PPCInterpreter_t* PPCInterpreter_getCurrentInstance();
void PPCInterpreter_setCurrentInstance(PPCInterpreter_t* hCPU);

uint64 PPCInterpreter_getMainCoreCycleCounter();

void PPCInterpreter_nextInstruction(PPCInterpreter_t* cpuInterpreter);
void PPCInterpreter_jumpToInstruction(PPCInterpreter_t* cpuInterpreter, uint32 newIP);

void PPCInterpreterSlim_executeInstruction(PPCInterpreter_t* hCPU);
void PPCInterpreterFull_executeInstruction(PPCInterpreter_t* hCPU);

// misc

uint32 PPCInterpreter_getXER(PPCInterpreter_t* hCPU);
void PPCInterpreter_setXER(PPCInterpreter_t* hCPU, uint32 v);

// Wii U clocks (deprecated. Moved to Espresso/Const.h)
#define ESPRESSO_CORE_CLOCK       1243125000
#define ESPRESSO_BUS_CLOCK        248625000
#define ESPRESSO_TIMER_CLOCK      (ESPRESSO_BUS_CLOCK/4) // 62156250

#define ESPRESSO_CORE_CLOCK_TO_TIMER_CLOCK(__cc) ((__cc)/20ULL)

// interrupt vectors
#define CPU_EXCEPTION_DSI			0x00000300
#define CPU_EXCEPTION_INTERRUPT		0x00000500 // todo: validate
#define CPU_EXCEPTION_FPUUNAVAIL	0x00000800 // todo: validate
#define CPU_EXCEPTION_SYSTEMCALL	0x00000C00 // todo: validate
#define CPU_EXCEPTION_DECREMENTER	0x00000900 // todo: validate

// FPU available check
//#define FPUCheckAvailable() if ((hCPU->msr & MSR_FP) == 0) { IPTException(hCPU, CPU_EXCEPTION_FPUUNAVAIL); return; }
#define FPUCheckAvailable() // since the emulated code always runs in usermode we can assume that MSR_FP is always set

// spr
void PPCSpr_set(PPCInterpreter_t* hCPU, uint32 spr, uint32 newValue);
uint32 PPCSpr_get(PPCInterpreter_t* hCPU, uint32 spr);

uint32 PPCInterpreter_getCoreIndex(PPCInterpreter_t* hCPU);
uint32 PPCInterpreter_getCurrentCoreIndex();

// decrement register
void PPCInterpreter_setDEC(PPCInterpreter_t* hCPU, uint32 newValue);

// timing for main processor
extern uint64 ppcCyclesSince2000; // on init this is set to the cycles that passed since 1.1.2000
extern uint64 ppcCyclesSince2000TimerClock; // on init this is set to the cycles that passed since 1.1.2000 / 20
extern uint64 ppcCyclesSince2000_UTC;
extern uint64 ppcMainThreadDECCycleValue; // value that was set to dec register
extern uint64 ppcMainThreadDECCycleStart; // at which cycle the dec register was set

// PPC timer
void PPCTimer_init();
void PPCTimer_waitForInit();
uint64 PPCTimer_getFromRDTSC();

uint64 PPCTimer_microsecondsToTsc(uint64 us);
uint64 PPCTimer_tscToMicroseconds(uint64 us);
uint64 PPCTimer_getRawTsc();

void PPCTimer_start();

// core info and control
extern uint32 ppcThreadQuantum;

uint8* PPCInterpreterGetAndModifyStackPointer(sint32 offset);
uint8* PPCInterpreterGetStackPointer();
void PPCInterpreterModifyStackPointer(sint32 offset);

uint32 PPCInterpreter_makeCallableExportDepr(void (*ppcCallableExport)(PPCInterpreter_t* hCPU));

static inline float flushDenormalToZero(float f)
{
	uint32 v = *(uint32*)&f;
	return *(float*)&v;
}

// HLE interface

typedef void(*HLECALL)(PPCInterpreter_t* hCPU);

typedef sint32 HLEIDX;
HLEIDX PPCInterpreter_registerHLECall(HLECALL hleCall, std::string hleName);
HLECALL PPCInterpreter_getHLECall(HLEIDX funcIndex);

// HLE scheduler

void PPCInterpreter_relinquishTimeslice();

void PPCCore_boostQuantum(sint32 numCycles);
void PPCCore_deboostQuantum(sint32 numCycles);

void PPCCore_switchToScheduler();
void PPCCore_switchToSchedulerWithLock();

PPCInterpreter_t* PPCCore_executeCallbackInternal(uint32 functionMPTR);
void PPCCore_init();

// LLE scheduler

void PPCCoreLLE_startSingleCoreScheduler(uint32 entrypoint);
