// SPDX-FileCopyrightText: 2023 Bastian Engel <bastian.engel00@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only

#include <rz_types.h>
#include <rz_util.h>
#include <rz_lib.h>
#include <rz_asm.h>
#include <rz_analysis.h>
#include "rl78/rl78_instr.h"
#include "rl78/rl78.h"

static int rl78_op(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr,
	const ut8 *buf, int len, RzAnalysisOpMask mask);
static void populate_jump_fields(const RL78Instr *instr, size_t instr_size, ut64 addr, RzAnalysisOp *op);
static char *get_reg_profile(RzAnalysis *analysis);

static int rl78_op(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr,
	const ut8 *buf, int len, RzAnalysisOpMask mask) {
	op->addr = addr;
	op->type = RZ_ANALYSIS_OP_TYPE_ILL;

	RL78Instr instr = { 0 };
	size_t bytes_read = 0;
	if (!rl78_dis(&instr, &bytes_read, buf, len)) {
		op->size = bytes_read;
		return bytes_read;
	} else {
		op->type = RZ_ANALYSIS_OP_TYPE_UNK;
	}

	op->size = bytes_read;
	op->addr = addr;
	switch (instr.operation) {
	case RL78_OPERATION_ADD:
	case RL78_OPERATION_ADDC:
	case RL78_OPERATION_ADDW:
		op->type = RZ_ANALYSIS_OP_TYPE_ADD;
		break;
	case RL78_OPERATION_AND:
	case RL78_OPERATION_AND1:
		op->type = RZ_ANALYSIS_OP_TYPE_AND;
		break;
	case RL78_OPERATION_BC:
	case RL78_OPERATION_BF:
	case RL78_OPERATION_BH:
	case RL78_OPERATION_BNC:
	case RL78_OPERATION_BNH:
	case RL78_OPERATION_BNZ:
	case RL78_OPERATION_BT:
	case RL78_OPERATION_BTCLR:
	case RL78_OPERATION_BZ:
		op->type = RZ_ANALYSIS_OP_TYPE_CJMP;
		populate_jump_fields(&instr, bytes_read, addr, op);
		break;
	case RL78_OPERATION_BR:
		op->type = RZ_ANALYSIS_OP_TYPE_JMP;
		populate_jump_fields(&instr, bytes_read, addr, op);
		break;
	// conditional skip instructions
	case RL78_OPERATION_SKC:
	case RL78_OPERATION_SKH:
	case RL78_OPERATION_SKNC:
	case RL78_OPERATION_SKNH:
	case RL78_OPERATION_SKNZ:
	case RL78_OPERATION_SKZ:
		// TODO set op->jump to instruction after next (i.e. skip)
		op->type = RZ_ANALYSIS_OP_TYPE_CJMP;
		break;
	case RL78_OPERATION_BRK:
		op->type = RZ_ANALYSIS_OP_TYPE_SWI;
		break;
	case RL78_OPERATION_CALL:
	case RL78_OPERATION_CALLT:
		op->type = RZ_ANALYSIS_OP_TYPE_CALL;
		populate_jump_fields(&instr, bytes_read, addr, op);
		break;
	case RL78_OPERATION_CLRB:
	case RL78_OPERATION_CLRW:
	case RL78_OPERATION_CLR1:
		// TODO (byte/word/bitclear)
		break;
	case RL78_OPERATION_CMP:
	case RL78_OPERATION_CMPS:
	case RL78_OPERATION_CMPW:
	case RL78_OPERATION_CMP0:
		op->type = RZ_ANALYSIS_OP_TYPE_CMP;
		break;
	case RL78_OPERATION_DEC:
	case RL78_OPERATION_DECW:
	case RL78_OPERATION_SUB:
	case RL78_OPERATION_SUBC:
	case RL78_OPERATION_SUBW:
		op->type = RZ_ANALYSIS_OP_TYPE_SUB;
		break;
	case RL78_OPERATION_EI:
	case RL78_OPERATION_DI:
		// TODO (interrupt enable/disable)
		break;
	case RL78_OPERATION_DIVHU:
	case RL78_OPERATION_DIVWU:
		op->type = RZ_ANALYSIS_OP_TYPE_DIV;
		break;
	case RL78_OPERATION_HALT:
		// TODO (halt)
		break;
	case RL78_OPERATION_INC:
	case RL78_OPERATION_INCW:
		op->type = RZ_ANALYSIS_OP_TYPE_ADD;
		break;
	case RL78_OPERATION_MULH:
	case RL78_OPERATION_MULHU:
	case RL78_OPERATION_MULU:
		// multiply-and-accumulate
	case RL78_OPERATION_MACH:
	case RL78_OPERATION_MACHU:
		op->type = RZ_ANALYSIS_OP_TYPE_MUL;
		break;
	case RL78_OPERATION_MOV:
	case RL78_OPERATION_MOVS:
	case RL78_OPERATION_MOVW:
	case RL78_OPERATION_MOV1:
		op->type = RZ_ANALYSIS_OP_TYPE_MOV;
		break;
	case RL78_OPERATION_NOP:
		op->type = RZ_ANALYSIS_OP_TYPE_NOP;
		break;
	case RL78_OPERATION_NOT1:
		op->type = RZ_ANALYSIS_OP_TYPE_NOT;
		break;
	case RL78_OPERATION_ONEB:
	case RL78_OPERATION_ONEW:
		// TODO (byte/word set to 1)
		break;
	case RL78_OPERATION_OR:
	case RL78_OPERATION_OR1:
		op->type = RZ_ANALYSIS_OP_TYPE_OR;
		break;
	case RL78_OPERATION_POP:
		op->type = RZ_ANALYSIS_OP_TYPE_POP;
		break;
	case RL78_OPERATION_PUSH:
		op->type = RZ_ANALYSIS_OP_TYPE_PUSH;
		break;
	case RL78_OPERATION_RET:
		op->type = RZ_ANALYSIS_OP_TYPE_RET;
		break;
	case RL78_OPERATION_RETB:
	case RL78_OPERATION_RETI:
		// TODO (return from software/hardware vectored interrupt)
		break;
	case RL78_OPERATION_ROL:
	case RL78_OPERATION_ROLC:
	case RL78_OPERATION_ROLWC:
		op->type = RZ_ANALYSIS_OP_TYPE_ROL;
		break;
	case RL78_OPERATION_ROR:
	case RL78_OPERATION_RORC:
		op->type = RZ_ANALYSIS_OP_TYPE_ROR;
		break;
	case RL78_OPERATION_SAR:
	case RL78_OPERATION_SARW:
		op->type = RZ_ANALYSIS_OP_TYPE_SHR;
		break;
	case RL78_OPERATION_SEL:
	case RL78_OPERATION_SET1:
	case RL78_OPERATION_SHL:
	case RL78_OPERATION_SHLW:
	case RL78_OPERATION_SHR:
	case RL78_OPERATION_SHRW:
		op->type = RZ_ANALYSIS_OP_TYPE_SHL;
		break;
	case RL78_OPERATION_STOP:
		// TODO (stop mode set)
		break;
	case RL78_OPERATION_XCH:
	case RL78_OPERATION_XCHW:
		op->type = RZ_ANALYSIS_OP_TYPE_XCHG;
		break;
	case RL78_OPERATION_XOR:
	case RL78_OPERATION_XOR1:
		op->type = RZ_ANALYSIS_OP_TYPE_XOR;
		break;
	default:
		rz_warn_if_reached();
		break;
	}

	return op->size;
}

