// Copyright © 2024 Rot127 <unisono@quyllur.org>
// SPDX-License-Identifier: BSD-3

#include "test_detail.h"
#include "test_compare.h"
#include <capstone/capstone.h>

TestDetail *test_detail_new()
{
	return cs_mem_calloc(sizeof(TestDetail), 1);
}

TestDetail *test_detail_clone(TestDetail *detail)
{
	assert(detail);
	TestDetail *clone = test_detail_new();

	clone->regs_read =
		detail->regs_read_count > 0 ?
			cs_mem_calloc(sizeof(char *), detail->regs_read_count) :
			NULL;
	clone->regs_read_count = detail->regs_read_count;
	for (size_t i = 0; i < detail->regs_read_count; ++i) {
		clone->regs_read[i] = strdup(detail->regs_read[i]);
	}

	clone->regs_write = detail->regs_write_count > 0 ?
				    cs_mem_calloc(sizeof(char *),
						  detail->regs_write_count) :
				    NULL;
	clone->regs_write_count = detail->regs_write_count;
	for (size_t i = 0; i < detail->regs_write_count; ++i) {
		clone->regs_write[i] = strdup(detail->regs_write[i]);
	}

	clone->regs_impl_read =
		detail->regs_impl_read_count > 0 ?
			cs_mem_calloc(sizeof(char *),
				      detail->regs_impl_read_count) :
			NULL;
	clone->regs_impl_read_count = detail->regs_impl_read_count;
	for (size_t i = 0; i < detail->regs_impl_read_count; ++i) {
		clone->regs_impl_read[i] = strdup(detail->regs_impl_read[i]);
	}

	clone->regs_impl_write =
		detail->regs_impl_write_count > 0 ?
			cs_mem_calloc(sizeof(char *),
				      detail->regs_impl_write_count) :
			NULL;
	clone->regs_impl_write_count = detail->regs_impl_write_count;
	for (size_t i = 0; i < detail->regs_impl_write_count; ++i) {
		clone->regs_impl_write[i] = strdup(detail->regs_impl_write[i]);
	}

	clone->groups =
		detail->groups_count > 0 ?
			cs_mem_calloc(sizeof(char *), detail->groups_count) :
			NULL;
	clone->groups_count = detail->groups_count;
	for (size_t i = 0; i < detail->groups_count; ++i) {
		clone->groups[i] = strdup(detail->groups[i]);
	}

	if (detail->aarch64) {
		clone->aarch64 = test_detail_aarch64_clone(detail->aarch64);
	}
	if (detail->arm) {
		clone->arm = test_detail_arm_clone(detail->arm);
	}
	if (detail->ppc) {
		clone->ppc = test_detail_ppc_clone(detail->ppc);
	}
	if (detail->tricore) {
		clone->tricore = test_detail_tricore_clone(detail->tricore);
	}
	if (detail->alpha) {
		clone->alpha = test_detail_alpha_clone(detail->alpha);
	}
	if (detail->bpf) {
		clone->bpf = test_detail_bpf_clone(detail->bpf);
	}
	if (detail->hppa) {
		clone->hppa = test_detail_hppa_clone(detail->hppa);
	}
	if (detail->xcore) {
		clone->xcore = test_detail_xcore_clone(detail->xcore);
	}
	if (detail->systemz) {
		clone->systemz = test_detail_systemz_clone(detail->systemz);
	}
	if (detail->sparc) {
		clone->sparc = test_detail_sparc_clone(detail->sparc);
	}
	if (detail->sh) {
		clone->sh = test_detail_sh_clone(detail->sh);
	}
	if (detail->mips) {
		clone->mips = test_detail_mips_clone(detail->mips);
	}
	if (detail->riscv) {
		clone->riscv = test_detail_riscv_clone(detail->riscv);
	}
	if (detail->m680x) {
		clone->m680x = test_detail_m680x_clone(detail->m680x);
	}
	if (detail->tms320c64x) {
		clone->tms320c64x =
			test_detail_tms320c64x_clone(detail->tms320c64x);
	}
	if (detail->mos65xx) {
		clone->mos65xx = test_detail_mos65xx_clone(detail->mos65xx);
	}
	if (detail->evm) {
		clone->evm = test_detail_evm_clone(detail->evm);
	}
	if (detail->loongarch) {
		clone->loongarch =
			test_detail_loongarch_clone(detail->loongarch);
	}
	if (detail->wasm) {
		clone->wasm = test_detail_wasm_clone(detail->wasm);
	}
	if (detail->x86) {
		clone->x86 = test_detail_x86_clone(detail->x86);
	}
	if (detail->m68k) {
		clone->m68k = test_detail_m68k_clone(detail->m68k);
	}
	if (detail->xtensa) {
		clone->xtensa = test_detail_xtensa_clone(detail->xtensa);
	}

	return clone;
}

