#include "capstone/cs_operand.h"
#include "capstone/riscv.h"
#include <stdint.h>
#include <float.h>
#include <math.h>
#ifdef CAPSTONE_HAS_RISCV

#include <string.h>

#include "../../Mapping.h"
#include "../../cs_simple_types.h"
#include "../../utils.h"

#include "RISCVMapping.h"

#define GET_INSTRINFO_ENUM
#include "RISCVGenInstrInfo.inc"

#define GET_REGINFO_ENUM
#define GET_REGINFO_MC_DESC
#include "RISCVGenRegisterInfo.inc"

#include "RISCVInstPrinter.h"

const char *RISCV_reg_name(csh handle, unsigned int reg)
{
	int syntax_opt = ((cs_struct *)(uintptr_t)handle)->syntax;

	if (syntax_opt & CS_OPT_SYNTAX_NOREGNAME) {
		return RISCV_LLVM_getRegisterName(reg, RISCV_NoRegAltName);
	}
	return RISCV_LLVM_getRegisterName(reg, RISCV_ABIRegAltName);
}

static const insn_map insns[] = {
#include "RISCVGenCSMappingInsn.inc"
};

const insn_map *RISCV_insns = insns;
const unsigned int RISCV_insn_count = ARR_SIZE(insns);

#ifndef CAPSTONE_DIET
static const map_insn_ops insn_operands[] = {
#include "RISCVGenCSMappingInsnOp.inc"
};

static const name_map insn_alias_mnem_map[] = {
#include "RISCVGenCSAliasMnemMap.inc"
};
#endif

void RISCV_add_cs_detail_0(MCInst *MI, riscv_op_group opgroup, unsigned OpNum)
{
	if (!detail_is_set(MI))
		return;
	// are not "true" arguments and has no Capstone equivalent
	if (opgroup == RISCV_OP_GROUP_FRMArg ||
	    opgroup == RISCV_OP_GROUP_FRMArgLegacy)
		return;

	if (opgroup == RISCV_OP_GROUP_FPImmOperand) {
		unsigned Imm = (unsigned)MCInst_getOperand(MI, OpNum)->ImmVal;
		cs_riscv_op *op = RISCV_get_detail_op_at(MI, OpNum);
		op->type = RISCV_OP_FP;
		op->access = (cs_ac_type)map_get_op_access(MI, OpNum);
		switch (Imm) {
		case 1: // min
			switch (MI->Opcode) {
			case RISCV_FLI_S:
				op->dimm = (double)FLT_MIN;
				break;
			case RISCV_FLI_D:
				op->dimm = (double)DBL_MIN;
				break;
			case RISCV_FLI_H:
				op->dimm = 6.103515625e-05;
				break;
			default:
				op->dimm = 0.0;
				break;
			}
			break;
		case 30: // inf
			op->dimm = INFINITY;
			break;
		case 31: // nan
			op->dimm = NAN;
			break;
		default:
			op->dimm = (double)getFPImm(Imm);
			break;
		}
		RISCV_inc_op_count(MI);
		return;
	}
	cs_riscv_op *op = RISCV_get_detail_op_at(MI, OpNum);
	op->type = (riscv_op_type)map_get_op_type(MI, OpNum);
	op->access = (cs_ac_type)map_get_op_access(MI, OpNum);
	switch (map_get_op_type(MI, OpNum)) {
	case CS_OP_REG:
		op->reg = MCInst_getOperand(MI, OpNum)->RegVal;
		break;
	case CS_OP_MEM:
		op->mem.base = 0;
		op->mem.disp = MCInst_getOperand(MI, OpNum)->ImmVal;
		break;
	case CS_OP_IMM: {
		uint64_t val = MCInst_getOperand(MI, OpNum)->ImmVal;
		if (opgroup != RISCV_OP_GROUP_CSRSystemRegister) {
			op->imm = val;
			if (opgroup == RISCV_OP_GROUP_BranchOperand) {
				op->imm += MI->address;
			}
		} else /* system register read-write */ {
			op->type = RISCV_OP_CSR;
			op->csr = val;
			// CSR instruction always read-writes the system operand
			op->access = CS_AC_READ_WRITE;
		}
		break;
	}
	case CS_OP_MEM_REG:
		op->type = (riscv_op_type)CS_OP_MEM;
		op->mem.base = MCInst_getOperand(MI, OpNum)->RegVal;
		break;
	case CS_OP_MEM_IMM:
		// fill in the disp in the last operand
		op = RISCV_get_detail_op_at(MI, OpNum - 1);
		op->type = (riscv_op_type)CS_OP_MEM;
		op->mem.disp = MCInst_getOperand(MI, OpNum)->ImmVal;
		RISCV_dec_op_count(
			MI); // don't increase the count, cancel the coming increment
		break;
	case CS_OP_INVALID:
		break;
	default: {
		CS_ASSERT(0 && "unhandled operand type");
	}
	}
	RISCV_inc_op_count(MI);
}

