/*  PCSX2 - PS2 Emulator for PCs
 *  Copyright (C) 2002-2010  PCSX2 Dev Team
 * 
 *  PCSX2 is free software: you can redistribute it and/or modify it under the terms
 *  of the GNU Lesser General Public License as published by the Free Software Found-
 *  ation, either version 3 of the License, or (at your option) any later version.
 *
 *  PCSX2 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 PCSX2.
 *  If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

extern bool  doEarlyExit (microVU& mVU);
extern void  mVUincCycles(microVU& mVU, int x);
extern void* mVUcompile  (microVU& mVU, u32 startPC, uptr pState);
extern void* mVUcompileSingleInstruction(microVU& mVU, u32 startPC, uptr pState, microFlagCycles& mFC);
__fi int getLastFlagInst(microRegInfo& pState, int* xFlag, int flagType, int isEbit) {
	if (isEbit) return findFlagInst(xFlag, 0x7fffffff);
	if (pState.needExactMatch & (1<<flagType)) return 3;
	return (((pState.flagInfo >> (2*flagType+2)) & 3) - 1) & 3;
}

void mVU0clearlpStateJIT() { if (!microVU0.prog.cleared) memzero(microVU0.prog.lpState); }
void mVU1clearlpStateJIT() { if (!microVU1.prog.cleared) memzero(microVU1.prog.lpState); }

void mVUDTendProgram(mV, microFlagCycles* mFC, int isEbit) {

	int fStatus = getLastFlagInst(mVUpBlock->pState, mFC->xStatus, 0, isEbit);
	int fMac	= getLastFlagInst(mVUpBlock->pState, mFC->xMac,    1, isEbit);
	int fClip	= getLastFlagInst(mVUpBlock->pState, mFC->xClip,   2, isEbit);
	int qInst	= 0;
	int pInst	= 0;
	microBlock stateBackup;
	memcpy(&stateBackup, &mVUregs, sizeof(mVUregs)); //backup the state, it's about to get screwed with.

	mVU.regAlloc->TDwritebackAll(); //Writing back ok, invalidating early kills the rec, so don't do it :P

	if (isEbit) {
		/*memzero(mVUinfo);
		memzero(mVUregsTemp);*/
		mVUincCycles(mVU, 100); // Ensures Valid P/Q instances (And sets all cycle data to 0)
		mVUcycles -= 100;
		qInst = mVU.q;
		pInst = mVU.p;
		if (mVUinfo.doDivFlag) {
			sFLAG.doFlag = true;
			sFLAG.write  = fStatus;
			mVUdivSet(mVU);
		}
		//Run any pending XGKick, providing we've got to it.
		if (mVUinfo.doXGKICK && xPC >= mVUinfo.XGKICKPC) {
			mVU_XGKICK_DELAY(mVU);
		}
		if (doEarlyExit(mVU)) {
			if (!isVU1) xFastCall((void*)mVU0clearlpStateJIT);
			else		xFastCall((void*)mVU1clearlpStateJIT);
		}
	}

	// Save P/Q Regs
	if (qInst) { xPSHUF.D(xmmPQ, xmmPQ, 0xe5); }
	xMOVSS(ptr32[&mVU.regs().VI[REG_Q].UL], xmmPQ);
	if (isVU1) {
		xPSHUF.D(xmmPQ, xmmPQ, pInst ? 3 : 2);
		xMOVSS(ptr32[&mVU.regs().VI[REG_P].UL], xmmPQ);
	}

	// Save Flag Instances
	xMOV(ptr32[&mVU.regs().VI[REG_STATUS_FLAG].UL],	getFlagReg(fStatus));
	mVUallocMFLAGa(mVU, gprT1, fMac);
	mVUallocCFLAGa(mVU, gprT2, fClip);
	xMOV(ptr32[&mVU.regs().VI[REG_MAC_FLAG].UL],	gprT1);
	xMOV(ptr32[&mVU.regs().VI[REG_CLIP_FLAG].UL],	gprT2);

	if (isEbit || isVU1) { // Clear 'is busy' Flags
		if (!mVU.index || !THREAD_VU1) {
			xAND(ptr32[&VU0.VI[REG_VPU_STAT].UL], (isVU1 ? ~0x100 : ~0x001)); // VBS0/VBS1 flag
			xAND(ptr32[&mVU.getVifRegs().stat], ~VIF1_STAT_VEW); // Clear VU 'is busy' signal for vif
		}
	}

	if (isEbit != 2) { // Save PC, and Jump to Exit Point
		xMOV(ptr32[&mVU.regs().VI[REG_TPC].UL], xPC);
		xJMP(mVU.exitFunct);
	}
	memcpy(&mVUregs, &stateBackup, sizeof(mVUregs)); //Restore the state for the rest of the recompile
}

