#include "stdafx.h"
#include "CpuDisUtils.h"
#include "CpuTypes.h"
#include "Cpu.h"
#include "Console.h"
#include "EmuSettings.h"
#include "DisassemblyInfo.h"
#include "LabelManager.h"
#include "DummyCpu.h"
#include "../Utilities/HexUtilities.h"
#include "../Utilities/FastString.h"

void CpuDisUtils::GetDisassembly(DisassemblyInfo &info, string &out, uint32_t memoryAddr, LabelManager* labelManager, EmuSettings* settings)
{
	FastString str(settings->CheckDebuggerFlag(DebuggerFlags::UseLowerCaseDisassembly));

	uint8_t opCode = info.GetOpCode();
	AddrMode addrMode = CpuDisUtils::OpMode[opCode];
	str.Write(CpuDisUtils::OpName[opCode]);
	str.Write(' ');

	uint32_t opAddr = CpuDisUtils::GetOperandAddress(info, memoryAddr);
	uint32_t opSize = info.GetOpSize();

	FastString operand(settings->CheckDebuggerFlag(DebuggerFlags::UseLowerCaseDisassembly));
	if(opSize > 1) {
		if(addrMode == AddrMode::Rel || addrMode == AddrMode::RelLng || opSize == 4) {
			AddressInfo address { (int32_t)opAddr, SnesMemoryType::CpuMemory };
			string label = labelManager ? labelManager->GetLabel(address) : "";
			if(label.size()) {
				operand.Write(label, true);
			} else {
				operand.WriteAll('$', HexUtilities::ToHex24(opAddr));
			}
		} else if(opSize == 2) {
			operand.WriteAll('$', HexUtilities::ToHex((uint8_t)opAddr));
		} else if(opSize == 3) {
			operand.WriteAll('$', HexUtilities::ToHex((uint16_t)opAddr));
		}
	}

	switch(addrMode) {
		case AddrMode::Abs: str.Write(operand); break;
		case AddrMode::AbsJmp: str.Write(operand); break;
		case AddrMode::AbsIdxXInd: str.WriteAll('(', operand, ",X)"); break;
		case AddrMode::AbsIdxX: str.WriteAll(operand, ",X"); break;
		case AddrMode::AbsIdxY: str.WriteAll(operand, ",Y"); break;
		case AddrMode::AbsInd:  str.WriteAll('(', operand, ')'); break;
		case AddrMode::AbsIndLng:  str.WriteAll('[', operand, ']'); break;
		case AddrMode::AbsLngIdxX: str.WriteAll(operand, ",X"); break;
		case AddrMode::AbsLng: str.Write(operand); break;
		case AddrMode::AbsLngJmp: str.Write(operand); break;
		case AddrMode::Acc: break;
		case AddrMode::BlkMov: str.WriteAll('$', operand[1], operand[2], ','); str.WriteAll('$', operand[3], operand[4]); break;
		case AddrMode::DirIdxIndX: str.WriteAll('(', operand, ",X)"); break;
		case AddrMode::DirIdxX: str.WriteAll(operand, ",X"); break;
		case AddrMode::DirIdxY: str.WriteAll(operand, ",Y"); break;
		case AddrMode::DirIndIdxY: str.WriteAll("(", operand, "),Y"); break;
		case AddrMode::DirIndLngIdxY: str.WriteAll("[", operand, "],Y"); break;
		case AddrMode::DirIndLng: str.WriteAll("[", operand, "]"); break;
		case AddrMode::DirInd: str.WriteAll("(", operand, ")"); break;
		case AddrMode::Dir: str.Write(operand); break;

		case AddrMode::Imm8: case AddrMode::Imm16: case AddrMode::ImmX: case AddrMode::ImmM:
			str.WriteAll('#', operand);
			break;

		case AddrMode::Sig8: str.WriteAll('#', operand); break; //BRK/COP signature
		case AddrMode::Imp: break;
		case AddrMode::RelLng: str.Write(operand); break;
		case AddrMode::Rel: str.Write(operand); break;
		case AddrMode::Stk: break;
		case AddrMode::StkRel: str.WriteAll(operand, ",S"); break;
		case AddrMode::StkRelIndIdxY: str.WriteAll('(', operand, ",S),Y"); break;

		default: throw std::runtime_error("invalid address mode");
	}

	out += str.ToString();
}

