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

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

/* non-endian safe - used for raw mapping with system registers */
R_API ut8 *r_reg_get_bytes(RReg *reg, int type, int *size) {
	R_RETURN_VAL_IF_FAIL (reg, NULL);
	RRegArena *arena;
	int i, sz, osize;
	ut8 *buf, *newbuf;
	if (size) {
		*size = 0;
	}
	if (type == -1) {
		osize = sz = 0;
		buf = malloc (8);
		if (!buf) {
			return NULL;
		}
		for (i = 0; i < R_REG_TYPE_LAST; i++) {
			arena = reg->regset[i].arena;
			sz += arena->size;
			newbuf = realloc (buf, sz);
			if (!newbuf) {
				break;
			}
			buf = newbuf;
			memcpy (buf + osize, arena->bytes, arena->size);
			osize = sz;
		}
		if (size) {
			*size = sz;
		}
		return buf;
	}
	if (type < 0 || type > (R_REG_TYPE_LAST - 1)) {
		return NULL;
	}
	sz = reg->regset[type].arena->size;
	if (size) {
		*size = sz;
	}
	if (!sz) {
		return NULL;
	}
	buf = malloc (sz);
	if (buf) {
		memcpy (buf, reg->regset[type].arena->bytes, sz);
	}
	return buf;
}

/* deserialize buffer into ALL register type arenas (inverse of r_reg_get_bytes with type=-1) */
R_API bool r_reg_read_regs(RReg *reg, ut8 *buf, const int len) {
	R_RETURN_VAL_IF_FAIL (reg && buf, false);
	int i, off = 0;
	RRegArena *arena;
	for (i = 0; i < R_REG_TYPE_LAST; i++) {
		if (reg->regset[i].arena) {
			arena = reg->regset[i].arena;
		} else {
			arena = reg->regset[i].arena = R_NEW0 (RRegArena);
			arena->size = len;
			arena->bytes = calloc (1, len);
			if (!arena->bytes) {
				r_reg_arena_free (arena);
				return false;
			}
		}
		if (!arena->bytes) {
			arena->size = 0;
			return false;
		}
		r_reg_arena_materialize (arena);
		memset (arena->bytes, 0, arena->size);
		memcpy (arena->bytes, buf + off,
			R_MIN (len - off, arena->size));
		off += arena->size;
		if (off > len) {
			return false;
		}
	}
	return true;
}

R_API bool r_reg_set_bytes(RReg *reg, int type, const ut8 *buf, const int len) {
	R_RETURN_VAL_IF_FAIL (reg && buf, false);
	if (len < 1) {
		return false;
	}
	if (type < 0 || type >= R_REG_TYPE_LAST) {
		return false;
	}
	struct r_reg_set_t *regset = &reg->regset[type];
	RRegArena *arena = regset->arena;
	if (!arena) {
		return false;
	}
	int maxsz = R_MAX (arena->size, len);
	int minsz = R_MIN (arena->size, len);
	if ((arena->size != len) || (!arena->bytes)) {
		if (!arena->shared) {
			free (arena->bytes);
		}
		arena->bytes = calloc (1, maxsz);
		if (!arena->bytes) {
			arena->size = 0;
			arena->shared = false;
			return false;
		}
		arena->size = maxsz;
		arena->shared = false;
	}
	if (arena->size != maxsz) {
		// not shared: reallocated above or sizes matched (no-op branch)
		ut8 *tmp = realloc (arena->bytes, maxsz);
		if (!tmp) {
			R_LOG_WARN ("Error resizing arena to %d", len);
			return false;
		}
		arena->size = maxsz;
		arena->bytes = tmp;
	}
	if (arena->bytes) {
		r_reg_arena_materialize (arena);
		if (!arena->bytes) {
			return false;
		}
		memset (arena->bytes, 0, arena->size);
		memcpy (arena->bytes, buf, minsz);
		return true;
	}
	return false;
}

R_API void r_reg_fit_arena(RReg *reg) {
	R_RETURN_IF_FAIL (reg);
	RRegArena *arena;
	RListIter *iter;
	RRegItem *r;
	int size, i, newsize;

	for (i = 0; i < R_REG_TYPE_LAST; i++) {
		arena = reg->regset[i].arena;
		if (!arena) {
			continue;
		}
		newsize = 0;
		r_list_foreach (reg->regset[i].regs, iter, r) {
			// XXX: bits2bytes doesnt seems to work fine
			size = BITS2BYTES (r->offset + r->size);
			newsize = R_MAX (size, newsize);
		}
		RRegVBank *vb;
		R_VEC_FOREACH (&reg->regset[i].vbanks, vb) {
			size = BITS2BYTES (vb->offset + vb->size * vb->count);
			newsize = R_MAX (size, newsize);
		}
		if (newsize < 1) {
			if (!arena->shared) {
				R_FREE (arena->bytes);
			} else {
				arena->bytes = NULL;
				arena->shared = false;
			}
			arena->size = 0;
		} else if (arena->shared) {
			// realloc on borrowed bytes would corrupt parent, allocate fresh
			ut8 *buf = calloc (1, newsize);
			if (buf) {
				arena->size = newsize;
				arena->bytes = buf;
				arena->shared = false;
			} else {
				arena->bytes = NULL;
				arena->size = 0;
				arena->shared = false;
			}
		} else {
			ut8 *buf = realloc (arena->bytes, newsize);
			if (buf) {
				arena->size = newsize;
				arena->bytes = buf;
				memset (arena->bytes, 0, arena->size);
			} else {
				arena->bytes = NULL;
				arena->size = 0;
			}
		}
	}
}

