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

#ifndef TEST_COMPARE_H
#define TEST_COMPARE_H

#include <stdint.h>
#include "test_mapping.h"
#include "../../../utils.h"

/// An integer encoding a boolean value from the test files.
/// libcyaml saves 0 by default, if an optional value was not set.
/// Due to that, boolean values are represented as integer with the
/// interpretation:
///
/// = 0 => unset
/// < 0 => false
/// > 0 => true
typedef int32_t tbool;

/// Compares the @actual bool against the @expected tbool:
/// It returns with @ret_val, if expected is set but the values mismatch.
#define compare_tbool_ret(actual, expected, ret_val) \
	if (expected != 0 && \
	    ((actual && expected <= 0) || (!actual && expected >= 0))) { \
		fprintf(stderr, \
			#actual " is %s but expected is %" PRId32 \
				" (=0 unset, >0 true, <0 false)\n", \
			actual ? "true" : "false", expected); \
		return ret_val; \
	}

/// Compares two unsigned int values.
/// It returns with @ret_val if they mismatch.
#define compare_uint_ret(actual, expected, ret_val) \
	if (((unsigned int)actual) != ((unsigned int)expected)) { \
		fprintf(stderr, \
			#actual " != " #expected ": 0x%" PRIx32 \
				" != 0x%" PRIx32 "\n", \
			actual, expected); \
		return ret_val; \
	}

/// Compares two uint8_t values.
/// It returns with @ret_val if they mismatch.
#define compare_uint8_ret(actual, expected, ret_val) \
	if (((uint8_t)actual) != ((uint8_t)expected)) { \
		fprintf(stderr, \
			#actual " != " #expected ": %" PRId8 " != %" PRId8 \
				"\n", \
			actual, expected); \
		return ret_val; \
	}

/// Compares two uint16_t values.
/// It returns with @ret_val if they mismatch.
#define compare_uint16_ret(actual, expected, ret_val) \
	if (((uint16_t)actual) != ((uint16_t)expected)) { \
		fprintf(stderr, \
			#actual " != " #expected ": 0x%" PRIx16 \
				" != 0x%" PRIx16 "\n", \
			actual, expected); \
		return ret_val; \
	}

/// Compares two uint32_t values.
/// It returns with @ret_val if they mismatch.
#define compare_uint32_ret(actual, expected, ret_val) \
	if (((uint32_t)actual) != ((uint32_t)expected)) { \
		fprintf(stderr, \
			#actual " != " #expected ": 0x%" PRIx32 \
				" != 0x%" PRIx32 "\n", \
			actual, expected); \
		return ret_val; \
	}

/// Compares two uint64_t values.
/// It returns with @ret_val if they mismatch.
#define compare_uint64_ret(actual, expected, ret_val) \
	if (((uint64_t)actual) != ((uint64_t)expected)) { \
		fprintf(stderr, \
			#actual " != " #expected ": 0x%" PRIx64 \
				" != 0x%" PRIx64 "\n", \
			actual, expected); \
		return ret_val; \
	}

/// Compares two int values.
/// It returns with @ret_val if they mismatch.
#define compare_int_ret(actual, expected, ret_val) \
	if (((int)actual) != ((int)expected)) { \
		fprintf(stderr, \
			#actual " != " #expected ": 0x%" PRIx32 \
				" != 0x%" PRIx32 "\n", \
			actual, expected); \
		return ret_val; \
	}

/// Compares two int8_t values.
/// It returns with @ret_val if they mismatch.
#define compare_int8_ret(actual, expected, ret_val) \
	if (((int8_t)actual) != ((int8_t)expected)) { \
		fprintf(stderr, \
			#actual " != " #expected ": 0x%" PRIx8 " != 0x%" PRIx8 \
				"\n", \
			actual, expected); \
		return ret_val; \
	}

/// Compares two int16_t values.
/// It returns with @ret_val if they mismatch.
#define compare_int16_ret(actual, expected, ret_val) \
	if (((int16_t)actual) != ((int16_t)expected)) { \
		fprintf(stderr, \
			#actual " != " #expected ": 0x%" PRIx16 \
				" != 0x%" PRIx16 "\n", \
			actual, expected); \
		return ret_val; \
	}

