/* radare - LGPL - Copyright 2008-2023 - pancake */

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

R_LIB_VERSION (r_lib);

/* XXX : this must be registered at runtime instead of hardcoded */
static const char *const r_lib_types[] = {
	"io", "dbg", "lang", "asm", "anal", "parse", "bin", "bin_xtr", "bin_ldr",
	"bp", "syscall", "fastcall", "crypto", "core", "egg", "fs", "arch", NULL
};

static const char *__lib_types_get(int idx) {
	if (idx < 0 || idx > R_LIB_TYPE_LAST - 1) {
		return "unk";
	}
	return r_lib_types[idx];
}

R_API int r_lib_types_get_i(const char *str) {
	int i;
	for (i = 0; r_lib_types[i]; i++) {
		if (!strcmp (str, r_lib_types[i])) {
			return i;
		}
	}
	return -1;
}

R_API void *r_lib_dl_open(const char *libname) {
	void *ret = NULL;
#if WANT_DYLINK
#if R2__UNIX__
	if (libname) {
#if __linux__
		ret = dlopen (libname, RTLD_NOW);
#endif
		if (!ret) {
			ret = dlopen (libname, RTLD_GLOBAL | RTLD_LAZY);
		}
	} else {
		ret = dlopen (NULL, RTLD_NOW);
	}
	if (!ret) {
		R_LOG_DEBUG ("r_lib_dl_open failed %s (%s)", libname, dlerror ());
	}
#elif R2__WINDOWS__
	LPTSTR libname_;
	if (libname && *libname) {
		libname_ = r_sys_conv_utf8_to_win (libname);
	} else {
		libname_ = calloc (MAX_PATH, sizeof (TCHAR));
		if (!libname_) {
			return NULL;
		}
		if (!GetModuleFileName (NULL, libname_, MAX_PATH)) {
			libname_[0] = '\0';
		}
	}
	ret = LoadLibrary (libname_);
	free (libname_);
	if (!ret) {
		R_LOG_DEBUG ("r_lib_dl_open failed %s", libname);
	}
#endif
#endif
	return ret;
}

R_API void *r_lib_dl_sym(void *handler, const char *name) {
#if WANT_DYLINK
#if R2__UNIX__
	return dlsym (handler, name);
#elif R2__WINDOWS__
	return GetProcAddress (handler, name);
#else
	return NULL;
#endif
#else
	return NULL;
#endif
}

R_API int r_lib_dl_close(void *handler) {
#if R2__UNIX__ && WANT_DYLINK
	return dlclose (handler);
#else
	return handler? 0: -1;
#endif
}

R_API char *r_lib_path(const char *libname) {
#if R2__WINDOWS__
	char *tmp = r_str_newf ("%s." R_LIB_EXT, libname);
	if (!tmp) {
		return NULL;
	}
	WCHAR *name = r_utf8_to_utf16 (tmp);
	free (tmp);
	WCHAR *path = NULL;
	if (!name) {
		goto err;
	}

	int count;
	if (!(count = SearchPathW (NULL, name, NULL, 0, NULL, NULL))) {
		r_sys_perror ("SearchPath");
		goto err;
	}
	path = malloc (count * sizeof (WCHAR));
	if (!path) {
		goto err;
	}
	if (!(count = SearchPathW (NULL, name, NULL, count, path, NULL))) {
		R_FREE (path);
		r_sys_perror ("SearchPath");
		goto err;
	}
	tmp = r_utf16_to_utf8 (path);
	free (name);
	free (path);
	return tmp;
err:
	free (name);
	return NULL;
#else
#if __APPLE__
	char *env = r_sys_getenv ("DYLD_LIBRARY_PATH");
	env = r_str_append (env, ":/lib:/usr/lib:/usr/local/lib");
#elif R2__UNIX__
	char *env = r_sys_getenv ("LD_LIBRARY_PATH");
	env = r_str_append (env, ":/lib:/usr/lib:/usr/local/lib");
#endif
	if (!env) {
		env = strdup (".");
	}
	char *next, *path0 = env;
	do {
		next = strchr (path0, ':');
		if (next) {
			*next = 0;
		}
		char *libpath = r_str_newf ("%s"R_SYS_DIR"%s." R_LIB_EXT, path0, libname);
		if (r_file_exists (libpath)) {
			free (env);
			return libpath;
		}
		free (libpath);
		path0 = next + 1;
	} while (next);
	free (env);
	return NULL;
#endif
}

