#ifndef ARM64_H
#define ARM64_H

#include "Util.h"

typedef enum {
	// registers
	ARM64_REG_TYPE_X,
	ARM64_REG_TYPE_W,

	// vector shit
	ARM64_REG_TYPE_Q,
	ARM64_REG_TYPE_D,
	ARM64_REG_TYPE_S,
	ARM64_REG_TYPE_H,
	ARM64_REG_TYPE_B,
} arm64_register_type;

enum {
	ARM64_REG_MASK_ANY_FLAG = (1 << 0),
	ARM64_REG_MASK_X_W = (1 << 1),
	ARM64_REG_MASK_VECTOR = (1 << 2),
	ARM64_REG_MASK_ALL = (ARM64_REG_MASK_X_W | ARM64_REG_MASK_VECTOR),

	ARM64_REG_MASK_ANY_X_W = (ARM64_REG_MASK_X_W | ARM64_REG_MASK_ANY_FLAG),
	ARM64_REG_MASK_ANY_VECTOR = (ARM64_REG_MASK_VECTOR | ARM64_REG_MASK_ANY_FLAG),
	ARM64_REG_MASK_ANY_ALL = (ARM64_REG_MASK_ALL | ARM64_REG_MASK_ANY_FLAG),
};

typedef enum {
	LDR_STR_TYPE_ANY, // NOTE: "ANY" will inevitably also match STUR and LDUR instructions
	LDR_STR_TYPE_POST_INDEX,
	LDR_STR_TYPE_PRE_INDEX,
	LDR_STR_TYPE_UNSIGNED,
} arm64_ldr_str_type;

typedef struct s_arm64_register {
	uint8_t mask;
	arm64_register_type type;
	uint8_t num;
} arm64_register;

#define ARM64_REG(type_, num_) (arm64_register){.mask = ARM64_REG_MASK_ALL, .type = type_, .num = num_}
#define ARM64_REG_X(x) ARM64_REG(ARM64_REG_TYPE_X, x)
#define ARM64_REG_W(x) ARM64_REG(ARM64_REG_TYPE_W, x)
#define ARM64_REG_Q(x) ARM64_REG(ARM64_REG_TYPE_Q, x)
#define ARM64_REG_S(x) ARM64_REG(ARM64_REG_TYPE_S, x)
#define ARM64_REG_H(x) ARM64_REG(ARM64_REG_TYPE_H, x)
#define ARM64_REG_B(x) ARM64_REG(ARM64_REG_TYPE_B, x)
#define ARM64_REG_ANY (arm64_register){.mask = ARM64_REG_MASK_ANY_ALL, .type = 0, .num = 0}
#define ARM64_REG_ANY_X_W (arm64_register){.mask = ARM64_REG_MASK_ANY_X_W, .type = 0, .num = 0}
#define ARM64_REG_ANY_VECTOR (arm64_register){.mask = ARM64_REG_MASK_ANY_VECTOR, .type = 0, .num = 0}
#define ARM64_REG_GET_TYPE(x) (x.type)
#define ARM64_REG_IS_X(x) (x.type == ARM64_REG_TYPE_X)
#define ARM64_REG_IS_W(x) (x.type == ARM64_REG_TYPE_W)
#define ARM64_REG_IS_VECTOR(x) (x.type == ARM64_REG_TYPE_Q || x.type == ARM64_REG_TYPE_D || x.type == ARM64_REG_TYPE_S || x.type == ARM64_REG_TYPE_H || x.type == ARM64_REG_TYPE_B)
#define ARM64_REG_GET_NUM(x) (x.num & 0x1f)
#define ARM64_REG_IS_ANY(x) (x.mask == ARM64_REG_MASK_ANY_ALL)
#define ARM64_REG_IS_ANY_X_W(x) (x.mask == ARM64_REG_MASK_ANY_X_W)
#define ARM64_REG_IS_ANY_VECTOR(x) (x.mask == ARM64_REG_MASK_ANY_VECTOR)
uint8_t arm64_reg_type_get_width(arm64_register_type type);
const char *arm64_reg_type_get_string(arm64_register_type type);
const char *arm64_reg_get_type_string(arm64_register reg);

