/* radare - LGPL - Copyright 2010-2025 - pancake */

#define R_LOG_ORIGIN "util.log"

#include <r_core.h>
#include <stdarg.h>

static R_TH_LOCAL RLog *rlog = NULL;
typedef struct r_log_cbuser_t {
	void *user;
	RLogCallback cb;
} RLogCallbackUser;

R_VEC_TYPE (RVecRLogCallbackUser, RLogCallbackUser);

static const char *level_tags[] = { // Log level to tag string lookup array
	[R_LOG_LEVEL_FATAL] = "FATAL",
	[R_LOG_LEVEL_ERROR] = "ERROR",
	[R_LOG_LEVEL_INFO]  = "INFO",
	[R_LOG_LEVEL_WARN]  = "WARN",
	[R_LOG_LEVEL_HINT]  = "HINT",
	[R_LOG_LEVEL_TODO]  = "TODO",
	[R_LOG_LEVEL_DEBUG] = "DEBUG",
	[R_LOG_LEVEL_TRACE] = "TRACE",
};

R_API int r_log_level_fromstring(const char *ll) {
	int i;
	for (i = 0; i < R_LOG_LEVEL_LAST; i++) {
		const char *m = r_log_level_tostring (i);
		if (r_str_casecmp (m, ll) == 0) {
			return i;
		}
	}
	return -1;
}

R_API const char *r_log_level_tostring(int i) {
	if (i >= 0 && i < R_LOG_LEVEL_LAST) {
		return level_tags[i];
	}
	return "UNKN";
}

R_API const char *r_log_level_tocolor(int level) {
	const char *k = Color_YELLOW;
	switch (level) {
	case R_LOG_LEVEL_FATAL:
	case R_LOG_LEVEL_ERROR:
		k = Color_RED;
		break;
	case R_LOG_LEVEL_INFO:
		k = Color_YELLOW;
		break;
	case R_LOG_LEVEL_WARN:
		k = Color_MAGENTA;
		break;
	case R_LOG_LEVEL_DEBUG:
		k = Color_GREEN;
		break;
	case R_LOG_LEVEL_TODO:
	case R_LOG_LEVEL_TRACE:
	case R_LOG_LEVEL_HINT:
		k = Color_CYAN;
		break;
	default:
		break;
	}
	return k;
}

// shouldnt be necessary as global thread-local instance
R_API bool r_log_init(void) {
	if (!rlog) {
		rlog = R_NEW0 (RLog);
		rlog->level = R_LOG_LEVEL_DEFAULT;
		rlog->hints = true;
	}
	return true;
}

R_API void r_log_fini(void) {
	if (rlog) {
		RLog *log = rlog;
		rlog = NULL;
		RVecRLogCallbackUser_free (log->cbs);
		free (log->file);
		free (log->filter);
		free (log);
	}
}

R_API void r_log_show_ts(bool ts) {
	if (r_log_init ()) {
		rlog->show_ts = ts;
	}
}

R_API RLogLevel r_log_get_level(void) {
	return rlog->level;
}

R_API RLogLevel r_log_get_traplevel(void) {
	return rlog? rlog->traplevel: R_LOG_LEVEL_FATAL;
}

R_API void r_log_set_level(RLogLevel level) {
	if (r_log_init ()) {
		rlog->level = level;
	}
}

R_API void r_log_set_traplevel(RLogLevel level) {
	if (r_log_init ()) {
		rlog->traplevel = level;
	}
}

R_API void r_log_set_hints(bool show_hints) {
	if (r_log_init ()) {
		rlog->hints = show_hints;
	}
}

R_API bool r_log_hints(void) {
	return r_log_init ()? rlog->hints: true;
}

R_API void r_log_set_filter(const char *s) {
	if (r_log_init ()) {
		R_FREE (rlog->filter);
		if (R_STR_ISNOTEMPTY (s)) {
			rlog->filter = strdup (s);
		}
	}
}

R_API void r_log_set_file(const char *filename) {
	if (r_log_init ()) {
		free (rlog->file);
		rlog->file = strdup (filename);
	}
}

R_API void r_log_show_origin(bool show_origin) {
	if (r_log_init ()) {
		rlog->show_origin = show_origin;
	}
}

R_API void r_log_show_source(bool show_source) {
	if (r_log_init ()) {
		rlog->show_source = show_source;
	}
}

