// Copyright (c) 2012- PPSSPP 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 or later versions.

// 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 git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.

#include <cstring>
#include "Core/HLE/HLE.h"

#include "Core/MIPS/MIPS.h"
#include "Core/MIPS/MIPSDis.h"
#include "Core/MIPS/MIPSTables.h"
#include "Core/MIPS/MIPSDebugInterface.h"

#include "Core/MIPS/MIPSVFPUUtils.h"

#define _RS   ((op>>21) & 0x1F)
#define _RT   ((op>>16) & 0x1F)
#define _RD   ((op>>11) & 0x1F)
#define _FS   ((op>>11) & 0x1F)
#define _FT   ((op>>16) & 0x1F)
#define _FD   ((op>>6 ) & 0x1F)
#define _POS  ((op>>6 ) & 0x1F)
#define _SIZE ((op>>11) & 0x1F)


#define RN(i) currentDebugMIPS->GetRegName(0,i)
#define FN(i) currentDebugMIPS->GetRegName(1,i)
//#define VN(i) currentDebugMIPS->GetRegName(2,i)


#define S_not(a,b,c) (a<<2)|(b)|(c<<5)
#define SgetA(v) (((v)>>2)&0x7)
#define SgetB(v) ((v)&3)
#define SgetC(v) (((v)>>5)&0x3)

#define HorizOff 32
#define VertOff 1
#define MtxOff 4

inline const char *VN(int v, VectorSize size)
{
	static const char *vfpuCtrlNames[VFPU_CTRL_MAX] = {
		"SPFX",
		"TPFX",
		"DPFX",
		"CC",
		"INF4",
		"RSV5",
		"RSV6",
		"REV",
		"RCX0",
		"RCX1",
		"RCX2",
		"RCX3",
		"RCX4",
		"RCX5",
		"RCX6",
		"RCX7",
	};
	if (size == V_Single && v >= 128 && v < 128 + VFPU_CTRL_MAX) {
		return vfpuCtrlNames[v - 128];
	} else if (size == V_Single && v == 255) {
		return "(interlock)";
	}

	return GetVectorNotation(v, size);
}

inline const char *MN(int v, MatrixSize size)
{
	return GetMatrixNotation(v, size);
}

inline const char *VSuff(MIPSOpcode op)
{
	int a = (op>>7)&1;
	int b = (op>>15)&1;
	a+=(b<<1);
	switch (a)
	{
	case 0: return ".s";
	case 1: return ".p";
	case 2: return ".t";
	case 3: return ".q";
	default: return "%";
	}
}

namespace MIPSDis
{
	void Dis_SV(MIPSOpcode op, char *out)
	{
		int offset = (signed short)(op&0xFFFC);
		int vt = ((op>>16)&0x1f)|((op&3)<<5);
		int rs = (op>>21) & 0x1f;
		const char *name = MIPSGetName(op);
		sprintf(out, "%s\t%s, %d(%s)",name,VN(vt, V_Single),offset,RN(rs));
	}

	void Dis_SVQ(MIPSOpcode op, char *out)
	{
		int offset = (signed short)(op&0xFFFC);
		int vt = (((op>>16)&0x1f))|((op&1)<<5);
		int rs = (op>>21) & 0x1f;
		const char *name = MIPSGetName(op);
		sprintf(out, "%s\t%s, %d(%s)",name,VN(vt,V_Quad),offset,RN(rs));
		if (op & 2)
			strcat(out, ", wb");
	}

	void Dis_SVLRQ(MIPSOpcode op, char *out)
	{
		int offset = (signed short)(op&0xFFFC);
		int vt = (((op>>16)&0x1f))|((op&1)<<5);
		int rs = (op>>21) & 0x1f;
		int lr = (op>>1)&1;
		const char *name = MIPSGetName(op);
		sprintf(out, "%s%s.q\t%s, %d(%s)",name,lr?"r":"l",VN(vt,V_Quad),offset,RN(rs));
	}

	void Dis_Mftv(MIPSOpcode op, char *out)
	{
		int vr = op & 0xFF;
		int rt = _RT;
		const char *name = MIPSGetName(op);
		sprintf(out, "%s%s\t%s, %s",name,vr>127?"c":"", RN(rt), VN(vr, V_Single));
	}