#define ARM64_REG_NUM_SP 31

typedef struct s_arm64_cond {
	bool isSet;
	uint8_t value;
} arm64_cond;
#define ARM64_COND(x) (arm64_cond){.isSet = true, .value = x}
#define ARM64_COND_ANY (arm64_cond){.isSet = false, .value = 0}
#define ARM64_COND_GET_VAL(x) (x.value & 0xf)
#define ARM64_COND_IS_SET(x) x.isSet

int arm64_gen_b_l(optional_bool optIsBl, optional_uint64_t optOrigin, optional_uint64_t optTarget, uint32_t *bytesOut, uint32_t *maskOut);
int arm64_dec_b_l(uint32_t inst, uint64_t origin, uint64_t *targetOut, bool *isBlOut);
int arm64_gen_b_c_cond(optional_bool optIsBc, optional_uint64_t optOrigin, optional_uint64_t optTarget, arm64_cond optCond, uint32_t *bytesOut, uint32_t *maskOut);
int arm64_dec_b_c_cond(uint32_t inst, uint64_t origin, uint64_t *targetOut, arm64_cond *condOut, bool *isBcOut);
int arm64_gen_adr_p(optional_bool optIsAdrp, optional_uint64_t optOrigin, optional_uint64_t optTarget, arm64_register reg, uint32_t *bytesOut, uint32_t *maskOut);
int arm64_dec_adr_p(uint32_t inst, uint64_t origin, uint64_t *targetOut, arm64_register *registerOut, bool *isAdrpOut);
int arm64_gen_mov_imm(char type, arm64_register destinationReg, optional_uint64_t optImm, optional_uint64_t optShift, uint32_t *bytesOut, uint32_t *maskOut);
int arm64_dec_mov_imm(uint32_t inst, arm64_register *destinationRegOut, uint64_t *immOut, uint64_t *shiftOut, char *typeOut);
int arm64_gen_add_imm(arm64_register destinationReg, arm64_register sourceReg, optional_uint64_t optImm, uint32_t *bytesOut, uint32_t *maskOut);
int arm64_dec_add_imm(uint32_t inst, arm64_register *destinationRegOut, arm64_register *sourceRegOut, uint16_t *immOut);
int arm64_gen_ldr_imm(char type, arm64_ldr_str_type instType, arm64_register destinationReg, arm64_register addrReg, optional_uint64_t optImm, uint32_t *bytesOut, uint32_t *maskOut);
int arm64_dec_ldr_imm(uint32_t inst, arm64_register *destinationReg, arm64_register *addrReg, uint64_t *immOut, char *typeOut, arm64_ldr_str_type *instTypeOut);
int arm64_gen_ldrs_imm(char type, arm64_ldr_str_type instType, arm64_register destinationReg, arm64_register addrReg, optional_uint64_t optImm, uint32_t *bytesOut, uint32_t *maskOut);
int arm64_dec_ldrs_imm(uint32_t inst, arm64_register *destinationRegOut, arm64_register *addrRegOut, uint64_t *immOut, char *typeOut, arm64_ldr_str_type *instTypeOut);
int arm64_gen_str_imm(char type, arm64_ldr_str_type instType, arm64_register sourceReg, arm64_register addrReg, optional_uint64_t optImm, uint32_t *bytesOut, uint32_t *maskOut);
int arm64_dec_str_imm(uint32_t inst, arm64_register *sourceRegOut, arm64_register *addrRegOut, uint64_t *immOut, char *typeOut, arm64_ldr_str_type *instTypeOut);
int arm64_gen_ldr_lit(arm64_register destinationReg, optional_uint64_t optImm, uint32_t *bytesOut, uint32_t *maskOut);
int arm64_dec_ldr_lit(uint32_t inst, arm64_register *destinationReg, int64_t *immOut);
int arm64_gen_cb_n_z(optional_bool isCbnz, arm64_register reg, optional_uint64_t optTarget, uint32_t *bytesOut, uint32_t *maskOut);
int arm64_dec_cb_n_z(uint32_t inst, uint64_t origin, bool *isCbnzOut, arm64_register *regOut, uint64_t *targetOut);
#endif