static bool log_colors_supported(void) {
#if R2__WINDOWS__
	DWORD mode;
	HANDLE err = GetStdHandle (STD_ERROR_HANDLE);
	if (!GetConsoleMode (err, &mode) || (mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING)) {
		// Pipes may be connected to an ANSI-aware terminal such as mintty.
		return true;
	}
	if (SetConsoleMode (err, mode | ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) {
		return true;
	}
	// ANSICON provides escape handling on Windows versions without native VT support.
	char *ansicon = r_sys_getenv ("ANSICON");
	const bool supported = R_STR_ISNOTEMPTY (ansicon);
	free (ansicon);
	return supported;
#else
	return true;
#endif
}

R_API void r_log_set_colors(bool color) {
	if (r_log_init ()) {
		rlog->color = color && log_colors_supported ();
	}
}

R_API void r_log_set_quiet(bool bq) {
	if (r_log_init ()) {
		rlog->quiet = bq;
	}
}

R_API bool r_log_match(int level, const char *origin) {
	if (!r_log_init ()) {
		return false;
	}
	if (R_STR_ISNOTEMPTY (origin) && R_STR_ISNOTEMPTY (rlog->filter)) {
		if (!strstr (origin, rlog->filter)) {
			return false;
		}
	}
	if (rlog->cbs) {
		RLogCallbackUser *iter;
		rlog->iterating = true;
		R_VEC_FOREACH (rlog->cbs, iter) {
			if (iter->cb (iter->user, level, origin, NULL)) {
				rlog->iterating = false;
				return true;
			}
		}
		rlog->iterating = false;
	}
	if (level == R_LOG_LEVEL_HINT && !rlog->hints) {
		return false;
	}
	return level <= rlog->level;
}

R_API void r_log_vmessage(RLogLevel level, const char *origin, const char *func, int line, const char *fmt, va_list ap) {
	char out[512];
	if (!r_log_init ()) {
		return;
	}
	vsnprintf (out, sizeof (out), fmt, ap);
	if (rlog->cbs) {
		RLogCallbackUser *iter;
		rlog->iterating = true;
		R_VEC_FOREACH (rlog->cbs, iter) {
			if (iter->cb (iter->user, level, origin, out)) {
				rlog->iterating = false;
				return;
			}
		}
		rlog->iterating = false;
	}
	RStrBuf *sb = r_strbuf_new ("");
	if (func && r_str_startswith (func, "./")) {
		func += 2;
	}
	if (rlog->color) {
		const char *k = r_log_level_tocolor (level);
		r_strbuf_appendf (sb, "%s%s:", k, r_log_level_tostring (level));
		if (rlog->show_origin) {
			r_strbuf_appendf (sb, " "Color_YELLOW "[%s]" Color_RESET, origin);
		} else {
			r_strbuf_appendf (sb, Color_RESET);
		}
		if (rlog->show_source) {
			r_strbuf_appendf (sb, " [%s:%d]", func, line);
		}
	} else {
		r_strbuf_appendf (sb, "%s:", r_log_level_tostring (level));
		if (rlog->show_origin) {
			r_strbuf_appendf (sb, " [%s]", origin);
		}
		if (rlog->show_source) {
			r_strbuf_appendf (sb, " [%s:%d]", func, line);
		}
	}
	if (rlog->show_ts) {
		r_strbuf_appendf (sb, " %s[ts:%" PFMT64u "]%s", rlog->color? Color_CYAN: "", r_time_now (), rlog->color? Color_RESET: "");
	}
	r_strbuf_appendf (sb, " %s\n", out);
	char *s = r_strbuf_drain (sb);
	sb = NULL;
	if (!rlog->quiet) {
		fprintf (stderr, "%s", s);
	}
	if (rlog->cb_printf) {
		rlog->cb_printf ("%s", s);
	}
	if (R_STR_ISNOTEMPTY (rlog->file)) {
		r_file_dump (rlog->file, (const ut8*)s, strlen (s), true);
	}
	free (s);
	if (rlog->traplevel && level != R_LOG_LEVEL_HINT && (level >= rlog->traplevel || level == R_LOG_LEVEL_FATAL)) {
		r_sys_backtrace ();
		r_sys_breakpoint ();
	}
}

R_API void r_log_message(RLogLevel level, const char *origin, const char *func, int line, const char *fmt, ...) {
	va_list ap;
	va_start (ap, fmt);
	r_log_vmessage (level, origin, func, line, fmt, ap);
	va_end (ap);
}

R_API void r_log_add_callback(RLogCallback cb, void *user) {
	if (!r_log_init ()) {
		return;
	}
	if (rlog->iterating) {
		R_LOG_ERROR ("cannot add log callback while iterating");
		return;
	}
	if (!rlog->cbs) {
		rlog->cbs = RVecRLogCallbackUser_new ();
	}
	RLogCallbackUser cbu = {
		.cb = cb,
		.user = user,
	};
	RVecRLogCallbackUser_push_back (rlog->cbs, &cbu);
}

R_API void r_log_del_callback(RLogCallback cb) {
	if (r_log_init () && rlog->cbs) {
		if (rlog->iterating) {
			R_LOG_ERROR ("cannot delete log callback while iterating");
			return;
		}
		size_t i = 0;
		RLogCallbackUser *p;
		R_VEC_FOREACH (rlog->cbs, p) {
			if (cb == p->cb) {
				RVecRLogCallbackUser_remove (rlog->cbs, i);
				return;
			}
			i++;
		}
	}
}

R_API void r_log(const char *funcname, const char *filename, ut32 lineno, RLogLevel level, const char *origin, const char *fmtstr, ...) {
	va_list args;
	va_start (args, fmtstr);
	r_log_vmessage (level, origin? origin: filename, funcname, lineno, fmtstr, args);
	va_end (args);
}