R_API RLib *r_lib_new(const char *symname, const char *symnamefunc) {
	RLib *lib = R_NEW (RLib);
	if (lib) {
		// __has_debug = r_sys_getenv_asbool ("R2_DEBUG"); /// XXX just use loglevel
		if (r_sys_getenv_asbool ("R2_DEBUG")) {
			r_log_set_level (R_LOGLVL_DEBUG);
		}
		lib->ignore_version = r_sys_getenv_asbool ("R2_IGNVER");
		lib->handlers = r_list_newf (free);
		lib->plugins = r_list_newf (free);
		lib->symname = strdup (symname? symname: R_LIB_SYMNAME);
		lib->symnamefunc = strdup (symnamefunc? symnamefunc: R_LIB_SYMFUNC);
	}
	return lib;
}

R_API void r_lib_free(RLib *lib) {
	if (lib) {
		r_lib_close (lib, NULL);
		r_list_free (lib->handlers);
		r_list_free (lib->plugins);
		free (lib->symname);
		free (lib->symnamefunc);
		free (lib);
	}
}

static bool __lib_dl_check_filename(const char *file) {
	return r_str_endswith (file, "." R_LIB_EXT);
}

R_API int r_lib_run_handler(RLib *lib, RLibPlugin *plugin, RLibStruct *symbol) {
	RLibHandler *h = plugin->handler;
	if (h && h->constructor) {
		R_LOG_DEBUG ("PLUGIN LOADED %p fcn %p", h, h->constructor);
		return h->constructor (plugin, h->user, symbol->data);
	}
	R_LOG_DEBUG ("Cannot find plugin constructor");
	return -1;
}

R_API RLibHandler *r_lib_get_handler(RLib *lib, int type) {
	RLibHandler *h;
	RListIter *iter;
	r_list_foreach (lib->handlers, iter, h) {
		if (h->type == type) {
			return h;
		}
	}
	return NULL;
}

R_API int r_lib_close(RLib *lib, const char *file) {
	RLibPlugin *p;
	RListIter *iter, *iter2;

	r_list_foreach_safe (lib->plugins, iter, iter2, p) {
		if ((!file || !strcmp (file, p->file))) {
			int ret = 0;
			if (p->handler && p->handler->destructor) {
				ret = p->handler->destructor (p, p->handler->user, p->data);
			}
			if (p->free) {
				p->free (p->data);
			}
			free (p->file);
			r_list_delete (lib->plugins, iter);
			if (file) {
				return ret;
			}
		}
	}
	if (!file) {
		return 0;
	}
	// delete similar plugin name
	r_list_foreach (lib->plugins, iter, p) {
		if (strstr (p->file, file)) {
			int ret = 0;
			if (p->handler && p->handler->destructor) {
				ret = p->handler->destructor (p,
					p->handler->user, p->data);
			}
			free (p->file);
			r_list_delete (lib->plugins, iter);
#if R2_590
			{
				const char *fileName = r_str_rstr (file, R_SYS_DIR);
				if (fileName) {
					ht_pp_delete (lib->plugins_ht, fileName + 1);
				}
			}
#endif
			return ret;
		}
	}
	return -1;
}

static bool __already_loaded(RLib *lib, const char *file) {
	const char *fileName = r_str_rstr (file, R_SYS_DIR);
	if (fileName) {
		RLibPlugin *p;
#if R2_590
		bool found;
		p = ht_pp_find (lib->plugins_ht, fileName + 1, &found);
		if (found && p) {
			return true;
		}
#else
		RListIter *iter;
		r_list_foreach (lib->plugins, iter, p) {
			const char *pFileName = r_str_rstr (p->file, R_SYS_DIR);
			if (pFileName && !strcmp (fileName, pFileName)) {
				return true;
			}
		}
#endif
	}
	return false;
}