/// Compares two int32_t values.
/// It returns with @ret_val if they mismatch.
#define compare_int32_ret(actual, expected, ret_val) \
	if (((int32_t)actual) != ((int32_t)expected)) { \
		fprintf(stderr, \
			#actual " != " #expected ": 0x%" PRIx32 \
				" != 0x%" PRIx32 "\n", \
			actual, expected); \
		return ret_val; \
	}

/// Compares two int64_t values.
/// It returns with @ret_val if they mismatch.
#define compare_int64_ret(actual, expected, ret_val) \
	if (((int64_t)actual) != ((int64_t)expected)) { \
		fprintf(stderr, \
			#actual " != " #expected ": 0x%" PRIx64 \
				" != 0x%" PRIx64 "\n", \
			actual, expected); \
		return ret_val; \
	}

/// Compares two floating point values.
/// It returns with @ret_val if they mismatch.
#define compare_fp_ret(actual, expected, ret_val) \
	if (actual != expected) { \
		fprintf(stderr, #actual " != " #expected ": %f != %f\n", \
			actual, expected); \
		return ret_val; \
	}

/// Compares enum id.
/// Actual is the value, expected is the enum idetifer as string.
/// It returns with @ret_val if they mismatch.
#define compare_enum_ret(actual, expected, ret_val) \
	if (expected) { \
		bool found = false; \
		uint32_t eval = enum_map_bin_search( \
			cs_enum_map, ARR_SIZE(cs_enum_map), expected, &found); \
		if (expected && (actual != eval || !found)) { \
			fprintf(stderr, \
				#actual " != " #expected ": %" PRId32 \
					" != %s%s\n", \
				actual, expected, \
				found ? "" : " <== id not found"); \
			return ret_val; \
		} \
	}

/// Checks if all bit flags in @expected are set in @actual.
/// Actual is the value with all bits set.
/// @expected is a list the @len enum identifiers as string.
/// It returns with @ret_val if they mismatch.
#define compare_bit_flags_ret(actual, expected, len, ret_val) \
	if (expected) { \
		for (size_t cmp_i = 0; cmp_i < len; ++cmp_i) { \
			bool found = false; \
			uint32_t eval = enum_map_bin_search( \
				cs_enum_map, ARR_SIZE(cs_enum_map), \
				expected[cmp_i], &found); \
			if (!(actual & eval) || !found) { \
				fprintf(stderr, \
					#actual " != " #expected ": %" PRId32 \
						" != %s%s\n", \
					actual, expected[cmp_i], \
					found ? " <== Flag not set" : \
						" <== id not found"); \
				return ret_val; \
			} \
		} \
	}

/// Checks if all bit flags in @expected are set in @actual.
/// Actual is the value with all bits set.
/// @expected is a list the @len enum identifiers as string.
/// It returns with @ret_val if they mismatch.
#define compare_bit_flags_64_ret(actual, expected, len, ret_val) \
	if (expected) { \
		for (size_t cmp_i = 0; cmp_i < len; ++cmp_i) { \
			bool found = false; \
			uint64_t eval = enum_map_bin_search( \
				cs_enum_map, ARR_SIZE(cs_enum_map), \
				expected[cmp_i], &found); \
			if (!(actual & eval) || !found) { \
				fprintf(stderr, \
					#actual " != " #expected ": %" PRId64 \
						" != %s%s\n", \
					actual, expected[cmp_i], \
					found ? " <== Flag not set" : \
						" <== id not found"); \
				return ret_val; \
			} \
		} \
	}

/// Compares register names.
/// Actual is the register id, expected is name as string.
/// It returns with @ret_val if they mismatch.
#define compare_reg_ret(handle, actual, expected, ret_val) \
	if (expected) { \
		const char *reg_name = cs_reg_name(handle, actual); \
		if (expected && !strings_match(reg_name, expected)) { \
			fprintf(stderr, \
				#actual " != " #expected ": '%s' != '%s'\n", \
				reg_name, expected); \
			return ret_val; \
		} \
	}

#endif // TEST_COMPARE_H
