// Copyright (C) 2003 Dolphin Project.

// 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, version 2.0.

// 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 2.0 for more details.

// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/

// Official SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/

#include <float.h>
#ifdef _WIN32
#define _interlockedbittestandset workaround_ms_header_bug_platform_sdk6_set
#define _interlockedbittestandreset workaround_ms_header_bug_platform_sdk6_reset
#define _interlockedbittestandset64 workaround_ms_header_bug_platform_sdk6_set64
#define _interlockedbittestandreset64 workaround_ms_header_bug_platform_sdk6_reset64
#include <intrin.h>
#undef _interlockedbittestandset
#undef _interlockedbittestandreset
#undef _interlockedbittestandset64
#undef _interlockedbittestandreset64
#else
static const unsigned short FPU_ROUND_NEAR = 0 << 10;
static const unsigned short FPU_ROUND_DOWN = 1 << 10;
static const unsigned short FPU_ROUND_UP   = 2 << 10;
static const unsigned short FPU_ROUND_CHOP = 3 << 10;
static const unsigned short FPU_ROUND_MASK = 3 << 10;
#include <xmmintrin.h>
#endif

#include "CPUDetect.h"
#include "../../CoreTiming.h"
#include "../../HW/Memmap.h"
#include "../../HW/GPFifo.h"
#include "../../HW/SystemTimers.h"
#include "../../Core.h"
#include "Interpreter.h"

#include "Interpreter_FPUtils.h"

/*

Most of these are together with fctiwx
mffsx: 800c3624
mffsx: 80043c98
mffsx: 8003dd48
mffsx: 8003dd9c
mffsx: 80036608
mffsx: 80036650 (huh?)

*/
// TODO(ector): More proper handling of SSE state.
// That is, set rounding mode etc when entering jit code or the interpreter loop
// Restore rounding mode when calling anything external

