#include <stdio.h>
#include "VUShared.h"

using namespace MIPSReflection;
using namespace VUShared;

// clang-format off
const char* VUShared::m_sBroadcast[4] =
{
	"x",
	"y",
	"z",
	"w",
};

const char* VUShared::m_sDestination[16] =
{
	"",     //0000
	"w",    //000w
	"z",    //00z0
	"zw",   //00zw
	"y",    //0y00
	"yw",   //0y0w
	"yz",   //0yz0
	"yzw",  //0yzw
	"x",    //x000
	"xw",   //x00w
	"xz",   //x0z0
	"xzw",  //x0zw
	"xy",   //xy00
	"xyw",  //xy0w
	"xyz",  //xyz0
	"xyzw", //xyzw
};
// clang-format on

uint32 VUShared::MakeDestFromComponent(uint32 component)
{
	assert(component <= VECTOR_COMPW);
	return (1 << (3 - component));
}

int32 VUShared::GetImm11Offset(uint16 nImm11)
{
	if(nImm11 & 0x400)
	{
		return -(0x800 - nImm11);
	}
	else
	{
		return (nImm11 & 0x3FF);
	}
}

int32 VUShared::GetBranch(uint16 nImm11)
{
	return GetImm11Offset(nImm11) * 8;
}

void VUShared::VerifyVuReflectionTable(MIPSReflection::INSTRUCTION* refl, VUShared::VUINSTRUCTION* vuRefl, size_t tableSize)
{
	for(unsigned int i = 0; i < tableSize; i++)
	{
		const char* reflMnem = refl[i].sMnemonic;
		const char* reflVuMnem = vuRefl[i].name;
		if((reflMnem == nullptr) || (reflVuMnem == nullptr))
		{
			assert(reflMnem == reflVuMnem);
		}
		else
		{
			assert(!strcmp(reflMnem, reflVuMnem));
		}
	}
}

void VUShared::ReflOpFdFsI(INSTRUCTION* pInstr, CMIPS* pCtx, uint32 nAddress, uint32 nOpcode, char* sText, unsigned int nCount)
{
	uint8 nFS = (uint8)((nOpcode >> 11) & 0x001F);
	uint8 nFD = (uint8)((nOpcode >> 6) & 0x001F);

	uint8 nDest = (uint8)((nOpcode >> 21) & 0x000F);

	snprintf(sText, nCount, "VF%d%s, VF%d%s, I", nFD, m_sDestination[nDest], nFS, m_sDestination[nDest]);
}

void VUShared::ReflOpFdFsQ(INSTRUCTION* pInstr, CMIPS* pCtx, uint32 nAddress, uint32 nOpcode, char* sText, unsigned int nCount)
{
	uint8 nFS = (uint8)((nOpcode >> 11) & 0x001F);
	uint8 nFD = (uint8)((nOpcode >> 6) & 0x001F);

	uint8 nDest = (uint8)((nOpcode >> 21) & 0x000F);

	snprintf(sText, nCount, "VF%d%s, VF%d%s, Q", nFD, m_sDestination[nDest], nFS, m_sDestination[nDest]);
}

void VUShared::ReflOpFdFsFt(INSTRUCTION* pInstr, CMIPS* pCtx, uint32 nAddress, uint32 nOpcode, char* sText, unsigned int nCount)
{
	uint8 nFT = (uint8)((nOpcode >> 16) & 0x001F);
	uint8 nFS = (uint8)((nOpcode >> 11) & 0x001F);
	uint8 nFD = (uint8)((nOpcode >> 6) & 0x001F);

	uint8 nDest = (uint8)((nOpcode >> 21) & 0x000F);

	snprintf(sText, nCount, "VF%d%s, VF%d%s, VF%d%s", nFD, m_sDestination[nDest], nFS, m_sDestination[nDest], nFT, m_sDestination[nDest]);
}