void test_detail_free(TestDetail *detail)
{
	if (!detail) {
		return;
	}

	for (size_t i = 0; i < detail->regs_read_count; ++i) {
		cs_mem_free(detail->regs_read[i]);
	}
	cs_mem_free(detail->regs_read);

	for (size_t i = 0; i < detail->regs_write_count; ++i) {
		cs_mem_free(detail->regs_write[i]);
	}
	cs_mem_free(detail->regs_write);

	for (size_t i = 0; i < detail->regs_impl_read_count; ++i) {
		cs_mem_free(detail->regs_impl_read[i]);
	}
	cs_mem_free(detail->regs_impl_read);

	for (size_t i = 0; i < detail->regs_impl_write_count; ++i) {
		cs_mem_free(detail->regs_impl_write[i]);
	}
	cs_mem_free(detail->regs_impl_write);

	for (size_t i = 0; i < detail->groups_count; ++i) {
		cs_mem_free(detail->groups[i]);
	}
	cs_mem_free(detail->groups);

	if (detail->aarch64) {
		test_detail_aarch64_free(detail->aarch64);
	}
	if (detail->arm) {
		test_detail_arm_free(detail->arm);
	}
	if (detail->ppc) {
		test_detail_ppc_free(detail->ppc);
	}
	if (detail->tricore) {
		test_detail_tricore_free(detail->tricore);
	}
	if (detail->alpha) {
		test_detail_alpha_free(detail->alpha);
	}
	if (detail->hppa) {
		test_detail_hppa_free(detail->hppa);
	}
	if (detail->bpf) {
		test_detail_bpf_free(detail->bpf);
	}
	if (detail->xcore) {
		test_detail_xcore_free(detail->xcore);
	}
	if (detail->systemz) {
		test_detail_systemz_free(detail->systemz);
	}
	if (detail->sparc) {
		test_detail_sparc_free(detail->sparc);
	}
	if (detail->sh) {
		test_detail_sh_free(detail->sh);
	}
	if (detail->mips) {
		test_detail_mips_free(detail->mips);
	}
	if (detail->riscv) {
		test_detail_riscv_free(detail->riscv);
	}
	if (detail->m680x) {
		test_detail_m680x_free(detail->m680x);
	}
	if (detail->tms320c64x) {
		test_detail_tms320c64x_free(detail->tms320c64x);
	}
	if (detail->mos65xx) {
		test_detail_mos65xx_free(detail->mos65xx);
	}
	if (detail->evm) {
		test_detail_evm_free(detail->evm);
	}
	if (detail->loongarch) {
		test_detail_loongarch_free(detail->loongarch);
	}
	if (detail->wasm) {
		test_detail_wasm_free(detail->wasm);
	}
	if (detail->x86) {
		test_detail_x86_free(detail->x86);
	}
	if (detail->m68k) {
		test_detail_m68k_free(detail->m68k);
	}
	if (detail->xtensa) {
		test_detail_xtensa_free(detail->xtensa);
	}
	cs_mem_free(detail);
}

static bool test_reg_rw_access(csh *handle, const cs_insn *insn,
			       TestDetail *expected)
{
	assert(handle && insn && expected);
	if (expected->regs_read_count <= 0 && expected->regs_write_count <= 0) {
		return true;
	}

	cs_regs regs_read, regs_write;
	uint8_t regs_read_count, regs_write_count;
	cs_err err = cs_regs_access(*handle, insn, regs_read, &regs_read_count,
				    regs_write, &regs_write_count);
	if (err != CS_ERR_OK) {
		fprintf(stderr, "cs_regs_access() failed with '%s'\n",
			cs_strerror(err));
		return false;
	}

	if (expected->regs_read_count > 0) {
		compare_uint32_ret(regs_read_count, expected->regs_read_count,
				   false);
		for (size_t i = 0; i < regs_read_count; ++i) {
			compare_reg_ret(*handle, regs_read[i],
					expected->regs_read[i], false);
		}
	}

	if (expected->regs_write_count > 0) {
		compare_uint32_ret(regs_write_count, expected->regs_write_count,
				   false);
		for (size_t i = 0; i < regs_write_count; ++i) {
			compare_reg_ret(*handle, regs_write[i],
					expected->regs_write[i], false);
		}
	}
	return true;
}