	void Dis_Vmftvc(MIPSOpcode op, char *out)
	{
		int vr = op & 0xFF;
		int vs = _VS;
		const char *name = MIPSGetName(op);
		sprintf(out, "%s\t%s, %s", name, VN(vs, V_Single), VN(vr, V_Single));
	}

	void Dis_VPFXST(MIPSOpcode op, char *out)
	{
		int data = op & 0xFFFFF;
		const char *name = MIPSGetName(op);
		sprintf(out, "%s\t[",name);
		static const char *regnam[4] = {"X","Y","Z","W"};
		static const char *constan[8] = {"0","1","2","1/2","3","1/3","1/4","1/6"};
		for (int i=0; i<4; i++)
		{
			int regnum = (data>>(i*2)) & 3;
			int abs		= (data>>(8+i)) & 1;
			int negate = (data>>(16+i)) & 1;
			int constants = (data>>(12+i)) & 1;
			if (negate)
				strcat(out, "-");
			if (abs && !constants)
				strcat(out, "|");
			if (!constants)
			{
				strcat(out, regnam[regnum]);
			}
			else
			{
				if (abs)
					regnum+=4;
				strcat(out, constan[regnum]);
			}
			if (abs && !constants)
				strcat(out, "|");
			if (i != 3)
				strcat(out, ",");
		}

		strcat(out, "]");
	}

	void Dis_VPFXD(MIPSOpcode op, char *out)
	{
		int data = op & 0xFFFFF;
		const char *name = MIPSGetName(op);
		sprintf(out, "%s\t[", name);
		static const char *satNames[4] = {"", "0:1", "X", "-1:1"};
		for (int i=0; i<4; i++)
		{
			int sat = (data>>i*2)&3;
			int mask = (data>>(8+i))&1;
			if (sat)
				strcat(out, satNames[sat]);
			if (mask)
				strcat(out, "M");
			if (i < 4 - 1)
				strcat(out, ",");
		}

		strcat(out, "]");
	}


	void Dis_Viim(MIPSOpcode op, char *out)
	{
		int vt = _VT;
		int imm = op&0xFFFF;
		//V(vt) = (float)imm;
		const char *name = MIPSGetName(op);

		int type = (op >> 23) & 7;
		if (type == 6)
			sprintf(out, "%s\t%s, %i", name, VN(vt, V_Single), imm);
		else if (type == 7)
			sprintf(out, "%s\t%s, %f", name, VN(vt, V_Single), Float16ToFloat32((u16)imm));
		else
			sprintf(out, "%s\tARGH", name);
	}

	void Dis_Vcst(MIPSOpcode op, char *out)
	{
		int conNum = (op>>16) & 0x1f;
		int vd = _VD;
		static const char *constants[32] = 
		{
			"(undef)",
			"MaxFloat",
			"Sqrt(2)",
			"Sqrt(1/2)",
			"2/Sqrt(PI)",
			"2/PI",
			"1/PI",
			"PI/4",
			"PI/2",
			"PI",
			"e",
			"Log2(e)",
			"Log10(e)",
			"ln(2)",
			"ln(10)",
			"2*PI",
			"PI/6",
			"Log10(2)",
			"Log2(10)",
			"Sqrt(3)/2"
		};
		const char *name = MIPSGetName(op);
		const char *c = constants[conNum];
		if (c==0) c = constants[0];
		sprintf(out,"%s%s\t%s, %s",name,VSuff(op),VN(vd,V_Single), c);
	}


	void Dis_MatrixSet1(MIPSOpcode op, char *out)
	{
		const char *name = MIPSGetName(op);
		int vd = _VD;
		MatrixSize sz = GetMtxSizeSafe(op);
		sprintf(out, "%s%s\t%s",name,VSuff(op),MN(vd, sz));
	}
	void Dis_MatrixSet2(MIPSOpcode op, char *out)
	{
		const char *name = MIPSGetName(op);
		int vd = _VD;
		int vs = _VS;
		MatrixSize sz = GetMtxSizeSafe(op);
		sprintf(out, "%s%s\t%s, %s",name,VSuff(op),MN(vd, sz),MN(vs,sz));
	}
	void Dis_MatrixSet3(MIPSOpcode op, char *out)
	{
		const char *name = MIPSGetName(op);
		int vd = _VD;
		int vs = _VS;
		int vt = _VT;
		MatrixSize sz = GetMtxSizeSafe(op);
		sprintf(out, "%s%s\t%s, %s, %s",name,VSuff(op),MN(vd, sz),MN(vs,sz),MN(vt,sz));
	}

