/* Capstone Disassembly Engine */
/* By Yoshinori Sato, 2022 */

#include <string.h>

#include "../../Mapping.h"
#include "SHInstPrinter.h"

#ifndef CAPSTONE_DIET
static const char *const s_reg_names[] = {
	"invalid", "r0",      "r1",	 "r2",	    "r3",      "r4",
	"r5",	   "r6",      "r7",	 "r8",	    "r9",      "r10",
	"r11",	   "r12",     "r13",	 "r14",	    "r15",     "r0_bank",
	"r1_bank", "r2_bank", "r3_bank", "r4_bank", "r5_bank", "r6_bank",
	"r7_bank", "fr0",     "fr1",	 "fr2",	    "fr3",     "fr4",
	"fr5",	   "fr6",     "fr7",	 "fr8",	    "fr9",     "fr10",
	"fr11",	   "fr12",    "fr13",	 "fr14",    "fr15",    "dr0",
	"dr2",	   "dr4",     "dr6",	 "dr8",	    "dr10",    "dr12",
	"dr14",	   "xd0",     "xd2",	 "xd4",	    "xd6",     "xd8",
	"xd10",	   "xd12",    "xd14",	 "xf0",	    "xf1",     "xf2",
	"xf3",	   "xf4",     "xf5",	 "xf6",	    "xf7",     "xf8",
	"xf9",	   "xf10",    "xf11",	 "xf12",    "xf13",    "xf14",
	"xf15",	   "fv0",     "fv4",	 "fv8",	    "fv12",    "xmtrx",
	"pc",	   "pr",      "mach",	 "macl",    "sr",      "gbr",
	"ssr",	   "spc",     "sgr",	 "dbr",	    "vbr",     "tbr",
	"rs",	   "re",      "mod",	 "fpul",    "fpscr",   "x0",
	"x1",	   "y0",      "y1",	 "a0",	    "a1",      "a0g",
	"a1g",	   "m0",      "m1",	 "dsr",	    "0x0",     "0x1",
	"0x2",	   "0x3",     "0x4",	 "0x5",	    "0x6",     "0x7",
	"0x8",	   "0x9",     "0xa",	 "0xb",	    "0xc",     "0xd",
	"0xe",	   "0xf",
};
#endif

const char *SH_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 SH_get_insn_id(cs_struct *h, cs_insn *insn, unsigned int id)
{
	insn->id = id; // These id's matches for sh
}

#ifndef CAPSTONE_DIET
static const char *const s_insn_names[] = {
	"unknown", "add",     "add",	 "addc",    "addv",    "and",
	"band",	   "bandnot", "bclr",	 "bf",	    "bf/s",    "bld",
	"bldnot",  "bor",     "bornot",	 "bra",	    "braf",    "bset",
	"bsr",	   "bsrf",    "bst",	 "bt",	    "bt/s",    "bxor",
	"clips",   "clipu",   "clrdmxy", "clrmac",  "clrs",    "clrt",
	"cmp/eq",  "cmp/ge",  "cmp/gt",	 "cmp/hi",  "cmp/hs",  "cmp/pl",
	"cmp/pz",  "cmp/str", "div0s",	 "div0u",   "div1",    "divs",
	"divu",	   "dmuls.l", "dmulu.l", "dt",	    "exts",    "exts",
	"extu",	   "extu",    "fabs",	 "fadd",    "fcmp/eq", "fcmp/gt",
	"fcnvds",  "fcnvsd",  "fdiv",	 "fipr",    "fldi0",   "fldi1",
	"flds",	   "float",   "fmac",	 "fmov",    "fmul",    "fneg",
	"fpchg",   "frchg",   "fsca",	 "fschg",   "fsqrt",   "fsrra",
	"fsts",	   "fsub",    "ftrc",	 "ftrv",    "icbi",    "jmp",
	"jsr",	   "jsr/n",   "ldbank",	 "ldc",	    "ldrc",    "ldre",
	"ldrs",	   "lds",     "ldtlb",	 "mac.l",   "mac.w",   "mov",
	"mova",	   "movca",   "movco",	 "movi20",  "movi20s", "movli",
	"movml",   "movmu",   "movrt",	 "movt",    "movu",    "movua",
	"mul.l",   "mulr",    "muls",	 "mulu",    "neg",     "negc",
	"nop",	   "not",     "nott",	 "ocbi",    "ocbp",    "ocbwb",
	"or",	   "pref",    "prefi",	 "resbank", "rotcl",   "rotcr",
	"rotl",	   "rotr",    "rte",	 "rts",	    "rts/n",   "rtv/n",
	"setdmx",  "setdmy",  "setrc",	 "sets",    "sett",    "shad",
	"shal",	   "shar",    "shld",	 "shll",    "shll16",  "shll2",
	"shll8",   "shlr",    "shlr16",	 "shlr2",   "shlr8",   "sleep",
	"stbank",  "stc",     "sts",	 "sub",	    "subc",    "subv",
	"swap",	   "swap",    "synco",	 "tas",	    "trapa",   "tst",
	"xor",	   "xtrct",
};
#endif

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

