/* 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", "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 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