uint32_t CpuDisUtils::GetOperandAddress(DisassemblyInfo &info, uint32_t memoryAddr)
{
	uint32_t opSize = info.GetOpSize();
	uint32_t opAddr = 0;
	uint8_t* byteCode = info.GetByteCode();
	if(opSize == 2) {
		opAddr = byteCode[1];
	} else if(opSize == 3) {
		opAddr = byteCode[1] | (byteCode[2] << 8);
	} else if(opSize == 4) {
		opAddr = byteCode[1] | (byteCode[2] << 8) | (byteCode[3] << 16);
	}

	AddrMode addrMode = CpuDisUtils::OpMode[byteCode[0]];
	if(addrMode == AddrMode::Rel || addrMode == AddrMode::RelLng) {
		if(opSize == 2) {
			opAddr = (memoryAddr & 0xFF0000) | (((int8_t)opAddr + memoryAddr + 2) & 0xFFFF);
		} else {
			opAddr = (memoryAddr & 0xFF0000) | (((int16_t)opAddr + memoryAddr + 3) & 0xFFFF);
		}
	}

	return opAddr;
}

int32_t CpuDisUtils::GetEffectiveAddress(DisassemblyInfo &info, Console *console, CpuState &state, CpuType type)
{
	if(HasEffectiveAddress(CpuDisUtils::OpMode[info.GetOpCode()])) {
		DummyCpu cpu(console, type);
		state.PS &= ~(ProcFlags::IndexMode8 | ProcFlags::MemoryMode8);
		state.PS |= info.GetFlags();
		cpu.SetDummyState(state);
		cpu.Exec();
		return cpu.GetLastOperand();
	}
	return -1;
}

bool CpuDisUtils::HasEffectiveAddress(AddrMode addrMode)
{
	switch(addrMode) {
		case AddrMode::Acc:
		case AddrMode::Imp:
		case AddrMode::Stk:
		case AddrMode::Sig8:
		case AddrMode::Imm8:
		case AddrMode::Rel:
		case AddrMode::RelLng:
		case AddrMode::Imm16:
		case AddrMode::BlkMov:
		case AddrMode::AbsLngJmp:
		case AddrMode::AbsLng:
		case AddrMode::ImmX:
		case AddrMode::ImmM:
		case AddrMode::AbsJmp:
			return false;

		case AddrMode::DirIdxIndX:
		case AddrMode::DirIdxX:
		case AddrMode::DirIdxY:
		case AddrMode::DirIndIdxY:
		case AddrMode::DirIndLngIdxY:
		case AddrMode::DirIndLng:
		case AddrMode::DirInd:
		case AddrMode::Dir:
		case AddrMode::StkRel:
		case AddrMode::StkRelIndIdxY:
		case AddrMode::Abs:
		case AddrMode::AbsIdxXInd:
		case AddrMode::AbsIdxX:
		case AddrMode::AbsIdxY:
		case AddrMode::AbsLngIdxX:
		case AddrMode::AbsInd:
		case AddrMode::AbsIndLng:
			return true;
	}

	throw std::runtime_error("Invalid mode");
}

uint8_t CpuDisUtils::GetOpSize(AddrMode addrMode, uint8_t flags)
{
	if(addrMode == AddrMode::ImmX) {
		return (flags & ProcFlags::IndexMode8) ? 2 : 3;
	} else if(addrMode == AddrMode::ImmM) {
		return (flags & ProcFlags::MemoryMode8) ? 2 : 3;
	}

	return CpuDisUtils::OpSize[(int)addrMode];
}

uint8_t CpuDisUtils::GetOpSize(uint8_t opCode, uint8_t flags)
{
	return GetOpSize(CpuDisUtils::OpMode[opCode], flags);
}

uint8_t CpuDisUtils::OpSize[0x1F] = {
	2, 2, 3, 0, 0, 3, 3, 3, 3, 3,
	3, 4, 4, 3, 4, 1, 3, 2, 2, 2,
	2, 2, 2, 2, 2, 1, 3, 2, 1, 2,
	2
};

