/* Capstone Disassembly Engine */
/* M680X Backend by Wolfgang Schwotzer <wolfgang.schwotzer@gmx.net> 2017 */

#ifdef CAPSTONE_HAS_M680X
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <capstone/platform.h>

#include "../../MCInst.h"
#include "../../SStream.h"
#include "../../MCRegisterInfo.h"
#include "../../utils.h"
#include "M680XInstPrinter.h"
#include "M680XDisassembler.h"
#include "M680XDisassemblerInternals.h"

#ifndef CAPSTONE_DIET
static const char s_reg_names[][10] = {
	"<invalid>", "A", "B", "E", "F", "0", "D", "W", "CC", "DP", "MD",
	"HX", "H", "X", "Y", "S", "U", "V", "Q", "PC", "TMP2", "TMP3",
};

static const char s_instruction_names[][6] = {
	"INVLD", "ABA", "ABX", "ABY", "ADC", "ADCA", "ADCB", "ADCD", "ADCR",
	"ADD", "ADDA", "ADDB", "ADDD", "ADDE", "ADDF", "ADDR", "ADDW",
	"AIM", "AIS", "AIX", "AND", "ANDA", "ANDB", "ANDCC", "ANDD", "ANDR",
	"ASL", "ASLA", "ASLB", "ASLD",
	"ASR", "ASRA", "ASRB", "ASRD", "ASRX",
	"BAND",
	"BCC", "BCLR", "BCS", "BEOR", "BEQ", "BGE", "BGND", "BGT", "BHCC",
	"BHCS", "BHI",
	"BIAND", "BIEOR", "BIH", "BIL",
	"BIOR", "BIT", "BITA", "BITB", "BITD", "BITMD", "BLE", "BLS", "BLT",
	"BMC",
	"BMI", "BMS",
	"BNE", "BOR", "BPL", "BRCLR", "BRSET", "BRA", "BRN", "BSET", "BSR",
	"BVC", "BVS",
	"CALL", "CBA", "CBEQ", "CBEQA", "CBEQX", "CLC", "CLI",
	"CLR", "CLRA", "CLRB", "CLRD", "CLRE", "CLRF", "CLRH", "CLRW", "CLRX",
	"CLV", "CMP",
	"CMPA", "CMPB", "CMPD", "CMPE", "CMPF", "CMPR", "CMPS", "CMPU", "CMPW",
	"CMPX", "CMPY",
	"COM", "COMA", "COMB", "COMD", "COME", "COMF", "COMW", "COMX", "CPD",
	"CPHX", "CPS", "CPX", "CPY",
	"CWAI", "DAA", "DBEQ", "DBNE", "DBNZ", "DBNZA", "DBNZX",
	"DEC", "DECA", "DECB", "DECD", "DECE", "DECF", "DECW",
	"DECX", "DES", "DEX", "DEY",
	"DIV", "DIVD", "DIVQ", "EDIV", "EDIVS", "EIM", "EMACS", "EMAXD",
	"EMAXM", "EMIND", "EMINM", "EMUL", "EMULS",
	"EOR", "EORA", "EORB", "EORD", "EORR", "ETBL",
	"EXG", "FDIV", "IBEQ", "IBNE", "IDIV", "IDIVS", "ILLGL",
	"INC", "INCA", "INCB", "INCD", "INCE", "INCF", "INCW", "INCX",
	"INS", "INX", "INY",
	"JMP", "JSR",
	"LBCC", "LBCS", "LBEQ", "LBGE", "LBGT", "LBHI", "LBLE", "LBLS", "LBLT",
	"LBMI", "LBNE", "LBPL", "LBRA", "LBRN", "LBSR", "LBVC", "LBVS",
	"LDA", "LDAA", "LDAB", "LDB", "LDBT", "LDD", "LDE", "LDF", "LDHX",
	"LDMD",
	"LDQ", "LDS", "LDU", "LDW", "LDX", "LDY",
	"LEAS", "LEAU", "LEAX", "LEAY",
	"LSL", "LSLA", "LSLB", "LSLD", "LSLX",
	"LSR", "LSRA", "LSRB", "LSRD", "LSRW", "LSRX",
	"MAXA", "MAXM", "MEM", "MINA", "MINM", "MOV", "MOVB", "MOVW", "MUL",
	"MULD",
	"NEG", "NEGA", "NEGB", "NEGD", "NEGX",
	"NOP", "NSA", "OIM", "ORA", "ORAA", "ORAB", "ORB", "ORCC", "ORD", "ORR",
	"PSHA", "PSHB", "PSHC", "PSHD", "PSHH", "PSHS", "PSHSW", "PSHU",
	"PSHUW", "PSHX", "PSHY",
	"PULA", "PULB", "PULC", "PULD", "PULH", "PULS", "PULSW", "PULU",
	"PULUW", "PULX", "PULY", "REV", "REVW",
	"ROL", "ROLA", "ROLB", "ROLD", "ROLW", "ROLX",
	"ROR", "RORA", "RORB", "RORD", "RORW", "RORX",
	"RSP", "RTC", "RTI", "RTS", "SBA", "SBC", "SBCA", "SBCB", "SBCD",
	"SBCR",
	"SEC", "SEI", "SEV", "SEX", "SEXW", "SLP", "STA", "STAA", "STAB", "STB",
	"STBT", "STD", "STE", "STF", "STOP", "STHX",
	"STQ", "STS", "STU", "STW", "STX", "STY",
	"SUB", "SUBA", "SUBB", "SUBD", "SUBE", "SUBF", "SUBR", "SUBW",
	"SWI", "SWI2", "SWI3",
	"SYNC", "TAB", "TAP", "TAX", "TBA", "TBEQ", "TBL", "TBNE", "TEST",
	"TFM", "TFR",
	"TIM", "TPA",
	"TST", "TSTA", "TSTB", "TSTD", "TSTE", "TSTF", "TSTW", "TSTX",
	"TSX", "TSY", "TXA", "TXS", "TYS", "WAI", "WAIT", "WAV", "WAVR",
	"XGDX", "XGDY",
};