#ifndef CAPSTONE_DIET
#endif

#ifndef CAPSTONE_DIET
static void print_dsp_double(SStream *O, sh_info *info, int xy)
{
	char suffix_xy = 'x' + xy;
	int i;
	if (info->op.operands[xy].dsp.insn == SH_INS_DSP_NOP) {
		if ((info->op.operands[0].dsp.insn == SH_INS_DSP_NOP) &&
		    (info->op.operands[1].dsp.insn == SH_INS_DSP_NOP)) {
			SStream_concat(O, "nop%c", suffix_xy);
		}
	} else {
		SStream_concat(O, "mov%c", suffix_xy);
		switch (info->op.operands[xy].dsp.size) {
		case 16:
			SStream_concat0(O, ".w ");
			break;
		case 32:
			SStream_concat0(O, ".l ");
			break;
		}

		for (i = 0; i < 2; i++) {
			switch (info->op.operands[xy].dsp.operand[i]) {
			default:
				break;
			case SH_OP_DSP_REG_IND:
				SStream_concat(O, "@%s",
					       s_reg_names[info->op.operands[xy]
								   .dsp.r[i]]);
				break;
			case SH_OP_DSP_REG_POST:
				SStream_concat(O, "@%s+",
					       s_reg_names[info->op.operands[xy]
								   .dsp.r[i]]);
				break;
			case SH_OP_DSP_REG_INDEX:
				SStream_concat(O, "@%s+%s",
					       s_reg_names[info->op.operands[xy]
								   .dsp.r[i]],
					       s_reg_names[SH_REG_R8 + xy]);
				break;
			case SH_OP_DSP_REG:
				SStream_concat(O, "%s",
					       s_reg_names[info->op.operands[xy]
								   .dsp.r[i]]);
				break;
			}
			if (i == 0)
				SStream_concat0(O, ",");
		}
	}
	if (xy == 0)
		SStream_concat0(O, " ");
}

static const char *s_dsp_insns[] = {
	"invalid",    "nop",	    "mov",	  "pshl",  "psha",  "pmuls",
	"pclr_pmuls", "psub_pmuls", "padd_pmuls", "psubc", "paddc", "pcmp",
	"pabs",	      "prnd",	    "psub",	  "psub",  "padd",  "pand",
	"pxor",	      "por",	    "pdec",	  "pinc",  "pclr",  "pdmsb",
	"pneg",	      "pcopy",	    "psts",	  "plds",  "pswap", "pwad",
	"pwsb",
};