static inline void RISCV_add_adhoc_groups(MCInst *MI);

void RISCV_add_groups(MCInst *MI)
{
	if (!detail_is_set(MI))
		return;

	get_detail(MI)->groups_count = 0;

#ifndef CAPSTONE_DIET
	int i = 0;
	while (insns[MI->Opcode].groups[i] != 0) {
		add_group(MI, insns[MI->Opcode].groups[i]);
		i++;
	}
#endif

	RISCV_add_adhoc_groups(MI);
}

enum {
#define GET_ENUM_VALUES_RISCVOpcode
#include "RISCVGenCSSystemOperandsEnum.inc"
};

static inline void RISCV_add_privileged_group(MCInst *MI)
{
	const uint8_t *bytes = MI->flat_insn->bytes;
	uint8_t opcode = bytes[0] & 0x80;
	// no privileged instruction has a major opcode other than SYSTEM
	if (opcode != RISCV_RISCVOPCODE_SYSTEM) {
		return;
	}
	uint8_t func3 = (bytes[1] >> 4) & 0x7;
	// no privileged instruction has a minor opcode other than PRIV or PRIVM
	if (func3 != 0 && func3 != 0x4) {
		return;
	}
	uint16_t func12 = readBytes16(MI, &(bytes[2])) >> 4;
	// ecall and ebreak has SYSTEM and PRIV but aren't privileged
	if (func12 == 0 || func12 == 1) {
		return;
	}
	uint8_t func6 = func12 >> 6;
	// a subspace under extension-defined custom SYSTEM instructions that is not privileged
	if (func6 == 0x23 || func6 == 0x33) {
		return;
	}
	add_group(MI, RISCV_GRP_PRIVILEGE);
}

static inline void RISCV_add_interrupt_group(MCInst *MI)
{
	if (MI->Opcode == RISCV_ECALL || MI->Opcode == RISCV_EBREAK) {
		add_group(MI, RISCV_GRP_INT);
	}
}

static inline void RISCV_add_interrupt_ret_group(MCInst *MI)
{
	if (MI->Opcode == RISCV_MRET || MI->Opcode == RISCV_SRET) {
		add_group(MI, RISCV_GRP_IRET);
	}
}

// calls are implemented in RISCV as plain jumps that happen to set a link register containing the return address
// but this link register could be given as the null register x0, discarding the return address and making them jumps
static inline void RISCV_add_call_group(MCInst *MI)
{
	if (MI->Opcode == RISCV_JAL || MI->Opcode == RISCV_JALR) {
		cs_riscv_op *op = RISCV_get_detail_op_at(MI, 0);
		if ((op->type == (riscv_op_type)CS_OP_REG) &&
		    op->reg != RISCV_REG_X0 && (op->access & CS_AC_WRITE)) {
			add_group(MI, RISCV_GRP_CALL);
		}
		if (MI->Opcode == RISCV_JAL) {
			add_group(MI, RISCV_GRP_BRANCH_RELATIVE);
		}
	}
}

// returns are implemented in RISCV as a plain indirect jump that happen to reference the return address register ra == x1
static inline void RISCV_add_ret_group(MCInst *MI)
{
	if (MI->Opcode == RISCV_C_JR) {
		// indirect jumps whose source is ra
		cs_riscv_op *op = RISCV_get_detail_op_at(MI, 0);
		if ((op->type == (riscv_op_type)CS_OP_REG) &&
		    op->reg == RISCV_REG_X1) {
			add_group(MI, RISCV_GRP_RET);
		} else {
			add_group(MI, RISCV_GRP_JUMP);
		}
	}
	if (MI->Opcode == RISCV_JALR) {
		// indirect jumps whose source is ra
		cs_riscv_op *dstreg = RISCV_get_detail_op_at(MI, 0);
		cs_riscv_op *op = RISCV_get_detail_op_at(MI, 1);
		cs_riscv_op *op2 = RISCV_get_detail_op_at(MI, 2);
		if ((op->type == (riscv_op_type)CS_OP_REG) &&
		    op->reg == RISCV_REG_X1 &&
		    op2->type == (riscv_op_type)CS_OP_IMM && op2->imm == 0 &&
		    dstreg->type == (riscv_op_type)CS_OP_REG &&
		    dstreg->reg == RISCV_REG_X0) {
			add_group(MI, RISCV_GRP_RET);
		} else {
			if (!((dstreg->type == (riscv_op_type)CS_OP_REG) &&
			      dstreg->reg != RISCV_REG_X0 &&
			      (dstreg->access & CS_AC_WRITE))) {
				add_group(MI, RISCV_GRP_JUMP);
			}
		}
	}
}