void VUShared::ReflOpFdFsFtBc(INSTRUCTION* pInstr, CMIPS* pCtx, uint32 nAddress, uint32 nOpcode, char* sText, unsigned int nCount)
{
	uint8 nFT = (uint8)((nOpcode >> 16) & 0x001F);
	uint8 nFS = (uint8)((nOpcode >> 11) & 0x001F);
	uint8 nFD = (uint8)((nOpcode >> 6) & 0x001F);

	uint8 nBc = (uint8)((nOpcode >> 0) & 0x0003);
	uint8 nDest = (uint8)((nOpcode >> 21) & 0x000F);

	snprintf(sText, nCount, "VF%d%s, VF%d%s, VF%d%s", nFD, m_sDestination[nDest], nFS, m_sDestination[nDest], nFT, m_sBroadcast[nBc]);
}

void VUShared::ReflOpFsDstItDec(INSTRUCTION* pInstr, CMIPS* pCtx, uint32 nAddress, uint32 nOpcode, char* sText, unsigned int nCount)
{
	uint8 nDest = static_cast<uint8>((nOpcode >> 21) & 0x000F);
	uint8 nIT = static_cast<uint8>((nOpcode >> 16) & 0x001F);
	uint8 nFS = static_cast<uint8>((nOpcode >> 11) & 0x001F);

	snprintf(sText, nCount, "VF%d%s, (--VI%d)", nFS, m_sDestination[nDest], nIT);
}

void VUShared::ReflOpFsDstItInc(INSTRUCTION* pInstr, CMIPS* pCtx, uint32 nAddress, uint32 nOpcode, char* sText, unsigned int nCount)
{
	uint8 nDest = static_cast<uint8>((nOpcode >> 21) & 0x000F);
	uint8 nIT = static_cast<uint8>((nOpcode >> 16) & 0x001F);
	uint8 nFS = static_cast<uint8>((nOpcode >> 11) & 0x001F);

	snprintf(sText, nCount, "VF%d%s, (VI%d++)", nFS, m_sDestination[nDest], nIT);
}

void VUShared::ReflOpFtFs(INSTRUCTION* pInstr, CMIPS* pCtx, uint32 nAddress, uint32 nOpcode, char* sText, unsigned int nCount)
{
	uint8 nFT = (uint8)((nOpcode >> 16) & 0x001F);
	uint8 nFS = (uint8)((nOpcode >> 11) & 0x001F);

	uint8 nDest = (uint8)((nOpcode >> 21) & 0x000F);

	snprintf(sText, nCount, "VF%d%s, VF%d%s", nFT, m_sDestination[nDest], nFS, m_sDestination[nDest]);
}

void VUShared::ReflOpFtIs(INSTRUCTION* pInstr, CMIPS* pCtx, uint32 nAddress, uint32 nOpcode, char* sText, unsigned int nCount)
{
	uint8 nDest = static_cast<uint8>((nOpcode >> 21) & 0x000F);
	uint8 nFT = static_cast<uint8>((nOpcode >> 16) & 0x001F);
	uint8 nIS = static_cast<uint8>((nOpcode >> 11) & 0x001F);

	snprintf(sText, nCount, "VF%d%s, VI%d", nFT, m_sDestination[nDest], nIS);
}

void VUShared::ReflOpFtDstIsDec(INSTRUCTION* instr, CMIPS* context, uint32 address, uint32 opcode, char* text, unsigned int count)
{
	auto dest = static_cast<uint8>((opcode >> 21) & 0x000F);
	auto ft = static_cast<uint8>((opcode >> 16) & 0x001F);
	auto is = static_cast<uint8>((opcode >> 11) & 0x001F);

	snprintf(text, count, "VF%d%s, (--VI%d)", ft, m_sDestination[dest], is);
}

void VUShared::ReflOpFtDstIsInc(INSTRUCTION*, CMIPS*, uint32, uint32 opcode, char* text, unsigned int count)
{
	auto dest = static_cast<uint8>((opcode >> 21) & 0x000F);
	auto ft = static_cast<uint8>((opcode >> 16) & 0x001F);
	auto is = static_cast<uint8>((opcode >> 11) & 0x001F);

	snprintf(text, count, "VF%d%s, (VI%d++)", ft, m_sDestination[dest], is);
}

