// SPDX-License-Identifier: GPL-2.0-or-later

/***************************************************************************
 *   Copyright (C) 2019 by Mete Balci                                      *
 *   metebalci@gmail.com                                                   *
 ***************************************************************************/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <helper/log.h>
#include "target.h"
#include "a64_disassembler.h"

#if HAVE_CAPSTONE

#include <capstone.h>

static void print_opcode(struct command_invocation *cmd, const cs_insn *insn)
{
	uint32_t opcode = 0;

	memcpy(&opcode, insn->bytes, insn->size);

	if (insn->size == 4) {

		uint16_t opcode_high = opcode >> 16;

		opcode = opcode & 0xffff;

		command_print(cmd,
				"0x%08" PRIx64"  %04x %04x\t%s\t%s",
				insn->address,
				opcode,
				opcode_high,
				insn->mnemonic,
				insn->op_str);

	} else {

		command_print(
				cmd,
				"0x%08" PRIx64"  %04x\t%s\t%s",
				insn->address,
				opcode,
				insn->mnemonic,
				insn->op_str);

	}
}

int a64_disassemble(struct command_invocation *cmd, struct target *target, target_addr_t address, size_t count)
{
	int ret;
	int csret;
	csh handle;

	csret = cs_open(CS_ARCH_ARM64, CS_MODE_LITTLE_ENDIAN, &handle);

	if (csret != CS_ERR_OK) {

		LOG_ERROR("cs_open() failed: %s", cs_strerror(csret));
		return ERROR_FAIL;

	}

	csret = cs_option(handle, CS_OPT_SKIPDATA, CS_OPT_ON);

	if (csret != CS_ERR_OK) {

		LOG_ERROR("cs_option() failed: %s", cs_strerror(csret));
		cs_close(&handle);
		return ERROR_FAIL;

	}

	cs_insn *insn = cs_malloc(handle);

	if (csret != CS_ERR_OK) {

		LOG_ERROR("cs_malloc() failed: %s", cs_strerror(csret));
		cs_close(&handle);
		return ERROR_FAIL;

	}

	while (count > 0) {

		uint8_t buffer[4];

		ret = target_read_buffer(target, address, sizeof(buffer), buffer);

		if (ret != ERROR_OK) {
			cs_free(insn, 1);
			cs_close(&handle);
			return ret;
		}

		size_t size = sizeof(buffer);
		const uint8_t *tmp = buffer;

		ret = cs_disasm_iter(handle, &tmp, &size, &address, insn);

		if (!ret) {

			LOG_ERROR("cs_disasm_iter() failed: %s", cs_strerror(cs_errno(handle)));
			cs_free(insn, 1);
			cs_close(&handle);
			return ERROR_FAIL;

		}

		print_opcode(cmd, insn);
		count--;

	}

	cs_free(insn, 1);
	cs_close(&handle);

	return ERROR_OK;
}

#else

int a64_disassemble(struct command_invocation *cmd, struct target *target, target_addr_t address, size_t count)
{
	command_print(cmd, "capstone disassembly framework required");

	return ERROR_FAIL;
}

#endif
