#ifndef R_ARCH_CAPSTONE_HELPER_INC_C
#define R_ARCH_CAPSTONE_HELPER_INC_C

typedef struct {
	cs_detail detail;
	cs_insn insn;
} RArchCSInsn;

static inline bool r_arch_cs_disasm(csh handle, const uint8_t **code, size_t *size, uint64_t *addr, RArchCSInsn *ci) {
	ci->insn.detail = &ci->detail;
	// capstone post printers inspect the mnemonic before fill_insn writes it
	ci->insn.mnemonic[0] = 0;
	return cs_disasm_iter (handle, code, size, addr, &ci->insn);
}

static inline bool r_arch_cs_disasm_iter(csh handle, const ut8 *buf, int len, ut64 addr, RArchCSInsn *ci) {
	if (len < 1) {
		return false;
	}
	const uint8_t *code = (const uint8_t *)buf;
	size_t size = len;
	uint64_t iter_addr = addr;
	return r_arch_cs_disasm (handle, &code, &size, &iter_addr, ci);
}

#endif

#ifdef CSINC

#ifndef CSINC_MODE
#define CSINC_MODE 0
#endif

#define TOKEN_PASTE(x, y) x ## y

#define CSINC_INS_ENDING CSINC_INS_ENDING_(CSINC)
#define CSINC_INS_ENDING_(y) TOKEN_PASTE(y, _INS_ENDING)

#define CSINC_ARCH CSINC_ARCH_(CSINC)
#define CSINC_ARCH_(y) TOKEN_PASTE(CS_ARCH_, y)

typedef struct {
	csh cs_handle;
	// store cpu and endian too ?
} CapstonePluginData;

static void initcs(csh *ud) {
	cs_opt_mem mem = {
		.malloc = malloc,
		.calloc = calloc,
		.realloc = realloc,
		.free = free,
		.vsnprintf = vsnprintf,
	};
	cs_option (*ud, CS_OPT_MEM, (size_t)&mem);
}

static bool r_arch_cs_init(RArchSession *as, csh *cs_handle) {
	int mode = CSINC_MODE;
	initcs (cs_handle);
#if 0
	const bool be = R_ARCH_CONFIG_IS_BIG_ENDIAN (as->config);
	if (be) {
		mode = CS_MODE_BIG_ENDIAN;
	}
#endif
#if 0
	if (mode != a->cs_omode || a->config->bits != a->cs_obits) {
		if (a->cs_handle != 0) {
			cs_close (&a->cs_handle);
			a->cs_handle = 0; // unnecessary
		}
		a->cs_omode = mode;
		a->cs_obits = a->config->bits;
	}
#endif
	int ret = cs_open (CSINC_ARCH, mode, cs_handle);
	if (ret != CS_ERR_OK) {
		R_LOG_ERROR ("Capstone failed: cs_open(%#x, %#x): %s", CSINC_ARCH, mode, cs_strerror (ret));
		*cs_handle = 0;
	} else {
		cs_option (*cs_handle, CS_OPT_DETAIL, CS_OPT_ON);
		// R2R db/anal/emu db/cmd/cmd_ahi
		if (CSINC_ARCH == CS_ARCH_X86) {
			cs_option (*cs_handle, CS_OPT_UNSIGNED, CS_OPT_ON);
		}
	}
	if (*cs_handle) {
		switch (as->config->syntax) {
		case R_ARCH_SYNTAX_ATT:
			cs_option (*cs_handle, CS_OPT_SYNTAX, CS_OPT_SYNTAX_ATT);
#if CS_API_MAJOR >= 6
			if (CSINC_ARCH == CS_ARCH_X86) {
				cs_option (*cs_handle, CS_OPT_DETAIL, CS_OPT_DETAIL_INTEL_ORDER);
			}
#endif
			break;
		case R_ARCH_SYNTAX_MASM:
			cs_option (*cs_handle, CS_OPT_SYNTAX, CS_OPT_SYNTAX_MASM);
			break;
		default:
			cs_option (*cs_handle, CS_OPT_SYNTAX, CS_OPT_SYNTAX_INTEL);
			break;
		}
	}
	return true;
}

static char *r_arch_cs_mnemonics(RArchSession *s, csh cs_handle, int id, bool json) {
	int i;
#if 0
	csh cs_handle = xnit_capstone (s->arch);
	if (cs_handle == 0) {
		a->cs_handle = cs_handle;
		return NULL;
	}
#endif

	PJ *pj = NULL;
	if (id != -1) {
		const char *name = cs_insn_name (cs_handle, id);
		if (name) {
			if (json) {
				pj = pj_new ();
				pj_a (pj);
				pj_s (pj, name);
				pj_end (pj);
				return pj_drain (pj);
			}
			return strdup (name);
		}
		return NULL;
	}

	RStrBuf *buf = NULL;
	if (json) {
		pj = pj_new ();
		pj_a (pj);
	} else {
		buf = r_strbuf_new ("");
	}
	for (i = 1; i < CSINC_INS_ENDING; i++) {
		const char *op = cs_insn_name (cs_handle, i);
		if (!op) {
			continue;
		}
		if (pj) {
			pj_s (pj, op);
		} else {
			r_strbuf_append (buf, op);
			r_strbuf_append (buf, "\n");
		}
	}
	if (pj) {
		pj_end (pj);
	}
	return pj? pj_drain (pj): r_strbuf_drain (buf);
}

#endif
