// SPDX-License-Identifier: LGPL-3.0-only
// SPDX-FileCopyrightText: 2021 Heersin <teablearcher@gmail.com>
// SPDX-FileCopyrightText: 2025-2026 Sergey Sharshunov <s.sharshunov@gmail.com>

#include "luac_common.h"

void luac_add_section(RzPVector /*<RzBinSection *>*/ *section_vec, char *name, ut64 poffset, ut64 voffset, ut32 size, bool is_func) {
	if (size == 0)
		return;
	RzBinSection *bin_sec = RZ_NEW0(RzBinSection);
	if (!bin_sec || !name) {
		free(bin_sec);
		return;
	}
	bin_sec->name = rz_str_dup(name);
	bin_sec->vaddr = voffset;
	bin_sec->paddr = poffset;
	bin_sec->size = size;
	bin_sec->vsize = size;
	bin_sec->is_data = is_func ? false : true;
	bin_sec->bits = 32;
	bin_sec->has_strings = false;
	bin_sec->arch = "luac";

	if (is_func) {
		bin_sec->perm = RZ_PERM_R | RZ_PERM_X;
	} else {
		bin_sec->perm = RZ_PERM_R;
	}

	if (!rz_pvector_push(section_vec, bin_sec)) {
		rz_bin_section_free(bin_sec);
	}
}

void luac_add_symbol(RzList /*<RzBinSymbol *>*/ *symbol_list, char *name, ut64 poffset, ut64 voffset, ut64 size, const char *type) {
	RzBinSymbol *bin_sym = RZ_NEW0(RzBinSymbol);
	if (!bin_sym) {
		return;
	}
	bin_sym->name = rz_str_dup(name);
	bin_sym->vaddr = voffset;
	bin_sym->paddr = poffset;
	bin_sym->size = size;
	bin_sym->type = type;
	bin_sym->bits = 32;
	rz_list_append(symbol_list, bin_sym);
}

void luac_add_entry(RzPVector /*<RzBinAddr *>*/ *entry_vec, ut64 offset, int entry_type) {
	RzBinAddr *entry = RZ_NEW0(RzBinAddr);
	if (!entry) {
		return;
	}
	entry->vaddr = offset;
	entry->paddr = offset;
	entry->type = entry_type;
	rz_pvector_push(entry_vec, entry);
}

void luac_add_string(RzList /*<RzBinString *>*/ *string_list, char *string, ut64 poffset, ut64 voffset, ut64 size, int cpu) {
	RzBinString *bin_string = RZ_NEW0(RzBinString);
	if (!bin_string) {
		return;
	}
	bin_string->paddr = poffset;
	bin_string->vaddr = voffset;
	bin_string->length = size;
	bin_string->size = (cpu == LUAJIT_CPU) ? size : size + 1;
	bin_string->string = rz_str_dup(string);
	bin_string->type = RZ_STRING_ENC_UTF8;
	rz_list_append(string_list, bin_string);
}

static void free_rz_section(RzBinSection *section) {
	if (!section) {
		return;
	}
	if (section->name) {
		RZ_FREE(section->name);
	}
	RZ_FREE(section);
}

static void free_rz_string(RzBinString *string) {
	if (!string) {
		return;
	}
	if (string->string) {
		RZ_FREE(string->string);
	}
	RZ_FREE(string);
}

void luac_build_info_free(LuacBinInfo *bin_info) {
	if (!bin_info) {
		return;
	}
	free(bin_info->header->src_file_name);
	free(bin_info->header);
	lua_free_proto_entry(bin_info->proto);
	bin_info->proto = NULL;
	rz_pvector_free(bin_info->protos_vec);
	rz_pvector_free(bin_info->all_const_vec);
	rz_pvector_free(bin_info->entry_vec);
	rz_list_free(bin_info->symbol_list);
	rz_pvector_free(bin_info->line_nums_vec);
	rz_pvector_free(bin_info->section_vec);
	rz_list_free(bin_info->string_list);
	free(bin_info);
}

static void free_line_nums(RzBinSourceLineSample *ln) {
	if (!ln) {
		return;
	}
	if (ln->file) {
		RZ_FREE(ln->file);
	}
	RZ_FREE(ln);
}