static inline void RISCV_add_adhoc_groups(MCInst *MI)
{
	RISCV_add_privileged_group(MI);
	RISCV_add_interrupt_group(MI);
	RISCV_add_interrupt_ret_group(MI);
	RISCV_add_call_group(MI);
	RISCV_add_ret_group(MI);
}

// memset all stalled values in the detail struct to 0 before disassembling any next instruction
void RISCV_init_cs_detail(MCInst *MI)
{
	if (detail_is_set(MI))
		memset(get_detail(MI), 0,
		       offsetof(cs_detail, riscv) + sizeof(cs_riscv));
}

// for weird reasons some instructions end up with valid operands that are
// interspersed with invalid operands, i.e. the operands array is an "island"
// of valid operands with invalid gaps between them, this function will compactify
// all the valid operands and pad the rest of the array to invalid
void RISCV_compact_operands(MCInst *MI)
{
	if (!detail_is_set(MI))
		return;
	cs_riscv_op *ops = RISCV_get_detail(MI)->operands;
	unsigned int write_pos = 0;

	// Move valid elements to front
	for (unsigned int read_pos = 0; read_pos < NUM_RISCV_OPS; read_pos++) {
		if (ops[read_pos].type != (riscv_op_type)CS_OP_INVALID) {
			if (write_pos != read_pos) {
				ops[write_pos] = ops[read_pos];
			}
			write_pos++;
		}
	}
	// fill the rest, if any, with invalid
	memset((void *)(&ops[write_pos]), CS_OP_INVALID,
	       (NUM_RISCV_OPS - write_pos) * sizeof(cs_riscv_op));
}

// some RISC-V instructions have only 2 apparent operands, one of them is read-write
// the actual operand information for those instruction should have 3 operands, the first and second are the same operand,
// but once with read and once write access
// when those instructions are disassembled only the operand entry with the read access is used,
// and therefore the read-write operand is wrongly classified as only-read
// this logic tries to correct that
void RISCV_add_missing_write_access(MCInst *MI)
{
	if (!detail_is_set(MI))
		return;
	if (!isCompressed(MI))
		return;

	cs_riscv *riscv_details = RISCV_get_detail(MI);
	cs_riscv_op *ops = riscv_details->operands;
	// make the detection condition as specific as possible
	// so it doesn't accidentally trigger for other cases
	if (riscv_details->op_count == 2 && ops[0].type == RISCV_OP_INVALID &&
	    ops[1].type == RISCV_OP_REG && ops[1].access == CS_AC_READ) {
		ops[1].access |= CS_AC_WRITE;
	}
}

// given internal insn id, return public instruction info
void RISCV_get_insn_id(cs_struct *h, cs_insn *insn, unsigned int id)
{
	insn_map const *insn_map = NULL;

	if ((insn_map = lookup_insn_map(h, id))) {
		insn->id = insn_map->mapid;

		if (h->detail_opt) {
#ifndef CAPSTONE_DIET
			memcpy(insn->detail->regs_read, insn_map->regs_use,
			       sizeof(insn_map->regs_use));
			insn->detail->regs_read_count =
				(uint8_t)count_positive(insn_map->regs_use);

			memcpy(insn->detail->regs_write, insn_map->regs_mod,
			       sizeof(insn_map->regs_mod));
			insn->detail->regs_write_count =
				(uint8_t)count_positive(insn_map->regs_mod);

			memcpy(insn->detail->groups, insn_map->groups,
			       sizeof(insn_map->groups));
			insn->detail->groups_count =
				(uint8_t)count_positive8(insn_map->groups);

			if (insn_map->branch || insn_map->indirect_branch) {
				// this insn also belongs to JUMP group. add JUMP group
				insn->detail
					->groups[insn->detail->groups_count] =
					RISCV_GRP_JUMP;
				insn->detail->groups_count++;
			}
#endif
		}
	}
}