	void Dis_MatrixMult(MIPSOpcode op, char *out)
	{
		const char *name = MIPSGetName(op);
		int vd = _VD;
		int vs = _VS;
		int vt = _VT;
		MatrixSize sz = GetMtxSizeSafe(op);
		// TODO: Xpose?
		sprintf(out, "%s%s\t%s, %s, %s",name,VSuff(op),MN(vd, sz),MN(Xpose(vs),sz),MN(vt,sz));
	}

	void Dis_Vmscl(MIPSOpcode op, char *out)
	{
		const char *name = MIPSGetName(op);
		int vd = _VD;
		int vs = _VS;
		int vt = _VT;
		MatrixSize sz = GetMtxSizeSafe(op);
		sprintf(out, "%s%s\t%s, %s, %s", name, VSuff(op), MN(vd, sz), MN(vs, sz), VN(vt, V_Single));
	}

	void Dis_VectorDot(MIPSOpcode op, char *out)
	{
		const char *name = MIPSGetName(op);
		int vd = _VD;
		int vs = _VS;
		int vt = _VT;
		VectorSize sz = GetVecSizeSafe(op);
		sprintf(out, "%s%s\t%s, %s, %s", name, VSuff(op), VN(vd, V_Single), VN(vs,sz), VN(vt, sz));
	}

	void Dis_Vtfm(MIPSOpcode op, char *out)
	{
		int vd = _VD;
		int vs = _VS;
		int vt = _VT;
		int ins = (op>>23) & 7;
		VectorSize sz = GetVecSizeSafe(op);
		MatrixSize msz = GetMtxSizeSafe(op);
		int n = GetNumVectorElements(sz);

		if (n == ins)
		{
			//homogenous
			sprintf(out, "vhtfm%i%s\t%s, %s, %s", n, VSuff(op), VN(vd, sz), MN(vs, msz), VN(vt, sz));
		}
		else if (n == ins+1)
		{
			sprintf(out, "vtfm%i%s\t%s, %s, %s", n, VSuff(op), VN(vd, sz), MN(vs, msz), VN(vt, sz));
		}
		else
		{
			sprintf(out,"BADVTFM");
		}
	}

	void Dis_Vflush(MIPSOpcode op, char *out)
	{
		sprintf(out,"vflush");
	}

	void Dis_Vcrs(MIPSOpcode op, char *out)
	{
		const char *name = MIPSGetName(op);
		int vt = _VT;
		int vs = _VS;
		int vd = _VD;
		VectorSize sz = GetVecSizeSafe(op);
		if (sz != V_Triple)
		{
			sprintf(out, "vcrs\tERROR");
		}
		else
			sprintf(out, "%s%s\t%s, %s, %s", name, VSuff(op), VN(vd, sz), VN(vs, sz), VN(vt,sz));
	}


	void Dis_Vcmp(MIPSOpcode op, char *out)
	{
		const char *name = MIPSGetName(op);
		int vt = _VT;
		int vs = _VS;
		int cond = op&15;
		VectorSize sz = GetVecSizeSafe(op);
		const char *condNames[16] = {"FL","EQ","LT","LE","TR","NE","GE","GT","EZ","EN","EI","ES","NZ","NN","NI","NS"};
		sprintf(out, "%s%s\t%s, %s, %s", name, VSuff(op), condNames[cond], VN(vs, sz), VN(vt,sz));
	}

	void Dis_Vcmov(MIPSOpcode op, char *out)
	{
		const char *name = MIPSGetName(op);
		VectorSize sz = GetVecSizeSafe(op);
		int vd = _VD;
		int vs = _VS;
		int tf = (op >> 19)&3;
		int imm3 = (op>>16)&7;
		if (tf > 1)
		{
			sprintf(out, "%s\tARGH%i", name, tf);
			return;
		}
		if (imm3<6)
			sprintf(out, "%s%s%s\t%s, %s, CC[%i]", name, tf==0?"t":"f", VSuff(op), VN(vd, sz), VN(vs,sz), imm3);
		else if (imm3 == 6)
			sprintf(out, "%s%s%s\t%s, %s, CC[...]", name, tf==0?"t":"f", VSuff(op), VN(vd, sz), VN(vs,sz));
	}

