/* 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
}

