#include <string.h>

#include "pgxp_mem.h"
#include "pgxp_cpu.h"
#include "pgxp_gte.h"
#include "pgxp_value.h"

PGXP_value Mem[3 * 2048 * 1024 / 4];		// mirror 2MB in 32-bit words * 3
const u32 UserMemOffset = 0;
const u32 ScratchOffset = 2048 * 1024 / 4;
const u32 RegisterOffset = 2 * 2048 * 1024 / 4;
const u32 InvalidAddress = 3 * 2048 * 1024 / 4;

void PGXP_InitMem()
{
	memset(Mem, 0, sizeof(Mem));
}

char* PGXP_GetMem()
{
	return (char*)(Mem); // Config.PGXP_GTE ? (char*)(Mem) : NULL;
}

/*  Playstation Memory Map (from Playstation doc by Joshua Walker)
0x0000_0000-0x0000_ffff		Kernel (64K)
0x0001_0000-0x001f_ffff		User Memory (1.9 Meg)

0x1f00_0000-0x1f00_ffff		Parallel Port (64K)

0x1f80_0000-0x1f80_03ff		Scratch Pad (1024 bytes)

0x1f80_1000-0x1f80_2fff		Hardware Registers (8K)

0x1fc0_0000-0x1fc7_ffff		BIOS (512K)

0x8000_0000-0x801f_ffff		Kernel and User Memory Mirror (2 Meg) Cached
0x9fc0_0000-0x9fc7_ffff		BIOS Mirror (512K) Cached

0xa000_0000-0xa01f_ffff		Kernel and User Memory Mirror (2 Meg) Uncached
0xbfc0_0000-0xbfc7_ffff		BIOS Mirror (512K) Uncached
*/
void ValidateAddress(u32 addr)
{
	int* pi = NULL;

	if ((addr >= 0x00000000) && (addr <= 0x007fffff)) {}	// Kernel + User Memory x 8
	else if ((addr >= 0x1f000000) && (addr <= 0x1f00ffff)) {}	// Parallel Port
	else if ((addr >= 0x1f800000) && (addr <= 0x1f8003ff)) {}	// Scratch Pad
	else if ((addr >= 0x1f801000) && (addr <= 0x1f802fff)) {}	// Hardware Registers
	else if ((addr >= 0x1fc00000) && (addr <= 0x1fc7ffff)) {}	// Bios
	else if ((addr >= 0x80000000) && (addr <= 0x807fffff)) {}	// Kernel + User Memory x 8 Cached mirror
	else if ((addr >= 0x9fc00000) && (addr <= 0x9fc7ffff)) {}	// Bios Cached Mirror
	else if ((addr >= 0xa0000000) && (addr <= 0xa07fffff)) {}	// Kernel + User Memory x 8 Uncached mirror
	else if ((addr >= 0xbfc00000) && (addr <= 0xbfc7ffff)) {}	// Bios Uncached Mirror
	else if (addr == 0xfffe0130) {}								// Used for cache flushing
	else
	{
		//	*pi = 5;
	}

}

u32 PGXP_ConvertAddress(u32 addr)
{
	u32 memOffs = 0;
	u32 paddr = addr;

	ValidateAddress(addr);

	switch (paddr >> 24)
	{
	case 0x80:
	case 0xa0:
	case 0x00:
		// RAM further mirrored over 8MB
		paddr = ((paddr & 0x7FFFFF) % 0x200000) >> 2;
		paddr = UserMemOffset + paddr;
		break;
	default:
		if ((paddr >> 20) == 0x1f8)
		{
			if (paddr >= 0x1f801000)
			{
				//	paddr = ((paddr & 0xFFFF) - 0x1000);
				//	paddr = (paddr % 0x2000) >> 2;
				paddr = ((paddr & 0xFFFF) - 0x1000) >> 2;
				paddr = RegisterOffset + paddr;
				break;
			}
			else
			{
				//paddr = ((paddr & 0xFFF) % 0x400) >> 2;
				paddr = (paddr & 0x3FF) >> 2;
				paddr = ScratchOffset + paddr;
				break;
			}
		}

		paddr = InvalidAddress;
		break;
	}

#ifdef GTE_LOG
	//GTE_LOG("PGXP_Read %x [%x] |", addr, paddr);
#endif

	return paddr;
}

PGXP_value* GetPtr(u32 addr)
{
	addr = PGXP_ConvertAddress(addr);

	if (addr != InvalidAddress)
		return &Mem[addr];
	return NULL;
}

PGXP_value* ReadMem(u32 addr)
{
	return GetPtr(addr);
}

void ValidateAndCopyMem(PGXP_value* dest, u32 addr, u32 value)
{
	PGXP_value* pMem = GetPtr(addr);
	if (pMem != NULL)
	{
		Validate(pMem, value);
		*dest = *pMem;
		return;
	}

	*dest = PGXP_value_invalid_address;
}

void ValidateAndCopyMem16(PGXP_value* dest, u32 addr, u32 value, int sign)
{
	u32 validMask = 0;
	psx_value val, mask;
	PGXP_value* pMem = GetPtr(addr);
	if (pMem != NULL)
	{
		mask.d = val.d = 0;
		// determine if high or low word
		if ((addr % 4) == 2)
		{
			val.w.h = value;
			mask.w.h = 0xFFFF;
			validMask = VALID_1;
		}
		else
		{
			val.w.l = value;
			mask.w.l = 0xFFFF;
			validMask = VALID_0;
		}

		// validate and copy whole value
		MaskValidate(pMem, val.d, mask.d, validMask);
		*dest = *pMem;

		// if high word then shift
		if ((addr % 4) == 2)
		{
			dest->x = dest->y;
			dest->lFlags = dest->hFlags;
			dest->compFlags[0] = dest->compFlags[1];
		}

		// truncate value
		dest->y = (dest->x < 0) ? -1.f * sign : 0.f;// 0.f;
		dest->hFlags = 0;
		dest->value = value;
		dest->compFlags[1] = VALID;	// iCB: High word is valid, just 0
		return;
	}

	*dest = PGXP_value_invalid_address;
}

void WriteMem(PGXP_value* value, u32 addr)
{
	PGXP_value* pMem = GetPtr(addr);

	if (pMem)
		*pMem = *value;
}

void WriteMem16(PGXP_value* src, u32 addr)
{
	PGXP_value* dest = GetPtr(addr);
	psx_value*	pVal = NULL;

	if (dest)
	{
	        pVal = (psx_value*)&dest->value;
		// determine if high or low word
		if ((addr % 4) == 2)
		{
			dest->y = src->x;
			dest->hFlags = src->lFlags;
			dest->compFlags[1] = src->compFlags[0];
			pVal->w.h = (u16)src->value;
		}
		else
		{
			dest->x = src->x;
			dest->lFlags = src->lFlags;
			dest->compFlags[0] = src->compFlags[0];
			pVal->w.l = (u16)src->value;
		}

		// overwrite z/w if valid
		if (src->compFlags[2] == VALID)
		{
			dest->z = src->z;
			dest->compFlags[2] = src->compFlags[2];
		}


		//dest->valid = dest->valid && src->valid;
		dest->gFlags |= src->gFlags;				// inherit flags from both values (?)
	}
}

u32 lastDMAAddr = 0;

void PGXP_SetLastDMA(u32 addr)
{
	lastDMAAddr = PGXP_ConvertAddress(addr);
}

u32	 PGXP_GetLastDMA()
{
	return lastDMAAddr;
}