void mVUendProgram(mV, microFlagCycles* mFC, int isEbit) {

	int fStatus = getLastFlagInst(mVUpBlock->pState, mFC->xStatus, 0, isEbit);
	int fMac	= getLastFlagInst(mVUpBlock->pState, mFC->xMac,    1, isEbit);
	int fClip	= getLastFlagInst(mVUpBlock->pState, mFC->xClip,   2, isEbit);
	int qInst	= 0;
	int pInst	= 0;
	mVU.regAlloc->flushAll();

	if (isEbit) {
		memzero(mVUinfo);
		memzero(mVUregsTemp);
		mVUincCycles(mVU, 100); // Ensures Valid P/Q instances (And sets all cycle data to 0)
		mVUcycles -= 100;
		qInst = mVU.q;
		pInst = mVU.p;
		if (mVUinfo.doDivFlag) {
			sFLAG.doFlag = true;
			sFLAG.write  = fStatus;
			mVUdivSet(mVU);
		}
		if (mVUinfo.doXGKICK) {
			mVU_XGKICK_DELAY(mVU);
		}
		if (doEarlyExit(mVU)) {
			if (!isVU1)
				xFastCall((void*)mVU0clearlpStateJIT);
			else
				xFastCall((void*)mVU1clearlpStateJIT);
		}
	}

	// Save P/Q Regs
	if (qInst) { xPSHUF.D(xmmPQ, xmmPQ, 0xe5); }
	xMOVSS(ptr32[&mVU.regs().VI[REG_Q].UL], xmmPQ);
	if (isVU1) {
		xPSHUF.D(xmmPQ, xmmPQ, pInst ? 3 : 2);
		xMOVSS(ptr32[&mVU.regs().VI[REG_P].UL], xmmPQ);
	}

	// Save Flag Instances
	xMOV(ptr32[&mVU.regs().VI[REG_STATUS_FLAG].UL],	getFlagReg(fStatus));
	mVUallocMFLAGa(mVU, gprT1, fMac);
	mVUallocCFLAGa(mVU, gprT2, fClip);
	xMOV(ptr32[&mVU.regs().VI[REG_MAC_FLAG].UL],	gprT1);
	xMOV(ptr32[&mVU.regs().VI[REG_CLIP_FLAG].UL],	gprT2);

	if (isEbit || isVU1) { // Clear 'is busy' Flags
		if (!mVU.index || !THREAD_VU1) {
			xAND(ptr32[&VU0.VI[REG_VPU_STAT].UL], (isVU1 ? ~0x100 : ~0x001)); // VBS0/VBS1 flag
			//xAND(ptr32[&mVU.getVifRegs().stat], ~VIF1_STAT_VEW); // Clear VU 'is busy' signal for vif
		}
	}

	if (isEbit != 2) { // Save PC, and Jump to Exit Point
		xMOV(ptr32[&mVU.regs().VI[REG_TPC].UL], xPC);
		xJMP(mVU.exitFunct);
	}
}