void VUShared::ReflOpClip(INSTRUCTION* pInstr, CMIPS* pCtx, uint32 nAddress, uint32 nOpcode, char* sText, unsigned int nCount)
{
	uint8 nFT = (uint8)((nOpcode >> 16) & 0x001F);
	uint8 nFS = (uint8)((nOpcode >> 11) & 0x001F);

	snprintf(sText, nCount, "VF%dxyz, VF%dw", nFS, nFT);
}

void VUShared::ReflOpAccFsI(INSTRUCTION* pInstr, CMIPS* pCtx, uint32 nAddress, uint32 nOpcode, char* sText, unsigned int nCount)
{
	uint8 nFS = static_cast<uint8>((nOpcode >> 11) & 0x001F);
	uint8 nDest = static_cast<uint8>((nOpcode >> 21) & 0x000F);

	snprintf(sText, nCount, "ACC%s, VF%d%s, I", m_sDestination[nDest], nFS, m_sDestination[nDest]);
}

void VUShared::ReflOpAccFsQ(INSTRUCTION* pInstr, CMIPS* pCtx, uint32 nAddress, uint32 nOpcode, char* sText, unsigned int nCount)
{
	uint8 nFS = static_cast<uint8>((nOpcode >> 11) & 0x001F);
	uint8 nDest = static_cast<uint8>((nOpcode >> 21) & 0x000F);

	snprintf(sText, nCount, "ACC%s, VF%d%s, Q", m_sDestination[nDest], nFS, m_sDestination[nDest]);
}

void VUShared::ReflOpAccFsFt(INSTRUCTION* pInstr, CMIPS* pCtx, uint32 nAddress, uint32 nOpcode, char* sText, unsigned int nCount)
{
	uint8 nFT = (uint8)((nOpcode >> 16) & 0x001F);
	uint8 nFS = (uint8)((nOpcode >> 11) & 0x001F);

	uint8 nDest = (uint8)((nOpcode >> 21) & 0x000F);

	snprintf(sText, nCount, "ACC%s, VF%d%s, VF%d%s", m_sDestination[nDest], nFS, m_sDestination[nDest], nFT, m_sDestination[nDest]);
}

void VUShared::ReflOpAccFsFtBc(INSTRUCTION* pInstr, CMIPS* pCtx, uint32 nAddress, uint32 nOpcode, char* sText, unsigned int nCount)
{
	uint8 nFT = (uint8)((nOpcode >> 16) & 0x001F);
	uint8 nFS = (uint8)((nOpcode >> 11) & 0x001F);

	uint8 nBc = (uint8)((nOpcode >> 0) & 0x0003);
	uint8 nDest = (uint8)((nOpcode >> 21) & 0x000F);

	snprintf(sText, nCount, "ACC%s, VF%d%s, VF%d%s", m_sDestination[nDest], nFS, m_sDestination[nDest], nFT, m_sBroadcast[nBc]);
}

void VUShared::ReflOpRFsf(INSTRUCTION* pInstr, CMIPS* pCtx, uint32 nAddress, uint32 nOpcode, char* sText, unsigned int nCount)
{
	uint8 nFS = (uint8)((nOpcode >> 11) & 0x001F);
	uint8 nFSF = (uint8)((nOpcode >> 21) & 0x0003);

	snprintf(sText, nCount, "R, VF%d%s", nFS, m_sBroadcast[nFSF]);
}

void VUShared::ReflOpFtR(INSTRUCTION* pInstr, CMIPS* pCtx, uint32 nAddress, uint32 nOpcode, char* sText, unsigned int nCount)
{
	uint8 nDest = (uint8)((nOpcode >> 21) & 0x000F);
	uint8 nFT = (uint8)((nOpcode >> 16) & 0x001F);

	snprintf(sText, nCount, "VF%d%s, R", nFT, m_sDestination[nDest]);
}

