#include <string.h>

#include "usf.h"
#include "usf_internal.h"

#include "os.h"
#include "cpu_hle.h"

#include "audio.h"
#include "interpreter_cpu.h"
#include "main.h"
#include "memory.h"


#define N64WORD(x)		(*(uint32_t*)PageVRAM((x)))
#define N64HALF(x)		(*(uint16_t*)PageVRAM((x)))
#define N64BYTE(x)		(*(uint8_t*)PageVRAM((x^3)))

#define N64DWORD(addr) (((long long)N64WORD((addr))) << 32) + N64WORD((addr)+4);



int __osRestoreInt(usf_state_t * state, int n)
{
	STATUS_REGISTER |= state->GPR[0x4].UW[0];
	return 1;
}

int __osDisableInt(usf_state_t * state, int n)
{
	state->GPR[0x2].UW[0] = STATUS_REGISTER & 1;
	STATUS_REGISTER &= 0xFFFFFFFE;
	return 1;
}


void osEnqueueThread(usf_state_t * state, uint32_t osThreadQueueAddr, uint32_t threadVAddr)
{

	OSThread *thread = (OSThread*) PageVRAM(threadVAddr);
	OSThread *oldThread = (OSThread*) PageVRAM(osThreadQueueAddr);
	OSThread *curThread = (OSThread*) PageVRAM(oldThread->next);

	while((int32_t)curThread->priority >= (int32_t)thread->priority) {
		oldThread = curThread;
		curThread = (OSThread*) PageVRAM(curThread->next);
	}

	thread->next = oldThread->next;
	oldThread->next = threadVAddr;
	thread->queue = osThreadQueueAddr;
}

int __osEnqueueThread(usf_state_t * state, int n) {
	osEnqueueThread(state, state->GPR[4].UW[0],state->GPR[5].UW[0]);
	return 1;
}

int osStartThread(usf_state_t * state, int n)
{
	OSMesgQueue *osThreadQueue = NULL;
	uint32_t osThreadQueueAddr = 0;
	uint32_t oldStatus = STATUS_REGISTER & 1;
	uint32_t osActiveThreadAddr = 0;
	uint32_t osActiveThread = 0;

	OSThread *thread = (OSThread*)PageVRAM(state->GPR[4].UW[0]);

	STATUS_REGISTER &= 0xFFFFFFFE;

	osThreadQueueAddr = ((*(uint32_t*)PageRAM2(n + 0x40)) & 0xFFFF) << 16;
	osThreadQueueAddr += *(int16_t*)PageRAM2(n + 0x50);

	osThreadQueue = (OSMesgQueue*) PageVRAM(osThreadQueueAddr);

	if(thread->state != 8 ) {
		DisplayError(state, "OMG, thread state is not OS_STATE_WAITING!\n");
        return 0;
	}

	thread->state = OS_STATE_RUNNABLE;
	osEnqueueThread(state,osThreadQueueAddr,state->GPR[4].UW[0]);

	osActiveThreadAddr = ((*(uint32_t*)PageRAM2(n + 0xDC)) & 0xFFFF) << 16;
	osActiveThreadAddr += *(int16_t*)PageRAM2(n + 0xE0);

	osActiveThread = *(uint32_t*)PageVRAM(osActiveThreadAddr);

	if(osActiveThread==0) {
		DisplayError(state,"OMG, active thread is NULL!\n");
        return 0;
	}

	STATUS_REGISTER |= oldStatus;
//	CheckInterrupts();

	return 1;
}


int osRecvMesg(usf_state_t * state, int n)
{
	//unsigned long devAddr = state->GPR[7].UW[0];
	//unsigned long vAddr = state->GPR[0x11].UW[0];
	//unsigned long nbytes = state->GPR[0x10].UW[0];

	//unsigned long oldStatus = STATUS_REGISTER & 1;

	RunFunction(state, n | (state->PROGRAM_COUNTER & 0xF0000000));

	//DisplayError("%08x\n%08x\n%08x",devAddr, vAddr, nbytes);

	return 1;
}

// doesnt even use?
int osSetIntMask(usf_state_t * state, int paddr) {
#if 0
	uint32_t globalIntMask = 0;
	uint32_t mask = STATUS_REGISTER & 0xFF01;
	uint32_t interrupts = 0;
	uint32_t intAddress = 0, newMask = 0, workMask = 0;

	uint32_t baseAddress = 0;

	globalIntMask = ((*(uint16_t*)PageRAM2(paddr + 0x8)) & 0xFFFF) << 16;
	globalIntMask += *(uint16_t*)PageRAM2(paddr + 0xc);
	globalIntMask = *(uint32_t*)PageVRAM(globalIntMask);

	interrupts = (globalIntMask ^ 0xffffffff) & 0xff00;
	mask |= interrupts;
	newMask = MI_INTR_MASK_REG;

	if(!newMask)
		newMask = ((globalIntMask >> 16) ^ 0xFFFFFFFF) & 0x3F;

	mask |= (newMask << 16);

	baseAddress = ((*(uint16_t*)PageRAM2(paddr + 0x5C)) & 0xFFFF) << 16;
	baseAddress += *(int16_t*)PageRAM2(paddr + 0x64);
	baseAddress += ((state->GPR[4].UW[0] & 0x3F0000) & globalIntMask) >> 15;;

	MI_INTR_MASK_REG = *(uint16_t*)PageVRAM(baseAddress);

	state->STATUS_REGISTER = ((state->GPR[4].UW[0] & 0xff01) & (globalIntMask & 0xff00)) | (STATUS_REGISTER & 0xFFFF00FF);

#endif
	return 1;
}


