#pragma once

#include <functional>
#include "MIPSArchitecture.h"
#include "MIPSReflection.h"

class CMA_MIPSIV : public CMIPSArchitecture
{
public:
	CMA_MIPSIV(MIPS_REGSIZE);
	virtual ~CMA_MIPSIV() = default;
	void CompileInstruction(uint32, CMipsJitter*, CMIPS*, uint32) override;
	void GetInstructionMnemonic(CMIPS*, uint32, uint32, char*, unsigned int) override;
	void GetInstructionOperands(CMIPS*, uint32, uint32, char*, unsigned int) override;
	MIPS_BRANCH_TYPE IsInstructionBranch(CMIPS*, uint32, uint32) override;
	uint32 GetInstructionEffectiveAddress(CMIPS*, uint32, uint32) override;

protected:
	enum
	{
		MAX_GENERAL_OPS = 0x40,
		MAX_SPECIAL_OPS = 0x40,
		MAX_SPECIAL2_OPS = 0x40,
		MAX_REGIMM_OPS = 0x20,
	};

	typedef std::function<void()> InstructionFunction;

	InstructionFunction m_pOpGeneral[MAX_GENERAL_OPS];
	InstructionFunction m_pOpSpecial[MAX_SPECIAL_OPS];
	InstructionFunction m_pOpSpecial2[MAX_SPECIAL2_OPS];
	InstructionFunction m_pOpRegImm[MAX_REGIMM_OPS];

	static void ReflOpTarget(MIPSReflection::INSTRUCTION*, CMIPS*, uint32, uint32, char*, unsigned int);
	static void ReflOpRtRsImm(MIPSReflection::INSTRUCTION*, CMIPS*, uint32, uint32, char*, unsigned int);
	static void ReflOpRsImm(MIPSReflection::INSTRUCTION*, CMIPS*, uint32, uint32, char*, unsigned int);
	static void ReflOpRtImm(MIPSReflection::INSTRUCTION*, CMIPS*, uint32, uint32, char*, unsigned int);
	static void ReflOpRsRtOff(MIPSReflection::INSTRUCTION*, CMIPS*, uint32, uint32, char*, unsigned int);
	static void ReflOpRsOff(MIPSReflection::INSTRUCTION*, CMIPS*, uint32, uint32, char*, unsigned int);
	static void ReflOpRtOffRs(MIPSReflection::INSTRUCTION*, CMIPS*, uint32, uint32, char*, unsigned int);
	static void ReflOpHintOffRs(MIPSReflection::INSTRUCTION*, CMIPS*, uint32, uint32, char*, unsigned int);
	static void ReflOpIdOffRs(MIPSReflection::INSTRUCTION*, CMIPS*, uint32, uint32, char*, unsigned int);
	static void ReflOpRdRsRt(MIPSReflection::INSTRUCTION*, CMIPS*, uint32, uint32, char*, unsigned int);
	static void ReflOpRdRtSa(MIPSReflection::INSTRUCTION*, CMIPS*, uint32, uint32, char*, unsigned int);
	static void ReflOpRdRtRs(MIPSReflection::INSTRUCTION*, CMIPS*, uint32, uint32, char*, unsigned int);
	static void ReflOpRs(MIPSReflection::INSTRUCTION*, CMIPS*, uint32, uint32, char*, unsigned int);
	static void ReflOpRd(MIPSReflection::INSTRUCTION*, CMIPS*, uint32, uint32, char*, unsigned int);
	static void ReflOpRdRs(MIPSReflection::INSTRUCTION*, CMIPS*, uint32, uint32, char*, unsigned int);
	static void ReflOpRsRt(MIPSReflection::INSTRUCTION*, CMIPS*, uint32, uint32, char*, unsigned int);

	static uint32 ReflEaTarget(MIPSReflection::INSTRUCTION*, CMIPS*, uint32, uint32);
	static uint32 ReflEaOffset(MIPSReflection::INSTRUCTION*, CMIPS*, uint32, uint32);

	static void ReflCOPMnemonic(MIPSReflection::INSTRUCTION*, CMIPS*, uint32, char*, unsigned int);
	static void ReflCOPOperands(MIPSReflection::INSTRUCTION*, CMIPS*, uint32, uint32, char*, unsigned int);
	static MIPS_BRANCH_TYPE ReflCOPIsBranch(MIPSReflection::INSTRUCTION*, CMIPS*, uint32);
	static uint32 ReflCOPEffeAddr(MIPSReflection::INSTRUCTION*, CMIPS*, uint32, uint32);

	MIPSReflection::INSTRUCTION m_ReflGeneral[MAX_GENERAL_OPS];
	MIPSReflection::INSTRUCTION m_ReflSpecial[MAX_SPECIAL_OPS];
	MIPSReflection::INSTRUCTION m_ReflRegImm[MAX_REGIMM_OPS];

	MIPSReflection::SUBTABLE m_ReflGeneralTable;
	MIPSReflection::SUBTABLE m_ReflSpecialTable;
	MIPSReflection::SUBTABLE m_ReflRegImmTable;

	uint8 m_nRS = 0;
	uint8 m_nRT = 0;
	uint8 m_nRD = 0;
	uint8 m_nSA = 0;
	uint16 m_nImmediate = 0;

protected:
	struct MemoryAccessTraits
	{
		typedef void (CMipsJitter::*JitterFunction)();

