#include "instruction_output.hpp"
#include "opcode_utility.hpp"
#include "chocolat/string/number_format.hpp"
#include "chocolat/string/string_aligner.hpp"

std::string
InstructionOutput::parameterString()const
{
	HexadecimalFormat param = instr->parameter();

	std::string byte=param.width(2).to_s();
	std::string word=param.width(4).to_s();
		
	std::string zero="$" + byte;
	std::string abs="$" + word;
	const char x[]=", x";
	const char y[]=", y";
		
	switch (instr->addressingMode()){
	case opcode::IMM: return "#"+byte;
	case opcode::ZERO: return zero;
	case opcode::ZERO_X: return zero + x;
	case opcode::ZERO_Y: return zero + y;
	case opcode::REL: return "$" + word;
	case opcode::ABS: return abs;
	case opcode::ABS_X: return abs + x;
	case opcode::ABS_Y: return abs + y;
	case opcode::IND: return "(" + word + ")";
	case opcode::PRE_IND: return "(" + byte + x + ")";
	case opcode::POST_IND: return "(" + byte + ")" +y;
	case opcode::REG_A: return "a";
	case opcode::REG_X: return "x";
	case opcode::REG_Y: return "y";
	default:
		if (instr->op()==opcode::UND){
			return HexadecimalFormat(instr->bytecode()->at(0)).width(2).to_s();
		} 
		return "";
	}
}
std::string
InstructionOutput::bytecodeString()const
{
	std::string result;
	for (unsigned i=0; i<instr->bytecode()->size(); ++i){
		HexadecimalFormat h(instr->bytecode()->at(i));
		result += h.width(2).to_s() + " ";
	}
	return result;
}

void
InstructionOutput::display(std::ostream& os)const
{
	StringAligner mnem(6, StringAligner::LEFT);
	StringAligner ope(12, StringAligner::LEFT);
		
	os << HexadecimalFormat(instr->address()) << ": "
	   << mnem.align(instr->mnemonic())
	   << ope.align(parameterString()) << "; " << bytecodeString();
}

std::string
InstructionQuadrupleOutput::operandString(const Operand& operand)const
{
	switch (operand.type) {
	case Operand::IMMEDIATE:
	case Operand::MEMORY:
	case Operand::INDEX:
		return parameterString();
	case Operand::REGISTER:
	default:
		switch (operand.reg) {
		case opcode::REG_A: return "a";
		case opcode::REG_X: return "x";
		case opcode::REG_Y: return "y";
		case opcode::REG_SP:return "sp";
		case opcode::REG_PS:return "ps";
		case opcode::REG_PC:return "pc";
		case opcode::PS_N:  return "ps[N]";
		case opcode::PS_Z:  return "ps[Z]";
		case opcode::PS_C:  return "ps[C]";
		case opcode::PS_V:  return "ps[V]";
		case opcode::PS_I:  return "ps[I]";
		case opcode::PS_D:  return "ps[D]";
		case opcode::PS_NZ: return "ps[NZ]";
		case opcode::PS_NZC: return "ps[NZC]";
		case opcode::PS_NVZ: return "ps[NVZ]";
		case opcode::PS_NVZC:return "ps[NVZC]";
		default: return "";
		}
	}
}
const char*
InstructionQuadrupleOutput::operationString()const
{
	switch (instr->op()) {
	case opcode::ADC:
		return "+";
	case opcode::SBC:
		return "-";
	case opcode::ASL:
		return "<<";
	case opcode::LSR:
		return ">>";
	case opcode::ROL:
		return "<<<";
	case opcode::ROR:
		return ">>>";
	case opcode::AND:
	case opcode::BIT:
		return "&";
	case opcode::ORA:
		return "|";
	case opcode::EOR:
		return "^";
	case opcode::INC: case opcode::INX: case opcode::INY:
		return "++";
	case opcode::DEC: case opcode::DEX: case opcode::DEY:
		return "--";
	case opcode::CMP: case opcode::CPX: case opcode::CPY:
		return ">";
	case opcode::CLC: case opcode::CLD:
	case opcode::CLI: case opcode::CLV:
		return "false";
	case opcode::SEC: case opcode::SED: case opcode::SEI:
		return "true ";
	case opcode::BCC: case opcode::BCS:
	case opcode::BEQ: case opcode::BNE:
	case opcode::BMI: case opcode::BPL:
	case opcode::BVC: case opcode::BVS:
		return "?";
	default:
		return "";
	}
}
std::string
InstructionQuadrupleOutput::quadrupleStringJump()const
{
	std::string result;
	switch (instr->op()) {
	case opcode::BCC: case opcode::BEQ:
	case opcode::BVC: case opcode::BMI:
		result += "if (";
		result += operandString(instr->first());
		result += ") then ";
		result += operandString(instr->second());
		return result;
	case opcode::BNE: case opcode::BPL:
	case opcode::BVS: case opcode::BCS:
		result += "if (!";
		result += operandString(instr->first());
		result += ") then ";
		result += operandString(instr->second());
		return result;
	case opcode::JMP:
		result += "goto ";
		result += operandString(instr->first());
		return result;
	case opcode::JSR:
		result += "call ";
		result += operandString(instr->first());
		return result;
	case opcode::RTS:
		return "return";
	case opcode::RTI:
		return "resume";
	case opcode::BRK:
		return "break";
	default:
		return "";
	}
}

static std::string align(StringAligner& aligner, std::string s)
{
	if (s.empty()){
		return "";
	} else {
		return aligner(s);
	}
}

std::string
InstructionQuadrupleOutput::quadrupleString()const
{
	std::string line;
	StringAligner left(4, StringAligner::LEFT);
	StringAligner right(8, StringAligner::RIGHT);
		
	line += align(right, operandString(instr->result()));
	line += " := ";
	switch (instr->op()) {
	case opcode::ASL: case opcode::LSR:
	case opcode::ROL: case opcode::ROR:
	case opcode::INC: case opcode::INX: case opcode::INY:
	case opcode::DEC: case opcode::DEX: case opcode::DEY:
		line += align(left,
					  operandString(instr->first()) + operationString());
		return line;
	case opcode::PHA: case opcode::PHP:
		return line + "push " + align(left, operandString(instr->first()));
	case opcode::PLA: case opcode::PLP:
		return line + align(left, operandString(instr->first())) + "pop";
	case opcode::UND:
		line = right("db");
		line += " := " + left(
				"#" + HexadecimalFormat(
						instr->bytecode()->at(0)).width(2).to_s());
		return line;
	case opcode::NOP:
		return "nop";
	default:
		line += align(left, operandString(instr->first()));
		line += align(left, operationString());
		line += align(left, operandString(instr->second()));
		return line;
	}
}

void
InstructionQuadrupleOutput::display(std::ostream& os)const
{
	StringAligner left(32, StringAligner::LEFT);
	StringAligner mn(4, StringAligner::LEFT);
	opcode::OperationType optype(instr->op());
	std::string line = (optype.is_jump)
			? quadrupleStringJump() : quadrupleString();
	os << HexadecimalFormat(instr->address()) << ":  "
	   << left(line);
	os << mn(instr->mnemonic());
}