R_API int r_lib_open(RLib *lib, const char *file) {
	/* ignored by filename */
	if (!__lib_dl_check_filename (file)) {
		R_LOG_ERROR ("Invalid library extension: %s", file);
		return -1;
	}

	if (__already_loaded (lib, file)) {
		R_LOG_ERROR ("Not loading library because it has already been loaded from '%s'", file);
		return -1;
	}

	void *handler = r_lib_dl_open (file);
	if (!handler) {
		R_LOG_DEBUG ("Cannot open library: '%s'", file);
		return -1;
	}

	RLibStructFunc strf = (RLibStructFunc) r_lib_dl_sym (handler, lib->symnamefunc);
	RLibStruct *stru = NULL;
	if (strf) {
		stru = strf ();
	}
	if (!stru) {
		stru = (RLibStruct *) r_lib_dl_sym (handler, lib->symname);
	}
	if (!stru) {
		R_LOG_DEBUG ("Cannot find symbol '%s' in library '%s'", lib->symname, file);
		r_lib_dl_close (handler);
		return -1;
	}

	int res = r_lib_open_ptr (lib, file, handler, stru);
	if (strf) {
		free (stru);
	}
	return res;
}

char *major_minor(const char *s) {
	char *a = strdup (s);
	char *p = strchr (a, '.');
	if (p) {
		p = strchr (p + 1, '.');
		if (p) {
			*p = 0;
		}
	}
	return a;
}

R_API int r_lib_open_ptr(RLib *lib, const char *file, void *handler, RLibStruct *stru) {
	r_return_val_if_fail (lib && file && stru, -1);
	if (stru->version && !lib->ignore_version) {
		char *mm0 = major_minor (stru->version);
		char *mm1 = major_minor (R2_VERSION);
		bool mismatch = strcmp (mm0, mm1);
		free (mm0);
		free (mm1);
		if (mismatch) {
			R_LOG_WARN ("Module version mismatch %s (%s) vs (%s)", file, stru->version, R2_VERSION);
			const char *dot = strchr (stru->version, '.');
			int major = atoi (stru->version);
			int minor = dot ? atoi (dot + 1) : 0;
			// The pkgname member was introduced in 4.2.0
			if (major > 4 || (major == 4 && minor >= 2)) {
				if (stru->pkgname) {
					printf ("r2pm -ci %s\n", stru->pkgname);
				}
			}
			return -1;
		}
	}
	RLibPlugin *p = R_NEW0 (RLibPlugin);
	if (R_UNLIKELY (!p)) {
		return -1;
	}
	p->type = stru->type;
	p->data = stru->data;
	p->file = strdup (file);
	p->dl_handler = handler;
	p->handler = r_lib_get_handler (lib, p->type);
	p->free = stru->free;

	int ret = r_lib_run_handler (lib, p, stru);
	if (ret == -1) {
		R_LOG_ERROR ("Library handler has failed for '%s'", file);
		free (p->file);
		free (p);
		r_lib_dl_close (handler);
	} else {
		r_list_append (lib->plugins, p);
#if R2_590
		const char *fileName = r_str_rstr (file, R_SYS_DIR);
		if (fileName) {
			ht_pp_insert (lib->plugins_ht, strdup (fileName), p);
		}
#endif
	}
	return ret;
}