int osVirtualToPhysical(usf_state_t * state, int paddr) {
	uintptr_t address = 0;
	uintptr_t vaddr = state->GPR[4].UW[0];

	address = (state->TLB_Map[vaddr >> 12] + vaddr) - (uintptr_t)state->N64MEM;

	if(address < 0x800000) {
		state->GPR[2].UW[0] = (uint32_t)address;
	} else
		state->GPR[2].UW[0] = 0xFFFFFFFF;

	return 1;
}

int osAiSetNextBuffer(usf_state_t * state, int paddr) {
	uint32_t var = 0, var2 = 0;
	var = ((*(int16_t*)PageRAM2(paddr + 0x4)) & 0xFFFF) << 16;
	var += *(int16_t*)PageRAM2(paddr + 0x8);

	var2 = N64WORD(var);
	if(AI_CONTROL_REG & 0x80000000)
		state->GPR[2].UW[0] = -1;

	AI_DRAM_ADDR_REG = state->GPR[4].UW[0];
	AI_LEN_REG = state->GPR[5].UW[0]&0x3FFF;
	AiLenChanged(state);
	state->GPR[2].UW[0] = 0;
	return 1;
}

int saveThreadContext(usf_state_t * state, int paddr) {
#if 0
	uint32_t OSThreadContextAddr = 0;

	OSThreadContextAddr = ((*(int16_t*)PageRAM2(paddr)) & 0xFFFF) << 16;
	OSThreadContextAddr += *(int16_t*)PageRAM2(paddr + 0x4);

	OSThreadContextAddr = N64WORD(OSThreadContextAddr);

	if((PageVRAM2(OSThreadContextAddr) & 0xffff) > 0xFF00) {
		DisplayError(state,"OMG! Too high!");
		return 0;
	}
#endif
	return 0;
}

int loadThreadContext(usf_state_t * state, int paddr) {
#if 0
    uint32_t i = 0, OSThreadContextAddr = 0, T9 = 0, osOSThread = 0, Addr2 = 0, GlobalBitMask = 0, Tmp = 0;
	uint32_t K0 = 0, K1 = 0, T0 = 0, R1 = 0, RCP = 0, intrList = 0;
	OSThread t;
	OSThreadContextAddr = ((*(int16_t*)PageRAM2(paddr)) & 0xFFFF) << 16;
	OSThreadContextAddr += *(int16_t*)PageRAM2(paddr + 0x8);

	Addr2 = ((*(int16_t*)PageRAM2(paddr + 0xC)) & 0xFFFF) << 16;
	Addr2 += *(int16_t*)PageRAM2(paddr + 0x10);

	GlobalBitMask = ((*(int16_t*)PageRAM2(paddr + 0x20)) & 0xFFFF) << 16;
	GlobalBitMask += *(int16_t*)PageRAM2(paddr + 0x28);
	GlobalBitMask = N64WORD(GlobalBitMask);

	intrList = ((*(int16_t*)PageRAM2(paddr + 0x14C + 0x0)) & 0xFFFF) << 16;
	intrList += *(int16_t*)PageRAM2(paddr + 0x150 + 0x0);

	return 0;

	if((PageVRAM2(OSThreadContextAddr) & 0xffff) > 0xFE80) {
		DisplayError(state, "OMG this number is too high!!!!\n");
	}

	osOSThread = N64WORD(OSThreadContextAddr);
	T9 = N64WORD(osOSThread);

	N64WORD(OSThreadContextAddr) = T9;
	N64WORD(Addr2) = osOSThread;

	N64WORD(osOSThread + 0x10) = OS_STATE_RUNNING; //T0 is globalbitmask

	K1 = N64WORD(osOSThread + 0x118); //osOSThread.context.k0

	STATUS_REGISTER = (K1 & 0xFFFF00FF) | (GlobalBitMask & 0xFF00);

	for(i = 1; i <= 0x19; i++) {
		state->GPR[i].DW = N64DWORD(osOSThread + 0x18 + (i * 8));
	}

	for(i = 0x1C; i <= 0x1F; i++) {
		state->GPR[i].DW = N64DWORD(osOSThread + 0x8 + (i * 8));
	}

	state->LO.DW = N64DWORD(osOSThread + 0x108);
	state->HI.DW = N64DWORD(osOSThread + 0x110);

	EPC_REGISTER = (uint32_t) N64WORD(osOSThread + 0x11C);

	state->FPCR[31] = (uint32_t) N64WORD(osOSThread + 0x12C);

	if(N64WORD(osOSThread + 0x18)) {
		for(i = 0; i <= 30; i+=2) {
			(*(uint64_t *)state->FPRDoubleLocation[i]) = N64DWORD(osOSThread + 0x130 + (i * 4));
		}
	} else {
	}

	state->GPR[0x1A].UW[0] = (uint32_t) osOSThread;

	RCP = N64WORD(osOSThread + 0x128);

	K0 = (intrList + ((RCP & (GlobalBitMask >> 16)) << 1));
	K1 = 0;

    r4300i_LH_VAddr(state, K0, &K1);

    // cheap hack?
    //K1 = 0xAAA;

    SW_Register(0x0430000C, K1);

	NextInstruction = JUMP;

	if ((STATUS_REGISTER & STATUS_ERL) != 0) {
		JumpToLocation = ERROREPC_REGISTER;
		STATUS_REGISTER &= ~STATUS_ERL;
	} else {
		JumpToLocation = EPC_REGISTER;
		STATUS_REGISTER &= ~STATUS_EXL;
	}

	LLBit = 0;
	CheckInterrupts();
#endif
	return 0;
}