void VUShared::ReflOpQFtf(INSTRUCTION* pInstr, CMIPS* pCtx, uint32 nAddress, uint32 nOpcode, char* sText, unsigned int nCount)
{
	uint8 nFT = (uint8)((nOpcode >> 16) & 0x001F);

	uint8 nFTF = (uint8)((nOpcode >> 23) & 0x0003);

	snprintf(sText, nCount, "Q, VF%d%s", nFT, m_sBroadcast[nFTF]);
}

void VUShared::ReflOpQFsfFtf(INSTRUCTION* pInstr, CMIPS* pCtx, uint32 nAddress, uint32 nOpcode, char* sText, unsigned int nCount)
{
	uint8 nFT = (uint8)((nOpcode >> 16) & 0x001F);
	uint8 nFS = (uint8)((nOpcode >> 11) & 0x001F);

	uint8 nFTF = (uint8)((nOpcode >> 23) & 0x0003);
	uint8 nFSF = (uint8)((nOpcode >> 21) & 0x0003);

	snprintf(sText, nCount, "Q, VF%d%s, VF%d%s", nFS, m_sBroadcast[nFSF], nFT, m_sBroadcast[nFTF]);
}

void VUShared::ReflOpIdIsIt(INSTRUCTION* pInstr, CMIPS* pCtx, uint32 nAddress, uint32 nOpcode, char* sText, unsigned int nCount)
{
	uint8 nIT = static_cast<uint8>((nOpcode >> 16) & 0x001F);
	uint8 nIS = static_cast<uint8>((nOpcode >> 11) & 0x001F);
	uint8 nID = static_cast<uint8>((nOpcode >> 6) & 0x001F);

	snprintf(sText, nCount, "VI%d, VI%d, VI%d", nID, nIS, nIT);
}

void VUShared::ReflOpItIsDst(INSTRUCTION* instr, CMIPS* context, uint32 address, uint32 opcode, char* text, unsigned int count)
{
	uint8 dest = static_cast<uint8>((opcode >> 21) & 0x000F);
	uint8 it = static_cast<uint8>((opcode >> 16) & 0x001F);
	uint8 is = static_cast<uint8>((opcode >> 11) & 0x001F);

	snprintf(text, count, "VI%d, (VI%d)%s", it, is, m_sDestination[dest]);
}

void VUShared::ReflOpItIsImm5(INSTRUCTION* pInstr, CMIPS* pCtx, uint32 nAddress, uint32 nOpcode, char* sText, unsigned int nCount)
{
	uint8 nIT = static_cast<uint8>((nOpcode >> 16) & 0x001F);
	uint8 nIS = static_cast<uint8>((nOpcode >> 11) & 0x001F);
	uint16 nImm = static_cast<uint8>((nOpcode >> 6) & 0x001F);
	if(nImm & 0x10)
	{
		nImm |= 0xFFE0;
	}

	snprintf(sText, nCount, "VI%d, VI%d, $%04X", nIT, nIS, nImm);
}

void VUShared::ReflOpItFsf(INSTRUCTION* pInstr, CMIPS* pCtx, uint32 nAddress, uint32 nOpcode, char* sText, unsigned int nCount)
{
	uint8 nIT = static_cast<uint8>((nOpcode >> 16) & 0x001F);
	uint8 nFS = static_cast<uint8>((nOpcode >> 11) & 0x001F);
	uint8 nFSF = static_cast<uint8>((nOpcode >> 21) & 0x0003);

	snprintf(sText, nCount, "VI%d, VF%d%s", nIT, nFS, m_sBroadcast[nFSF]);
}

////////////////////////////////////////////

VUShared::VUINSTRUCTION* VUShared::DereferenceInstruction(VUSUBTABLE* pSubTable, uint32 nOpcode)
{
	unsigned int nIndex = (nOpcode >> pSubTable->nShift) & pSubTable->nMask;
	return &(pSubTable->pTable[nIndex]);
}

void VUShared::SubTableAffectedOperands(VUINSTRUCTION* pInstr, CMIPS* pCtx, uint32 nAddress, uint32 nOpcode, OPERANDSET& operandSet)
{
	pInstr = DereferenceInstruction(pInstr->subTable, nOpcode);
	if(pInstr->pGetAffectedOperands == nullptr)
	{
		//We should always have something that tells us what is affected (even if it's nothing)
		assert(0);
		return;
	}
	pInstr->pGetAffectedOperands(pInstr, pCtx, nAddress, nOpcode, operandSet);
}

