// SPDX-License-Identifier: LGPL-3.0-only

#include "rz_util.h"
#include "rz_types.h"
#include "rz_parse.h"
#include "tcc.h"

extern int tcc_sym_push(char *typename, int typesize, int meta);

/* parse C code and return it in key-value form */

static void __appendString(const char *msg, char **s) {
	if (!s) {
		printf("%s\n", msg);
	} else if (*s) {
		char *p = malloc(strlen(msg) + strlen(*s) + 1);
		if (p) {
			strcpy(p, *s);
			free(*s);
			*s = p;
			strcpy(p + strlen(p), msg);
		}
	} else {
		*s = strdup(msg);
	}
}

static bool __typeLoad(void *p, const char *k, const char *v) {
	if (!p) {
		return false;
	}
	int btype = 0;
	RzAnalysis *analysis = (RzAnalysis *)p;
	//rz_cons_printf ("tk %s=%s\n", k, v);
	// TODO: Add unions support
	if (!strncmp(v, "struct", 6) && strncmp(k, "struct.", 7)) {
		// structure
		btype = VT_STRUCT;
		const char *typename = k;
		int typesize = 0;
		// TODO: Add typesize here
		char *query = sdb_fmt("struct.%s", k);
		char *members = sdb_get(analysis->sdb_types, query, 0);
		char *next, *ptr = members;
		if (members) {
			do {
				char *name = sdb_anext(ptr, &next);
				if (!name) {
					break;
				}
				query = sdb_fmt("struct.%s.%s", k, name);
				char *subtype = sdb_get(analysis->sdb_types, query, 0);
				if (!subtype) {
					break;
				}
				char *tmp = strchr(subtype, ',');
				if (tmp) {
					*tmp++ = 0;
					tmp = strchr(tmp, ',');
					if (tmp) {
						*tmp++ = 0;
					}
					char *subname = tmp;
					// TODO: Go recurse here
					query = sdb_fmt("struct.%s.%s.meta", subtype, subname);
					btype = sdb_num_get(analysis->sdb_types, query, 0);
					tcc_sym_push(subtype, 0, btype);
				}
				free(subtype);
				ptr = next;
			} while (next);
			free(members);
		}
		tcc_sym_push((char *)typename, typesize, btype);
	}
	return true;
}

static void __errorFunc(void *opaque, const char *msg) {
	__appendString(msg, opaque);
	char **p = (char **)opaque;
	if (p && *p) {
		int n = strlen(*p);
		char *ptr = malloc(n + 2);
		if (!ptr) {
			return;
		}
		strcpy(ptr, *p);
		ptr[n] = '\n';
		ptr[n + 1] = 0;
		free(*p);
		*p = ptr;
	}
}

RZ_API char *rz_parse_c_file(RzAnalysis *analysis, const char *path, const char *dir, char **error_msg) {
	char *str = NULL;
	TCCState *T = tcc_new(analysis->cpu, analysis->bits, analysis->os);
	if (!T) {
		return NULL;
	}
	tcc_set_callback(T, &__appendString, &str);
	tcc_set_error_func(T, (void *)error_msg, __errorFunc);
	sdb_foreach(analysis->sdb_types, __typeLoad, analysis);
	if (tcc_add_file(T, path, dir) == -1) {
		free(str);
		str = NULL;
	}
	tcc_delete(T);
	return str;
}

RZ_API char *rz_parse_c_string(RzAnalysis *analysis, const char *code, char **error_msg) {
	char *str = NULL;
	TCCState *T = tcc_new(analysis->cpu, analysis->bits, analysis->os);
	if (!T) {
		return NULL;
	}
	tcc_set_callback(T, &__appendString, &str);
	tcc_set_error_func(T, (void *)error_msg, __errorFunc);
	sdb_foreach(analysis->sdb_types, __typeLoad, NULL);
	if (tcc_compile_string(T, code) != 0) {
		free(str);
		str = NULL;
	}
	tcc_delete(T);
	return str;
}

// XXX do not use globals
RZ_API void rz_parse_c_reset(RzParse *p) {
	anon_sym = SYM_FIRST_ANOM;
}
