// SPDX-FileCopyrightText: 2018 courk <courk@courk.cc>
// SPDX-License-Identifier: LGPL-3.0-only

#include "pic.h"
#include "pic_midrange.h"

static const char *pic_midrange_regname(ut32 reg) {
	const char *PicMidrangeBank0[] = {
		"indf0",
		"indf1",
		"pcl",
		"status",
		"fsr0l",
		"fsr0h",
		"fsr1l",
		"fsr1h",
		"bsr",
		"wreg",
		"pclath",
		"intcon",
		"porta",
		"portb",
		"portc",
		"portd",
		"porte",
		"pir1",
		"pir2",
		"pir3",
		"---",
		"tmr0",
		"tmr1l",
		"tmr1h",
		"t1con",
		"t1con",
		"tmr2",
		"pr2",
		"t2con",
		"---",
		"cpscon0",
		"cpscon1",
	};

	if (reg >= RZ_ARRAY_SIZE(PicMidrangeBank0)) {
		return NULL;
	}
	return PicMidrangeBank0[reg];
}

static const PicMidrangeOpAsmInfo pic_midrange_op_info[PIC_MIDRANGE_OPCODE_INVALID] = {
	{ "nop", PIC_MIDRANGE_OP_ARGS_NONE },
	{ "return", PIC_MIDRANGE_OP_ARGS_NONE },
	{ "retfie", PIC_MIDRANGE_OP_ARGS_NONE },
	{ "option", PIC_MIDRANGE_OP_ARGS_NONE },
	{ "sleep", PIC_MIDRANGE_OP_ARGS_NONE },
	{ "clrwdt", PIC_MIDRANGE_OP_ARGS_NONE },
	{ "clrf", PIC_MIDRANGE_OP_ARGS_7F },
	{ "clrw", PIC_MIDRANGE_OP_ARGS_NONE },
	{ "tris", PIC_MIDRANGE_OP_ARGS_2F },
	{ "movwf", PIC_MIDRANGE_OP_ARGS_7F },
	{ "subwf", PIC_MIDRANGE_OP_ARGS_1D_7F },
	{ "decf", PIC_MIDRANGE_OP_ARGS_1D_7F },
	{ "iorwf", PIC_MIDRANGE_OP_ARGS_1D_7F },
	{ "andwf", PIC_MIDRANGE_OP_ARGS_1D_7F },
	{ "xorwf", PIC_MIDRANGE_OP_ARGS_1D_7F },
	{ "addwf", PIC_MIDRANGE_OP_ARGS_1D_7F },
	{ "movf", PIC_MIDRANGE_OP_ARGS_1D_7F },
	{ "comf", PIC_MIDRANGE_OP_ARGS_1D_7F },
	{ "incf", PIC_MIDRANGE_OP_ARGS_1D_7F },
	{ "decfsz", PIC_MIDRANGE_OP_ARGS_1D_7F },
	{ "rrf", PIC_MIDRANGE_OP_ARGS_1D_7F },
	{ "rlf", PIC_MIDRANGE_OP_ARGS_1D_7F },
	{ "swapf", PIC_MIDRANGE_OP_ARGS_1D_7F },
	{ "incfsz", PIC_MIDRANGE_OP_ARGS_1D_7F },
	{ "bcf", PIC_MIDRANGE_OP_ARGS_3B_7F },
	{ "bsf", PIC_MIDRANGE_OP_ARGS_3B_7F },
	{ "btfsc", PIC_MIDRANGE_OP_ARGS_3B_7F },
	{ "btfss", PIC_MIDRANGE_OP_ARGS_3B_7F },
	{ "call", PIC_MIDRANGE_OP_ARGS_11K },
	{ "goto", PIC_MIDRANGE_OP_ARGS_11K },
	{ "movlw", PIC_MIDRANGE_OP_ARGS_8K },
	{ "retlw", PIC_MIDRANGE_OP_ARGS_8K },
	{ "iorlw", PIC_MIDRANGE_OP_ARGS_8K },
	{ "andlw", PIC_MIDRANGE_OP_ARGS_8K },
	{ "xorlw", PIC_MIDRANGE_OP_ARGS_8K },
	{ "sublw", PIC_MIDRANGE_OP_ARGS_8K },
	{ "addlw", PIC_MIDRANGE_OP_ARGS_8K },
	{ "reset", PIC_MIDRANGE_OP_ARGS_NONE },
	{ "callw", PIC_MIDRANGE_OP_ARGS_NONE },
	{ "brw", PIC_MIDRANGE_OP_ARGS_NONE },
	{ "moviw", PIC_MIDRANGE_OP_ARGS_1N_2M },
	{ "movwi", PIC_MIDRANGE_OP_ARGS_1N_2M },
	{ "movlb", PIC_MIDRANGE_OP_ARGS_4K },
	{ "lslf", PIC_MIDRANGE_OP_ARGS_1D_7F },
	{ "lsrf", PIC_MIDRANGE_OP_ARGS_1D_7F },
	{ "asrf", PIC_MIDRANGE_OP_ARGS_1D_7F },
	{ "subwfb", PIC_MIDRANGE_OP_ARGS_1D_7F },
	{ "addwfc", PIC_MIDRANGE_OP_ARGS_1D_7F },
	{ "addfsr", PIC_MIDRANGE_OP_ARGS_1N_6K },
	{ "movlp", PIC_MIDRANGE_OP_ARGS_7F },
	{ "bra", PIC_MIDRANGE_OP_ARGS_9K },
	{ "moviw", PIC_MIDRANGE_OP_ARGS_1N_6K },
	{ "movwi", PIC_MIDRANGE_OP_ARGS_1N_6K }
};