void VUShared::ReflOpAffNone(VUINSTRUCTION* pInstr, CMIPS* pCtx, uint32 nAddress, uint32 nOpcode, OPERANDSET& operandSet)
{
	//Nothing is affected
}

void VUShared::ReflOpAffWrAMfRdFsFt(VUINSTRUCTION*, CMIPS*, uint32, uint32 opcode, OPERANDSET& operandSet)
{
	auto dest = static_cast<uint8>((opcode >> 21) & 0x000F);
	auto ft = static_cast<uint8>((opcode >> 16) & 0x001F);
	auto fs = static_cast<uint8>((opcode >> 11) & 0x001F);

	//TODO: Write A
	operandSet.readF0 = fs;
	operandSet.readElemF0 = dest;
	operandSet.readF1 = ft;
	operandSet.readElemF1 = dest;
	operandSet.writeMACflags = true;
}

void VUShared::ReflOpAffWrAMfRdFsFtBc(VUINSTRUCTION*, CMIPS*, uint32, uint32 opcode, OPERANDSET& operandSet)
{
	auto dest = static_cast<uint8>((opcode >> 21) & 0x000F);
	auto ft = static_cast<uint8>((opcode >> 16) & 0x001F);
	auto fs = static_cast<uint8>((opcode >> 11) & 0x001F);
	auto bc = static_cast<uint8>((opcode >> 0) & 0x0003);

	//TODO: Write A
	operandSet.readF0 = fs;
	operandSet.readElemF0 = dest;
	operandSet.readF1 = ft;
	operandSet.readElemF1 = MakeDestFromComponent(bc);
	operandSet.writeMACflags = true;
}

void VUShared::ReflOpAffWrAMfRdFsI(VUINSTRUCTION*, CMIPS*, uint32, uint32 opcode, OPERANDSET& operandSet)
{
	auto dest = static_cast<uint8>((opcode >> 21) & 0x000F);
	auto fs = static_cast<uint8>((opcode >> 11) & 0x001F);

	//TODO: Write A
	operandSet.readF0 = fs;
	operandSet.readElemF0 = dest;
	operandSet.writeMACflags = true;
}

void VUShared::ReflOpAffWrAMfRdFsQ(VUINSTRUCTION*, CMIPS*, uint32, uint32 opcode, OPERANDSET& operandSet)
{
	auto dest = static_cast<uint8>((opcode >> 21) & 0x000F);
	auto fs = static_cast<uint8>((opcode >> 11) & 0x001F);

	//TODO: Write A
	operandSet.readF0 = fs;
	operandSet.readElemF0 = dest;
	operandSet.readQ = true;
	operandSet.writeMACflags = true;
}

void VUShared::ReflOpAffWrCfRdFsFt(VUINSTRUCTION*, CMIPS*, uint32, uint32 opcode, OPERANDSET& operandSet)
{
	auto dest = static_cast<uint8>((opcode >> 21) & 0x000F);
	auto ft = static_cast<uint8>((opcode >> 16) & 0x001F);
	auto fs = static_cast<uint8>((opcode >> 11) & 0x001F);

	//TODO: Write CF
	operandSet.readF0 = fs;
	operandSet.readElemF0 = dest;
	operandSet.readF1 = ft;
	operandSet.readElemF1 = MakeDestFromComponent(VECTOR_COMPW);
}

void VUShared::ReflOpAffWrFdRdFsFt(VUINSTRUCTION*, CMIPS*, uint32, uint32 opcode, OPERANDSET& operandSet)
{
	auto dest = static_cast<uint8>((opcode >> 21) & 0x000F);
	auto ft = static_cast<uint8>((opcode >> 16) & 0x001F);
	auto fs = static_cast<uint8>((opcode >> 11) & 0x001F);
	auto fd = static_cast<uint8>((opcode >> 6) & 0x001F);

	operandSet.writeF = fd;
	operandSet.readF0 = fs;
	operandSet.readElemF0 = dest;
	operandSet.readF1 = ft;
	operandSet.readElemF1 = dest;
}