	void Dis_Vfad(MIPSOpcode op, char *out)
	{
		const char *name = MIPSGetName(op);
		int vd = _VD;
		int vs = _VS;
		VectorSize sz = GetVecSizeSafe(op);
		sprintf(out, "%s%s\t%s, %s", name, VSuff(op), VN(vd, V_Single), VN(vs,sz));
	}

	void Dis_VScl(MIPSOpcode op, char *out)
	{
		const char *name = MIPSGetName(op);
		int vd = _VD;
		int vs = _VS;
		int vt = _VT;
		VectorSize sz = GetVecSizeSafe(op);
		sprintf(out, "%s%s\t%s, %s, %s", name, VSuff(op), VN(vd, sz), VN(vs,sz), VN(vt, V_Single));
	}

	void Dis_VectorSet1(MIPSOpcode op, char *out)
	{
		const char *name = MIPSGetName(op);
		int vd = _VD;
		VectorSize sz = GetVecSizeSafe(op);
		sprintf(out, "%s%s\t%s",name,VSuff(op),VN(vd, sz));
	}
	void Dis_VectorSet2(MIPSOpcode op, char *out)
	{
		const char *name = MIPSGetName(op);
		int vd = _VD;
		int vs = _VS;
		VectorSize sz = GetVecSizeSafe(op);
		sprintf(out, "%s%s\t%s, %s",name,VSuff(op),VN(vd, sz),VN(vs, sz));
	}
	void Dis_VectorSet3(MIPSOpcode op, char *out)
	{
		const char *name = MIPSGetName(op);
		int vd = _VD;
		int vs = _VS;
		int vt = _VT;
		VectorSize sz = GetVecSizeSafe(op);
		sprintf(out, "%s%s\t%s, %s, %s", name, VSuff(op), VN(vd, sz), VN(vs,sz), VN(vt, sz));
	}

	void Dis_VRot(MIPSOpcode op, char *out)
	{
		int vd = _VD;
		int vs = _VS;
		int imm = (op>>16) & 0x1f;
		bool negSin = (imm & 0x10) ? true : false;
		char c[5] = "0000";
		char temp[16]={""};
		if (((imm>>2)&3)==(imm&3))
		{
			for (int i=0; i<4; i++)
				c[i]='S';
		}
		c[(imm>>2) & 3] = 'S';
		c[imm&3] = 'C';
		VectorSize sz = GetVecSizeSafe(op);
		int numElems = GetNumVectorElements(sz);
		int pos = 0;
		temp[pos++] = '[';
		for (int i=0; i<numElems; i++)
		{
			if (c[i] == 'S' && negSin)
				temp[pos++] = '-';
			temp[pos++] = c[i];
			if (i != numElems-1)
				temp[pos++] = ',';
		}
		temp[pos++] = ']';
		temp[pos]=0;
		const char *name = MIPSGetName(op);
		sprintf(out, "%s%s\t%s, %s, %s",name,VSuff(op),VN(vd, sz),VN(vs, V_Single),temp);
	}

	void Dis_CrossQuat(MIPSOpcode op, char *out)
	{
		VectorSize sz = GetVecSizeSafe(op);
		const char *name;
		switch (sz)
		{
		case V_Triple:
			name = "vcrsp";
			//Ah, a regular cross product.
			break;
		case V_Quad:
			name = "vqmul";
			//Ah, a quaternion multiplication.
			break;
		default:
			// invalid
			name = "???";
			break;
		}
		int vd = _VD;
		int vs = _VS;
		int vt = _VT;
		sprintf(out, "%s%s\t%s, %s, %s", name, VSuff(op), VN(vd, sz), VN(vs,sz), VN(vt, sz));
	}