// Recompiles Code for Proper Flags and Q/P regs on Block Linkings
void mVUsetupBranch(mV, microFlagCycles& mFC) {
	
	mVU.regAlloc->flushAll();	// Flush Allocated Regs
	mVUsetupFlags(mVU, mFC);	// Shuffle Flag Instances

	// Shuffle P/Q regs since every block starts at instance #0
	if (mVU.p || mVU.q) { xPSHUF.D(xmmPQ, xmmPQ, shufflePQ); }
}

void normBranchCompile(microVU& mVU, u32 branchPC) {
	microBlock* pBlock;
	blockCreate(branchPC/8);
	pBlock = mVUblocks[branchPC/8]->search((microRegInfo*)&mVUregs);
	if (pBlock)	{ xJMP(pBlock->x86ptrStart); }
	else		{ mVUcompile(mVU, branchPC, (uptr)&mVUregs); }
}

void normJumpCompile(mV, microFlagCycles& mFC, bool isEvilJump) {
	memcpy(&mVUpBlock->pStateEnd, &mVUregs, sizeof(microRegInfo));
	mVUsetupBranch(mVU, mFC);
	mVUbackupRegs(mVU);

	if(!mVUpBlock->jumpCache) { // Create the jump cache for this block
		mVUpBlock->jumpCache = new microJumpCache[mProgSize/2];
	}

	if (isEvilJump)		xMOV(gprT2, ptr32[&mVU.evilBranch]);
	else				xMOV(gprT2, ptr32[&mVU.branch]);
	if (doJumpCaching)	xMOV(gprT3, (uptr)mVUpBlock);
	else				xMOV(gprT3, (uptr)&mVUpBlock->pStateEnd);

	if(mVUup.eBit && isEvilJump)// E-bit EvilJump
	{
		//Xtreme G 3 does 2 conditional jumps, the first contains an E Bit on the first instruction
		//So if it is taken, you need to end the program, else you get infinite loops.
		mVUendProgram(mVU, &mFC, 2);
		xMOV(ptr32[&mVU.regs().VI[REG_TPC].UL], gprT2);
		xJMP(mVU.exitFunct);
	}
	
	if (!mVU.index) xFastCall((void*)(void(*)())mVUcompileJIT<0>, gprT2, gprT3); //(u32 startPC, uptr pState)
	else			xFastCall((void*)(void(*)())mVUcompileJIT<1>, gprT2, gprT3);

	mVUrestoreRegs(mVU);
	xJMP(gprT1);  // Jump to rec-code address
}

void normBranch(mV, microFlagCycles& mFC) {

	// E-bit or T-Bit or D-Bit Branch
	if (mVUup.dBit && doDBitHandling) 
	{
		u32 tempPC = iPC;
		xTEST(ptr32[&VU0.VI[REG_FBRST].UL], (isVU1 ? 0x400 : 0x4));
		xForwardJump32 eJMP(Jcc_Zero);
		xOR(ptr32[&VU0.VI[REG_VPU_STAT].UL], (isVU1 ? 0x200 : 0x2));
		xOR(ptr32[&mVU.regs().flags], VUFLAG_INTCINTERRUPT);
		iPC = branchAddr(mVU)/4;
		mVUDTendProgram(mVU, &mFC, 1);
		eJMP.SetTarget();
		iPC = tempPC;		
	}
	if (mVUup.tBit)
	{
		u32 tempPC = iPC;
		xTEST(ptr32[&VU0.VI[REG_FBRST].UL], (isVU1 ? 0x800 : 0x8));
		xForwardJump32 eJMP(Jcc_Zero);
		xOR(ptr32[&VU0.VI[REG_VPU_STAT].UL], (isVU1 ? 0x400 : 0x4));
		xOR(ptr32[&mVU.regs().flags], VUFLAG_INTCINTERRUPT);
		iPC = branchAddr(mVU)/4;
		mVUDTendProgram(mVU, &mFC, 1);
		eJMP.SetTarget();
		iPC = tempPC;	
	}
	if (mVUup.eBit) { 
		if(mVUlow.badBranch) 
			DevCon.Warning("End on evil Unconditional branch! - Not implemented! - If game broken report to PCSX2 Team"); 
		
		iPC = branchAddr(mVU)/4; 
		mVUendProgram(mVU, &mFC, 1); 
		return; 
	}
	
	if(mVUlow.badBranch)
	{
		u32 badBranchAddr = branchAddr(mVU)+8;
		incPC(3);
		if(mVUlow.branch == 2 || mVUlow.branch == 10)	//Delay slot branch needs linking
		{
			DevCon.Warning("Found %s in delay slot, linking - If game broken report to PCSX2 Team", mVUlow.branch == 2 ? "BAL" : "JALR");
			xMOV(gprT3, badBranchAddr);
			xSHR(gprT3, 3);
			mVUallocVIb(mVU, gprT3, _It_);

		}
		incPC(-3);
	}
	
	// Normal Branch
	mVUsetupBranch(mVU, mFC);
	normBranchCompile(mVU, branchAddr(mVU));
}

