/* 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 "../../cs_priv.h"
#include "../../MCInst.h"
#include "../../SStream.h"
#include "../../MCRegisterInfo.h"
#include "../../Mapping.h"
#include "../../MCInstPrinter.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", "spc", "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",	 "addx",  "addy",  "aded",  "adex",  "adey",  "aim",   "ais",
	"aix",	 "and",	  "anda",  "andb",  "andx",  "andy",  "andcc", "andd",
	"andr",	 "asl",	  "asla",  "aslb",  "asld",  "aslw",  "aslx",  "asly",
	"asr",	 "asra",  "asrb",  "asrd",  "asrw",  "asrx",  "asry",  "band",
	"bcc",	 "bclr",  "bcs",   "beor",  "beq",   "bge",   "bgnd",  "bgt",
	"bhcc",	 "bhcs",  "bhi",   "biand", "bieor", "bih",   "bil",   "bior",
	"bit",	 "bita",  "bitb",  "bitx",  "bity",  "bitd",  "bitmd", "ble",
	"bls",	 "blt",	  "bmc",   "bmi",   "bms",   "bne",   "bor",   "bpl",
	"brclr", "brset", "bra",   "brn",   "bset",  "bsr",   "btas",  "bvc",
	"bvs",	 "call",  "cba",   "cbeq",  "cbeqa", "cbeqx", "clc",   "cli",
	"clr",	 "clra",  "clrb",  "clrd",  "clre",  "clrf",  "clrh",  "clrw",
	"clrx",	 "clry",  "clv",   "cmp",   "cmpa",  "cmpb",  "cmpd",  "cmpe",
	"cmpf",	 "cmpr",  "cmps",  "cmpu",  "cmpw",  "cmpx",  "cmpy",  "com",
	"coma",	 "comb",  "comd",  "come",  "comf",  "comw",  "comx",  "comy",
	"cpd",	 "cped",  "cpes",  "cpex",  "cpey",  "cphx",  "cps",   "cpx",
	"cpy",	 "cwai",  "daa",   "dbeq",  "dbne",  "dbnz",  "dbnza", "dbnzx",
	"dec",	 "deca",  "decb",  "decd",  "dece",  "decf",  "decw",  "decx",
	"decy",	 "des",	  "dex",   "dey",   "div",   "divd",  "divq",  "ediv",
	"edivs", "eim",	  "emacs", "emaxd", "emaxm", "emind", "eminm", "emul",
	"emuls", "eor",	  "eora",  "eorb",  "eord",  "eorr",  "eorx",  "eory",
	"etbl",	 "exg",	  "fdiv",  "gldaa", "gldab", "gldd",  "glds",  "gldx",
	"gldy",	 "gstaa", "gstab", "gstd",  "gsts",  "gstx",  "gsty",  "ibeq",
	"ibne",	 "idiv",  "idivs", "illgl", "inc",   "inca",  "incb",  "incd",
	"ince",	 "incf",  "incw",  "incx",  "incy",  "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",  "lsry",  "maxa",  "maxm",  "mem",   "mina",  "minm",
	"mov",	 "movb",  "movw",  "mul",   "muld",  "neg",   "nega",  "negb",
	"negd",	 "negw",  "negx",  "negy",  "nop",   "nsa",   "oim",   "ora",
	"oraa",	 "orab",  "orb",   "orcc",  "ord",   "orr",   "orx",   "ory",
	"psha",	 "pshb",  "pshc",  "pshcw", "pshd",  "pshh",  "pshs",  "pshsw",
	"pshu",	 "pshuw", "pshx",  "pshy",  "pula",  "pulb",  "pulc",  "pulcw",
	"puld",	 "pulh",  "puls",  "pulsw", "pulu",  "puluw", "pulx",  "puly",
	"rev",	 "revw",  "rol",   "rola",  "rolb",  "rold",  "rolw",  "rolx",
	"roly",	 "ror",	  "rora",  "rorb",  "rord",  "rorw",  "rorx",  "rory",
	"rsp",	 "rtc",	  "rti",   "rts",   "sba",   "sbc",   "sbca",  "sbcb",
	"sbcd",	 "sbcr",  "sbed",  "sbex",  "sbey",  "sec",   "sei",   "sev",
	"sex",	 "sexw",  "sha",   "sla",   "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",  "subx",  "suby",  "swi",
	"swi2",	 "swi3",  "sync",  "sys",   "tab",   "tap",   "tax",   "tba",
	"tbeq",	 "tbl",	  "tbne",  "test",  "tfm",   "tfr",   "tim",   "tpa",
	"trap",	 "tst",	  "tsta",  "tstb",  "tstd",  "tste",  "tstf",  "tstw",
	"tstx",	 "tsty",  "tsx",   "tsy",   "txa",   "txs",   "tys",   "wai",
	"wait",	 "wav",	  "wavr",  "xgdx",  "xgdy",
};

static const 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_concat0(OS, handle->reg_name((csh)handle, reg));
#endif
}

static void printInstructionName(cs_struct *handle, SStream *OS,
				 unsigned int insn)
{
#ifndef CAPSTONE_DIET
	SStream_concat0(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_concat0(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_concat0(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_concat0(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_concat0(O, "fcb $<unknown>");

		return;
	}

	printInstructionName(MI->csh, O, info->insn);
	SStream_concat0(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_concat0(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)) {
		CS_ASSERT(M680X_REG_ENDING == ARR_SIZE(s_reg_names));
		return CS_ERR_MODE;
	}

	if (M680X_INS_ENDING != ARR_SIZE(s_instruction_names)) {
		CS_ASSERT(M680X_INS_ENDING == ARR_SIZE(s_instruction_names));
		return CS_ERR_MODE;
	}

	if (M680X_GRP_ENDING != ARR_SIZE(s_group_names)) {
		CS_ASSERT(M680X_GRP_ENDING == ARR_SIZE(s_group_names));
		return CS_ERR_MODE;
	}

#endif

	return CS_ERR_OK;
}

#endif
