/* Capstone Disassembly Engine */
/* M68K Backend by Daniel Collin <daniel@collin.com> 2015-2016 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "M68KInstPrinter.h"

#include "M68KDisassembler.h"

#include "../../Mapping.h"
#include "../../cs_priv.h"
#include "../../utils.h"

#include "../../MCInst.h"
#include "../../MCInstrDesc.h"
#include "../../MCRegisterInfo.h"

#ifndef CAPSTONE_DIET
static const char s_spacing[] = " ";

static const char *const s_reg_names[] = {
	"invalid", "d0",    "d1",    "d2",  "d3",  "d4",   "d5",   "d6",
	"d7",	   "a0",    "a1",    "a2",  "a3",  "a4",   "a5",   "a6",
	"a7",	   "fp0",   "fp1",   "fp2", "fp3", "fp4",  "fp5",  "fp6",
	"fp7",	   "pc",    "sr",    "ccr", "sfc", "dfc",  "usp",  "vbr",
	"cacr",	   "caar",  "msp",   "isp", "tc",  "itt0", "itt1", "dtt0",
	"dtt1",	   "mmusr", "urp",   "srp",

	"fpcr",	   "fpsr",  "fpiar",

	"tt0",	   "tt1",   "crp",
};

static const char *const s_instruction_names[] = {
	"invalid",  "abcd",	"add",	    "adda",	"addi",	     "addq",
	"addx",	    "and",	"andi",	    "asl",	"asr",	     "bhs",
	"blo",	    "bhi",	"bls",	    "bcc",	"bcs",	     "bne",
	"beq",	    "bvc",	"bvs",	    "bpl",	"bmi",	     "bge",
	"blt",	    "bgt",	"ble",	    "bra",	"bsr",	     "bchg",
	"bclr",	    "bset",	"btst",	    "bfchg",	"bfclr",     "bfexts",
	"bfextu",   "bfffo",	"bfins",    "bfset",	"bftst",     "bkpt",
	"callm",    "cas",	"cas2",	    "chk",	"chk2",	     "clr",
	"cmp",	    "cmpa",	"cmpi",	    "cmpm",	"cmp2",	     "cinvl",
	"cinvp",    "cinva",	"cpushl",   "cpushp",	"cpusha",    "dbt",
	"dbf",	    "dbhi",	"dbls",	    "dbcc",	"dbcs",	     "dbne",
	"dbeq",	    "dbvc",	"dbvs",	    "dbpl",	"dbmi",	     "dbge",
	"dblt",	    "dbgt",	"dble",	    "dbra",	"divs",	     "divsl",
	"divu",	    "divul",	"eor",	    "eori",	"exg",	     "ext",
	"extb",	    "fabs",	"fsabs",    "fdabs",	"facos",     "fadd",
	"fsadd",    "fdadd",	"fasin",    "fatan",	"fatanh",    "fbf",
	"fbeq",	    "fbogt",	"fboge",    "fbolt",	"fbole",     "fbogl",
	"fbor",	    "fbun",	"fbueq",    "fbugt",	"fbuge",     "fbult",
	"fbule",    "fbne",	"fbt",	    "fbsf",	"fbseq",     "fbgt",
	"fbge",	    "fblt",	"fble",	    "fbgl",	"fbgle",     "fbngle",
	"fbngl",    "fbnle",	"fbnlt",    "fbnge",	"fbngt",     "fbsne",
	"fbst",	    "fcmp",	"fcos",	    "fcosh",	"fdbf",	     "fdbeq",
	"fdbogt",   "fdboge",	"fdbolt",   "fdbole",	"fdbogl",    "fdbor",
	"fdbun",    "fdbueq",	"fdbugt",   "fdbuge",	"fdbult",    "fdbule",
	"fdbne",    "fdbt",	"fdbsf",    "fdbseq",	"fdbgt",     "fdbge",
	"fdblt",    "fdble",	"fdbgl",    "fdbgle",	"fdbngle",   "fdbngl",
	"fdbnle",   "fdbnlt",	"fdbnge",   "fdbngt",	"fdbsne",    "fdbst",
	"fdiv",	    "fsdiv",	"fddiv",    "fetox",	"fetoxm1",   "fgetexp",
	"fgetman",  "fint",	"fintrz",   "flog10",	"flog2",     "flogn",
	"flognp1",  "fmod",	"fmove",    "fsmove",	"fdmove",    "fmovecr",
	"fmovem",   "fmul",	"fsmul",    "fdmul",	"fneg",	     "fsneg",
	"fdneg",    "fnop",	"frem",	    "frestore", "fsave",     "fscale",
	"fsgldiv",  "fsglmul",	"fsin",	    "fsincos",	"fsinh",     "fsqrt",
	"fssqrt",   "fdsqrt",	"fsf",	    "fseq",	"fsogt",     "fsoge",
	"fsolt",    "fsole",	"fsogl",    "fsor",	"fsun",	     "fsueq",
	"fsugt",    "fsuge",	"fsult",    "fsule",	"fsne",	     "fst",
	"fssf",	    "fsseq",	"fsgt",	    "fsge",	"fslt",	     "fsle",
	"fsgl",	    "fsgle",	"fsngle",   "fsngl",	"fsnle",     "fsnlt",
	"fsnge",    "fsngt",	"fssne",    "fsst",	"fsub",	     "fssub",
	"fdsub",    "ftan",	"ftanh",    "ftentox",	"ftrapf",    "ftrapeq",
	"ftrapogt", "ftrapoge", "ftrapolt", "ftrapole", "ftrapogl",  "ftrapor",
	"ftrapun",  "ftrapueq", "ftrapugt", "ftrapuge", "ftrapult",  "ftrapule",
	"ftrapne",  "ftrapt",	"ftrapsf",  "ftrapseq", "ftrapgt",   "ftrapge",
	"ftraplt",  "ftraple",	"ftrapgl",  "ftrapgle", "ftrapngle", "ftrapngl",
	"ftrapnle", "ftrapnlt", "ftrapnge", "ftrapngt", "ftrapsne",  "ftrapst",
	"ftst",	    "ftwotox",	"halt",	    "illegal",	"jmp",	     "jsr",
	"lea",	    "link",	"lpstop",   "lsl",	"lsr",	     "move",
	"movea",    "movec",	"movem",    "movep",	"moveq",     "moves",
	"move16",   "muls",	"mulu",	    "nbcd",	"neg",	     "negx",
	"nop",	    "not",	"or",	    "ori",	"pack",	     "pea",
	"pflush",   "pflusha",	"pflushan", "pflushn",	"ploadr",    "ploadw",
	"plpar",    "plpaw",	"pmove",    "pmovefd",	"ptestr",    "ptestw",
	"pulse",    "rems",	"remu",	    "reset",	"rol",	     "ror",
	"roxl",	    "roxr",	"rtd",	    "rte",	"rtm",	     "rtr",
	"rts",	    "sbcd",	"st",	    "sf",	"shi",	     "sls",
	"scc",	    "shs",	"scs",	    "slo",	"sne",	     "seq",
	"svc",	    "svs",	"spl",	    "smi",	"sge",	     "slt",
	"sgt",	    "sle",	"stop",	    "sub",	"suba",	     "subi",
	"subq",	    "subx",	"swap",	    "tas",	"trap",	     "trapv",
	"trapt",    "trapf",	"traphi",   "trapls",	"trapcc",    "traphs",
	"trapcs",   "traplo",	"trapne",   "trapeq",	"trapvc",    "trapvs",
	"trappl",   "trapmi",	"trapge",   "traplt",	"trapgt",    "traple",
	"tst",	    "unlk",	"unpk",	    "bgnd",	"tbls",	     "tblu",
	"tblsn",    "tblun",
};
#endif

#ifndef CAPSTONE_DIET
static const char *getRegName(m68k_reg reg)
{
	return s_reg_names[(int)reg];
}

static void printRegbits(SStream *O, bool *need_sep, uint32_t data,
			 const char *prefix)
{
	unsigned int first;
	int i;

	for (i = 0; i < 8; ++i) {
		if (!(data & (1 << i)))
			continue;

		first = i;
		while (i < 7 && (data & (1 << (i + 1))))
			i++;

		if (*need_sep)
			SStream_concat1(O, '/');
		*need_sep = true;

		SStream_concat(O, "%s%" PRIu32, prefix, first);

		if ((unsigned int)i > first)
			SStream_concat(O, "-%s%" PRIu32, prefix,
				       (unsigned int)i);
	}
}

static void registerBits(SStream *O, const cs_m68k_op *op)
{
	unsigned int data = op->register_bits;
	bool need_sep = false;

	if (!data) {
		SStream_concat(O, "%s", "#$0");
		return;
	}

	printRegbits(O, &need_sep, data & 0xff, "d");
	printRegbits(O, &need_sep, (data >> 8) & 0xff, "a");
	printRegbits(O, &need_sep, (data >> 16) & 0xff, "fp");
}

static void registerPair(SStream *O, const cs_m68k_op *op)
{
	SStream_concat(O, "%s:%s", s_reg_names[op->reg_pair.reg_0],
		       s_reg_names[op->reg_pair.reg_1]);
}

static void printScaleFactor(SStream *O, uint8_t scale, int threshold)
{
	if (scale > threshold)
		SStream_concat(O, "%s*%s%" PRId8, s_spacing, s_spacing, scale);
}

static void printIndexReg(SStream *O, const cs_m68k_op *op)
{
	SStream_concat(O, "%s.%c", getRegName(op->mem.index_reg),
		       op->mem.index_size ? 'l' : 'w');
}

static void printBitfield(SStream *O, const cs_m68k_op *op)
{
	if (!op->mem.bitfield)
		return;
	SStream_concat0(O, "{");
	if (M68K_BF_IS_REG(op->mem.offset))
		SStream_concat(O, "d%" PRId8, M68K_BF_REG_NUM(op->mem.offset));
	else
		SStream_concat(O, "%" PRId8, op->mem.offset);
	SStream_concat0(O, ":");
	if (M68K_BF_IS_REG(op->mem.width))
		SStream_concat(O, "d%" PRId8, M68K_BF_REG_NUM(op->mem.width));
	else
		SStream_concat(O, "%" PRId8, op->mem.width);
	SStream_concat0(O, "}");
}

static void printImmediate(SStream *O, const cs_m68k *inst,
			   const cs_m68k_op *op)
{
	if (inst->op_size.type == M68K_SIZE_TYPE_FPU) {
#if defined(_KERNEL_MODE)
		SStream_concat(O, "#<float_point_unsupported>");
		return;
#else
		if (inst->op_size.fpu_size == M68K_FPU_SIZE_SINGLE)
			SStream_concat(O, "#%f", op->simm);
		else if (inst->op_size.fpu_size == M68K_FPU_SIZE_DOUBLE)
			SStream_concat(O, "#%f", op->dimm);
		else
			SStream_concat(O, "#<unsupported>");
		return;
#endif
	}
	SStream_concat(O, "#$%" PRIx64, op->imm);
}

static void printIndex8BitDisp(SStream *O, unsigned int pc,
			       const cs_m68k_op *op)
{
	if (op->address_mode == M68K_AM_PCI_INDEX_8_BIT_DISP) {
		SStream_concat(O, "$%" PRIx32 "(pc,%s", pc + 2 + op->mem.disp,
			       s_spacing);
	} else {
		SStream_concat(O, "%s$%" PRIx16 "(%s,%s",
			       op->mem.disp < 0 ? "-" : "", abs(op->mem.disp),
			       getRegName(op->mem.base_reg), s_spacing);
	}
	printIndexReg(O, op);
	printScaleFactor(O, op->mem.scale, 1);
	SStream_concat0(O, ")");
}

static void printRegAddrMode(SStream *O, unsigned int pc, const cs_m68k_op *op)
{
	switch (op->address_mode) {
	case M68K_AM_REG_DIRECT_DATA:
		SStream_concat(O, "d%" PRId32, (op->reg - M68K_REG_D0));
		break;
	case M68K_AM_REG_DIRECT_ADDR:
		SStream_concat(O, "a%" PRId32, (op->reg - M68K_REG_A0));
		break;
	case M68K_AM_REGI_ADDR:
		SStream_concat(O, "(a%" PRId32 ")", (op->reg - M68K_REG_A0));
		break;
	case M68K_AM_REGI_ADDR_POST_INC:
		SStream_concat(O, "(a%" PRId32 ")+", (op->reg - M68K_REG_A0));
		break;
	case M68K_AM_REGI_ADDR_PRE_DEC:
		SStream_concat(O, "-(a%" PRId32 ")", (op->reg - M68K_REG_A0));
		break;
	case M68K_AM_REGI_ADDR_DISP:
		SStream_concat(O, "%s$%" PRIx16 "(a%" PRId32 ")",
			       op->mem.disp < 0 ? "-" : "", abs(op->mem.disp),
			       (op->mem.base_reg - M68K_REG_A0));
		break;
	case M68K_AM_PCI_DISP:
		SStream_concat(O, "$%" PRIx32 "(pc)", pc + 2 + op->mem.disp);
		break;
	default:
		break;
	}
}

static void printBaseDisp(SStream *O, unsigned int pc, const cs_m68k_op *op)
{
	int is_pc = (op->address_mode == M68K_AM_PCI_INDEX_BASE_DISP);

	if (is_pc) {
		SStream_concat(O, "$%" PRIx32, pc + 2 + op->mem.in_disp);
	} else if (op->mem.in_disp != 0) {
		SStream_concat(O, "%s$%" PRIx32,
			       op->mem.in_disp >= 0 ? "" : "-",
			       abs(op->mem.in_disp));
	}

	SStream_concat0(O, "(");

	if (is_pc) {
		SStream_concat0(O, "pc");
	} else if (op->mem.base_reg != M68K_REG_INVALID) {
		SStream_concat(O, "a%" PRId32, op->mem.base_reg - M68K_REG_A0);
	}

	if ((is_pc || op->mem.base_reg != M68K_REG_INVALID) &&
	    op->mem.index_reg != M68K_REG_INVALID)
		SStream_concat(O, ",%s", s_spacing);

	if (op->mem.index_reg != M68K_REG_INVALID) {
		printIndexReg(O, op);
		printScaleFactor(O, op->mem.scale, 0);
	}

	SStream_concat0(O, ")");
}

static void printMemIndirect(SStream *O, unsigned int pc, const cs_m68k_op *op)
{
	int is_pc = (op->address_mode == M68K_AM_PC_MEMI_POST_INDEX ||
		     op->address_mode == M68K_AM_PC_MEMI_PRE_INDEX);
	int is_post = (op->address_mode == M68K_AM_MEMI_POST_INDEX ||
		       op->address_mode == M68K_AM_PC_MEMI_POST_INDEX);
	int is_pre = (op->address_mode == M68K_AM_MEMI_PRE_INDEX ||
		      op->address_mode == M68K_AM_PC_MEMI_PRE_INDEX);

	SStream_concat0(O, "([");

	if (is_pc) {
		SStream_concat(O, "$%" PRIx32, pc + 2 + op->mem.in_disp);
	} else if (op->mem.in_disp != 0) {
		SStream_concat(O, "%s$%" PRIx32,
			       op->mem.in_disp >= 0 ? "" : "-",
			       abs(op->mem.in_disp));
	}

	if (op->mem.base_reg != M68K_REG_INVALID) {
		if (op->mem.in_disp != 0)
			SStream_concat(O, ",%s%s", s_spacing,
				       getRegName(op->mem.base_reg));
		else
			SStream_concat(O, "%s", getRegName(op->mem.base_reg));
	}

	if (is_post)
		SStream_concat0(O, "]");

	if (op->mem.index_reg != M68K_REG_INVALID) {
		SStream_concat(O, ",%s", s_spacing);
		printIndexReg(O, op);
	}

	printScaleFactor(O, op->mem.scale, 0);

	if (is_pre)
		SStream_concat0(O, "]");

	if (op->mem.out_disp != 0) {
		SStream_concat(O, ",%s%s$%" PRIx32, s_spacing,
			       op->mem.out_disp >= 0 ? "" : "-",
			       abs(op->mem.out_disp));
	}

	SStream_concat0(O, ")");
}

static void printAddressingMode(SStream *O, unsigned int pc,
				const cs_m68k *inst, const cs_m68k_op *op)
{
	switch (op->address_mode) {
	case M68K_AM_NONE:
		switch (op->type) {
		case M68K_OP_REG_BITS:
			registerBits(O, op);
			break;
		case M68K_OP_REG_PAIR:
			registerPair(O, op);
			break;
		case M68K_OP_REG:
			SStream_concat(O, "%s", s_reg_names[op->reg]);
			break;
		default:
			break;
		}
		break;

	case M68K_AM_REG_DIRECT_DATA:
	case M68K_AM_REG_DIRECT_ADDR:
	case M68K_AM_REGI_ADDR:
	case M68K_AM_REGI_ADDR_POST_INC:
	case M68K_AM_REGI_ADDR_PRE_DEC:
	case M68K_AM_REGI_ADDR_DISP:
	case M68K_AM_PCI_DISP:
		printRegAddrMode(O, pc, op);
		break;
	case M68K_AM_ABSOLUTE_DATA_SHORT:
		SStream_concat(O, "$%" PRIx32 ".w", (uint32_t)op->imm);
		break;
	case M68K_AM_ABSOLUTE_DATA_LONG:
		SStream_concat(O, "$%" PRIx64 ".l", (uint64_t)op->imm);
		break;
	case M68K_AM_IMMEDIATE:
		printImmediate(O, inst, op);
		break;
	case M68K_AM_PCI_INDEX_8_BIT_DISP:
	case M68K_AM_AREGI_INDEX_8_BIT_DISP:
		printIndex8BitDisp(O, pc, op);
		break;
	case M68K_AM_PCI_INDEX_BASE_DISP:
	case M68K_AM_AREGI_INDEX_BASE_DISP:
		printBaseDisp(O, pc, op);
		break;
	case M68K_AM_PC_MEMI_POST_INDEX:
	case M68K_AM_PC_MEMI_PRE_INDEX:
	case M68K_AM_MEMI_PRE_INDEX:
	case M68K_AM_MEMI_POST_INDEX:
		printMemIndirect(O, pc, op);
		break;
	case M68K_AM_BRANCH_DISPLACEMENT:
		SStream_concat(O, "$%" PRIx32, pc + 2 + op->br_disp.disp);
	default:
		break;
	}

	printBitfield(O, op);
}

static void printCAS2(SStream *O, unsigned int pc, const cs_m68k *ext)
{
	printAddressingMode(O, pc, ext, &ext->operands[0]);
	SStream_concat0(O, ",");
	printAddressingMode(O, pc, ext, &ext->operands[1]);
	SStream_concat0(O, ",");

	SStream_concat(O, "(%s):(%s)",
		       s_reg_names[ext->operands[2].reg_pair.reg_0],
		       s_reg_names[ext->operands[2].reg_pair.reg_1]);
}

static void printCacheOp(SStream *O, unsigned int pc, const cs_m68k *ext)
{
	static const char *const cache_names[] = { "nc", "dc", "ic", "bc" };
	unsigned int sel = (unsigned int)ext->operands[0].imm;
	int i;

	if (sel < ARR_SIZE(cache_names))
		SStream_concat0(O, cache_names[sel]);
	else
		SStream_concat(O, "#$%" PRIx64, ext->operands[0].imm);

	for (i = 1; i < ext->op_count; ++i) {
		SStream_concat(O, ",%s", s_spacing);
		printAddressingMode(O, pc, ext, &ext->operands[i]);
	}
}
#endif

static void printOpSize(SStream *O, const cs_m68k *ext)
{
	switch (ext->op_size.type) {
	case M68K_SIZE_TYPE_INVALID:
		break;
	case M68K_SIZE_TYPE_CPU:
		switch (ext->op_size.cpu_size) {
		case M68K_CPU_SIZE_BYTE:
			SStream_concat0(O, ".b");
			break;
		case M68K_CPU_SIZE_WORD:
			SStream_concat0(O, ".w");
			break;
		case M68K_CPU_SIZE_LONG:
			SStream_concat0(O, ".l");
			break;
		case M68K_CPU_SIZE_NONE:
			break;
		}
		break;
	case M68K_SIZE_TYPE_FPU:
		switch (ext->op_size.fpu_size) {
		case M68K_FPU_SIZE_SINGLE:
			SStream_concat0(O, ".s");
			break;
		case M68K_FPU_SIZE_DOUBLE:
			SStream_concat0(O, ".d");
			break;
		case M68K_FPU_SIZE_EXTENDED:
			SStream_concat0(O, ".x");
			break;
		case M68K_FPU_SIZE_NONE:
			break;
		}
		break;
	}
}

void M68K_printInst(MCInst *MI, SStream *O, void *PrinterInfo)
{
#ifndef CAPSTONE_DIET
	m68k_info *info = (m68k_info *)PrinterInfo;
	cs_m68k *ext = &info->extension;
	cs_detail *detail = NULL;
	int i = 0;

	if (detail_is_set(MI)) {
		detail = get_detail(MI);
		int regs_read_count = MIN((int)ARR_SIZE(detail->regs_read),
					  info->regs_read_count);
		int regs_write_count = MIN((int)ARR_SIZE(detail->regs_write),
					   info->regs_write_count);
		int groups_count =
			MIN((int)ARR_SIZE(detail->groups), info->groups_count);

		memcpy(&detail->m68k, ext, sizeof(cs_m68k));
		memcpy(&detail->regs_read, &info->regs_read,
		       regs_read_count * sizeof(info->regs_read[0]));
		detail->regs_read_count = regs_read_count;

		memcpy(&detail->regs_write, &info->regs_write,
		       regs_write_count * sizeof(info->regs_write[0]));
		detail->regs_write_count = regs_write_count;

		memcpy(&detail->groups, &info->groups, groups_count);
		detail->groups_count = groups_count;
	}

	if (MI->Opcode == M68K_INS_INVALID) {
		if (ext->op_count)
			SStream_concat(O, "dc.w $%" PRIx32,
				       (uint32_t)ext->operands[0].imm);
		else
			SStream_concat(O, "dc.w $<unknown>");
		return;
	}

	SStream_concat0(O, (char *)s_instruction_names[MI->Opcode]);
	printOpSize(O, ext);
	SStream_concat0(O, " ");

	if (MI->Opcode == M68K_INS_CAS2) {
		printCAS2(O, info->pc, ext);
		return;
	}

	if (MI->Opcode >= M68K_INS_CINVL && MI->Opcode <= M68K_INS_CPUSHA) {
		printCacheOp(O, info->pc, ext);
		return;
	}

	for (i = 0; i < ext->op_count; ++i) {
		printAddressingMode(O, info->pc, ext, &ext->operands[i]);
		if ((i + 1) != ext->op_count)
			SStream_concat(O, ",%s", s_spacing);
	}
#endif
}

const char *M68K_reg_name(csh handle, unsigned int reg)
{
#ifdef CAPSTONE_DIET
	return NULL;
#else
	if (reg >= ARR_SIZE(s_reg_names)) {
		return NULL;
	}
	return s_reg_names[(int)reg];
#endif
}

void M68K_get_insn_id(cs_struct *h, cs_insn *insn, unsigned int id)
{
	insn->id = id; // These id's matches for 68k
}

const char *M68K_insn_name(csh handle, unsigned int id)
{
#ifdef CAPSTONE_DIET
	return NULL;
#else
	return s_instruction_names[id];
#endif
}

#ifndef CAPSTONE_DIET
static const name_map group_name_maps[] = {
	{ M68K_GRP_INVALID, NULL },
	{ M68K_GRP_JUMP, "jump" },
	{ M68K_GRP_RET, "ret" },
	{ M68K_GRP_IRET, "iret" },
	{ M68K_GRP_BRANCH_RELATIVE, "branch_relative" },
};
#endif

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

#ifndef CAPSTONE_DIET
void M68K_reg_access(const cs_insn *insn, cs_regs regs_read,
		     uint8_t *regs_read_count, cs_regs regs_write,
		     uint8_t *regs_write_count)
{
	uint8_t read_count, write_count;

	read_count = insn->detail->regs_read_count;
	write_count = insn->detail->regs_write_count;

	// implicit registers
	memcpy(regs_read, insn->detail->regs_read,
	       read_count * sizeof(insn->detail->regs_read[0]));
	memcpy(regs_write, insn->detail->regs_write,
	       write_count * sizeof(insn->detail->regs_write[0]));

	*regs_read_count = read_count;
	*regs_write_count = write_count;
}
#endif