	void Dis_Vbfy(MIPSOpcode op, char *out)
	{
		VectorSize sz = GetVecSizeSafe(op);
		int vd = _VD;
		int vs = _VS;
		const char *name = MIPSGetName(op);
		sprintf(out, "%s%s\t%s, %s",name,VSuff(op),VN(vd, sz),VN(vs, sz));
	}

	void Dis_Vf2i(MIPSOpcode op, char *out)
	{
		VectorSize sz = GetVecSizeSafe(op);
		int vd = _VD;
		int vs = _VS;
		int imm = (op>>16)&0x1f;
		const char *name = MIPSGetName(op);
		sprintf(out, "%s%s\t%s, %s, %i",name,VSuff(op),VN(vd, sz),VN(vs, sz),imm);
	}

	void Dis_Vs2i(MIPSOpcode op, char *out)
	{
		VectorSize sz = GetVecSizeSafe(op);
		int vd = _VD;
		int vs = _VS;
		const char *name = MIPSGetName(op);
		sprintf(out, "%s%s\t%s, %s",name,VSuff(op),VN(vd, sz),VN(vs, sz));
	}

	void Dis_Vi2x(MIPSOpcode op, char *out)
	{
		VectorSize sz = GetVecSizeSafe(op);
		VectorSize dsz = GetHalfVectorSizeSafe(sz);
		if (((op>>16)&3)==0)
			dsz = V_Single;

		int vd = _VD;
		int vs = _VS;
		const char *name = MIPSGetName(op);
		sprintf(out, "%s%s\t%s, %s",name,VSuff(op),VN(vd, dsz),VN(vs, sz));
	}

	void Dis_Vwbn(MIPSOpcode op, char *out)
	{
		VectorSize sz = GetVecSizeSafe(op);

		int vd = _VD;
		int vs = _VS;
		int imm = (int)((op >> 16) & 0xFF);
		const char *name = MIPSGetName(op);
		sprintf(out, "%s%s\t%s, %s, %d", name, VSuff(op), VN(vd, sz), VN(vs, sz), imm);
	}

	void Dis_Vf2h(MIPSOpcode op, char *out)
	{
		VectorSize sz = GetVecSizeSafe(op);
		VectorSize dsz = GetHalfVectorSizeSafe(sz);
		if (((op>>16)&3)==0)
			dsz = V_Single;

		int vd = _VD;
		int vs = _VS;
		const char *name = MIPSGetName(op);
		sprintf(out, "%s%s\t%s, %s", name, VSuff(op), VN(vd, dsz), VN(vs, sz));
	}

	void Dis_Vh2f(MIPSOpcode op, char *out)
	{
		VectorSize sz = GetVecSizeSafe(op);
		VectorSize dsz = GetDoubleVectorSizeSafe(sz);

		int vd = _VD;
		int vs = _VS;
		const char *name = MIPSGetName(op);
		sprintf(out, "%s%s\t%s, %s", name, VSuff(op), VN(vd, dsz), VN(vs, sz));
	}

	void Dis_ColorConv(MIPSOpcode op, char *out)
	{
		VectorSize sz = GetVecSizeSafe(op);
		VectorSize dsz = GetHalfVectorSizeSafe(sz);

		int vd = _VD;
		int vs = _VS;
		const char *name = MIPSGetName(op);
		sprintf(out, "%s%s\t%s, %s", name, VSuff(op), VN(vd, dsz), VN(vs, sz));
	}

	void Dis_Vrnds(MIPSOpcode op, char *out)
	{
		int vd = _VD;
		const char *name = MIPSGetName(op);
		sprintf(out, "%s%s\t%s", name, VSuff(op), VN(vd, V_Single));
	}

	void Dis_VrndX(MIPSOpcode op, char *out)
	{
		VectorSize sz = GetVecSizeSafe(op);

		int vd = _VD;
		const char *name = MIPSGetName(op);
		sprintf(out, "%s%s\t%s", name, VSuff(op), VN(vd, sz));
	}

	void Dis_VBranch(MIPSOpcode op, char *out)
	{
		u32 off = disPC;
		int imm = (signed short)(op&0xFFFF)<<2;
		int imm3 = (op>>18)&7;
		off += imm + 4;
		const char *name = MIPSGetName(op);
		sprintf(out, "%s\t->$%08x	(CC[%i])",name,off,imm3);
	}

}