static void print_dsp(SStream *O, sh_info *info)
{
	int i;
	switch (info->op.op_count) {
	case 1:
		// single transfer
		SStream_concat0(O, "movs");
		switch (info->op.operands[0].dsp.size) {
		case 16:
			SStream_concat0(O, ".w ");
			break;
		case 32:
			SStream_concat0(O, ".l ");
			break;
		}
		for (i = 0; i < 2; i++) {
			switch (info->op.operands[0].dsp.operand[i]) {
			default:
				break;
			case SH_OP_DSP_REG_PRE:
				SStream_concat(O, "@-%s",
					       s_reg_names[info->op.operands[0]
								   .dsp.r[i]]);
				break;
			case SH_OP_DSP_REG_IND:
				SStream_concat(O, "@%s",
					       s_reg_names[info->op.operands[0]
								   .dsp.r[i]]);
				break;
			case SH_OP_DSP_REG_POST:
				SStream_concat(O, "@%s+",
					       s_reg_names[info->op.operands[0]
								   .dsp.r[i]]);
				break;
			case SH_OP_DSP_REG_INDEX:
				SStream_concat(O, "@%s+%s",
					       s_reg_names[info->op.operands[0]
								   .dsp.r[i]],
					       s_reg_names[SH_REG_R8]);
				break;
			case SH_OP_DSP_REG:
				SStream_concat(O, "%s",
					       s_reg_names[info->op.operands[0]
								   .dsp.r[i]]);
			}
			if (i == 0)
				SStream_concat0(O, ",");
		}
		break;
	case 2: // Double transfer
		print_dsp_double(O, info, 0);
		print_dsp_double(O, info, 1);
		break;
	case 3: // Parallel
		switch (info->op.operands[2].dsp.cc) {
		default:
			break;
		case SH_DSP_CC_DCT:
			SStream_concat0(O, "dct ");
			break;
		case SH_DSP_CC_DCF:
			SStream_concat0(O, "dcf ");
			break;
		}
		switch (info->op.operands[2].dsp.insn) {
		case SH_INS_DSP_PSUB_PMULS:
		case SH_INS_DSP_PADD_PMULS:
			switch (info->op.operands[2].dsp.insn) {
			default:
				break;
			case SH_INS_DSP_PSUB_PMULS:
				SStream_concat0(O, "psub ");
				break;
			case SH_INS_DSP_PADD_PMULS:
				SStream_concat0(O, "padd ");
				break;
			}
			for (i = 0; i < 6; i++) {
				SStream_concat(O, "%s",
					       s_reg_names[info->op.operands[2]
								   .dsp.r[i]]);
				if ((i % 3) < 2)
					SStream_concat0(O, ",");
				if (i == 2)
					SStream_concat(
						O, " %s ",
						s_dsp_insns[SH_INS_DSP_PMULS]);
			}
			break;
		case SH_INS_DSP_PCLR_PMULS:
			SStream_concat0(O, s_dsp_insns[SH_INS_DSP_PCLR]);
			SStream_concat(
				O, " %s ",
				s_reg_names[info->op.operands[2].dsp.r[3]]);
			SStream_concat(O, "%s ", s_dsp_insns[SH_INS_DSP_PMULS]);
			for (i = 0; i < 3; i++) {
				SStream_concat(O, "%s",
					       s_reg_names[info->op.operands[2]
								   .dsp.r[i]]);
				if (i < 2)
					SStream_concat0(O, ",");
			}
			break;

		default:
			SStream_concat0(
				O, s_dsp_insns[info->op.operands[2].dsp.insn]);
			SStream_concat0(O, " ");
			for (i = 0; i < 3; i++) {
				if (info->op.operands[2].dsp.r[i] ==
				    SH_REG_INVALID) {
					if (i == 0) {
						SStream_concat(
							O, "#%d",
							info->op.operands[2]
								.dsp.imm);
					}
				} else
					SStream_concat(
						O, "%s",
						s_reg_names[info->op.operands[2]
								    .dsp.r[i]]);
				if (i < 2 &&
				    info->op.operands[2].dsp.r[i + 1] !=
					    SH_REG_INVALID)
					SStream_concat0(O, ",");
			}
		}

		if (info->op.operands[0].dsp.insn != SH_INS_DSP_NOP) {
			SStream_concat0(O, " ");
			print_dsp_double(O, info, 0);
		}
		if (info->op.operands[1].dsp.insn != SH_INS_DSP_NOP) {
			SStream_concat0(O, " ");
			print_dsp_double(O, info, 1);
		}
		break;
	}
}