static const char *PicMidrangeFsrOps[] = { "++FSR%d", "--FSR%d", "FSR%d++",
	"FSR%d--" };

/**
 * \brief Decode a Pic 16 instruction to it's corresponding opcode enum.
 * */
PicMidrangeOpcode pic_midrange_get_opcode(ut16 instr) {
	switch (instr >> 11) { // 3 first MSB bits
	case 0x4: return PIC_MIDRANGE_OPCODE_CALL;
	case 0x5: return PIC_MIDRANGE_OPCODE_GOTO;
	}

	switch (instr >> 10) { // 4 first MSB bits
	case 0x4: return PIC_MIDRANGE_OPCODE_BCF;
	case 0x5: return PIC_MIDRANGE_OPCODE_BSF;
	case 0x6: return PIC_MIDRANGE_OPCODE_BTFSC;
	case 0x7: return PIC_MIDRANGE_OPCODE_BTFSS;
	case 0xd: return PIC_MIDRANGE_OPCODE_RETLW;
	}

	switch (instr >> 9) { // 5 first MSB bits
	case 0x19: return PIC_MIDRANGE_OPCODE_BRA;
	}

	switch (instr >> 8) { // 6 first MSB bits
	case 0x2: return PIC_MIDRANGE_OPCODE_SUBWF;
	case 0x3: return PIC_MIDRANGE_OPCODE_DECF;
	case 0x4: return PIC_MIDRANGE_OPCODE_IORWF;
	case 0x5: return PIC_MIDRANGE_OPCODE_ANDWF;
	case 0x6: return PIC_MIDRANGE_OPCODE_XORWF;
	case 0x7: return PIC_MIDRANGE_OPCODE_ADDWF;
	case 0x8: return PIC_MIDRANGE_OPCODE_MOVF;
	case 0x9: return PIC_MIDRANGE_OPCODE_COMF;
	case 0xa: return PIC_MIDRANGE_OPCODE_INCF;
	case 0xb: return PIC_MIDRANGE_OPCODE_DECFSZ;
	case 0xc: return PIC_MIDRANGE_OPCODE_RRF;
	case 0xd: return PIC_MIDRANGE_OPCODE_RLF;
	case 0xe: return PIC_MIDRANGE_OPCODE_SWAPF;
	case 0xf: return PIC_MIDRANGE_OPCODE_INCFSZ;
	case 0x38: return PIC_MIDRANGE_OPCODE_IORLW;
	case 0x39: return PIC_MIDRANGE_OPCODE_ANDLW;
	case 0x3a: return PIC_MIDRANGE_OPCODE_XORLW;
	case 0x30: return PIC_MIDRANGE_OPCODE_MOVLW;
	case 0x3c: return PIC_MIDRANGE_OPCODE_SUBLW;
	case 0x3e: return PIC_MIDRANGE_OPCODE_ADDLW;
	case 0x35: return PIC_MIDRANGE_OPCODE_LSLF;
	case 0x36: return PIC_MIDRANGE_OPCODE_LSRF;
	case 0x37: return PIC_MIDRANGE_OPCODE_ASRF;
	case 0x3b: return PIC_MIDRANGE_OPCODE_SUBWFB;
	case 0x3d: return PIC_MIDRANGE_OPCODE_ADDWFC;
	}

	switch (instr >> 7) { // 7 first MSB bits
	case 0x1: return PIC_MIDRANGE_OPCODE_MOVWF;
	case 0x2: return PIC_MIDRANGE_OPCODE_CLRW;
	case 0x3: return PIC_MIDRANGE_OPCODE_CLRF;
	case 0x62: return PIC_MIDRANGE_OPCODE_ADDFSR;
	case 0x63: return PIC_MIDRANGE_OPCODE_MOVLP;
	case 0x7e: return PIC_MIDRANGE_OPCODE_MOVIW_2;
	case 0x7f: return PIC_MIDRANGE_OPCODE_MOVWI_2;
	}

	switch (instr >> 5) { // 9 first MSB bits
	case 0x1: return PIC_MIDRANGE_OPCODE_MOVLB;
	}

	switch (instr >> 3) { // 11 first MSB bits
	case 0x2: return PIC_MIDRANGE_OPCODE_MOVIW_1;
	case 0x3: return PIC_MIDRANGE_OPCODE_MOVWI_1;
	}

	switch (instr >> 2) { // 12 first MSB bits
	case 0x19: return PIC_MIDRANGE_OPCODE_TRIS;
	}

	switch (instr) {
	case 0x0: return PIC_MIDRANGE_OPCODE_NOP;
	case 0x1: return PIC_MIDRANGE_OPCODE_RESET;
	case 0xa: return PIC_MIDRANGE_OPCODE_CALLW;
	case 0xb: return PIC_MIDRANGE_OPCODE_BRW;
	case 0x8: return PIC_MIDRANGE_OPCODE_RETURN;
	case 0x9: return PIC_MIDRANGE_OPCODE_RETFIE;
	case 0x62: return PIC_MIDRANGE_OPCODE_OPTION;
	case 0x63: return PIC_MIDRANGE_OPCODE_SLEEP;
	case 0x64: return PIC_MIDRANGE_OPCODE_CLRWDT;
	}

	return PIC_MIDRANGE_OPCODE_INVALID;
}

