// SPDX-FileCopyrightText: 2015 danielps
// SPDX-License-Identifier: LGPL-3.0-only

#include <rz_types.h>
#include <rz_util.h>

#include "v810_disas.h"

static const char *instrs[] = {
	[V810_MOV] = "mov",
	[V810_ADD] = "add",
	[V810_SUB] = "sub",
	[V810_CMP] = "cmp",
	[V810_SHL] = "shl",
	[V810_SHR] = "shr",
	[V810_JMP] = "jmp",
	[V810_SAR] = "sar",
	[V810_MUL] = "mul",
	[V810_DIV] = "div",
	[V810_MULU] = "mulu",
	[V810_DIVU] = "divu",
	[V810_OR] = "or",
	[V810_AND] = "and",
	[V810_XOR] = "xor",
	[V810_NOT] = "not",
	[V810_MOV_IMM5] = "mov",
	[V810_ADD_IMM5] = "add",
	[V810_CMP_IMM5] = "cmp",
	[V810_SHL_IMM5] = "shl",
	[V810_SHR_IMM5] = "shr",
	[V810_SAR_IMM5] = "sar",
	[V810_MOVEA] = "movea",
	[V810_MOVHI] = "movhi",
	[V810_ADDI] = "addi",
	[V810_ORI] = "ori",
	[V810_ANDI] = "andi",
	[V810_XORI] = "xori",
	[V810_JR] = "jr",
	[V810_JAL] = "jal",
	[V810_LDB] = "ld.b",
	[V810_LDH] = "ld.h",
	[V810_LDW] = "ld.w",
	[V810_STB] = "st.b",
	[V810_STH] = "st.h",
	[V810_STW] = "st.w",
	[V810_INB] = "in.b",
	[V810_INH] = "in.h",
	[V810_CAXI] = "caxi",
	[V810_INW] = "in.w",
	[V810_OUTB] = "out.b",
	[V810_OUTH] = "out.h",
	[V810_OUTW] = "out.w",
	[V810_SETF] = "setf",
	[V810_LDSR] = "ldsr",
	[V810_STSR] = "stsr",
	[V810_TRAP] = "trap",
	[V810_HALT] = "halt",
	[V810_RETI] = "reti",
	[V810_SEI] = "sei",
	[V810_CLI] = "cli",
};

static const char *bit_instrs[] = {
	[V810_BIT_SCH0U] = "sch0bsu",
	[V810_BIT_SCH0D] = "sch0bsd",
	[V810_BIT_SCH1U] = "sch1bsu",
	[V810_BIT_SCH1D] = "sch1bsd",
	[V810_BIT_ORU] = "orbsu",
	[V810_BIT_ANDU] = "andbsu",
	[V810_BIT_XORU] = "xorbsu",
	[V810_BIT_MOVU] = "movbsu",
	[V810_BIT_ORNU] = "ornbsu",
	[V810_BIT_ANDNU] = "andnbsu",
	[V810_BIT_XORNU] = "xornbsu",
	[V810_BIT_NOTU] = "notbsu",
};

static const char *ext_instrs[] = {
	[V810_EXT_CMPF_S] = "cmpf.s",
	[V810_EXT_CVT_WS] = "cvt.ws",
	[V810_EXT_CVT_SW] = "cvt.sw",
	[V810_EXT_ADDF_S] = "addf.s",
	[V810_EXT_SUBF_S] = "subf.s",
	[V810_EXT_MULF_S] = "mulf.s",
	[V810_EXT_DIVF_S] = "divf.s",
	[V810_EXT_XB] = "xb",
	[V810_EXT_XH] = "xh",
	[V810_EXT_REV] = "rev",
	[V810_EXT_TRNC_SW] = "trnc.sw",
	[V810_EXT_MPYHW] = "mpyhw",
};

static const char *conds[] = {
	[V810_COND_V] = "v",
	[V810_COND_L] = "l",
	[V810_COND_E] = "e",
	[V810_COND_NH] = "nh",
	[V810_COND_N] = "n",
	[V810_COND_NONE] = "r",
	[V810_COND_LT] = "lt",
	[V810_COND_LE] = "le",
	[V810_COND_NV] = "nv",
	[V810_COND_NL] = "nl",
	[V810_COND_NE] = "ne",
	[V810_COND_H] = "h",
	[V810_COND_P] = "p",
	[V810_COND_GE] = "ge",
	[V810_COND_GT] = "gt",
};