//Messy handler warning!!
//This handles JALR/BAL in the delay slot of a conditional branch.  We do this because the normal handling
//Doesn't seem to work properly, even if the link is made to the correct address, so we do it manually instead.
//Normally EvilBlock handles all this stuff, but something to do with conditionals and links don't quite work right :/
void condJumpProcessingEvil(mV, microFlagCycles& mFC, int JMPcc) {

	u32 bPC = iPC-1; // mVUcompile can modify iPC, mVUpBlock, and mVUregs so back them up
	u32 badBranchAddr;
	iPC = bPC-2;
	setCode();
	badBranchAddr = branchAddr(mVU);

	xCMP(ptr16[&mVU.branch], 0);
	
	xForwardJump32 eJMP(xInvertCond((JccComparisonType)JMPcc));

	mVUcompileSingleInstruction(mVU, badBranchAddr, (uptr)&mVUregs, mFC);

	xMOV(gprT3, badBranchAddr+8);
	iPC = bPC;
	setCode();
	xSHR(gprT3, 3);
	mVUallocVIb(mVU, gprT3, _It_); //Link to branch addr + 8
	
	normJumpCompile(mVU, mFC, true); //Compile evil branch, just in time!
	
	eJMP.SetTarget();
	
	incPC(2);  // Point to delay slot of evil Branch (as the original branch isn't taken)
	mVUcompileSingleInstruction(mVU, xPC, (uptr)&mVUregs, mFC);

	xMOV(gprT3, xPC);
	iPC = bPC;
	setCode();
	xSHR(gprT3, 3);
	mVUallocVIb(mVU, gprT3, _It_);
		
	normJumpCompile(mVU, mFC, true); //Compile evil branch, just in time!

}
void condBranch(mV, microFlagCycles& mFC, int JMPcc) {
	mVUsetupBranch(mVU, mFC);
	
	if (mVUup.tBit)
	{
		u32 tempPC = iPC;
		xTEST(ptr32[&VU0.VI[REG_FBRST].UL], (isVU1 ? 0x800 : 0x8));
		xForwardJump32 eJMP(Jcc_Zero);
		xOR(ptr32[&VU0.VI[REG_VPU_STAT].UL], (isVU1 ? 0x400 : 0x4));
		xOR(ptr32[&mVU.regs().flags], VUFLAG_INTCINTERRUPT);
		mVUDTendProgram(mVU, &mFC, 2);
		xCMP(ptr16[&mVU.branch], 0);
		xForwardJump32 tJMP(xInvertCond((JccComparisonType)JMPcc));
			incPC(4); // Set PC to First instruction of Non-Taken Side
			xMOV(ptr32[&mVU.regs().VI[REG_TPC].UL], xPC);
			xJMP(mVU.exitFunct);
		tJMP.SetTarget();
		incPC(-4); // Go Back to Branch Opcode to get branchAddr
		iPC = branchAddr(mVU)/4;
		xMOV(ptr32[&mVU.regs().VI[REG_TPC].UL], xPC);
		xJMP(mVU.exitFunct);
		eJMP.SetTarget();
		iPC = tempPC;	
	}
	if (mVUup.dBit && doDBitHandling) 
	{
		u32 tempPC = iPC;
		xTEST(ptr32[&VU0.VI[REG_FBRST].UL], (isVU1 ? 0x400 : 0x4));
		xForwardJump32 eJMP(Jcc_Zero);
		xOR(ptr32[&VU0.VI[REG_VPU_STAT].UL], (isVU1 ? 0x200 : 0x2));
		xOR(ptr32[&mVU.regs().flags], VUFLAG_INTCINTERRUPT);
		mVUDTendProgram(mVU, &mFC, 2);
		xCMP(ptr16[&mVU.branch], 0);
		xForwardJump32 dJMP(xInvertCond((JccComparisonType)JMPcc));
			incPC(4); // Set PC to First instruction of Non-Taken Side
			xMOV(ptr32[&mVU.regs().VI[REG_TPC].UL], xPC);
			xJMP(mVU.exitFunct);
		dJMP.SetTarget();
		incPC(-4); // Go Back to Branch Opcode to get branchAddr
		iPC = branchAddr(mVU)/4;
		xMOV(ptr32[&mVU.regs().VI[REG_TPC].UL], xPC);
		xJMP(mVU.exitFunct);
		eJMP.SetTarget();
		iPC = tempPC;		
	}
	xCMP(ptr16[&mVU.branch], 0);

	if (mVUup.eBit) { // Conditional Branch With E-Bit Set
		if(mVUlow.evilBranch) 
			DevCon.Warning("End on evil branch! - Not implemented! - If game broken report to PCSX2 Team");
		
		incPC(3);
		mVUendProgram(mVU, &mFC, 2);
		xForwardJump32 eJMP(xInvertCond((JccComparisonType)JMPcc));
			incPC(1); // Set PC to First instruction of Non-Taken Side
			xMOV(ptr32[&mVU.regs().VI[REG_TPC].UL], xPC);
			xJMP(mVU.exitFunct);
			eJMP.SetTarget();
		incPC(-4); // Go Back to Branch Opcode to get branchAddr
		iPC = branchAddr(mVU)/4;
		xMOV(ptr32[&mVU.regs().VI[REG_TPC].UL], xPC);
		xJMP(mVU.exitFunct);
		return;
	}
	else { // Normal Conditional Branch
		incPC(3);
		if(mVUlow.evilBranch) //We are dealing with an evil evil block, so we need to process this slightly differently
		{
			
			if(mVUlow.branch == 10 || mVUlow.branch == 2) //Evil branch is a jump of some measure
			{
				//Because of how it is linked, we need to make sure the target is recompiled if taken
				condJumpProcessingEvil(mVU, mFC, JMPcc);
				return;
			}
		}
		microBlock* bBlock;
		incPC2(1); // Check if Branch Non-Taken Side has already been recompiled
		blockCreate(iPC/2);
		bBlock = mVUblocks[iPC/2]->search((microRegInfo*)&mVUregs);
		incPC2(-1);
		if (bBlock)	{ // Branch non-taken has already been compiled
			xJcc(xInvertCond((JccComparisonType)JMPcc), bBlock->x86ptrStart);
			incPC(-3); // Go back to branch opcode (to get branch imm addr)
			normBranchCompile(mVU, branchAddr(mVU));
		}
		else { 
			s32* ajmp = xJcc32((JccComparisonType)JMPcc); 
			u32 bPC = iPC; // mVUcompile can modify iPC, mVUpBlock, and mVUregs so back them up
			microBlock* pBlock = mVUpBlock;
			memcpy(&pBlock->pStateEnd, &mVUregs, sizeof(microRegInfo));

			incPC2(1);  // Get PC for branch not-taken
			mVUcompile(mVU, xPC, (uptr)&mVUregs);

			iPC = bPC;
			incPC(-3); // Go back to branch opcode (to get branch imm addr)
			uptr jumpAddr = (uptr)mVUblockFetch(mVU, branchAddr(mVU), (uptr)&pBlock->pStateEnd);
			*ajmp = (jumpAddr - ((uptr)ajmp + 4));
		}
	}
}