static void analysis_pic_midrange_extract_args(
	ut16 instr,
	PicMidrangeOpArgs args,
	PicMidrangeOpArgsVal *args_val) {

	memset(args_val, 0, sizeof(PicMidrangeOpArgsVal));

	switch (args) {
	case PIC_MIDRANGE_OP_ARGS_NONE: return;
	case PIC_MIDRANGE_OP_ARGS_2F:
		args_val->f = instr & PIC_MIDRANGE_OP_ARGS_2F_MASK_F;
		return;
	case PIC_MIDRANGE_OP_ARGS_7F:
		args_val->f = instr & PIC_MIDRANGE_OP_ARGS_7F_MASK_F;
		return;
	case PIC_MIDRANGE_OP_ARGS_1D_7F:
		args_val->f = instr & PIC_MIDRANGE_OP_ARGS_1D_7F_MASK_F;
		args_val->d =
			(instr & PIC_MIDRANGE_OP_ARGS_1D_7F_MASK_D) >> 7;
		return;
	case PIC_MIDRANGE_OP_ARGS_1N_6K: {
		args_val->n = (instr & PIC_MIDRANGE_OP_ARGS_1N_6K_MASK_N) >> 6;
		ut16 k = instr & PIC_MIDRANGE_OP_ARGS_1N_6K_MASK_K;
		args_val->k = SEXT(16, k, 6);
		return;
	}
	case PIC_MIDRANGE_OP_ARGS_3B_7F:
		args_val->b = (instr & PIC_MIDRANGE_OP_ARGS_3B_7F_MASK_B) >> 7;
		args_val->f = instr & PIC_MIDRANGE_OP_ARGS_3B_7F_MASK_F;
		return;
	case PIC_MIDRANGE_OP_ARGS_4K:
		args_val->k = instr & PIC_MIDRANGE_OP_ARGS_4K_MASK_K;
		return;
	case PIC_MIDRANGE_OP_ARGS_8K:
		args_val->k = instr & PIC_MIDRANGE_OP_ARGS_8K_MASK_K;
		return;
	case PIC_MIDRANGE_OP_ARGS_9K: {
		ut16 k = instr & PIC_MIDRANGE_OP_ARGS_9K_MASK_K;
		args_val->k = SEXT(16, k, 9);
		return;
	}
	case PIC_MIDRANGE_OP_ARGS_11K:
		args_val->k = instr & PIC_MIDRANGE_OP_ARGS_11K_MASK_K;
		return;
	case PIC_MIDRANGE_OP_ARGS_1N_2M:
		args_val->n = (instr & PIC_MIDRANGE_OP_ARGS_1N_2M_MASK_N) >> 2;
		args_val->m = instr & PIC_MIDRANGE_OP_ARGS_1N_2M_MASK_M;
		return;
	}
}

