/* radare - LGPL - Copyright 2009-2026 - pancake */

#include <r_reg.h>
#include <r_util.h>
#include <r_lib.h>

static const char *parse_alias(RReg *reg, char **tok, const int n) {
	if (n == 2) {
		const int type = r_reg_alias_fromstring (tok[0] + 1);
		const bool works = r_reg_alias_setname (reg, type, tok[1]);
		return works? NULL : "Invalid alias";
	}
	return "Invalid syntax";
}

// Sizes prepended with a dot are expressed in bits
// strtoul with base 0 allows the input to be in decimal/octal/hex format

// bit counts are stored in ints, larger values are rejected through end
static int parse_size(char *s, char **end) {
	ut64 bits;
	if (*s == '.') {
		bits = strtoull (s + 1, end, 10);
	} else {
		char *has_dot = strchr (s, '.');
		if (has_dot) {
			*has_dot++ = 0;
		}
		const ut64 bytes = strtoull (s, end, 0);
		bits = (bytes > INT_MAX / 8)? UT64_MAX: bytes << 3;
		if (has_dot) {
			const ut64 extra = strtoull (has_dot, end, 0);
			bits = (extra > INT_MAX)? UT64_MAX: bits + extra;
		}
	}
	if (bits > INT_MAX) {
		*end = "!";
		return 0;
	}
	return (int)bits;
}

// returns the bit offset just past the last reserved byte in the given arena.
// considers both materialized RRegItems and declared RRegVBank ranges so that
// `$` resolves to the true tail when a vbank has reserved space.
static int parse_def_tail(RReg *reg, int type) {
	int last = 0;
	RListIter *iter;
	RRegItem *ri;
	r_list_foreach (reg->regset[type].regs, iter, ri) {
		if (ri->size >= 8 && ri->offset >= 0) {
			int pos = ri->offset + ri->size;
			if (pos > last) {
				last = pos;
			}
		}
	}
	RRegVBank *vb;
	R_VEC_FOREACH (&reg->regset[type].vbanks, vb) {
		int pos = vb->offset + vb->size * vb->count;
		if (pos > last) {
			last = pos;
		}
	}
	return last;
}

static const char *parse_vbank_suffix(char *name, int *count) {
	*count = 0;
	if (!name[0] || name[1] != '[' || !r_reg_vbank_prefix (name[0])) {
		return NULL;
	}
	const char *p = name + 2;
	if (!isdigit ((ut8)*p) || *p == '0') {
		return "Invalid vbank count";
	}
	char *end = NULL;
	long n = strtol (p, &end, 10);
	if (n > R_REG_VBANK_MAX_REGS) {
		return "Invalid vbank count";
	}
	if (*end != ']' || end[1]) {
		return "Invalid vbank suffix";
	}
	name[1] = 0;
	*count = (int)n;
	return NULL;
}

static bool vbank_item_match(const char *name, char prefix, int count) {
	const char *p = name + 1;
	if (name[0] != prefix || !isdigit ((ut8)*p)) {
		return false;
	}
	if (*p == '0' && p[1]) {
		return false;
	}
	char *end = NULL;
	long n = strtol (p, &end, 10);
	return !*end && n < count;
}

static bool profile_has_duplicate(RReg *reg, const char *name, int count) {
	const char prefix = name[0];
	int i;
	for (i = 0; i < R_REG_TYPE_LAST; i++) {
		RRegSet *rs = &reg->regset[i];
		if (count > 0) {
			// a bank claims every item its prefix and count can name
			RListIter *iter;
			RRegItem *ri;
			r_list_foreach (rs->regs, iter, ri) {
				if (vbank_item_match (ri->name, prefix, count)) {
					return true;
				}
			}
		} else if (rs->ht_regs && ht_pp_find (rs->ht_regs, name, NULL)) {
			return true;
		}
		RRegVBank *vb;
		R_VEC_FOREACH (&rs->vbanks, vb) {
			if (count > 0? vb->prefix == prefix: vbank_item_match (name, vb->prefix, vb->count)) {
				return true;
			}
		}
	}
	return false;
}

