/* radare - LGPL - Copyright 2007-2018 - pancake, ret2libc */

#define LOG_CONFIGSTR_SIZE 512
#define LOG_OUTPUTBUF_SIZE 512

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

// TODO: Use thread-local storage to make these variables thread-safe
static RList *log_cbs = NULL; // Functions to call when outputting log string
static int cfg_loglvl = R_LOGLVL_ERROR; // Log level output
static int cfg_logtraplvl = R_LOGLVL_FATAL; // Log trap level
static bool cfg_logsrcinfo = false; // Print out debug source info with the output
static bool cfg_logcolors = false; // Output colored log text based on level
static char cfg_logfile[LOG_CONFIGSTR_SIZE] = ""; // Output text to filename
static const char *level_tags[] = { // Log level to tag string lookup array
	[R_LOGLVL_SILLY]     = "SILLY",
	[R_LOGLVL_VERBOSE]   = "VERBOSE",
	[R_LOGLVL_DEBUG]     = "DEBUG",
	[R_LOGLVL_INFO]      = "INFO",
	[R_LOGLVL_WARN]      = "WARNING",
	[R_LOGLVL_ERROR]     = "ERROR",
	[R_LOGLVL_FATAL]     = "FATAL"
};

// cconfig.c configuration callback functions below
R_API void r_log_set_level(RLogLevel level) {
	cfg_loglvl = level;
}

R_API void r_log_set_traplevel(RLogLevel level) {
	cfg_logtraplvl = level;
}

R_API void r_log_set_file(const char *filename) {
	int value_len = r_str_nlen (filename, LOG_CONFIGSTR_SIZE) + 1;
	strncpy (cfg_logfile, filename, value_len);
}

R_API void r_log_set_srcinfo(bool show_info) {
	cfg_logsrcinfo = show_info;
}

R_API void r_log_set_colors(bool show_info) {
	cfg_logcolors = show_info;
}

/**
 * \brief Add a logging callback
 * \param cbfunc RLogCallback style function to be called
 */
R_API void r_log_add_callback(RLogCallback cbfunc) {
	if (!log_cbs) {
		log_cbs = r_list_new ();
	}
	if (!r_list_contains (log_cbs, cbfunc)) {
		r_list_append (log_cbs, cbfunc);
	}
}

/**
 * \brief Remove a logging callback
 * \param cbfunc RLogCallback style function to be called
 */
R_API void r_log_del_callback(RLogCallback cbfunc) {
	if (log_cbs) {
		r_list_delete_data (log_cbs, cbfunc);
	}
}

R_API void r_vlog(const char *funcname, const char *filename,
	ut32 lineno, RLogLevel level, const char *tag, const char *fmtstr, va_list args) {
	va_list args_copy;
	va_copy (args_copy, args);

	if (level < cfg_loglvl && level < cfg_logtraplvl) {
		// Don't print if output level is lower than current level
		// Don't ignore fatal/trap errors
		va_end (args_copy);
		return;
	}

	// TODO: Colors

	// Build output string with src info, and formatted output
	char output_buf[LOG_OUTPUTBUF_SIZE] = ""; // Big buffer for building the output string
	if (!tag) {
		tag = R_BETWEEN (0, level, R_ARRAY_SIZE (level_tags) - 1)? level_tags[level]: "";
	}
	int offset = snprintf (output_buf, LOG_OUTPUTBUF_SIZE, "%s: ", tag);
	if (cfg_logsrcinfo) {
		offset += snprintf (output_buf + offset, LOG_OUTPUTBUF_SIZE - offset, "%s in %s:%i: ", funcname, filename, lineno);
	}
	vsnprintf (output_buf + offset, LOG_OUTPUTBUF_SIZE - offset, fmtstr, args);

	// Actually print out the string with our callbacks
	if (log_cbs && r_list_length (log_cbs) > 0) {
		RListIter *it;
		RLogCallback cb;

		r_list_foreach (log_cbs, it, cb) {
			cb (output_buf, funcname, filename, lineno, level, NULL, fmtstr, args_copy);
		}
	} else {
		fprintf (stderr, "%s", output_buf);
	}
	va_end (args_copy);

	// Log to file if enabled
	if (cfg_logfile[0] != 0x00) {
		FILE *file = r_sandbox_fopen (cfg_logfile, "a+"); // TODO: Optimize (static? Needs to remake on cfg change though)
		if (!file) {
			file = r_sandbox_fopen (cfg_logfile, "w+");
		}
		if (file) {
			fprintf (file, "%s", output_buf);
			fclose (file);
		} else {
			eprintf ("%s failed to write to file: %s\n", MACRO_LOG_FUNC, cfg_logfile);
		}
	}

	if (level >= cfg_logtraplvl && level != R_LOGLVL_NONE) {
		fflush (stdout); // We're about to exit HARD, flush buffers before dying
		fflush (stderr);
		// TODO: call r_cons_flush if libr_cons is being used
		r_sys_breakpoint (); // *oof*
	}
}

/**
 * \brief Internal logging function used by preprocessor macros
 * \param funcname Contains the function name of the calling function
 * \param filename Contains the filename that funcname is defined in
 * \param lineno The line number that this log call is being made from in filename
 * \param lvl Logging level for output
 * \param fmtstr A printf like string

  This function is used by the R_LOG_* preprocessor macros for logging
*/
R_API void r_log(const char *funcname, const char *filename, ut32 lineno, RLogLevel level, const char *tag, const char *fmtstr, ...) {
	va_list args;

	va_start (args, fmtstr);
	r_vlog (funcname, filename, lineno, level, tag, fmtstr, args);
	va_end (args);
}