R_API bool r_lib_opendir(RLib *lib, const char *path) {
#if WANT_DYLINK
	r_return_val_if_fail (lib && path, false);
#ifdef R2_LIBR_PLUGINS
	if (!path) {
		path = R2_LIBR_PLUGINS;
	}
#endif
	if (!path) {
		return false;
	}
#if R2__WINDOWS__
	wchar_t file[1024];
	WIN32_FIND_DATAW dir;
	HANDLE fh;
	wchar_t directory[MAX_PATH];
	char *wctocbuff;
	wchar_t *wcpath = r_utf8_to_utf16 (path);
	if (!wcpath) {
		return false;

	}
	swprintf (directory, _countof (directory), L"%ls\\*.*", wcpath);
	fh = FindFirstFileW (directory, &dir);
	if (fh == INVALID_HANDLE_VALUE) {
		R_LOG_DEBUG ("Cannot open directory %ls", wcpath);
		free (wcpath);
		return false;
	}
	do {
		swprintf (file, _countof (file), L"%ls/%ls", wcpath, dir.cFileName);
		wctocbuff = r_utf16_to_utf8 (file);
		if (wctocbuff) {
			if (__lib_dl_check_filename (wctocbuff)) {
				R_LOG_DEBUG ("Loading %s", file);
				r_lib_open (lib, wctocbuff);
			} else {
				R_LOG_DEBUG ("Cannot open %ls", dir.cFileName);
			}
			free (wctocbuff);
		}
	} while (FindNextFileW (fh, &dir));
	FindClose (fh);
	free (wcpath);
#else
	char file[1024];
	struct dirent *de;
	DIR *dh = opendir (path);
	if (!dh) {
		R_LOG_DEBUG ("Cannot open directory '%s'", path);
		return false;
	}
	while ((de = (struct dirent *)readdir (dh))) {
		if (de->d_name[0] == '.' || strstr (de->d_name, ".dSYM")) {
			continue;
		}
		snprintf (file, sizeof (file), "%s/%s", path, de->d_name);
		if (__lib_dl_check_filename (file)) {
			R_LOG_DEBUG ("Loading %s", file);
			r_lib_open (lib, file);
		} else {
			R_LOG_DEBUG ("Cannot open %s", file);
		}
	}
	closedir (dh);
#endif
#endif
	return true;
}

R_API bool r_lib_add_handler(RLib *lib,
	int type, const char *desc,
	int (*cb)(RLibPlugin *, void *, void *),  /* constructor */
	int (*dt)(RLibPlugin *, void *, void *),  /* destructor */
	void *user)
{
	RLibHandler *h;
	RListIter *iter;
	RLibHandler *handler = NULL;

	// TODO r2_590 resolve using lib->handlers_ht
	r_list_foreach (lib->handlers, iter, h) {
		if (type == h->type) {
			R_LOG_DEBUG ("Redefining library handler constructor for %d", type);
			handler = h;
			break;
		}
	}
	if (!handler) {
		handler = R_NEW (RLibHandler);
		if (!handler) {
			return false;
		}
		handler->type = type;
		r_list_append (lib->handlers, handler);
	}
	r_str_ncpy (handler->desc, desc, sizeof (handler->desc) - 1);
	handler->user = user;
	handler->constructor = cb;
	handler->destructor = dt;

	return true;
}

// R2_590 - delete handler by type doesnt make sense. lets do it by plug name instead
R_API bool r_lib_del_handler(RLib *lib, int type) {
	RLibHandler *h = NULL;
	RListIter *iter;
#if R2_590
// XXX delete plugin by name, by filename or by type >? wtf this function is broken
		bool found;
		// h = ht_pp_find (lib->plugins_ht, fileName, &found);
		if (found && h) {
			// ht_pp_delete (lib->plugins_ht, fileName);
			return true;
		}
#else
#endif
	// TODO: remove all handlers for that type? or only one?
	/* No _safe loop necessary because we return immediately after the delete. */
	r_list_foreach (lib->handlers, iter, h) {
		if (type == h->type) {
			r_list_delete (lib->handlers, iter);
// TODOL delete handler from hashtable
			return true;
		}
	}
	return false;
}

// TODO _list methods should not exist.. only used in ../core/cmd_log.c: r_lib_list (core->lib);
R_API void r_lib_list(RLib *lib) {
	RListIter *iter;
	RLibPlugin *p;
	r_list_foreach (lib->plugins, iter, p) {
		printf (" %5s %p %s \n", __lib_types_get (p->type),
			p->dl_handler, p->file);
	}
}