//TODO: implement bool r_reg_set_def_string()
static const char *parse_def(RReg *reg, char **tok, const int n) {
	char *end = "";
	int type, type2;

	if (n != 5 && n != 6) {
		return "Invalid syntax: Wrong number of columns";
	}
	char *p = strchr (tok[0], '@');
	if (p) {
		char *tok0 = strdup (tok[0]);
		char *at = tok0 + (p - tok[0]);
		*at++ = 0;
		type = r_reg_type_by_name (tok0);
		type2 = r_reg_type_by_name (at);
		free (tok0);
	} else {
		type2 = type = r_reg_type_by_name (tok[0]);
		/* Hack to put flags in the same arena as gpr */
		if (type == R_REG_TYPE_FLG) {
			type2 = R_REG_TYPE_GPR;
		}
	}
	if (type < 0 || type2 < 0) {
		return "Invalid register type";
	}

	int vcount = 0;
	const char *vberr = parse_vbank_suffix (tok[1], &vcount);
	if (vberr) {
		return vberr;
	}
	if (profile_has_duplicate (reg, tok[1], vcount)) {
		if (vcount > 0) {
			R_LOG_WARN ("Duplicated register bank definition for '%c[%d]' has been ignored", tok[1][0], vcount);
		} else {
			R_LOG_WARN ("Duplicated register definition for '%s' has been ignored", tok[1]);
		}
		return NULL;
	}
	if (vcount > 0) {
		// virtual register bank declaration: lazy, no per-item RRegItem yet
		int vsize = parse_size (tok[2], &end);
		if (*end || !vsize) {
			return "Invalid size";
		}
		int voff;
		if (!strcmp (tok[3], "$")) {
			voff = parse_def_tail (reg, type2);
		} else if (tok[3][0] == '?') {
			return "vbank offset must be numeric or '$'";
		} else {
			voff = parse_size (tok[3], &end);
			if (*end) {
				return "Invalid offset";
			}
		}
		int vpacked = parse_size (tok[4], &end);
		if (*end) {
			return "Invalid packed size";
		}
		if ((st64)vsize * vcount > INT_MAX - voff) {
			return "Invalid size";
		}
		RRegVBank *vb = RVecRegVBank_emplace_back (&reg->regset[type2].vbanks);
		if (!vb) {
			return "Unable to allocate memory";
		}
		vb->prefix = tok[1][0];
		vb->type = type;
		vb->arena = type2;
		vb->count = vcount;
		vb->size = vsize;
		vb->packed_size = vpacked;
		vb->offset = voff;
		r_reg_hasbits_use (reg, vsize);
		int tail = voff + vsize * vcount;
		if (tail > reg->size) {
			reg->size = tail;
		}
		reg->regset[type2].maskregstype |= (1ULL << type);
		return NULL;
	}

	RRegItem *item = R_NEW0 (RRegItem);
	if (!item) {
		return "Unable to allocate memory";
	}
	item->type = type;
	item->name = strdup (tok[1]);
	// All the numeric arguments are strictly checked
	item->size = parse_size (tok[2], &end);
	if (*end || !item->size) {
		r_reg_item_free (item);
		return "Invalid size";
	}
	if (!strcmp (tok[3], "?")) {
		item->offset = -1;
	} else if (!strcmp (tok[3], "?0")) {
		item->offset = -1;
	} else if (!strcmp (tok[3], "?1")) {
		item->offset = -2; // TODO: use an enum here
	} else if (!strcmp (tok[3], "$")) {
		item->offset = parse_def_tail (reg, type);
	} else {
		item->offset = parse_size (tok[3], &end);
	}
	if (*end || item->offset > INT_MAX - item->size) {
		r_reg_item_free (item);
		return "Invalid offset";
	}
	item->packed_size = parse_size (tok[4], &end);
	if (*end) {
		r_reg_item_free (item);
		return "Invalid packed size";
	}

	// Update the list of supported bit sizes
	if (!r_reg_hasbits_use (reg, item->size)) {
		R_LOG_DEBUG ("r_reg_hasbits_use failed with %d", item->size);
	}

	// This is optional
	if (n == 6) {
		if (*tok[5] == '#') {
			// Remove # from the comment
			item->comment = strdup (tok[5] + 1);
		} else {
			item->flags = strdup (tok[5]);
		}
	}

	item->arena = type2;
	if (!reg->regset[type2].regs || reg->regset[type2].regs->length == 0) {
		r_list_free(reg->regset[type2].regs);
		reg->regset[type2].regs = r_list_newf ((RListFree)r_reg_item_free);
	}
	r_list_append (reg->regset[type2].regs, r_ref (item));
	if (!reg->regset[type2].ht_regs) {
		reg->regset[type2].ht_regs = ht_pp_new0 ();
	}
	ht_pp_insert (reg->regset[type2].ht_regs, item->name, item);

	// Update the overall profile size
	if (item->offset + item->size > reg->size) {
		reg->size = item->offset + item->size;
	}
	// Update the overall type of registers into a regset
	reg->regset[type2].maskregstype |= (1ULL << type);
	return NULL;
}

