/*  Pcsx2 - Pc Ps2 Emulator
 *  Copyright (C) 2002-2003  Pcsx2 Team
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <string.h>
#include <stdlib.h>
#include <malloc.h>

#include "PsxCommon.h"
#include "VU.h"
#include "iCore.h"
#include "iR3000A.h"

extern u32 g_psxMaxRecMem;
int g_psxWriteOk=1;
static u32 writectrl;

#ifdef PCSX2_VIRTUAL_MEM

int psxMemInit()
{
	// all mem taken care by memInit
	return 0;
}

void psxMemReset()
{
	memset(psxM, 0, 0x00200000);
}

void psxMemShutdown()
{
}

u8 psxMemRead8(u32 mem)
{
	u32 t = (mem >> 16) & 0x1fff;
	
	switch(t) {
		case 0x1f80:
			mem&= 0x1fffffff;
			if (mem < 0x1f801000)
				return psxHu8(mem);
			else
				return psxHwRead8(mem);
			break;

#ifdef _DEBUG
		case 0x1d00: assert(0);
#endif

		case 0x1f40:
			mem &= 0x1fffffff;
			return psxHw4Read8(mem);

		case 0x1000: return DEV9read8(mem & 0x1FFFFFFF);

		default:
			assert( g_psxWriteOk );
			return *(u8*)PSXM(mem);
	}
}

u16 psxMemRead16(u32 mem)
{
	u32 t = (mem >> 16) & 0x1fff;

	switch(t) {
		case 0x1f80:
			mem&= 0x1fffffff;
			if (mem < 0x1f801000)
				return psxHu16(mem);
			else
				return psxHwRead16(mem);
			break;

		case 0x1d00:
#ifdef SIF_LOG
			SIF_LOG("Sif reg read %x value %x\n", mem, psxHu16(mem));
#endif
			switch(mem & 0xF0)
			{
				case 0x40: return psHu16(0x1000F240) | 0x0002;
				case 0x60: return 0;
				default: return *(u16*)(PS2MEM_HW+0xf200+(mem&0xf0));
			}
			break;

		case 0x1f90:
			return SPU2read(mem & 0x1FFFFFFF);
		case 0x1000:
			return DEV9read16(mem & 0x1FFFFFFF);

		default:
			assert( g_psxWriteOk );
			return *(u16*)PSXM(mem);
	}
}

u32 psxMemRead32(u32 mem)
{
	u32 t = (mem >> 16) & 0x1fff;

	switch(t) {
		case 0x1f80:
			mem&= 0x1fffffff;
			if (mem < 0x1f801000)
				return psxHu32(mem);
			else
				return psxHwRead32(mem);
			break;

		case 0x1d00:
#ifdef SIF_LOG
			SIF_LOG("Sif reg read %x value %x\n", mem, psxHu32(mem));
#endif
			switch(mem & 0xF0)
			{
				case 0x40: return psHu32(0x1000F240) | 0xF0000002;
				case 0x60: return 0;
				default: return *(u32*)(PS2MEM_HW+0xf200+(mem&0xf0));
			}
			break;

		case 0x1fff: return g_psxWriteOk;
		case 0x1000:
			return DEV9read32(mem & 0x1FFFFFFF);

		default:
			//assert(g_psxWriteOk);
			if( mem == 0xfffe0130 )
				return writectrl;
			else if( mem == 0xffffffff )
				return writectrl;
			else if( g_psxWriteOk )
				return *(u32*)PSXM(mem);
			else return 0;
	}
}

void psxMemWrite8(u32 mem, u8 value)
{
	u32 t = (mem >> 16) & 0x1fff;

	switch(t) {
		case 0x1f80:
			mem&= 0x1fffffff;
			if (mem < 0x1f801000)
				psxHu8(mem) = value;
			else
				psxHwWrite8(mem, value);
			break;

		case 0x1f40:
			mem&= 0x1fffffff;
			psxHw4Write8(mem, value);
			break;

		case 0x1d00:
			SysPrintf("sw8 [0x%08X]=0x%08X\n", mem, value);
			*(u8*)(PS2MEM_HW+0xf200+(mem&0xff)) = value;
			break;

		case 0x1000:
			DEV9write8(mem & 0x1fffffff, value);
			return;

		default:
			assert(g_psxWriteOk);
			*(u8  *)PSXM(mem) = value;
			psxCpu->Clear(mem&~3, 1);
			break;
	}
}

void psxMemWrite16(u32 mem, u16 value)
{
	u32 t = (mem >> 16) & 0x1fff;
	switch(t) {
		case 0x1f80:
			mem&= 0x1fffffff;
			if (mem < 0x1f801000)
				psxHu16(mem) = value;
			else
				psxHwWrite16(mem, value);
			break;

		case 0x1d00:
			switch (mem & 0xf0) {
				case 0x10:
					// write to ps2 mem
					psHu16(0x1000F210) = value;
					return;
				case 0x40:
				{
					u32 temp = value & 0xF0;
					// write to ps2 mem
					if(value & 0x20 || value & 0x80)
					{
						psHu16(0x1000F240) &= ~0xF000;
						psHu16(0x1000F240) |= 0x2000;
					}
						
					if(psHu16(0x1000F240) & temp) psHu16(0x1000F240) &= ~temp;
					else psHu16(0x1000F240) |= temp;
					return;
				}
				case 0x60:
					psHu32(0x1000F260) = 0;
					return;
				default:
					assert(0);
			}
			return;

		case 0x1f90:
			SPU2write(mem & 0x1FFFFFFF, value); return;
			
		case 0x1000:
			DEV9write16(mem & 0x1fffffff, value); return;
		default:
			assert( g_psxWriteOk );
			*(u16 *)PSXM(mem) = value;
			psxCpu->Clear(mem&~3, 1);
			break;
	}
}

void psxMemWrite32(u32 mem, u32 value)
{
	u32 t = (mem >> 16) & 0x1fff;
	switch(t) {
		case 0x1f80:
			mem&= 0x1fffffff;
			if (mem < 0x1f801000)
				psxHu32(mem) = value;
			else
				psxHwWrite32(mem, value);
			break;

		case 0x1d00:
			switch (mem & 0xf0) {
				case 0x10:
					// write to ps2 mem
					psHu32(0x1000F210) = value;
					return;
				case 0x20:
					// write to ps2 mem
					psHu32(0x1000F220) &= ~value;
					return;
				case 0x30:
					// write to ps2 mem
					psHu32(0x1000F230) |= value;
					return;
				case 0x40:
				{
					u32 temp = value & 0xF0;
					// write to ps2 mem
					if(value & 0x20 || value & 0x80)
					{
						psHu32(0x1000F240) &= ~0xF000;
						psHu32(0x1000F240) |= 0x2000;
					}

					
					if(psHu32(0x1000F240) & temp) psHu32(0x1000F240) &= ~temp;
					else psHu32(0x1000F240) |= temp;
					return;
				}
				case 0x60:
					psHu32(0x1000F260) = 0;
					return;

				default:
					*(u32*)(PS2MEM_HW+0xf200+(mem&0xf0)) = value;
			}
				
			return;

		case 0x1000:
			DEV9write32(mem & 0x1fffffff, value);
			return;

		case 0x1ffe:
			if( mem == 0xfffe0130 ) {
				writectrl = value;
				switch (value) {
					case 0x800: case 0x804:
					case 0xc00: case 0xc04:
					case 0xcc0: case 0xcc4:
					case 0x0c4:
						g_psxWriteOk = 0;
#ifdef PSXMEM_LOG
//						PSXMEM_LOG("writectrl: writenot ok\n");
#endif
						break;
					case 0x1e988:
					case 0x1edd8:
						g_psxWriteOk = 1;
#ifdef PSXMEM_LOG
//						PSXMEM_LOG("writectrl: write ok\n");
#endif
						break;
					default:
#ifdef PSXMEM_LOG
						PSXMEM_LOG("unk %8.8lx = %x\n", mem, value);
#endif
						break;
				}
			}
			break;

		default:
			
			if( g_psxWriteOk ) {
				*(u32 *)PSXM(mem) = value;
				psxCpu->Clear(mem&~3, 1);
			}

			break;
	}
}

#else

// TLB functions

s8 *psxM;
s8 *psxP;
s8 *psxH;
s8 *psxS;
uptr *psxMemWLUT;
uptr *psxMemRLUT;

int psxMemInit()
{
	int i;

	psxMemRLUT = (uptr*)_aligned_malloc(0x10000 * sizeof(uptr),16);
	psxMemWLUT = (uptr*)_aligned_malloc(0x10000 * sizeof(uptr),16);
	memset(psxMemRLUT, 0, 0x10000 * sizeof(uptr));
	memset(psxMemWLUT, 0, 0x10000 * sizeof(uptr));

	psxM = (char*)SysMmap(PS2MEM_PSX_, 0x00200000);
	psxP = (char*)SysMmap(PS2MEM_BASE_+0x1f000000, 0x00010000);
	psxH = (char*)SysMmap(PS2MEM_BASE_+0x1f800000, 0x00010000);
	psxS = (char*)SysMmap(PS2MEM_BASE_+0x1d000000, 0x00010000);

    assert( (uptr)psxM <= 0xffffffff && (uptr)psxP <= 0xffffffff && (uptr)psxH <= 0xffffffff && (uptr)psxS <= 0xffffffff);

	if (psxMemRLUT == NULL || psxMemWLUT == NULL || 
		psxM == NULL || psxP == NULL || psxH == NULL) {
		SysMessage(_("Error allocating memory")); return -1;
	}

	memset(psxM, 0, 0x00200000);
	memset(psxP, 0, 0x00010000);
	memset(psxH, 0, 0x00010000);
	memset(psxS, 0, 0x00010000);


// MemR
	for (i=0; i<0x0080; i++) psxMemRLUT[i + 0x0000] = (uptr)&psxM[(i & 0x1f) << 16];
	for (i=0; i<0x0080; i++) psxMemRLUT[i + 0x8000] = (uptr)&psxM[(i & 0x1f) << 16];
	for (i=0; i<0x0080; i++) psxMemRLUT[i + 0xa000] = (uptr)&psxM[(i & 0x1f) << 16];

	for (i=0; i<0x0001; i++) psxMemRLUT[i + 0x1f00] = (uptr)&psxP[i << 16];

	for (i=0; i<0x0001; i++) psxMemRLUT[i + 0x1f80] = (uptr)&psxH[i << 16];
	for (i=0; i<0x0001; i++) psxMemRLUT[i + 0xbf80] = (uptr)&psxH[i << 16];

	for (i=0; i<0x0040; i++) psxMemRLUT[i + 0x1fc0] = (uptr)&PS2MEM_ROM[i << 16];
	for (i=0; i<0x0040; i++) psxMemRLUT[i + 0x9fc0] = (uptr)&PS2MEM_ROM[i << 16];
	for (i=0; i<0x0040; i++) psxMemRLUT[i + 0xbfc0] = (uptr)&PS2MEM_ROM[i << 16];

	for (i=0; i<0x0004; i++) psxMemRLUT[i + 0x1e00] = (uptr)&PS2MEM_ROM1[i << 16];
	for (i=0; i<0x0004; i++) psxMemRLUT[i + 0x9e00] = (uptr)&PS2MEM_ROM1[i << 16];
	for (i=0; i<0x0004; i++) psxMemRLUT[i + 0xbe00] = (uptr)&PS2MEM_ROM1[i << 16];

	for (i=0; i<0x0001; i++) psxMemRLUT[i + 0x1d00] = (uptr)&psxS[i << 16];
	for (i=0; i<0x0001; i++) psxMemRLUT[i + 0xbd00] = (uptr)&psxS[i << 16];

// MemW
	for (i=0; i<0x0080; i++) psxMemWLUT[i + 0x0000] = (uptr)&psxM[(i & 0x1f) << 16];
	for (i=0; i<0x0080; i++) psxMemWLUT[i + 0x8000] = (uptr)&psxM[(i & 0x1f) << 16];
	for (i=0; i<0x0080; i++) psxMemWLUT[i + 0xa000] = (uptr)&psxM[(i & 0x1f) << 16];

	for (i=0; i<0x0001; i++) psxMemWLUT[i + 0x1f00] = (uptr)&psxP[i << 16];

	for (i=0; i<0x0001; i++) psxMemWLUT[i + 0x1f80] = (uptr)&psxH[i << 16];
	for (i=0; i<0x0001; i++) psxMemWLUT[i + 0xbf80] = (uptr)&psxH[i << 16];

//	for (i=0; i<0x0008; i++) psxMemWLUT[i + 0xbfc0] = (uptr)&psR[i << 16];

//	for (i=0; i<0x0001; i++) psxMemWLUT[i + 0x1d00] = (uptr)&psxS[i << 16];
//	for (i=0; i<0x0001; i++) psxMemWLUT[i + 0xbd00] = (uptr)&psxS[i << 16];

	return 0;
}

void psxMemReset() {
	memset(psxM, 0, 0x00200000);
	memset(psxP, 0, 0x00010000);
	//memset(psxS, 0, 0x00010000);
}

void psxMemShutdown()
{
	SysMunmap((uptr)psxM, 0x00200000); psxM = NULL;
	SysMunmap((uptr)psxP, 0x00010000); psxP = NULL;
	SysMunmap((uptr)psxH, 0x00010000); psxH = NULL;
    SysMunmap((uptr)psxS, 0x00010000); psxS = NULL;
	_aligned_free(psxMemRLUT);
	_aligned_free(psxMemWLUT);
}

u8 psxMemRead8(u32 mem) {
	char *p;
	u32 t;

	t = (mem >> 16) & 0x1fff;
	if (t == 0x1f80) {
		mem&= 0x1fffffff;
		if (mem < 0x1f801000)
			return psxHu8(mem);
		else
			return psxHwRead8(mem);
	} else
	if (t == 0x1f40) {
		mem&= 0x1fffffff;
		return psxHw4Read8(mem);
	} else {
		p = (char *)(psxMemRLUT[mem >> 16]);
		if (p != NULL) {
			return *(u8 *)(p + (mem & 0xffff));
		} else {
			if (t == 0x1000) return DEV9read8(mem & 0x1FFFFFFF);
#ifdef PSXMEM_LOG
			PSXMEM_LOG("err lb %8.8lx\n", mem);
#endif
			return 0;
		}
	}
}

u16 psxMemRead16(u32 mem) {
	char *p;
	u32 t;

	t = (mem >> 16) & 0x1fff;
	if (t == 0x1f80) {
		mem&= 0x1fffffff;
		if (mem < 0x1f801000)
			return psxHu16(mem);
		else
			return psxHwRead16(mem);
	} else {
		p = (char *)(psxMemRLUT[mem >> 16]);
		if (p != NULL) {
			if (t == 0x1d00) {
				u16 ret;
				switch(mem & 0xF0)
				{
				case 0x00:
					ret= psHu16(0x1000F200);
					break;
				case 0x10:
					ret= psHu16(0x1000F210);
					break;
				case 0x40:
					ret= psHu16(0x1000F240) | 0x0002;
					break;
				case 0x60:
					ret = 0;
					break;
				default:
					ret = psxHu16(mem);
					break;
				}
#ifdef SIF_LOG
				SIF_LOG("Sif reg read %x value %x\n", mem, ret);			
#endif
				return ret;
			}
			return *(u16 *)(p + (mem & 0xffff));
		} else {
			if (t == 0x1F90)
				return SPU2read(mem & 0x1FFFFFFF);
			if (t == 0x1000) return DEV9read16(mem & 0x1FFFFFFF);
#ifdef PSXMEM_LOG
			PSXMEM_LOG("err lh %8.8lx\n", mem);
#endif
			return 0;
		}
	}
}

u32 psxMemRead32(u32 mem) {
	char *p;
	u32 t;
	t = (mem >> 16) & 0x1fff;
	if (t == 0x1f80) {
		mem&= 0x1fffffff;
		if (mem < 0x1f801000)
			return psxHu32(mem);
		else
			return psxHwRead32(mem);
	} else {
		//see also Hw.c
		p = (char *)(psxMemRLUT[mem >> 16]);
		if (p != NULL) {
			if (t == 0x1d00) {
				u32 ret;
				switch(mem & 0xF0)
				{
				case 0x00:
					ret= psHu32(0x1000F200);
					break;
				case 0x10:
					ret= psHu32(0x1000F210);
					break;
				case 0x20:
					ret= psHu32(0x1000F220);
					break;
				case 0x30:	// EE Side
					ret= psHu32(0x1000F230);
					break;
				case 0x40:
					ret= psHu32(0x1000F240) | 0xF0000002;
					break;
				case 0x60:
					ret = 0;
					break;
				default:
					ret = psxHu32(mem);
					break;
				}
#ifdef SIF_LOG
				SIF_LOG("Sif reg read %x value %x\n", mem, ret);			
#endif
				return ret;
			}
			return *(u32 *)(p + (mem & 0xffff));
		} else {
			if (t == 0x1000) return DEV9read32(mem & 0x1FFFFFFF);
			
			if (mem != 0xfffe0130) {
#ifdef PSXMEM_LOG
				if (g_psxWriteOk) PSXMEM_LOG("err lw %8.8lx\n", mem);
#endif
			} else {
				return writectrl;
			}
			return 0;
		}
	}
}

void psxMemWrite8(u32 mem, u8 value) {
	char *p;
	u32 t;

	t = (mem >> 16) & 0x1fff;
	if (t == 0x1f80) {
		mem&= 0x1fffffff;
		if (mem < 0x1f801000)
			psxHu8(mem) = value;
		else
			psxHwWrite8(mem, value);
	} else
	if (t == 0x1f40) {
		mem&= 0x1fffffff;
		psxHw4Write8(mem, value);
	} else {
		p = (char *)(psxMemWLUT[mem >> 16]);
		if (p != NULL) {
			*(u8  *)(p + (mem & 0xffff)) = value;
			psxCpu->Clear(mem&~3, 1);
		} else {
			if ((t & 0x1FFF)==0x1D00) SysPrintf("sw8 [0x%08X]=0x%08X\n", mem, value);
			if (t == 0x1d00) {
				psxSu8(mem) = value; return;
			}
			if (t == 0x1000) {
				DEV9write8(mem & 0x1fffffff, value); return;
			}
#ifdef PSXMEM_LOG
			PSXMEM_LOG("err sb %8.8lx = %x\n", mem, value);
#endif
		}
	}
}

void psxMemWrite16(u32 mem, u16 value) {
	char *p;
	u32 t;

	t = (mem >> 16) & 0x1fff;
	if (t == 0x1f80) {
		mem&= 0x1fffffff;
		if (mem < 0x1f801000)
			psxHu16(mem) = value;
		else
			psxHwWrite16(mem, value);
	} else {
		p = (char *)(psxMemWLUT[mem >> 16]);
		if (p != NULL) {
			if ((t & 0x1FFF)==0x1D00) SysPrintf("sw16 [0x%08X]=0x%08X\n", mem, value);
			*(u16 *)(p + (mem & 0xffff)) = value;
			psxCpu->Clear(mem&~3, 1);
		} else {
			if (t == 0x1d00) {
					switch (mem & 0xf0) {
						case 0x10:
							// write to ps2 mem
							psHu16(0x1000F210) = value;
							return;
						case 0x40:
						{
							u32 temp = value & 0xF0;
							// write to ps2 mem
							if(value & 0x20 || value & 0x80)
							{
								psHu16(0x1000F240) &= ~0xF000;
								psHu16(0x1000F240) |= 0x2000;
							}

							
							if(psHu16(0x1000F240) & temp) psHu16(0x1000F240) &= ~temp;
							else psHu16(0x1000F240) |= temp;
							return;
						}
						case 0x60:
							psHu32(0x1000F260) = 0;
							return;

					}
				psxSu16(mem) = value; return;
			}
			if (t == 0x1F90) {
				SPU2write(mem & 0x1FFFFFFF, value); return;
			}
			if (t == 0x1000) {
				DEV9write16(mem & 0x1fffffff, value); return;
			}
#ifdef PSXMEM_LOG
			PSXMEM_LOG("err sh %8.8lx = %x\n", mem, value);
#endif
		}
	}
}

void psxMemWrite32(u32 mem, u32 value) {
	char *p;
	u32 t;
	
	t = (mem >> 16) & 0x1fff;
	if (t == 0x1f80) {
		mem&= 0x1fffffff;
		if (mem < 0x1f801000)
			psxHu32(mem) = value;
		else
			psxHwWrite32(mem, value);
	} else {
		//see also Hw.c
		p = (char *)(psxMemWLUT[mem >> 16]);
		if (p != NULL) {
			*(u32 *)(p + (mem & 0xffff)) = value;
			psxCpu->Clear(mem&~3, 1);
		} else {
			if (mem != 0xfffe0130) {
				if (t == 0x1d00) {
#ifdef MEM_LOG
				MEM_LOG("iop Sif reg write %x value %x\n", mem, value);			
#endif
					switch (mem & 0xf0) {
						case 0x10:
							// write to ps2 mem
							psHu32(0x1000F210) = value;
							return;
						case 0x20:
							// write to ps2 mem
							psHu32(0x1000F220) &= ~value;
							return;
						case 0x30:
							// write to ps2 mem
							psHu32(0x1000F230) |= value;
							return;
						case 0x40:
						{
							u32 temp = value & 0xF0;
							// write to ps2 mem
							if(value & 0x20 || value & 0x80)
							{
								psHu32(0x1000F240) &= ~0xF000;
								psHu32(0x1000F240) |= 0x2000;
							}

							
							if(psHu32(0x1000F240) & temp) psHu32(0x1000F240) &= ~temp;
							else psHu32(0x1000F240) |= temp;
							return;
						}
						case 0x60:
							psHu32(0x1000F260) = 0;
							return;

					}
					psxSu32(mem) = value; 

					// write to ps2 mem
					if( (mem & 0xf0) != 0x60 )
						*(u32*)(PS2MEM_HW+0xf200+(mem&0xf0)) = value;
					return;   
				}
				if (t == 0x1000) {
					DEV9write32(mem & 0x1fffffff, value); return;
				}

				//if (!g_psxWriteOk) psxCpu->Clear(mem&~3, 1);
#ifdef PSXMEM_LOG
				if (g_psxWriteOk) { PSXMEM_LOG("err sw %8.8lx = %x\n", mem, value); }
#endif
			} else {
				int i;

				writectrl = value;
				switch (value) {
					case 0x800: case 0x804:
					case 0xc00: case 0xc04:
					case 0xcc0: case 0xcc4:
					case 0x0c4:
						if (g_psxWriteOk == 0) break;
						g_psxWriteOk = 0;
						memset(psxMemWLUT + 0x0000, 0, 0x80 * sizeof(uptr));
						memset(psxMemWLUT + 0x8000, 0, 0x80 * sizeof(uptr));
						memset(psxMemWLUT + 0xa000, 0, 0x80 * sizeof(uptr));
#ifdef PSXMEM_LOG
//						PSXMEM_LOG("writectrl: writenot ok\n");
#endif
						break;
					case 0x1e988:
					case 0x1edd8:
						if (g_psxWriteOk == 1) break;
						g_psxWriteOk = 1;
						for (i=0; i<0x0080; i++) psxMemWLUT[i + 0x0000] = (uptr)&psxM[(i & 0x1f) << 16];
						for (i=0; i<0x0080; i++) psxMemWLUT[i + 0x8000] = (uptr)&psxM[(i & 0x1f) << 16];
						for (i=0; i<0x0080; i++) psxMemWLUT[i + 0xa000] = (uptr)&psxM[(i & 0x1f) << 16];
#ifdef PSXMEM_LOG
//						PSXMEM_LOG("writectrl: write ok\n");
#endif
						break;
					default:
#ifdef PSXMEM_LOG
						PSXMEM_LOG("unk %8.8lx = %x\n", mem, value);
#endif
						break;
				}
			}
		}
	}
}

#endif