namespace Interpreter
{

const u32 MASKS = 0x1F80;  // mask away the interrupts.
const u32 DAZ = 0x40;
const u32 FTZ = 0x8000;

void FPSCRtoFPUSettings(UReg_FPSCR fp)
{
	// Set FPU rounding mode to mimic the PowerPC's
#ifdef _M_IX86
	// This shouldn't really be needed anymore since we use SSE
#ifdef _WIN32
	const int table[4] = 
	{
		_RC_NEAR,
		_RC_CHOP,
		_RC_UP,
		_RC_DOWN
	};
	_set_controlfp(_MCW_RC, table[fp.RN]);
#else
	const unsigned short table[4] = 
	{
		FPU_ROUND_NEAR,
		FPU_ROUND_CHOP,
		FPU_ROUND_UP,
		FPU_ROUND_DOWN
	};
	unsigned short mode;
	asm ("fstcw %0" : : "m" (mode));
	mode = (mode & ~FPU_ROUND_MASK) | table[fp.RN];
	asm ("fldcw %0" : : "m" (mode));
#endif
#endif
	if (fp.VE || fp.OE || fp.UE || fp.ZE || fp.XE)
	{
		//PanicAlert("FPSCR - exceptions enabled. Please report. VE=%i OE=%i UE=%i ZE=%i XE=%i",
		//	fp.VE, fp.OE, fp.UE, fp.ZE, fp.XE);
		// Pokemon Colosseum does this. Gah.
	}

	// Also corresponding SSE rounding mode setting
	static const u32 ssetable[4] = 
	{
		(0 << 13) | MASKS,
		(3 << 13) | MASKS,
		(2 << 13) | MASKS,
		(1 << 13) | MASKS,
	};
	u32 csr = ssetable[FPSCR.RN];
	if (FPSCR.NI)
	{
		// Either one of these two breaks Beyond Good & Evil.
		// if (cpu_info.bSSSE3)
		//     csr |= DAZ;
		// csr |= FTZ;
	}
	_mm_setcsr(csr);
}

void mtfsb0x(UGeckoInstruction _inst)
{
	u32 b = 0x80000000 >> _inst.CRBD;

	/*if (b & 0x9ff80700)
		PanicAlert("mtfsb0 clears bit %d, PC=%x", _inst.CRBD, PC);*/

	FPSCR.Hex &= ~b;
	FPSCRtoFPUSettings(FPSCR);

	if (_inst.Rc) PanicAlert("mtfsb0x: inst_.Rc");
}

void mtfsb1x(UGeckoInstruction _inst)
{
	// this instruction can affect FX
	u32 b = 0x80000000 >> _inst.CRBD;
	if (b & FPSCR_ANY_X)
		SetFPException(b);
	else
		FPSCR.Hex |= b;
	FPSCRtoFPUSettings(FPSCR);	

	if (_inst.Rc) PanicAlert("mtfsb1x: inst_.Rc");
}

void mtfsfix(UGeckoInstruction _inst)
{
	u32 mask = (0xF0000000 >> (4 * _inst.CRFD));
	u32 imm = (_inst.hex << 16) & 0xF0000000;

	/*u32 cleared = ~(imm >> (4 * _inst.CRFD)) & FPSCR.Hex & mask;
	if (cleared & 0x9ff80700)
		PanicAlert("mtfsfi clears %08x, PC=%x", cleared, PC);*/

	FPSCR.Hex = (FPSCR.Hex & ~mask) | (imm >> (4 * _inst.CRFD));

	FPSCRtoFPUSettings(FPSCR);

	if (_inst.Rc) PanicAlert("mtfsfix: inst_.Rc");
}

void mtfsfx(UGeckoInstruction _inst)
{
	u32 fm = _inst.FM;
	u32 m = 0;
	for (int i = 0; i < 8; i++)
	{
		if (fm & (1 << i))
			m |= (0xF << (i * 4));
	}

	/*u32 cleared = ~((u32)(riPS0(_inst.FB))) & FPSCR.Hex & m;
	if (cleared & 0x9ff80700)
		PanicAlert("mtfsf clears %08x, PC=%x", cleared, PC);*/

	FPSCR.Hex = (FPSCR.Hex & ~m) | ((u32)(riPS0(_inst.FB)) & m);
	FPSCRtoFPUSettings(FPSCR);

	if (_inst.Rc) PanicAlert("mtfsfx: inst_.Rc");
}

void mcrxr(UGeckoInstruction _inst)
{
	// USES_XER
	SetCRField(_inst.CRFD, PowerPC::ppcState.spr[SPR_XER] >> 28); 
	PowerPC::ppcState.spr[SPR_XER] &= ~0xF0000000; // clear 0-3
}

void mfcr(UGeckoInstruction _inst)
{
	m_GPR[_inst.RD] = GetCR();
}

void mtcrf(UGeckoInstruction _inst)
{
	u32 crm = _inst.CRM;
	if (crm == 0xFF)
	{
		SetCR(m_GPR[_inst.RS]);
	}
	else
	{
		//TODO: use lookup table? probably not worth it
		u32 mask = 0;
		for (int i = 0; i < 8; i++) {
			if (crm & (1 << i))
				mask |= 0xF << (i*4);
		}
		SetCR((GetCR() & ~mask) | (m_GPR[_inst.RS] & mask));
	}
}


void mfmsr(UGeckoInstruction _inst)
{
	//Privileged?
	m_GPR[_inst.RD] = MSR;
}

void mfsr(UGeckoInstruction _inst)
{
	m_GPR[_inst.RD] = PowerPC::ppcState.sr[_inst.SR];
}

void mfsrin(UGeckoInstruction _inst)
{
	int index = (m_GPR[_inst.RB] >> 28) & 0xF;
	m_GPR[_inst.RD] = PowerPC::ppcState.sr[index];
}

void mtmsr(UGeckoInstruction _inst)
{
	// Privileged?
	MSR = m_GPR[_inst.RS];
	m_EndBlock = true;
}

// Segment registers. MMU control.

void SetSR(int index, u32 value) {
	DEBUG_LOG(POWERPC, "%08x: MMU: Segment register %i set to %08x", PowerPC::ppcState.pc, index, value);
	PowerPC::ppcState.sr[index] = value;
}

void mtsr(UGeckoInstruction _inst)
{
	int index = _inst.SR;
	u32 value = m_GPR[_inst.RS];
	SetSR(index, value);
}

void mtsrin(UGeckoInstruction _inst)
{
	int index = (m_GPR[_inst.RB] >> 28) & 0xF;
	u32 value = m_GPR[_inst.RS];
	SetSR(index, value);
}



void mftb(UGeckoInstruction _inst)
{
	int iIndex = (_inst.TBR >> 5) | ((_inst.TBR & 0x1F) << 5);
	if (iIndex == SPR_TL)		m_GPR[_inst.RD] = TL;
	else if (iIndex == SPR_TU)	m_GPR[_inst.RD] = TU;
	else					_dbg_assert_(POWERPC, 0);
}


void mfspr(UGeckoInstruction _inst)
{
	u32 iIndex = ((_inst.SPR & 0x1F) << 5) + ((_inst.SPR >> 5) & 0x1F);

	//TODO - check processor privilege level - many of these require privilege
	//XER LR CTR are the only ones available in user mode, time base can be read too.
	//Gamecube games always run in superuser mode, but hey....

	switch (iIndex) 
	{
	//case SPR_DEC:
	//	MessageBox(NULL, "Read from DEC", "????", MB_OK);
	//	break;
	case SPR_WPAR:
		{
			// If wpar_empty ever is false, Paper Mario hangs. Strange.			
			bool wpar_empty = true; //GPFifo::IsEmpty();
			if (!wpar_empty)
				rSPR(iIndex) |= 1;  // BNE = buffer not empty
			else
				rSPR(iIndex) &= ~1;
		}		
		break;
	}
	m_GPR[_inst.RD] = rSPR(iIndex);
}

void mtspr(UGeckoInstruction _inst)
{
	u32 iIndex = (_inst.SPRU << 5) | (_inst.SPRL & 0x1F);
	u32 oldValue = rSPR(iIndex);
	rSPR(iIndex) = m_GPR[_inst.RD];

	//TODO - check processor privilege level - many of these require privilege
	//XER LR CTR are the only ones available in user mode, time base can be read too.
	//Gamecube games always run in superuser mode, but hey....

	//Our DMA emulation is highly inaccurate - instead of properly emulating the queue
	//and so on, we simply make all DMA:s complete instantaneously.

	switch(iIndex)
	{
	case SPR_TL:
	case SPR_TU:
		PanicAlert("Illegal Write to TL/TU");
		break;

	case SPR_TL_W:
		TL = m_GPR[_inst.RD];
		break;

	case SPR_TU_W:
		TU = m_GPR[_inst.RD];
		break;

	case SPR_HID0: // HID0
		{
			UReg_HID0 old_hid0;
			old_hid0.Hex = oldValue;
			if (HID0.ICE != old_hid0.ICE)
			{
				NOTICE_LOG(POWERPC, "Instruction Cache Enable (HID0.ICE) = %d", (int)HID0.ICE);
			}
			if (HID0.ILOCK != old_hid0.ILOCK)
			{
				NOTICE_LOG(POWERPC, "Instruction Cache Lock (HID0.ILOCK) = %d", (int)HID0.ILOCK);
			}
			if (HID0.ICFI)
			{
				HID0.ICFI = 0;
				NOTICE_LOG(POWERPC, "Flush Instruction Cache! ICE=%d", (int)HID0.ICE);
				// this is rather slow
				// most games do it only once during initialization
				PowerPC::ppcState.iCache.Reset();
			}
		}
		break;
	case SPR_HID2: // HID2
		{
			UReg_HID2 old_hid2;
			old_hid2.Hex = oldValue;

			//if (HID2.LCE && !old_hid2.LCE)
			//	PanicAlert("Locked cache enabled!");

			if (HID2.PSE == 0)
				PanicAlert("WARNING: PSE (paired single enable) in HID2 was unset");

			//	bool WriteGatherPipeEnable = (bool)HID2.WPE; //TODO?
			//	bool LockedCacheEnable = (bool)HID2.LCE;
			//	int DMAQueueLength = HID2.DMAQL; // Ignore - our DMA:s are instantaneous
			//	bool PairedSingleEnable = HID2.PSE;
			//	bool QuantizeEnable = HID2.LSQE;
			//TODO(ector): Protect LC memory if LCE is false.
			//TODO(ector): Honor PSE.

			//_assert_msg_(POWERPC, WriteGatherPipeEnable, "Write gather pipe not enabled!");
			//if ((HID2.PSE == 0))
			//	MessageBox(NULL, "PSE in HID2 is set", "Warning", MB_OK);
		}
		break;

	case SPR_WPAR:
		_assert_msg_(POWERPC, m_GPR[_inst.RD] == 0x0C008000, "Gather pipe @ %08x");
		GPFifo::ResetGatherPipe();
		break;

	// Graphics Quantization Registers
	case SPR_GQR0:
	case SPR_GQR0 + 1:
	case SPR_GQR0 + 2:
	case SPR_GQR0 + 3:
	case SPR_GQR0 + 4:
	case SPR_GQR0 + 5:
	case SPR_GQR0 + 6:
	case SPR_GQR0 + 7:
		break;

	case SPR_DMAL:
		// Locked cache<->Memory DMA
		// Total fake, we ignore that DMAs take time.
		if (DMAL.DMA_T) 
		{
			u32 dwMemAddress = DMAU.MEM_ADDR << 5;
			u32 dwCacheAddress = DMAL.LC_ADDR << 5;
			u32 iLength = ((DMAU.DMA_LEN_U << 2) | DMAL.DMA_LEN_L);
			// INFO_LOG(POWERPC, "DMA: mem = %x, cache = %x, len = %u, LD = %d, PC=%x", dwMemAddress, dwCacheAddress, iLength, (int)DMAL.DMA_LD, PC);
			if (iLength == 0) 
				iLength = 128;
			if (DMAL.DMA_LD)
				Memory::DMA_MemoryToLC(dwCacheAddress, dwMemAddress, iLength);
			else
				Memory::DMA_LCToMemory(dwMemAddress, dwCacheAddress, iLength);
		}
		DMAL.DMA_T = 0;
		break;

	case SPR_L2CR:
		//PanicAlert("mtspr( L2CR )!");
		break;

	case SPR_DEC:
		if (!(oldValue >> 31) && (m_GPR[_inst.RD]>>31))   //top bit from 0 to 1
		{
			PanicAlert("Interesting - Software triggered Decrementer exception");
			PowerPC::ppcState.Exceptions |= EXCEPTION_DECREMENTER;
		}
		else
		{
			SystemTimers::DecrementerSet();
		}
		break;

	// Page table base etc
	case SPR_SDR:
		Memory::SDRUpdated();
		break;
	}
}

void crand(UGeckoInstruction _inst)
{
	SetCRBit(_inst.CRBD, GetCRBit(_inst.CRBA) & GetCRBit(_inst.CRBB));
}

void crandc(UGeckoInstruction _inst)
{
	SetCRBit(_inst.CRBD, GetCRBit(_inst.CRBA) & (1 ^ GetCRBit(_inst.CRBB)));
}

void creqv(UGeckoInstruction _inst)
{
	SetCRBit(_inst.CRBD, 1 ^ (GetCRBit(_inst.CRBA) ^ GetCRBit(_inst.CRBB)));
}

void crnand(UGeckoInstruction _inst)
{
	SetCRBit(_inst.CRBD, 1 ^ (GetCRBit(_inst.CRBA) & GetCRBit(_inst.CRBB)));
}

void crnor(UGeckoInstruction _inst)
{
	SetCRBit(_inst.CRBD, 1 ^ (GetCRBit(_inst.CRBA) | GetCRBit(_inst.CRBB)));
}

void cror(UGeckoInstruction _inst)
{
	SetCRBit(_inst.CRBD, (GetCRBit(_inst.CRBA) | GetCRBit(_inst.CRBB)));
}

void crorc(UGeckoInstruction _inst)
{
	SetCRBit(_inst.CRBD, (GetCRBit(_inst.CRBA) | (1 ^ GetCRBit(_inst.CRBB))));
}

void crxor(UGeckoInstruction _inst)
{
	SetCRBit(_inst.CRBD, (GetCRBit(_inst.CRBA) ^ GetCRBit(_inst.CRBB)));
}

void mcrf(UGeckoInstruction _inst)
{
	int cr_f = GetCRField(_inst.CRFS);
	SetCRField(_inst.CRFD, cr_f);
}

void isync(UGeckoInstruction _inst)
{
	//shouldnt do anything
}

// the following commands read from FPSCR

void mcrfs(UGeckoInstruction _inst)
{
	//if (_inst.CRFS != 3 && _inst.CRFS != 4)
	//	PanicAlert("msrfs at %x, CRFS = %d, CRFD = %d", PC, (int)_inst.CRFS, (int)_inst.CRFD);

	UpdateFPSCR();
	u32 fpflags = ((FPSCR.Hex >> (4 * (7 - _inst.CRFS))) & 0xF);
	switch (_inst.CRFS) {
	case 0:
		FPSCR.FX = 0;
		FPSCR.OX = 0;
		break;
	case 1:
		FPSCR.UX = 0;
		FPSCR.ZX = 0;
		FPSCR.XX = 0;
		FPSCR.VXSNAN = 0;
		break;
	case 2:
		FPSCR.VXISI = 0;
		FPSCR.VXIDI = 0;
		FPSCR.VXZDZ = 0;
		FPSCR.VXIMZ = 0;
		break;
	case 3:
		FPSCR.VXVC = 0;
		break;
	case 5:
		FPSCR.VXSOFT = 0;
		FPSCR.VXSQRT = 0;
		FPSCR.VXCVI = 0;
		break;
	}
	SetCRField(_inst.CRFD, fpflags);	
}

void mffsx(UGeckoInstruction _inst)
{
	// load from FPSCR
	// This may or may not be accurate - but better than nothing, I guess
	// TODO(ector): grab all overflow flags etc and set them in FPSCR

	UpdateFPSCR();
	riPS0(_inst.FD)	= (u64)FPSCR.Hex;
	if (_inst.Rc) PanicAlert("mffsx: inst_.Rc");
}

}  // namespace