		void* getProxyFunction = nullptr;
		void* setProxyFunction = nullptr;

		JitterFunction loadFunction = nullptr;
		JitterFunction storeFunction = nullptr;
		JitterFunction signExtFunction = nullptr;
		uint32 elementSize = 0;
	};

	struct MemoryAccessIdxTraits
	{
		typedef void (CMipsJitter::*SignExtFunction)();
		typedef void (CMipsJitter::*MemAccessFunction)(size_t);

		void* getProxyFunction = nullptr;
		void* setProxyFunction = nullptr;

		MemAccessFunction loadFunction = nullptr;
		MemAccessFunction storeFunction = nullptr;
		SignExtFunction signExtFunction = nullptr;
		uint32 elementSize = 0;
		bool upper64BitSignExtend = false;
	};

	//Instruction compiler templates
	typedef std::function<void(uint8)> TemplateParamedOperationFunctionType;
	typedef std::function<void()> TemplateOperationFunctionType;

	bool Ensure64BitRegs();

	void Template_Add32(bool);
	void Template_Add64(bool);
	void Template_Sub32(bool);
	void Template_Sub64(bool);
	void Template_Load32Idx(const MemoryAccessIdxTraits&);
	void Template_Store32Idx(const MemoryAccessIdxTraits&);
	void Template_ShiftCst32(const TemplateParamedOperationFunctionType&);
	void Template_ShiftVar32(const TemplateOperationFunctionType&);
	void Template_Mult32(bool, unsigned int);
	void Template_Div32(bool, unsigned int, unsigned int = 0);
	void Template_MovEqual(bool);
	void Template_SetLessThanImm(bool);
	void Template_SetLessThanReg(bool);
	void Template_BranchEq(bool, bool);
	void Template_BranchGez(bool, bool);
	void Template_BranchLez(bool, bool);

private:
	void SetupInstructionTables();
	void SetupReflectionTables();

	void SPECIAL();
	void SPECIAL2();
	void REGIMM();

	//General
	void J();
	void JAL();
	void BEQ();
	void BNE();
	void BLEZ();
	void BGTZ();
	void ADDI();
	void ADDIU();
	void SLTI();
	void SLTIU();
	void ANDI();
	void ORI();
	void XORI();
	void LUI();
	void COP0();
	void COP1();
	void COP2();
	void BEQL();
	void BNEL();
	void BLEZL();
	void BGTZL();
	void DADDI();
	void DADDIU();
	void LDL();
	void LDR();
	void LB();
	void LH();
	void LWL();
	void LW();
	void LBU();
	void LHU();
	void LWR();
	void LWU();
	void SB();
	void SH();
	void SWL();
	void SW();
	void SDL();
	void SDR();
	void SWR();
	void CACHE();
	void LWC1();
	void PREF();
	void LDC2();
	void LD();
	void SWC1();
	void SDC2();
	void SD();

	//Special
	void SLL();
	void SRL();
	void SRA();
	void SLLV();
	void SRLV();
	void SRAV();
	void JR();
	void JALR();
	void MOVZ();
	void MOVN();
	void SYSCALL();
	void BREAK();
	void SYNC();
	void DSLLV();
	void DSRLV();
	void DSRAV();
	void MFHI();
	void MTHI();
	void MFLO();
	void MTLO();
	void MULT();
	void MULTU();
	void DIV();
	void DIVU();
	void ADD();
	void ADDU();
	void SUB();
	void SUBU();
	void AND();
	void OR();
	void XOR();
	void NOR();
	void SLT();
	void SLTU();
	void DADD();
	void DADDU();
	void DSUB();
	void DSUBU();
	void TEQ();
	void DSLL();
	void DSRL();
	void DSRA();
	void DSLL32();
	void DSRL32();
	void DSRA32();

	//Special2

	//RegImm
	void BLTZ();
	void BGEZ();
	void BLTZL();
	void BGEZL();
	void TEQI();
	void BLTZAL();
	void BGEZAL();
	void BLTZALL();
	void BGEZALL();

	static const MemoryAccessIdxTraits g_byteAccessIdxTraits;
	static const MemoryAccessIdxTraits g_ubyteAccessIdxTraits;
	static const MemoryAccessIdxTraits g_halfAccessIdxTraits;
	static const MemoryAccessIdxTraits g_uhalfAccessIdxTraits;
	static const MemoryAccessIdxTraits g_wordAccessIdxTraits;
	static const MemoryAccessIdxTraits g_uwordAccessIdxTraits;

	//Opcode tables
	typedef void (CMA_MIPSIV::*InstructionFuncConstant)();

	static InstructionFuncConstant m_cOpGeneral[MAX_GENERAL_OPS];
	static InstructionFuncConstant m_cOpSpecial[MAX_SPECIAL_OPS];
	static InstructionFuncConstant m_cOpRegImm[MAX_REGIMM_OPS];

	//Reflection tables
	static MIPSReflection::INSTRUCTION m_cReflGeneral[MAX_GENERAL_OPS];
	static MIPSReflection::INSTRUCTION m_cReflSpecial[MAX_SPECIAL_OPS];
	static MIPSReflection::INSTRUCTION m_cReflRegImm[MAX_REGIMM_OPS];
};