R_API RRegArena *r_reg_arena_clone(RRegArena *a) {
	R_RETURN_VAL_IF_FAIL (a, NULL);
	RRegArena *na = R_NEW0 (RRegArena);
	if (a->bytes && a->size > 0) {
		na->bytes = r_mem_dup (a->bytes, a->size);
		na->size = a->size;
	}
	// clone always owns its bytes (shared=false via R_NEW0)
	return na;
}

R_API RRegArena *r_reg_arena_new(int size) {
	// if arena is resized, the constructor should take 0 arguments imho
	RRegArena *arena = R_NEW0 (RRegArena);
	if (size < 1) {
		size = 1;
	}
	if (!(arena->bytes = calloc (1, size + 8))) {
		R_FREE (arena);
	} else {
		arena->size = size;
	}
	return arena;
}

R_API void r_reg_arena_free(RRegArena *ra) {
	if (ra) {
		if (!ra->shared) {
			free (ra->bytes);
		}
		free (ra);
	}
}

R_API void r_reg_arena_materialize(RRegArena *a) {
	if (R_LIKELY (!a || !a->shared)) {
		return;
	}
	const int sz = a->size;
	ut8 *fresh = calloc (1, sz + 8);
	if (R_UNLIKELY (!fresh)) {
		a->bytes = NULL;
		a->size = 0;
		a->shared = false;
		return;
	}
	if (a->bytes && sz > 0) {
		memcpy (fresh, a->bytes, sz);
	}
	a->bytes = fresh;
	a->shared = false;
}

R_API void r_reg_arena_swap(RReg *reg, int copy) {
	R_RETURN_IF_FAIL (reg);
	// swap current arena to head (previous), break COW shares first
	int i;
	for (i = 0; i < R_REG_TYPE_LAST; i++) {
		if (!reg->regset[i].pool) {
			continue;
		}
		if (r_list_length (reg->regset[i].pool) <= 1) {
			break;
		}
		RListIter *ia = reg->regset[i].cur;
		RListIter *ib = reg->regset[i].pool->head;
		RRegArena *new_cur = ib->data; // will receive writes after swap
		RRegArena *old_cur = ia->data;
		const ut8 *new_bytes = new_cur->bytes;
		const ut8 *old_bytes = old_cur->bytes;
		// pop frees from the tail, so no arena may borrow from one that moves above it
		RListIter *it;
		RRegArena *a;
		r_list_foreach (reg->regset[i].pool, it, a) {
			if (a->shared && a->bytes && (a->bytes == new_bytes || a->bytes == old_bytes || a == new_cur || a == old_cur)) {
				r_reg_arena_materialize (a);
			}
		}
		void *tmp = ia->data;
		ia->data = ib->data;
		ib->data = tmp;
		reg->regset[i].arena = ia->data;
	}
}

R_API void r_reg_arena_pop(RReg *reg) {
	R_RETURN_IF_FAIL (reg);
	RRegArena *a;
	int i;
	for (i = 0; i < R_REG_TYPE_LAST; i++) {
		if (!reg->regset[i].pool) {
			continue;
		}
		if (r_list_length (reg->regset[i].pool) < 2) {
			continue;
		}
		a = r_list_pop (reg->regset[i].pool);
		r_reg_arena_free (a);
		a = reg->regset[i].pool->tail->data;
		if (a) {
			reg->regset[i].arena = a;
			reg->regset[i].cur = reg->regset[i].pool->tail;
		}
	}
}

R_API int r_reg_arena_push(RReg *reg) {
	R_RETURN_VAL_IF_FAIL (reg, 0);
	// push must grow every regset's pool so depth stays in sync across types;
	// size>0 arenas go through CoW (borrow parent bytes, materialize on write),
	// empty arenas get a fresh size=1 placeholder matching r_reg_arena_new(0).
	int i;
	for (i = 0; i < R_REG_TYPE_LAST; i++) {
		RRegArena *a = reg->regset[i].arena;
		if (!a) {
			continue;
		}
		RRegArena *b;
		if (a->size > 0) {
			b = R_NEW0 (RRegArena);
			if (!b) {
				continue;
			}
			b->size = a->size;
			b->bytes = a->bytes;
			b->shared = true;
		} else {
			b = r_reg_arena_new (0);
			if (!b) {
				continue;
			}
		}
		r_list_push (reg->regset[i].pool, b);
		reg->regset[i].arena = b;
		reg->regset[i].cur = reg->regset[i].pool->tail;
	}
	if (reg->regset[0].pool) {
		return r_list_length (reg->regset[0].pool);
	}
	return 0;
}