static const char *sysreg_names[] = {
	[V810_SREG_EIPC] = "EIPC",
	[V810_SREG_EIPSW] = "EIPSW",
	[V810_SREG_FEPC] = "FEPC",
	[V810_SREG_FEPSW] = "FEPSW",
	[V810_SREG_ECR] = "ECR",
	[V810_SREG_PSW] = "PSW",
	[V810_SREG_PIR] = "PIR",
	[V810_SREG_TKCW] = "TKCW",
	[V810_SREG_CHCW] = "CHCW",
	[V810_SREG_ADTRE] = "ADTRE",
};

static int decode_reg_reg(const ut16 instr, struct v810_cmd *cmd) {
	ut8 opcode;

	opcode = OPCODE(instr);

	if (opcode >= sizeof(instrs) / sizeof(char *)) {
		return -1;
	}

	snprintf(cmd->instr, V810_INSTR_MAXLEN - 1, "%s", instrs[opcode]);

	if (opcode == V810_JMP) {
		snprintf(cmd->operands, V810_INSTR_MAXLEN - 1, "[r%u]",
			REG1(instr));
	} else {
		snprintf(cmd->operands, V810_INSTR_MAXLEN - 1, "r%u, r%u",
			REG1(instr), REG2(instr));
	}

	return 2;
}

static int decode_imm_reg(const ut16 instr, struct v810_cmd *cmd) {
	ut8 opcode;
	ut8 immed;

	opcode = OPCODE(instr);

	if (opcode >= sizeof(instrs) / sizeof(char *)) {
		return -1;
	}

	snprintf(cmd->instr, V810_INSTR_MAXLEN - 1, "%s", instrs[opcode]);

	immed = IMM5(instr);

	switch (opcode) {
	case V810_MOV_IMM5:
	case V810_ADD_IMM5:
	case V810_CMP_IMM5:
		snprintf(cmd->operands, V810_INSTR_MAXLEN - 1, "%d, r%u",
			(st8)SIGN_EXT_T5(immed), REG2(instr));
		break;
	case V810_LDSR:
	case V810_STSR:
		if (immed > 0x19 || (immed > 0x7 && immed < 0x18)) {
			snprintf(cmd->operands, V810_INSTR_MAXLEN - 1, "s%u, r%u",
				(ut8)immed, REG2(instr));
		} else if (sysreg_names[immed]) {
			snprintf(cmd->operands, V810_INSTR_MAXLEN - 1, "%s, r%u",
				sysreg_names[immed], REG2(instr));
		}
		break;
	case V810_SETF:
	case V810_SHL_IMM5:
	case V810_SHR_IMM5:
	case V810_SAR_IMM5:
		snprintf(cmd->operands, V810_INSTR_MAXLEN - 1, "%u, r%u",
			immed, REG2(instr));
		break;
	case V810_TRAP:
		snprintf(cmd->operands, V810_INSTR_MAXLEN - 1, "%u",
			immed);
		break;
	}

	return 2;
}

static int decode_bcond(const ut16 instr, struct v810_cmd *cmd) {
	st16 disp;
	ut8 cond;

	cond = (instr >> 9) & 0xF;
	disp = DISP9(instr);

	if (cond == V810_COND_NOP) {
		snprintf(cmd->instr, V810_INSTR_MAXLEN - 1, "nop");
	} else {
		snprintf(cmd->instr, V810_INSTR_MAXLEN - 1, "b%s", conds[cond]);
		snprintf(cmd->operands, V810_INSTR_MAXLEN - 1, "%d", disp);
	}

	return 2;
}

static int decode_jump(const ut16 word1, const ut16 word2, struct v810_cmd *cmd) {
	snprintf(cmd->instr, V810_INSTR_MAXLEN - 1, "%s",
		instrs[OPCODE(word1)]);
	snprintf(cmd->operands, V810_INSTR_MAXLEN - 1, "%d",
		DISP26(word1, word2));

	return 4;
}

static int decode_3operands(const ut16 word1, const ut16 word2, struct v810_cmd *cmd) {
	snprintf(cmd->instr, V810_INSTR_MAXLEN - 1, "%s",
		instrs[OPCODE(word1)]);

	if (OPCODE(word1) == V810_ADDI) {
		snprintf(cmd->operands, V810_INSTR_MAXLEN - 1, "%d, r%d, r%d",
			(st16)word2, REG1(word1), REG2(word1));
	} else {
		snprintf(cmd->operands, V810_INSTR_MAXLEN - 1, "0x%x, r%d, r%d",
			word2, REG1(word1), REG2(word1));
	}

	return 4;
}