static inline bool is_big(int endian) {
	return (endian & R_SYS_ENDIAN_BIG) == R_SYS_ENDIAN_BIG;
}

static RRegItem *widest_host(RList *regs, RRegItem *bit) {
	RRegItem *host = NULL;
	RListIter *iter;
	RRegItem *ri;
	r_list_foreach (regs, iter, ri) {
		if (ri->size <= 8 || ri->offset < 0 || ri->offset % 8) {
			continue;
		}
		if (ri->offset > bit->offset || bit->offset + bit->size > ri->offset + ri->size) {
			continue;
		}
		if (!host || ri->size > host->size) {
			host = ri;
		}
	}
	return host;
}

// sub-byte items are declared at their little endian byte of the host register
static void mirror_bits(RReg *reg) {
	int i;
	for (i = 0; i < R_REG_TYPE_LAST; i++) {
		RList *regs = reg->regset[i].regs;
		RListIter *iter;
		RRegItem *ri;
		r_list_foreach (regs, iter, ri) {
			if (ri->size >= 8 || ri->offset < 0) {
				continue;
			}
			RRegItem *host = widest_host (regs, ri);
			if (!host) {
				continue;
			}
			int base = host->offset / 8;
			int last = base + (host->size + 7) / 8 - 1;
			int byte = last - (ri->offset / 8 - base);
			ri->offset = (byte * 8) + (ri->offset % 8);
		}
	}
}

R_API void r_reg_set_endian(RReg *reg, int endian) {
	R_RETURN_IF_FAIL (reg);
	const bool flip = is_big (reg->endian) != is_big (endian);
	reg->endian = endian;
	if (flip) {
		mirror_bits (reg);
	}
}

#define PARSER_MAX_TOKENS 8
R_API bool r_reg_set_profile_string(RReg *reg, const char *str) {
	R_RETURN_VAL_IF_FAIL (reg && str, false);
	char *tok[PARSER_MAX_TOKENS + 1];
	char tmp[128];
	int i, j, l;
	const char *p = str;

	if (R_STR_ISEMPTY (str)) {
		return true;
	}

	// Same profile, no need to change
	if (reg->reg_profile_str && !strcmp (reg->reg_profile_str, str)) {
		// R_LOG_WARN ("is the same do nothing");
	//	r_reg_free_internal (reg, false);
	//	r_reg_init (reg);
		return true;
	}
	// reset all the arenas before setting the new reg profile
	r_reg_arena_pop (reg);
	// Purge the old registers
	r_reg_free_internal (reg, true);
	r_reg_arena_shrink (reg);
#if 0
	for (i = 0; i < R_REG_TYPE_LAST; i++) {
		RRegSet *rs = &reg->regset[i];
		if (rs && rs->arena) {
			rs->arena->size = 64;
		}
	}
#endif
	// Cache the profile string
	reg->reg_profile_str = strdup (str);

	// Line number
	l = 0;
	bool have_a0 = false;
	// For every line
	do {
		// Increment line number
		l++;
		// Skip comment lines
		if (*p == '#') {
			const char *q = p;
			while (*q != '\n') {
				q++;
			}
			reg->reg_profile_cmt = r_str_appendlen (
				reg->reg_profile_cmt, p, (int)(q - p) + 1);
			p = q;
			continue;
		}
		j = 0;
		// For every word
		while (*p) {
			// Skip whitespace
			while (*p == ' ' || *p == '\t') {
				p++;
			}
			// EOL ?
			if (*p == '\n') {
				break;
			}
			if (*p == '#') {
				// Place the rest of the line in the token if a comment is encountered
				for (i = 0; *p != '\n'; p++) {
					if (i < sizeof (tmp) - 1) {
						tmp[i++] = *p;
					}
				}
			} else {
				// Save all characters up to a space/tab
				// Use isgraph instead of isprint because the latter considers ' ' printable
				for (i = 0; isgraph ((const unsigned char)*p) && i < sizeof (tmp) - 1;) {
					tmp[i++] = *p++;
				}
			}
			tmp[i] = '\0';
			// Limit the number of tokens
			if (j >= PARSER_MAX_TOKENS) {
				break;
			}
			// Save the token
			tok[j++] = strdup (tmp);
		}
		tok[j] = NULL;
		if (j) {
			// Do the actual parsing
			char *first = tok[0];
			// Check whether it's defining an alias or a register
			if (r_str_startswith (first, "=RS")) {
				if (tok[1]) {
					reg->bits_default = atoi (tok[1]);
				} else {
					R_LOG_ERROR ("Missing argument for =RS");
				}
			} else {
				const char *r = NULL;
				if (*first == '^') {
					reg->endian = R_SYS_ENDIAN;
					switch (first[1]) {
					case 'l':
						reg->endian = R_SYS_ENDIAN_LITTLE;
						break;
					case 'b':
						reg->endian = R_SYS_ENDIAN_BIG;
						break;
					case 'm':
						reg->endian = R_SYS_ENDIAN_MIDDLE;
						break;
					}
				} else if (*first == '=') {
					r = parse_alias (reg, tok, j);
					if (!have_a0 && r_str_startswith (first + 1, "A0")) {
						have_a0 = true;
					}
				} else {
					r = parse_def (reg, tok, j);
				}
				if (r) {
					R_LOG_ERROR ("Parse error @ line %d (%s)", l, r);
					// Clean up
					r_reg_free_internal (reg, false);
					r_reg_init (reg);
					for (i = 0; i < j; i++) {
						free (tok[i]);
					}
					return false;
				}
			}
			// Clean up
			for (i = 0; i < j; i++) {
				free (tok[i]);
			}
		}
	} while (*p++);
	(void)have_a0;
	if (is_big (reg->endian)) {
		mirror_bits (reg);
	}
	reg->size = 0;
	for (i = 0; i < R_REG_TYPE_LAST; i++) {
		RRegSet *rs = &reg->regset[i];
		if (rs && rs->arena) {
			reg->size += rs->arena->size;
		}
	}
	// Align to byte boundary if needed
	// if (reg->size & 7) {
	//	reg->size += 8 - (reg->size & 7);
	// }
	//reg->size >>= 3; // bits to bytes (divide by 8)
	r_reg_fit_arena (reg);
	// dup the last arena to allow regdiffing
	r_reg_arena_push (reg);
	r_reg_reindex (reg);
	r_reg_ro_reset (reg, reg->roregs);
	// reset arenas
	return true;
}