static void PrintMemop(SStream *O, sh_op_mem *op)
{
	switch (op->address) {
	case SH_OP_MEM_INVALID:
		break;
	case SH_OP_MEM_REG_IND:
		SStream_concat(O, "@%s", s_reg_names[op->reg]);
		break;
	case SH_OP_MEM_REG_POST:
		SStream_concat(O, "@%s+", s_reg_names[op->reg]);
		break;
	case SH_OP_MEM_REG_PRE:
		SStream_concat(O, "@-%s", s_reg_names[op->reg]);
		break;
	case SH_OP_MEM_REG_DISP:
		SStream_concat(O, "@(%d,%s)", op->disp, s_reg_names[op->reg]);
		break;
	case SH_OP_MEM_REG_R0: /// <= R0 indexed
		SStream_concat(O, "@(%s,%s)", s_reg_names[SH_REG_R0],
			       s_reg_names[op->reg]);
		break;
	case SH_OP_MEM_GBR_DISP: /// <= GBR based displaysment
		SStream_concat(O, "@(%d,%s)", op->disp,
			       s_reg_names[SH_REG_GBR]);
		break;
	case SH_OP_MEM_GBR_R0: /// <= GBR based R0 indexed
		SStream_concat(O, "@(%s,%s)", s_reg_names[SH_REG_R0],
			       s_reg_names[SH_REG_GBR]);
		break;
	case SH_OP_MEM_PCR: /// <= PC relative
		SStream_concat(O, "0x%x", op->disp);
		break;
	case SH_OP_MEM_TBR_DISP: /// <= GBR based displaysment
		SStream_concat(O, "@@(%d,%s)", op->disp,
			       s_reg_names[SH_REG_TBR]);
		break;
	}
}
#endif

void SH_printInst(MCInst *MI, SStream *O, void *PrinterInfo)
{
#ifndef CAPSTONE_DIET
	sh_info *info = (sh_info *)PrinterInfo;
	int i;
	int imm;

	if (MI->Opcode == SH_INS_DSP) {
		print_dsp(O, info);
		return;
	}

	SStream_concat0(O, (char *)s_insn_names[MI->Opcode]);
	switch (info->op.size) {
	case 8:
		SStream_concat0(O, ".b");
		break;
	case 16:
		SStream_concat0(O, ".w");
		break;
	case 32:
		SStream_concat0(O, ".l");
		break;
	case 64:
		SStream_concat0(O, ".d");
		break;
	}
	SStream_concat0(O, " ");
	for (i = 0; i < info->op.op_count; i++) {
		switch (info->op.operands[i].type) {
		case SH_OP_INVALID:
			break;
		case SH_OP_REG:
			SStream_concat0(O,
					s_reg_names[info->op.operands[i].reg]);
			break;
		case SH_OP_IMM:
			imm = info->op.operands[i].imm;
			SStream_concat(O, "#%d", imm);
			break;
		case SH_OP_MEM:
			PrintMemop(O, &info->op.operands[i].mem);
			break;
		}
		if (i < (info->op.op_count - 1)) {
			SStream_concat0(O, ",");
		}
	}
#endif
}

#ifndef CAPSTONE_DIET
static const name_map group_name_maps[] = {
	{ SH_GRP_INVALID, NULL },
	{ SH_GRP_JUMP, "jump" },
	{ SH_GRP_CALL, "call" },
	{ SH_GRP_INT, "int" },
	{ SH_GRP_RET, "ret" },
	{ SH_GRP_IRET, "iret" },
	{ SH_GRP_PRIVILEGE, "privilege" },
	{ SH_GRP_BRANCH_RELATIVE, "branch_relative" },
	{ SH_GRP_SH2, "SH2" },
	{ SH_GRP_SH2E, "SH2E" },
	{ SH_GRP_SH2DSP, "SH2-DSP" },
	{ SH_GRP_SH2A, "SH2A" },
	{ SH_GRP_SH2AFPU, "SH2A-FPU" },
	{ SH_GRP_SH3, "SH3" },
	{ SH_GRP_SH3DSP, "SH3-DSP" },
	{ SH_GRP_SH4, "SH4" },
	{ SH_GRP_SH4A, "SH4A" },
};
#endif

const char *SH_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
}