static int decode_load_store(const ut16 word1, const ut16 word2, struct v810_cmd *cmd) {
	snprintf(cmd->instr, V810_INSTR_MAXLEN - 1, "%s",
		instrs[OPCODE(word1)]);

	switch (OPCODE(word1)) {
	case V810_STB:
	case V810_STH:
	case V810_STW:
	case V810_OUTB:
	case V810_OUTH:
	case V810_OUTW:
		snprintf(cmd->operands, V810_INSTR_MAXLEN - 1,
			"r%d, %hd[r%d]",
			REG2(word1), (st16)word2, REG1(word1));
		break;
	case V810_LDB:
	case V810_LDH:
	case V810_LDW:
	case V810_INB:
	case V810_INH:
	case V810_INW:
	case V810_CAXI:
		snprintf(cmd->operands, V810_INSTR_MAXLEN - 1,
			"%hd[r%d], r%d",
			(st16)word2, REG1(word1), REG2(word1));
		break;
	}

	return 4;
}

static int decode_bit_op(const ut16 instr, struct v810_cmd *cmd) {
	ut8 subop;

	subop = REG1(instr);
	if (subop >= RZ_ARRAY_SIZE(bit_instrs)) {
		return 0;
	}
	snprintf(cmd->instr, V810_INSTR_MAXLEN - 1, "%s", bit_instrs[subop]);

	return 2;
}

static int decode_extended(const ut16 word1, const ut16 word2, struct v810_cmd *cmd) {
	ut8 subop = OPCODE(word2);
	if (subop > 0xC) {
		return -1;
	}

	snprintf(cmd->instr, V810_INSTR_MAXLEN - 1, "%s",
		ext_instrs[subop]);

	switch (subop) {
	case V810_EXT_CMPF_S:
	case V810_EXT_CVT_WS:
	case V810_EXT_CVT_SW:
	case V810_EXT_ADDF_S:
	case V810_EXT_SUBF_S:
	case V810_EXT_MULF_S:
	case V810_EXT_DIVF_S:
	case V810_EXT_REV:
	case V810_EXT_TRNC_SW:
	case V810_EXT_MPYHW:
		snprintf(cmd->operands, V810_INSTR_MAXLEN - 1, "r%d, r%d",
			REG1(word1), REG2(word1));
		break;
	case V810_EXT_XB:
	case V810_EXT_XH:
		snprintf(cmd->operands, V810_INSTR_MAXLEN - 1, "r%d",
			REG2(word1));
		break;
	default:
		return -1;
	}

	return 4;
}

int v810_decode_command(const ut8 *instr, int len, struct v810_cmd *cmd) {
	int ret;
	ut16 word1 = 0;
	ut16 word2 = 0;

	word1 = rz_read_le16(instr);
	if (len >= 4) {
		word2 = rz_read_le16(instr + 2);
	}

	switch (OPCODE(word1)) {
	case V810_MOV:
	case V810_ADD:
	case V810_SUB:
	case V810_CMP:
	case V810_SHL:
	case V810_SHR:
	case V810_JMP:
	case V810_SAR:
	case V810_MUL:
	case V810_DIV:
	case V810_MULU:
	case V810_DIVU:
	case V810_OR:
	case V810_AND:
	case V810_NOT:
	case V810_XOR:
		ret = decode_reg_reg(word1, cmd);
		break;
	case V810_MOV_IMM5:
	case V810_ADD_IMM5:
	case V810_SETF:
	case V810_CMP_IMM5:
	case V810_SHL_IMM5:
	case V810_SHR_IMM5:
	case V810_CLI:
	case V810_SAR_IMM5:
	case V810_TRAP:
	case V810_RETI:
	case V810_HALT:
	case V810_LDSR:
	case V810_STSR:
	case V810_SEI:
		ret = decode_imm_reg(word1, cmd);
		break;
	case V810_MOVEA:
	case V810_ADDI:
	case V810_ORI:
	case V810_ANDI:
	case V810_XORI:
	case V810_MOVHI:
		ret = decode_3operands(word1, word2, cmd);
		break;
	case V810_JR:
	case V810_JAL:
		ret = decode_jump(word1, word2, cmd);
		break;
	case V810_LDB:
	case V810_LDH:
	case V810_LDW:
	case V810_STB:
	case V810_STH:
	case V810_STW:
	case V810_INB:
	case V810_INH:
	case V810_CAXI:
	case V810_INW:
	case V810_OUTB:
	case V810_OUTH:
	case V810_OUTW:
		ret = decode_load_store(word1, word2, cmd);
		break;
	case V810_BSTR:
		ret = decode_bit_op(word1, cmd);
		break;
	case V810_EXT:
		ret = decode_extended(word1, word2, cmd);
		break;
	default:
		if ((OPCODE(word1) >> 3) == 0x4) {
			ret = decode_bcond(word1, cmd);
		} else {
			ret = -1;
		}
	}

	if ((ret > 0) && (len < ret)) {
		ret = -1;
	}

	return ret;
}