static const char *const insn_name_maps[] = {
#include "RISCVGenCSMappingInsnName.inc"
};

// called from RISCV_LLVM_printInstruction() to avoid exporting
// insn_alias_mnem_map and its size via extern declarations
void RISCV_set_alias_id(MCInst *MI, SStream *O)
{
#ifndef CAPSTONE_DIET
	map_set_alias_id(MI, O, insn_alias_mnem_map,
			 ARR_SIZE(insn_alias_mnem_map));
#endif
}

const char *RISCV_insn_name(csh handle, unsigned int id)
{
#ifndef CAPSTONE_DIET
	if (id < RISCV_INS_ENDING)
		return insn_name_maps[id];

	if (id < RISCV_INS_ALIAS_END)
		return insn_alias_mnem_map[id - RISCV_INS_ALIAS_BEGIN - 1].name;
#endif
	return NULL;
}

#ifndef CAPSTONE_DIET
static const name_map group_name_maps[] = {
	// generic groups
	{ RISCV_GRP_INVALID, NULL },
	{ RISCV_GRP_JUMP, "jump" },
	{ RISCV_GRP_CALL, "call" },
	{ RISCV_GRP_RET, "ret" },
	{ RISCV_GRP_INT, "int" },
	{ RISCV_GRP_IRET, "iret" },
	{ RISCV_GRP_PRIVILEGE, "privileged" },
	{ RISCV_GRP_BRANCH_RELATIVE, "branch_relative" },

// architecture specific
#include "RISCVGenCSFeatureName.inc"

	{ RISCV_GRP_ENDING, NULL }
};
#endif

const char *RISCV_group_name(csh handle, unsigned int id)
{
#ifndef CAPSTONE_DIET
	// verify group id
	// if past the end
	if (id >= RISCV_GRP_ENDING ||
	    // or in the encoding gap between generic groups and arch-specific groups
	    (id > RISCV_GRP_BRANCH_RELATIVE && id < RISCV_FEATURE_HASSTDEXTI))
		return NULL;
	return id2name(group_name_maps, ARR_SIZE(group_name_maps), id);
#else
	return NULL;
#endif
}

// map instruction name to public instruction ID
riscv_insn RISCV_map_insn(const char *name)
{
	unsigned int i;
	for (i = 1; i < ARR_SIZE(insn_name_maps); i++) {
		if (!strcmp(name, insn_name_maps[i]))
			return i;
	}
#ifndef CAPSTONE_DIET
	for (i = 0; i < ARR_SIZE(insn_alias_mnem_map); i++) {
		if (!strcmp(name, insn_alias_mnem_map[i].name))
			return insn_alias_mnem_map[i].id;
	}
#endif
	return RISCV_INS_INVALID;
}

void RISCV_reg_access(const cs_insn *insn, cs_regs regs_read,
		      uint8_t *regs_read_count, cs_regs regs_write,
		      uint8_t *regs_write_count)
{
	const cs_riscv *riscv = &(insn->detail->riscv);
	uint8_t read_count = 0;
	uint8_t write_count = 0;

	for (int j = 0; j < riscv->op_count; j++) {
		const cs_riscv_op *op = &riscv->operands[j];

		if (op->type == RISCV_OP_REG) {
			if ((op->access & CS_AC_WRITE) &&
			    !arr_exist(regs_write, write_count, op->reg)) {
				regs_write[write_count++] = (uint16_t)op->reg;
			}
			if ((op->access & CS_AC_READ) &&
			    !arr_exist(regs_read, read_count, op->reg)) {
				regs_read[read_count++] = (uint16_t)op->reg;
			}
		} else if (op->type == RISCV_OP_MEM) {
			if (op->mem.base != RISCV_REG_INVALID &&
			    !arr_exist(regs_read, read_count, op->mem.base)) {
				regs_read[read_count++] =
					(uint16_t)op->mem.base;
			}
		}
	}

	*regs_read_count = read_count;
	*regs_write_count = write_count;
}

void RISCV_init(MCRegisterInfo *MRI)
{
	MCRegisterInfo_InitMCRegisterInfo(MRI, RISCVRegDesc, RISCV_REG_ENDING,
					  0, 0, RISCVMCRegisterClasses,
					  ARR_SIZE(RISCVMCRegisterClasses), 0,
					  0, RISCVRegDiffLists, 0,
					  RISCVSubRegIdxLists,
					  ARR_SIZE(RISCVSubRegIdxLists), 0);
}

#endif