static name_map s_group_names[] = {
	{ M680X_GRP_INVALID, "<invalid>" },
	{ M680X_GRP_JUMP,  "JUMP" },
	{ M680X_GRP_CALL,  "CALL" },
	{ M680X_GRP_RET, "RETURN" },
	{ M680X_GRP_INT, "INTERRUPT" },
	{ M680X_GRP_IRET,  "INTERRUPT_RETURN" },
	{ M680X_GRP_PRIV,  "PRIVILEGED" },
	{ M680X_GRP_BRAREL,  "BRANCH_RELATIVE" },
};
#endif

static void printRegName(cs_struct *handle, SStream *OS, unsigned int reg)
{
#ifndef CAPSTONE_DIET
	SStream_concat(OS, handle->reg_name((csh)handle, reg));
#endif
}

static void printInstructionName(cs_struct *handle, SStream *OS,
	unsigned int insn)
{
#ifndef CAPSTONE_DIET
	SStream_concat(OS, handle->insn_name((csh)handle, insn));
#endif
}

static uint32_t get_unsigned(int32_t value, int byte_size)
{
	switch (byte_size) {
	case 1:
		return (uint32_t)(value & 0xff);

	case 2:
		return (uint32_t)(value & 0xffff);

	default:
	case 4:
		return (uint32_t)value;
	}
}

static void printIncDec(bool isPost, SStream *O, m680x_info *info,
	cs_m680x_op *op)
{
	static const char s_inc_dec[][3] = { "--", "-", "", "+", "++" };

	if (!op->idx.inc_dec)
		return;

	if ((!isPost && !(op->idx.flags & M680X_IDX_POST_INC_DEC)) ||
		(isPost && (op->idx.flags & M680X_IDX_POST_INC_DEC))) {
		const char *prePostfix = "";

		if (info->cpu_type == M680X_CPU_TYPE_CPU12)
			prePostfix = (op->idx.inc_dec < 0) ? "-" : "+";
		else if (op->idx.inc_dec >= -2 && (op->idx.inc_dec <= 2)) {
			prePostfix = (char *)s_inc_dec[op->idx.inc_dec + 2];
		}

		SStream_concat(O, prePostfix);
	}
}

static void printOperand(MCInst *MI, SStream *O, m680x_info *info,
	cs_m680x_op *op)
{
	switch (op->type) {
	case M680X_OP_REGISTER:
		printRegName(MI->csh, O, op->reg);
		break;

	case M680X_OP_CONSTANT:
		SStream_concat(O, "%u", op->const_val);
		break;

	case M680X_OP_IMMEDIATE:
		if (MI->csh->imm_unsigned)
			SStream_concat(O, "#%u",
				get_unsigned(op->imm, op->size));
		else
			SStream_concat(O, "#%d", op->imm);

		break;

	case M680X_OP_INDEXED:
		if (op->idx.flags & M680X_IDX_INDIRECT)
			SStream_concat(O, "[");

		if (op->idx.offset_reg != M680X_REG_INVALID)
			printRegName(MI->csh, O, op->idx.offset_reg);
		else if (op->idx.offset_bits > 0) {
			if (op->idx.base_reg == M680X_REG_PC)
				SStream_concat(O, "$%04X", op->idx.offset_addr);
			else
				SStream_concat(O, "%d", op->idx.offset);
		}
		else if (op->idx.inc_dec != 0 &&
			info->cpu_type == M680X_CPU_TYPE_CPU12)
			SStream_concat(O, "%d", abs(op->idx.inc_dec));

		if (!(op->idx.flags & M680X_IDX_NO_COMMA))
			SStream_concat(O, ",");

		printIncDec(false, O, info, op);

		printRegName(MI->csh, O, op->idx.base_reg);

		if (op->idx.base_reg == M680X_REG_PC &&
			(op->idx.offset_bits > 0))
			SStream_concat(O, "R");

		printIncDec(true, O, info, op);

		if (op->idx.flags & M680X_IDX_INDIRECT)
			SStream_concat(O, "]");

		break;

	case M680X_OP_RELATIVE:
		SStream_concat(O, "$%04X", op->rel.address);
		break;

	case M680X_OP_DIRECT:
		SStream_concat(O, "$%02X", op->direct_addr);
		break;

	case M680X_OP_EXTENDED:
		if (op->ext.indirect)
			SStream_concat(O, "[$%04X]", op->ext.address);
		else {
			if (op->ext.address < 256) {
				SStream_concat(O, ">$%04X", op->ext.address);
			}
			else {
				SStream_concat(O, "$%04X", op->ext.address);
			}
		}

		break;

	default:
		SStream_concat(O, "<invalid_operand>");
		break;
	}
}