string CpuDisUtils::OpName[256] = {
	//0    1      2      3      4      5      6      7      8      9      A      B      C      D      E      F
	"BRK", "ORA", "COP", "ORA", "TSB", "ORA", "ASL", "ORA", "PHP", "ORA", "ASL", "PHD", "TSB", "ORA", "ASL", "ORA", // 0
	"BPL", "ORA", "ORA", "ORA", "TRB", "ORA", "ASL", "ORA", "CLC", "ORA", "INC", "TCS", "TRB", "ORA", "ASL", "ORA", // 1
	"JSR", "AND", "JSL", "AND", "BIT", "AND", "ROL", "AND", "PLP", "AND", "ROL", "PLD", "BIT", "AND", "ROL", "AND", // 2
	"BMI", "AND", "AND", "AND", "BIT", "AND", "ROL", "AND", "SEC", "AND", "DEC", "TSC", "BIT", "AND", "ROL", "AND", // 3
	"RTI", "EOR", "WDM", "EOR", "MVP", "EOR", "LSR", "EOR", "PHA", "EOR", "LSR", "PHK", "JMP", "EOR", "LSR", "EOR", // 4
	"BVC", "EOR", "EOR", "EOR", "MVN", "EOR", "LSR", "EOR", "CLI", "EOR", "PHY", "TCD", "JMP", "EOR", "LSR", "EOR", // 5
	"RTS", "ADC", "PER", "ADC", "STZ", "ADC", "ROR", "ADC", "PLA", "ADC", "ROR", "RTL", "JMP", "ADC", "ROR", "ADC", // 6
	"BVS", "ADC", "ADC", "ADC", "STZ", "ADC", "ROR", "ADC", "SEI", "ADC", "PLY", "TDC", "JMP", "ADC", "ROR", "ADC", // 7
	"BRA", "STA", "BRL", "STA", "STY", "STA", "STX", "STA", "DEY", "BIT", "TXA", "PHB", "STY", "STA", "STX", "STA", // 8
	"BCC", "STA", "STA", "STA", "STY", "STA", "STX", "STA", "TYA", "STA", "TXS", "TXY", "STZ", "STA", "STZ", "STA", // 9
	"LDY", "LDA", "LDX", "LDA", "LDY", "LDA", "LDX", "LDA", "TAY", "LDA", "TAX", "PLB", "LDY", "LDA", "LDX", "LDA", // A
	"BCS", "LDA", "LDA", "LDA", "LDY", "LDA", "LDX", "LDA", "CLV", "LDA", "TSX", "TYX", "LDY", "LDA", "LDX", "LDA", // B
	"CPY", "CMP", "REP", "CMP", "CPY", "CMP", "DEC", "CMP", "INY", "CMP", "DEX", "WAI", "CPY", "CMP", "DEC", "CMP", // C
	"BNE", "CMP", "CMP", "CMP", "PEI", "CMP", "DEC", "CMP", "CLD", "CMP", "PHX", "STP", "JML", "CMP", "DEC", "CMP", // D
	"CPX", "SBC", "SEP", "SBC", "CPX", "SBC", "INC", "SBC", "INX", "SBC", "NOP", "XBA", "CPX", "SBC", "INC", "SBC", // E
	"BEQ", "SBC", "SBC", "SBC", "PEA", "SBC", "INC", "SBC", "SED", "SBC", "PLX", "XCE", "JSR", "SBC", "INC", "SBC"  // F
};