// read profile from file
R_API bool r_reg_set_profile(RReg *reg, const char *profile) {
	R_RETURN_VAL_IF_FAIL (reg && profile, false);
	char *str = r_file_slurp (profile, NULL);
	if (!str) {
		char *base = r_sys_getenv (R_LIB_ENV);
		if (base) {
			char *file = r_str_append (base, profile);
			str = r_file_slurp (file, NULL);
			free (file);
		}
	}
	if (!str) {
		R_LOG_WARN ("Cannot find '%s'", profile);
		return false;
	}
	bool ret = r_reg_set_profile_string (reg, str);
	free (str);
	return ret;
}

static char *gdb_to_r2_profile(const char *gdb) {
	R_RETURN_VAL_IF_FAIL (gdb, NULL);
	RStrBuf *sb = r_strbuf_new ("");
	if (!sb) {
		return NULL;
	}
	char *ptr1, *gptr, *gptr1;
	char *profile = strdup (gdb);
	if (!profile) {
		r_strbuf_free (sb);
		return NULL;
	}
	char name[16], groups[128], type[16];
	const int all = 1, gpr = 2, save = 4, restore = 8, float_ = 16,
		  sse = 32, vector = 64, system = 128, mmx = 256;
	int number, rel, offset, size, type_bits, ret;
	// Every line is -
	// Name Number Rel Offset Size Type Groups
	char *ptr = profile;
	while (isspace ((ut8)*ptr)) {
		ptr++;
	}

	// It's possible someone includes the heading line too. Skip it
	if (r_str_startswith (ptr, "Name")) {
		if (!(ptr = strchr (ptr, '\n'))) {
			free (profile);
			r_strbuf_free (sb);
			return NULL;
		}
		ptr++;
	}
	for (;;) {
		// Skip whitespace at beginning of line and empty lines
		while (isspace ((ut8)*ptr)) {
			ptr++;
		}
		if (!*ptr) {
			break;
		}
		if ((ptr1 = strchr (ptr, '\n'))) {
			*ptr1 = '\0';
		} else {
			R_LOG_WARN ("Could not parse line: %s (missing newline)", ptr);
			free (profile);
			r_strbuf_free (sb);
			return false;
		}
		ret = r_str_scanf (ptr, "%.s %d %d %d %d %.s %.s", sizeof (name), name, &number, &rel, &offset, &size, sizeof (type), type, sizeof (groups), groups);
		// Groups is optional, others are not
		if (ret < 6) {
			if (*ptr != '*') {
				R_LOG_WARN ("Could not parse line: %s", ptr);
				free (profile);
				r_strbuf_free (sb);
				return false;
			}
			ptr = ptr1 + 1;
			continue;
		}
		// If name is '', then skip
		if (r_str_startswith (name, "''")) {
			if (!ptr1) {
				break;
			}
			ptr = ptr1 + 1;
			continue;
		}
		// If size is 0, skip
		if (size == 0) {
			if (!ptr1) {
				break;
			}
			ptr = ptr1 + 1;
			continue;
		}
		// Parse group
		gptr = groups;
		type_bits = 0;
		while (1) {
			if ((gptr1 = strchr (gptr, ','))) {
				*gptr1 = '\0';
			}
			if (r_str_startswith (gptr, "general")) {
				type_bits |= gpr;
			} else if (r_str_startswith (gptr, "all")) {
				type_bits |= all;
			} else if (r_str_startswith (gptr, "save")) {
				type_bits |= save;
			} else if (r_str_startswith (gptr, "restore")) {
				type_bits |= restore;
			} else if (r_str_startswith (gptr, "float")) {
				type_bits |= float_;
			} else if (r_str_startswith (gptr, "sse")) { // this is vector
				type_bits |= sse;
			} else if (r_str_startswith (gptr, "mmx")) { // this is vector too
				type_bits |= mmx;
			} else if (r_str_startswith (gptr, "vector")) {
				type_bits |= vector;
			} else if (r_str_startswith (gptr, "system")) {
				type_bits |= system;
			}
			if (!gptr1) {
				break;
			}
			gptr = gptr1 + 1;
		}
		// If type is not defined, skip
		if (!*type) {
			if (!ptr1) {
				break;
			}
			ptr = ptr1 + 1;
			continue;
		}
		// TODO: More mappings between gdb and r2 reg groups. For now, either fpu or gpr
		if (!(type_bits & sse) && !(type_bits & float_)) {
			type_bits |= gpr;
		}
		const char *type = ((type_bits & mmx) || (type_bits & float_) || (type_bits & sse)) ? "fpu" : "gpr";
		if (isupper (*name)) {
			// assume uppercase register names are only used for privileged registers
			type = "pri"; // family=priv
			r_str_case (name, false);
		}
		r_strbuf_appendf (sb, "%s\t%s\t.%d\t%d\t0\n", type, name, size * 8, offset);
		// Go to next line
		if (!ptr1) {
			break;
		}
		ptr = ptr1 + 1;
		continue;
	}
	char *out = r_strbuf_drain (sb);
	free (profile);
	return out;
}