static const char *getDelimiter(m680x_info *info, cs_m680x *m680x)
{
	bool indexed = false;
	int count = 0;
	int i;

	if (info->insn == M680X_INS_TFM)
		return ",";

	if (m680x->op_count > 1) {
		for (i  = 0; i < m680x->op_count; ++i) {
			if (m680x->operands[i].type == M680X_OP_INDEXED)
				indexed = true;

			if (m680x->operands[i].type != M680X_OP_REGISTER)
				count++;
		}
	}

	return (indexed && (count >= 1)) ? ";" : ",";
};

void M680X_printInst(MCInst *MI, SStream *O, void *PrinterInfo)
{
	m680x_info *info = (m680x_info *)PrinterInfo;
	cs_m680x *m680x = &info->m680x;
	cs_detail *detail = MI->flat_insn->detail;
	int suppress_operands = 0;
	const char *delimiter = getDelimiter(info, m680x);
	int i;

	if (detail != NULL)
		memcpy(&detail->m680x, m680x, sizeof(cs_m680x));

	if (info->insn == M680X_INS_INVLD || info->insn == M680X_INS_ILLGL) {
		if (m680x->op_count)
			SStream_concat(O, "FCB $%02X", m680x->operands[0].imm);
		else
			SStream_concat(O, "FCB $<unknown>");

		return;
	}

	printInstructionName(MI->csh, O, info->insn);
	SStream_concat(O, " ");

	if ((m680x->flags & M680X_FIRST_OP_IN_MNEM) != 0)
		suppress_operands++;

	if ((m680x->flags & M680X_SECOND_OP_IN_MNEM) != 0)
		suppress_operands++;

	for (i  = 0; i < m680x->op_count; ++i) {
		if (i >= suppress_operands) {
			printOperand(MI, O, info, &m680x->operands[i]);

			if ((i + 1) != m680x->op_count)
				SStream_concat(O, delimiter);
		}
	}
}

const char *M680X_reg_name(csh handle, unsigned int reg)
{
#ifndef CAPSTONE_DIET

	if (reg >= ARR_SIZE(s_reg_names))
		return NULL;

	return s_reg_names[(int)reg];
#else
	return NULL;
#endif
}

const char *M680X_insn_name(csh handle, unsigned int id)
{
#ifndef CAPSTONE_DIET

	if (id >= ARR_SIZE(s_instruction_names))
		return NULL;
	else
		return s_instruction_names[(int)id];

#else
	return NULL;
#endif
}

const char *M680X_group_name(csh handle, unsigned int id)
{
#ifndef CAPSTONE_DIET
	return id2name(s_group_names, ARR_SIZE(s_group_names), id);
#else
	return NULL;
#endif
}

cs_err M680X_instprinter_init(cs_struct *ud)
{
#ifndef CAPSTONE_DIET

	if (M680X_REG_ENDING != ARR_SIZE(s_reg_names)) {
		fprintf(stderr, "Internal error: Size mismatch in enum "
			"m680x_reg and s_reg_names\n");

		return CS_ERR_MODE;
	}

	if (M680X_INS_ENDING != ARR_SIZE(s_instruction_names)) {
		fprintf(stderr, "Internal error: Size mismatch in enum "
			"m680x_insn and s_instruction_names\n");

		return CS_ERR_MODE;
	}

	if (M680X_GRP_ENDING != ARR_SIZE(s_group_names)) {
		fprintf(stderr, "Internal error: Size mismatch in enum "
			"m680x_group_type and s_group_names\n");

		return CS_ERR_MODE;
	}

#endif

	return CS_ERR_OK;
}

#endif