LuacBinInfo *luac_build_info(RZ_NONNULL LuaProto *proto) {
	if (!proto) {
		RZ_LOG_ERROR("Invalid luac file\n");
		return NULL;
	}

	LuacBinInfo *ret = RZ_NEW0(LuacBinInfo);
	rz_return_val_if_fail(ret, NULL);

	ret->protos_vec = rz_pvector_new((RzPVectorFree)NULL);
	ret->all_const_vec = rz_pvector_new((RzPVectorFree)NULL);
	ret->entry_vec = rz_pvector_new((RzPVectorFree)free);
	ret->symbol_list = rz_list_newf((RzListFree)rz_bin_symbol_free);
	ret->section_vec = rz_pvector_new((RzPVectorFree)free_rz_section);
	ret->line_nums_vec = rz_pvector_new((RzPVectorFree)free_line_nums);
	ret->string_list = rz_list_newf((RzListFree)free_rz_string);

	if (!(ret->entry_vec && ret->symbol_list && ret->section_vec && ret->string_list)) {
		rz_pvector_free(ret->entry_vec);
		rz_list_free(ret->symbol_list);
		rz_pvector_free(ret->section_vec);
		rz_pvector_free(ret->line_nums_vec);
		rz_list_free(ret->string_list);
	}

	_luac_build_info(proto, ret);

	LuaConstEntry *const_entry = NULL;
	void **it;
	rz_pvector_foreach (ret->all_const_vec, it) {
		const_entry = *it;
		if (const_entry->tag == LUA_VSTRING_IDX) {
			const LuaConstEntry *tmp_const = rz_pvector_at(ret->all_const_vec, *(ut8 *)const_entry->data);
			rz_warn_if_fail(tmp_const);
			RZ_FREE(const_entry->data);
			const int new_len = tmp_const->data_len + 1;
			const_entry->data = RZ_NEWS(ut8, new_len);
			rz_mem_copy(const_entry->data, new_len, tmp_const->data, new_len);
			const_entry->data_len = tmp_const->data_len;
			const_entry->tag = tmp_const->tag;
			const_entry->offset = tmp_const->offset;
			const_entry->prev_offset = tmp_const->prev_offset;
			const_entry->voffset = tmp_const->voffset;
			const_entry->string_start = tmp_const->string_start;
		}
	}

	// add entry of main
	ut64 main_entry_offset = 0x0 + PROTO_VBASE;
	luac_add_entry(ret->entry_vec, main_entry_offset, RZ_BIN_ENTRY_TYPE_PROGRAM);

	size_t paddr = proto->code_offset + proto->code_skipped;
	RzBinSymbol *msym = rz_bin_symbol_new("entry0", paddr, main_entry_offset);
	msym->type = RZ_BIN_TYPE_FUNC_STR;
	msym->bind = RZ_BIN_BIND_GLOBAL_STR;
	msym->size = proto->code_size; // section->size;
	msym->paddr = paddr;
	rz_list_append(ret->symbol_list, msym);
	return ret;
}

void _luac_build_info(LuaProto *proto, LuacBinInfo *info) {
	/* process proto header info */
	char *symbol_name = NULL;
	char *proto_name = NULL;
	char **upvalue_names = NULL;
	RzListIter *iter;

	ut64 current_offset = 0;
	ut64 current_size = 0;
	char *section_name = NULL;

	const ut64 proto_vaddr = PROTO_VADDRESS(proto->index);

	// 1.0 set section name as function_name.code
	current_offset = proto->code_offset + proto->code_skipped;
	current_size = proto->code_size;
	proto_name = rz_str_newf("fcn.%08" PFMT64x, (ut64)proto_vaddr);
	section_name = rz_str_newf("%s.code", proto_name);
	luac_add_section(info->section_vec, section_name, current_offset, proto_vaddr, current_size, true);

	char *name = proto->line_defined == 0 ? rz_str_dup("entry0") : rz_str_newf("proto%d", proto->index);
	RzBinSymbol *proto_sym = rz_bin_symbol_new(name, current_offset, proto_vaddr);
	proto_sym->bind = RZ_BIN_BIND_GLOBAL_STR;
	proto_sym->type = RZ_BIN_TYPE_FUNC_STR;
	proto_sym->bits = 32;
	proto_sym->size = current_size;
	rz_list_append(info->symbol_list, proto_sym);
	RZ_FREE(name);
	RZ_FREE(section_name);

	// 1.2 set const section
	current_offset = proto->const_offset;
	current_size = proto->const_size;

	section_name = rz_str_newf("%s.const", proto_name);
	const ut64 const_voffset = proto_vaddr + CONST_OFFSET;
	luac_add_section(info->section_vec, section_name, current_offset, const_voffset, current_size, false);
	RZ_FREE(section_name);

	LuaLineinfoEntry *line_info_entry;
	rz_list_foreach (proto->line_info_entries, iter, line_info_entry) {
		RzBinSourceLineSample *new_line_sample = RZ_NEW0(RzBinSourceLineSample);
		if (!new_line_sample) {
			return;
		}
		new_line_sample->address = line_info_entry->offset;
		new_line_sample->column = 0;
		new_line_sample->line = line_info_entry->info_data;
		new_line_sample->file = rz_str_dup((const char *)proto->proto_name);
		rz_pvector_push(info->line_nums_vec, new_line_sample);
	}

	// 1.3 upvalue section
	current_offset = proto->upvalue_offset;
	current_size = proto->upvalue_size;
	section_name = rz_str_newf("%s.upvalues", proto_name);
	const ut64 upvalues_voffset = proto_vaddr + UPVALUE_OFFSET;
	luac_add_section(info->section_vec, section_name, current_offset, upvalues_voffset, current_size, false);
	RZ_FREE(section_name);

	// 1.4 debug section
	current_offset = proto->local_var_offset;
	current_size = proto->local_var_size;
	section_name = rz_str_newf("%s.lvars", proto_name);
	const ut64 lvars_voffset = proto_vaddr + LVARS_OFFSET;
	luac_add_section(info->section_vec, section_name, current_offset, lvars_voffset, current_size, false);
	RZ_FREE(section_name);
	RZ_FREE(proto_name);

	// 1.5 construct constant symbols
	LuaConstEntry *const_entry = NULL;
	void **it;
	rz_pvector_foreach (proto->const_entries, it) {
		const_entry = *it;
		rz_pvector_push(info->all_const_vec, const_entry);
	}

	(void)symbol_name;
	rz_pvector_push(info->protos_vec, proto);

	// 1.6 parse sub proto
	LuaProto *sub_proto;
	rz_list_foreach (proto->proto_entries, iter, sub_proto) {
		_luac_build_info(sub_proto, info);
	}

	free(upvalue_names);
	free(proto_name);
}