/**
 * \brief Get opcode information (mnemonic and arguments) corresponding
 * to a given \c PicMidrangeOpcode.
 *
 * \param opcode
 * \return \c PicMidrangeOpInfo pointer.
 * */
const PicMidrangeOpAsmInfo *pic_midrange_get_op_info(PicMidrangeOpcode opcode) {
	if (opcode >= PIC_MIDRANGE_OPCODE_INVALID) {
		return NULL;
	}
	return &pic_midrange_op_info[opcode];
}

#define F pic_midrange_regname(op->args.f)
#define K pic_midrange_op_args_k_for_op(&op->args, op->opcode)

bool pic_midrange_decode_op(RZ_OUT RZ_NONNULL PicMidrangeOp *op, ut64 addr, RZ_NONNULL const ut8 *b, ut64 l) {
	rz_return_val_if_fail(op && b, false);
	if (l < 2) {
		return false;
	}

	op->instr = rz_read_le16(b) & 0x3fff;
	PicMidrangeOpcode opcode = pic_midrange_get_opcode(op->instr);
	if (opcode == PIC_MIDRANGE_OPCODE_INVALID) {
		return false;
	}
	const PicMidrangeOpAsmInfo *op_info = pic_midrange_get_op_info(opcode);
	if (!op_info) {
		return false;
	}

	op->opcode = opcode;
	op->size = 2;
	op->addr = addr;
	op->mnemonic = op_info->mnemonic;
	analysis_pic_midrange_extract_args(op->instr, op_info->args, &op->args);

	switch (op_info->args) {
	case PIC_MIDRANGE_OP_ARGS_NONE:
		break;
	case PIC_MIDRANGE_OP_ARGS_2F:
	case PIC_MIDRANGE_OP_ARGS_7F:
		if (F) {
			rz_strf(op->operands, "%s", F);
		} else {
			rz_strf(op->operands, "0x%x", op->args.f);
		}
		break;
	case PIC_MIDRANGE_OP_ARGS_1D_7F:
		if (F) {
			rz_strf(op->operands, "%s, %d", F, op->args.d);
		} else {
			rz_strf(op->operands, "0x%x, %d", op->args.f, op->args.d);
		}
		break;
	case PIC_MIDRANGE_OP_ARGS_1N_6K:
		if (opcode == PIC_MIDRANGE_OPCODE_ADDFSR) {
			rz_strf(op->operands, "FSR%d, %s%#x", op->args.n, K < 0 ? "-" : "", (unsigned)abs(K));
		} else {
			rz_strf(op->operands, "%s%#x[FSR%d]", K < 0 ? "-" : "", (unsigned)abs(K), op->args.n);
		}
		break;
	case PIC_MIDRANGE_OP_ARGS_3B_7F:
		if (F) {
			rz_strf(op->operands, "%s, %d", F, op->args.b);
		} else {
			rz_strf(op->operands, "0x%x, %d", op->args.f, op->args.b);
		}
		break;
	case PIC_MIDRANGE_OP_ARGS_4K:
	case PIC_MIDRANGE_OP_ARGS_8K:
	case PIC_MIDRANGE_OP_ARGS_11K:
		rz_strf(op->operands, "0x%x", (unsigned int)K);
		break;
	case PIC_MIDRANGE_OP_ARGS_9K:
		rz_strf(op->operands, "%s%#x", K < 0 ? "-" : "", (unsigned)abs(K));
		break;
	case PIC_MIDRANGE_OP_ARGS_1N_2M:
		rz_strf(op->operands, PicMidrangeFsrOps[op->args.m], op->args.n);
		break;
	default:
		break;
	}
	return true;
}