R_API void r_reg_arena_zero(RReg *reg) {
	R_RETURN_IF_FAIL (reg);
	int i;
	for (i = 0; i < R_REG_TYPE_LAST; i++) {
		RRegArena *a = reg->regset[i].arena;
		if (!a || a->size <= 0) {
			continue;
		}
		if (a->shared) {
			// borrowed bytes: allocate fresh zeroed buffer instead of memset
			ut8 *fresh = calloc (1, a->size + 8);
			if (!fresh) {
				continue;
			}
			a->bytes = fresh;
			a->shared = false;
		} else {
			memset (a->bytes, 0, a->size);
		}
	}
}

R_API ut8 *r_reg_arena_peek(RReg *reg, int *size) {
	R_RETURN_VAL_IF_FAIL (reg, NULL);
	RRegSet *regset = r_reg_regset_get (reg, R_REG_TYPE_GPR);
	if (!regset || !regset->arena || (regset->arena->size < 1)) {
		return NULL;
	}
	ut8 *ret = malloc (regset->arena->size);
	if (!ret) {
		return NULL;
	}
	memcpy (ret, regset->arena->bytes, regset->arena->size);
	if (size) {
		*size = regset->arena->size;
	}
	return ret;
}

R_API void r_reg_arena_poke(RReg *reg, const ut8 *ret, int len) {
	R_RETURN_IF_FAIL (reg && ret);
	RRegSet *regset = r_reg_regset_get (reg, R_REG_TYPE_GPR);
	if (!ret || !regset || !regset->arena || !regset->arena->bytes) {
		return;
	}
	if (len > 0 && len != regset->arena->size) {
		R_LOG_WARN ("Invalid size of the arena bytes to poke (%d vs %d)",
			len, regset->arena->size);
		return;
	}
	r_reg_arena_materialize (regset->arena);
	memcpy (regset->arena->bytes, ret, regset->arena->size);
}

R_API ut8 *r_reg_arena_dup(RReg *reg, const ut8 *source) {
	R_RETURN_VAL_IF_FAIL (reg && source, NULL);
	RRegSet *regset = r_reg_regset_get (reg, R_REG_TYPE_GPR);
	if (!regset || !regset->arena || (regset->arena->size < 1)) {
		return NULL;
	}
	ut8 *ret = malloc (regset->arena->size);
	if (R_LIKELY (ret)) {
		memcpy (ret, source, regset->arena->size);
	}
	return ret;
}

R_API int r_reg_arena_set_bytes(RReg *reg, const char *str) {
	R_RETURN_VAL_IF_FAIL (reg && str, -1);
	str = r_str_trim_head_ro (str);
	int len = r_hex_str_is_valid (str);
	if (len == -1) {
		R_LOG_WARN ("Invalid input");
		return -1;
	}
	int bin_str_len = (len + 1) / 2; //2 hex chrs for 1 byte
	ut8 *bin_str = malloc (bin_str_len);
	if (!bin_str) {
		R_LOG_WARN ("Failed to decode hex str");
		return -1;
	}
	r_hex_str2bin (str, bin_str);

	int i, n = 0; //n - cumulative sum of arena's sizes
	for (i = 0; i < R_REG_TYPE_LAST; i++) {
		int sz = reg->regset[i].arena->size;
		int bl = bin_str_len - n; //bytes left
		int bln = bl - n;
		if (bln > 0 && bln < sz) {
			r_reg_set_bytes (reg, i, bin_str + n, bln);
			break;
		}
		r_reg_set_bytes (reg, i, bin_str + n, bin_str_len - n);
		n += sz;
	}
	free (bin_str);
	return 0;
}

R_API void r_reg_arena_shrink(RReg *reg) {
	R_RETURN_IF_FAIL (reg);
	int i;
	const size_t bytes_size = 1024;
	for (i = 0; i < R_REG_TYPE_LAST; i++) {
		RRegArena *a;
		RListIter *iter;
		r_list_foreach (reg->regset[i].pool, iter, a) {
			if (!a->shared) {
				free (a->bytes);
			}
			/* ha ha ha */
			a->bytes = calloc (bytes_size, 1);
			a->size = a->bytes? bytes_size: 0;
			a->shared = false;
			/* looks like sizing down the arena breaks the regsync */
			/* and sizing it up fixes reallocation when fit() is called */
		}
	}
}