R_API char *r_reg_parse_gdb_profile(const char *profile_file) {
	char *str = r_file_slurp (profile_file, NULL);
	if (!str) {
		char *base = r_sys_getenv (R_LIB_ENV);
		if (base) {
			char *file = r_str_appendf (base, R_SYS_DIR "%s", profile_file);
			if (file) {
				str = r_file_slurp (file, NULL);
				free (file);
			}
		}
	}
	if (str) {
		char *ret = gdb_to_r2_profile (str);
		free (str);
		return ret;
	}
	R_LOG_WARN ("r_reg_parse_gdb_profile: Cannot find '%s'", profile_file);
	return NULL;
}

R_API char *r_reg_profile_to_cc(RReg *reg) {
	R_RETURN_VAL_IF_FAIL (reg, NULL);
	const char *r0 = r_reg_alias_getname (reg, R_REG_ALIAS_R0);
	if (!r0) {
		r0 = r_reg_alias_getname (reg, R_REG_ALIAS_A0);
	}
	if (!r0) {
		return NULL;
	}
	RStrBuf *sb = r_strbuf_new (NULL);
	r_strbuf_appendf (sb, "%s reg(", r0);
	int i;
	for (i = R_REG_ALIAS_A0; i <= R_REG_ALIAS_A9; i++) {
		const char *arg = r_reg_alias_getname (reg, i);
		if (!arg) {
			break;
		}
		r_strbuf_appendf (sb, "%s%s", i == R_REG_ALIAS_A0? "": ", ", arg);
	}
	r_strbuf_append (sb, ")");
	return r_strbuf_drain (sb);
}