static bool test_impl_reg_rw_access(csh *handle, const cs_insn *insn,
				    TestDetail *expected)
{
	assert(handle && insn && expected);
	if (expected->regs_impl_read_count <= 0 &&
	    expected->regs_impl_write_count <= 0) {
		return true;
	}
	cs_detail *actual = insn->detail;

	// Test exclusively the implicitly read or written register.
	if (expected->regs_impl_read_count > 0) {
		compare_uint32_ret(actual->regs_read_count,
				   expected->regs_impl_read_count, false);
		for (size_t i = 0; i < actual->regs_read_count; ++i) {
			compare_reg_ret(*handle, actual->regs_read[i],
					expected->regs_impl_read[i], false);
		}
	}

	if (expected->regs_impl_write_count > 0) {
		compare_uint32_ret(actual->regs_write_count,
				   expected->regs_impl_write_count, false);
		for (size_t i = 0; i < actual->regs_write_count; ++i) {
			compare_reg_ret(*handle, actual->regs_write[i],
					expected->regs_impl_write[i], false);
		}
	}
	return true;
}

bool test_expected_detail(csh *handle, const cs_insn *insn,
			  TestDetail *expected)
{
	assert(handle && insn && insn->detail && expected);
	cs_detail *actual = insn->detail;

	if (!test_reg_rw_access(handle, insn, expected)) {
		return false;
	}

	if (!test_impl_reg_rw_access(handle, insn, expected)) {
		return false;
	}

	if (expected->groups_count > 0) {
		compare_uint32_ret(actual->groups_count, expected->groups_count,
				   false);
		for (size_t i = 0; i < actual->groups_count; ++i) {
			if (strings_match(cs_group_name(*handle,
							actual->groups[i]),
					  expected->groups[i])) {
				continue;
			}
			compare_enum_ret(actual->groups[i], expected->groups[i],
					 false);
		}
	}

	if (expected->aarch64) {
		return test_expected_aarch64(handle, &actual->aarch64,
					     expected->aarch64);
	}
	if (expected->arm) {
		return test_expected_arm(handle, &actual->arm, expected->arm);
	}
	if (expected->ppc) {
		return test_expected_ppc(handle, &actual->ppc, expected->ppc);
	}
	if (expected->tricore) {
		return test_expected_tricore(handle, &actual->tricore,
					     expected->tricore);
	}
	if (expected->alpha) {
		return test_expected_alpha(handle, &actual->alpha,
					   expected->alpha);
	}
	if (expected->bpf) {
		return test_expected_bpf(handle, &actual->bpf, expected->bpf);
	}
	if (expected->hppa) {
		return test_expected_hppa(handle, &actual->hppa,
					  expected->hppa);
	}
	if (expected->xcore) {
		return test_expected_xcore(handle, &actual->xcore,
					   expected->xcore);
	}
	if (expected->systemz) {
		return test_expected_systemz(handle, &actual->systemz,
					     expected->systemz);
	}
	if (expected->sparc) {
		return test_expected_sparc(handle, &actual->sparc,
					   expected->sparc);
	}
	if (expected->sh) {
		return test_expected_sh(handle, &actual->sh, expected->sh);
	}
	if (expected->mips) {
		return test_expected_mips(handle, &actual->mips,
					  expected->mips);
	}
	if (expected->riscv) {
		return test_expected_riscv(handle, &actual->riscv,
					   expected->riscv);
	}
	if (expected->m680x) {
		return test_expected_m680x(handle, &actual->m680x,
					   expected->m680x);
	}
	if (expected->tms320c64x) {
		return test_expected_tms320c64x(handle, &actual->tms320c64x,
						expected->tms320c64x);
	}
	if (expected->mos65xx) {
		return test_expected_mos65xx(handle, &actual->mos65xx,
					     expected->mos65xx);
	}
	if (expected->evm) {
		return test_expected_evm(handle, &actual->evm, expected->evm);
	}
	if (expected->loongarch) {
		return test_expected_loongarch(handle, &actual->loongarch,
					       expected->loongarch);
	}
	if (expected->wasm) {
		return test_expected_wasm(handle, &actual->wasm,
					  expected->wasm);
	}
	if (expected->x86) {
		return test_expected_x86(handle, &actual->x86, expected->x86);
	}
	if (expected->m68k) {
		return test_expected_m68k(handle, &actual->m68k,
					  expected->m68k);
	}
	if (expected->xtensa) {
		return test_expected_xtensa(handle, &actual->xtensa,
					    expected->xtensa);
	}
	return true;
}