typedef AddrMode M;
AddrMode CpuDisUtils::OpMode[256] = {
	//0       1              2            3                 4           5           6           7                 8       9           A       B       C              D           E           F           
	M::Sig8,  M::DirIdxIndX, M::Sig8,     M::StkRel,        M::Dir,     M::Dir,     M::Dir,     M::DirIndLng,     M::Stk, M::ImmM,    M::Acc, M::Stk, M::Abs,        M::Abs,     M::Abs,     M::AbsLng,     // 0
	M::Rel,   M::DirIndIdxY, M::DirInd,   M::StkRelIndIdxY, M::Dir,     M::DirIdxX, M::DirIdxX, M::DirIndLngIdxY, M::Imp, M::AbsIdxY, M::Acc, M::Imp, M::Abs,        M::AbsIdxX, M::AbsIdxX, M::AbsLngIdxX, // 1
	M::Abs,   M::DirIdxIndX, M::AbsLng,   M::StkRel,        M::Dir,     M::Dir,     M::Dir,     M::DirIndLng,     M::Stk, M::ImmM,    M::Acc, M::Stk, M::Abs,        M::Abs,     M::Abs,     M::AbsLng,     // 2
	M::Rel,   M::DirIndIdxY, M::DirInd,   M::StkRelIndIdxY, M::DirIdxX, M::DirIdxX, M::DirIdxX, M::DirIndLngIdxY, M::Imp, M::AbsIdxY, M::Acc, M::Imp, M::AbsIdxX,    M::AbsIdxX, M::AbsIdxX, M::AbsLngIdxX, // 3
	M::Stk,   M::DirIdxIndX, M::Imm8,     M::StkRel,        M::BlkMov,  M::Dir,     M::Dir,     M::DirIndLng,     M::Stk, M::ImmM,    M::Acc, M::Stk, M::Abs,        M::Abs,     M::Abs,     M::AbsLng,     // 4
	M::Rel,   M::DirIndIdxY, M::DirInd,   M::StkRelIndIdxY, M::BlkMov,  M::DirIdxX, M::DirIdxX, M::DirIndLngIdxY, M::Imp, M::AbsIdxY, M::Stk, M::Imp, M::AbsLng,     M::AbsIdxX, M::AbsIdxX, M::AbsLngIdxX, // 5
	M::Stk,   M::DirIdxIndX, M::RelLng,   M::StkRel,        M::Dir,     M::Dir,     M::Dir,     M::DirIndLng,     M::Stk, M::ImmM,    M::Acc, M::Stk, M::AbsInd,     M::Abs,     M::Abs,     M::AbsLng,     // 6
	M::Rel,   M::DirIndIdxY, M::DirInd,   M::StkRelIndIdxY, M::DirIdxX, M::DirIdxX, M::DirIdxX, M::DirIndLngIdxY, M::Imp, M::AbsIdxY, M::Stk, M::Imp, M::AbsIdxXInd, M::AbsIdxX, M::AbsIdxX, M::AbsLngIdxX, // 7
	M::Rel,   M::DirIdxIndX, M::RelLng,   M::StkRel,        M::Dir,     M::Dir,     M::Dir,     M::DirIndLng,     M::Imp, M::ImmM,    M::Imp, M::Stk, M::Abs,        M::Abs,     M::Abs,     M::AbsLng,     // 8
	M::Rel,   M::DirIndIdxY, M::DirInd,   M::StkRelIndIdxY, M::DirIdxX, M::DirIdxX, M::DirIdxY, M::DirIndLngIdxY, M::Imp, M::AbsIdxY, M::Imp, M::Imp, M::Abs,        M::AbsIdxX, M::AbsIdxX, M::AbsLngIdxX, // 9
	M::ImmX,  M::DirIdxIndX, M::ImmX,     M::StkRel,        M::Dir,     M::Dir,     M::Dir,     M::DirIndLng,     M::Imp, M::ImmM,    M::Imp, M::Stk, M::Abs,        M::Abs,     M::Abs,     M::AbsLng,     // A
	M::Rel,   M::DirIndIdxY, M::DirInd,   M::StkRelIndIdxY, M::DirIdxX, M::DirIdxX, M::DirIdxY, M::DirIndLngIdxY, M::Imp, M::AbsIdxY, M::Imp, M::Imp, M::AbsIdxX,    M::AbsIdxX, M::AbsIdxY, M::AbsLngIdxX, // B
	M::ImmX,  M::DirIdxIndX, M::Imm8,     M::StkRel,        M::Dir,     M::Dir,     M::Dir,     M::DirIndLng,     M::Imp, M::ImmM,    M::Imp, M::Imp, M::Abs,        M::Abs,     M::Abs,     M::AbsLng,     // C
	M::Rel,   M::DirIndIdxY, M::DirInd,   M::StkRelIndIdxY, M::Dir,     M::DirIdxX, M::DirIdxX, M::DirIndLngIdxY, M::Imp, M::AbsIdxY, M::Stk, M::Imp, M::AbsIndLng,  M::AbsIdxX, M::AbsIdxX, M::AbsLngIdxX, // D
	M::ImmX,  M::DirIdxIndX, M::Imm8,     M::StkRel,        M::Dir,     M::Dir,     M::Dir,     M::DirIndLng,     M::Imp, M::ImmM,    M::Imp, M::Imp, M::Abs,        M::Abs,     M::Abs,     M::AbsLng,     // E
	M::Rel,   M::DirIndIdxY, M::DirInd,   M::StkRelIndIdxY, M::Imm16,   M::DirIdxX, M::DirIdxX, M::DirIndLngIdxY, M::Imp, M::AbsIdxY, M::Stk, M::Imp, M::AbsIdxXInd, M::AbsIdxX, M::AbsIdxX, M::AbsLngIdxX  // F
};