static void populate_jump_fields(const RL78Instr *instr, size_t instr_size, ut64 addr, RzAnalysisOp *op) {
	const RL78Operand *target = &instr->op0;
	if (instr->operation == RL78_OPERATION_BT ||
		instr->operation == RL78_OPERATION_BF ||
		instr->operation == RL78_OPERATION_BTCLR) {
		target = &instr->op1;
	}

	switch (target->type) {
	case RL78_OP_TYPE_ABSOLUTE_ADDR_20:
	case RL78_OP_TYPE_ABSOLUTE_ADDR_16:
		op->jump = target->v0;
		break;
	case RL78_OP_TYPE_RELATIVE_ADDR_16:
	case RL78_OP_TYPE_RELATIVE_ADDR_8:
		op->jump = addr + target->v0;
	case RL78_OP_TYPE_SYMBOL:
		// TODO indirect call to symbol (CALL AX)
		break;
	default:
		rz_warn_if_reached();
		break;
	}

	if (op->type == RZ_ANALYSIS_OP_TYPE_CJMP) {
		op->fail = addr + instr_size;
	}
}

static char *get_reg_profile(RzAnalysis *analysis) {
	const char *p =
		"=PC	pc\n"
		"=SP	sp\n"
		"=ZF	z\n"
		"=CF	cy\n"
		"=SN	%s\n" // x8 on linux or android, x16 for the rest
		// ABI: https://www.renesas.com/eu/en/document/mat/cc-rl-compiler-users-manual
		"=A0	ax\n"
		"=A1	bc\n"
		"=A2	de\n"
		// general-purpose registers
		"gpr	hl	.16	0	0\n"
		"gpr	de	.16	0	0\n"
		"gpr	bc	.16	0	0\n"
		"gpr	ax	.16	0	0\n"
		"gpr	h	.8	0	0\n"
		"gpr	l	.8	0	0\n"
		"gpr	d	.8	0	0\n"
		"gpr	e	.8	0	0\n"
		"gpr	b	.8	0	0\n"
		"gpr	c	.8	0	0\n"
		"gpr	a	.8	0	0\n"
		"gpr	x	.8	0	0\n"

		// flags
		"flg	psw	.8	0	0       ie_z_rbs1_ac_rbs0_isp1_isp0_cy\n"
		"flg	ie	.1	0	0	interrupt_enable\n"
		"flg	z	.1	0	0	zero\n"
		"flg	rbs1	.1	0	0	register_bank_select_bit_1\n"
		"flg	ac	.1	0	0	auxiliary_carry\n" // set if carry or borrow at bit 3
		"flg	rbs0	.1	0	0	register_bank_select_bit_0\n"
		"flg	isp1	.1	0	0	in_service_priority_flags_bit_1\n"
		"flg	isp0	.1	0	0	in_service_priority_flags_bit_0\n"
		"flg	cy	.1	0	0	carry\n";

	return rz_str_dup(p);
}

static int rl78_archinfo(RzAnalysis *a, RzAnalysisInfoType query) {
	switch (query) {
	case RZ_ANALYSIS_ARCHINFO_MIN_OP_SIZE:
		return 1;
	case RZ_ANALYSIS_ARCHINFO_MAX_OP_SIZE:
		return 5;
	case RZ_ANALYSIS_ARCHINFO_TEXT_ALIGN:
		return 1;
	case RZ_ANALYSIS_ARCHINFO_DATA_ALIGN:
		return 1;
	case RZ_ANALYSIS_ARCHINFO_CAN_USE_POINTERS:
		return true;
	default:
		return -1;
	}
}

RzAnalysisPlugin rz_analysis_plugin_rl78 = {
	.name = "rl78",
	.desc = "Renesas RL78 analysis plugin",
	.license = "LGPL3",
	.arch = "rl78",
	.bits = 16,
	.op = &rl78_op,
	.archinfo = rl78_archinfo,
	.get_reg_profile = &get_reg_profile
};