void normJump(mV, microFlagCycles& mFC) {
	if (mVUlow.constJump.isValid) { // Jump Address is Constant
		if (mVUup.eBit) { // E-bit Jump
			iPC = (mVUlow.constJump.regValue*2) & (mVU.progMemMask);
			mVUendProgram(mVU, &mFC, 1);
			return;
		}
		int jumpAddr = (mVUlow.constJump.regValue*8)&(mVU.microMemSize-8);
		mVUsetupBranch(mVU, mFC);
		normBranchCompile(mVU, jumpAddr);
		return;
	}
	if(mVUlow.badBranch)
	{		
		incPC(3);
		if(mVUlow.branch == 2 || mVUlow.branch == 10)	//Delay slot BAL needs linking, only need to do BAL here, JALR done earlier
		{
			DevCon.Warning("Found %x in delay slot, linking - If game broken report to PCSX2 Team", mVUlow.branch == 2 ? "BAL" : "JALR");
			incPC(-2);
			mVUallocVIa(mVU, gprT1, _Is_);
			xADD(gprT1, 8);
			xSHR(gprT1, 3);
			incPC(2);
			mVUallocVIb(mVU, gprT1, _It_);

		}
		incPC(-3);
	}
	if (mVUup.dBit && doDBitHandling) 
	{
		xTEST(ptr32[&VU0.VI[REG_FBRST].UL], (isVU1 ? 0x400 : 0x4));
		xForwardJump32 eJMP(Jcc_Zero);
		xOR(ptr32[&VU0.VI[REG_VPU_STAT].UL], (isVU1 ? 0x200 : 0x2));
		xOR(ptr32[&mVU.regs().flags], VUFLAG_INTCINTERRUPT);
		mVUDTendProgram(mVU, &mFC, 2);
		xMOV(gprT1, ptr32[&mVU.branch]);
		xMOV(ptr32[&mVU.regs().VI[REG_TPC].UL], gprT1);
		xJMP(mVU.exitFunct);
		eJMP.SetTarget();	
	}
	if (mVUup.tBit)
	{
		xTEST(ptr32[&VU0.VI[REG_FBRST].UL], (isVU1 ? 0x800 : 0x8));
		xForwardJump32 eJMP(Jcc_Zero);
		xOR(ptr32[&VU0.VI[REG_VPU_STAT].UL], (isVU1 ? 0x400 : 0x4));
		xOR(ptr32[&mVU.regs().flags], VUFLAG_INTCINTERRUPT);
		mVUDTendProgram(mVU, &mFC, 2);
		xMOV(gprT1, ptr32[&mVU.branch]);
		xMOV(ptr32[&mVU.regs().VI[REG_TPC].UL], gprT1);
		xJMP(mVU.exitFunct);
		eJMP.SetTarget();
	}
	if (mVUup.eBit) { // E-bit Jump
		mVUendProgram(mVU, &mFC, 2);
		xMOV(gprT1, ptr32[&mVU.branch]);
		xMOV(ptr32[&mVU.regs().VI[REG_TPC].UL], gprT1);
		xJMP(mVU.exitFunct);
	}
	else {
		normJumpCompile(mVU, mFC, false);
	}
}