void VUShared::ReflOpAffWrFdRdFsI(VUINSTRUCTION*, CMIPS*, uint32, uint32 opcode, OPERANDSET& operandSet)
{
	auto dest = static_cast<uint8>((opcode >> 21) & 0x000F);
	auto fs = static_cast<uint8>((opcode >> 11) & 0x001F);
	auto fd = static_cast<uint8>((opcode >> 6) & 0x001F);

	operandSet.writeF = fd;
	operandSet.readF0 = fs;
	operandSet.readElemF0 = dest;
}

void VUShared::ReflOpAffWrFdMfRdFsFt(VUINSTRUCTION*, CMIPS*, uint32, uint32 opcode, OPERANDSET& operandSet)
{
	auto dest = static_cast<uint8>((opcode >> 21) & 0x000F);
	auto ft = static_cast<uint8>((opcode >> 16) & 0x001F);
	auto fs = static_cast<uint8>((opcode >> 11) & 0x001F);
	auto fd = static_cast<uint8>((opcode >> 6) & 0x001F);

	operandSet.writeF = fd;
	operandSet.readF0 = fs;
	operandSet.readElemF0 = dest;
	operandSet.readF1 = ft;
	operandSet.readElemF1 = dest;
	operandSet.writeMACflags = true;
}

void VUShared::ReflOpAffWrFdMfRdFsFtBc(VUINSTRUCTION*, CMIPS*, uint32, uint32 opcode, OPERANDSET& operandSet)
{
	auto dest = static_cast<uint8>((opcode >> 21) & 0x000F);
	auto ft = static_cast<uint8>((opcode >> 16) & 0x001F);
	auto fs = static_cast<uint8>((opcode >> 11) & 0x001F);
	auto fd = static_cast<uint8>((opcode >> 6) & 0x001F);
	auto bc = static_cast<uint8>((opcode >> 0) & 0x0003);

	operandSet.writeF = fd;
	operandSet.readF0 = fs;
	operandSet.readElemF0 = dest;
	operandSet.readF1 = ft;
	operandSet.readElemF1 = MakeDestFromComponent(bc);
	operandSet.writeMACflags = true;
}

void VUShared::ReflOpAffWrFdMfRdFsI(VUINSTRUCTION*, CMIPS*, uint32, uint32 opcode, OPERANDSET& operandSet)
{
	auto dest = static_cast<uint8>((opcode >> 21) & 0x000F);
	auto fs = static_cast<uint8>((opcode >> 11) & 0x001F);
	auto fd = static_cast<uint8>((opcode >> 6) & 0x001F);

	operandSet.writeF = fd;
	operandSet.readF0 = fs;
	operandSet.readElemF0 = dest;
	operandSet.writeMACflags = true;
}

void VUShared::ReflOpAffWrFdMfRdFsQ(VUINSTRUCTION*, CMIPS*, uint32, uint32 opcode, OPERANDSET& operandSet)
{
	auto dest = static_cast<uint8>((opcode >> 21) & 0x000F);
	auto fs = static_cast<uint8>((opcode >> 11) & 0x001F);
	auto fd = static_cast<uint8>((opcode >> 6) & 0x001F);

	operandSet.writeF = fd;
	operandSet.readF0 = fs;
	operandSet.readElemF0 = dest;
	operandSet.readQ = true;
	operandSet.writeMACflags = true;
}

void VUShared::ReflOpAffWrFtRdFs(VUINSTRUCTION*, CMIPS*, uint32, uint32 opcode, OPERANDSET& operandSet)
{
	auto dest = static_cast<uint8>((opcode >> 21) & 0x000F);
	auto ft = static_cast<uint8>((opcode >> 16) & 0x001F);
	auto fs = static_cast<uint8>((opcode >> 11) & 0x001F);

	operandSet.readF0 = fs;
	operandSet.readElemF0 = dest;
	operandSet.writeF = ft;
}
