/**
 * \file
 */

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <glib.h>

#include "mono-compiler.h"
#include "mono-logger-internals.h"
#include <mono/utils/mono-threads-debug.h>


typedef struct {
	GLogLevelFlags	level;
	MonoTraceMask	mask;
} MonoLogLevelEntry;

GLogLevelFlags mono_internal_current_level	= (GLogLevelFlags)INT_MAX;
MonoTraceMask  mono_internal_current_mask	= (MonoTraceMask)~0;
gboolean mono_trace_log_header			= FALSE;

static GQueue		*level_stack		= NULL;
static const char	*mono_log_domain	= "Mono";
static MonoPrintCallback print_callback, printerr_callback;

static MonoLogCallParm logCallback;

typedef struct {
   MonoLogCallback legacy_callback;
   gpointer user_data;
} UserSuppliedLoggerUserData;

/**
 * mono_trace_init:
 *
 * Initializes the mono tracer.
 */
void 
mono_trace_init (void)
{
	if(level_stack == NULL) {
		mono_internal_current_level = G_LOG_LEVEL_ERROR;
		level_stack = g_queue_new();

		char *mask = g_getenv ("MONO_LOG_MASK");
		char *level = g_getenv ("MONO_LOG_LEVEL");
		char *header = g_getenv ("MONO_LOG_HEADER");
		char *dest = g_getenv ("MONO_LOG_DEST");

		mono_trace_set_mask_string(mask);
		mono_trace_set_level_string(level);
		mono_trace_set_logheader_string(header);
		mono_trace_set_logdest_string(dest);

		g_free (mask);
		g_free (level);
		g_free (header);
		g_free (dest);
	}
}

/**
 * mono_trace_cleanup:
 *
 * Releases the mono tracer.
 */
void 
mono_trace_cleanup (void)
{
	if(level_stack != NULL) {
		while(!g_queue_is_empty (level_stack)) {
			g_free (g_queue_pop_head (level_stack));
		}

		logCallback.closer();
		g_queue_free (level_stack);
		level_stack = NULL;
	}
}

/**
 * mono_tracev_inner:
 * \param level Verbose level of the specified message
 * \param mask Type of the specified message
 * Traces a new message, depending on the current logging level
 * and trace mask.
 */
void 
mono_tracev_inner (GLogLevelFlags level, MonoTraceMask mask, const char *format, va_list args)
{
	char *log_message;
	if (level_stack == NULL) {
		mono_trace_init ();
		if(level > mono_internal_current_level || !(mask & mono_internal_current_mask))
			return;
	}

	g_assert (logCallback.opener); // mono_trace_init should have provided us with one!

	if (g_vasprintf (&log_message, format, args) < 0)
		return;
	logCallback.writer (mono_log_domain, level, logCallback.header, log_message);
	g_free (log_message);
}

/**
 * mono_trace_set_level:
 * \param level Verbose level to set
 * Sets the current logging level. Every subsequent call to
 * \c mono_trace will check the visibility of a message against this
 * value.
 */
void 
mono_trace_set_level (GLogLevelFlags level)
{
	if(level_stack == NULL)
		mono_trace_init();

	mono_internal_current_level = level;
}

/**
 * mono_trace_set_mask:
 * \param mask Mask of visible message types.
 * Sets the current logging level. Every subsequent call to
 * \c mono_trace will check the visibility of a message against this
 * value.
 */
void 
mono_trace_set_mask (MonoTraceMask mask)
{
	if(level_stack == NULL)
		mono_trace_init();

	mono_internal_current_mask = mask;
}

/**
 * mono_trace_set_logdest:
 * \param dest Destination for logging
 * Sets the current logging destination. This can be a file or, if supported,
 * syslog.
 */
void 
mono_trace_set_logdest_string (const char *dest)
{
	MonoLogCallParm logger;

	if(level_stack == NULL)
		mono_trace_init();

#if HOST_ANDROID
	logger.opener = mono_log_open_logcat;
	logger.writer = mono_log_write_logcat;
	logger.closer = mono_log_close_logcat;
	logger.dest   = (char*) dest;
#elif defined (HOST_IOS)
	logger.opener = mono_log_open_asl;
	logger.writer = mono_log_write_asl;
	logger.closer = mono_log_close_asl;
	logger.dest   = (char*) dest;
#else
	if (dest && !strcmp("flight-recorder", dest)) {
		logger.opener = mono_log_open_recorder;
		logger.writer = mono_log_write_recorder;
		logger.closer = mono_log_close_recorder;
		logger.dest   = (char *) dest;

		// Increase log level with flight recorder
		if (mono_internal_current_level == G_LOG_LEVEL_ERROR || mono_internal_current_level == G_LOG_LEVEL_CRITICAL)
			mono_trace_set_level (G_LOG_LEVEL_WARNING);

	} else if (dest && !strcmp("syslog", dest)) {
		logger.opener = mono_log_open_syslog;
		logger.writer = mono_log_write_syslog;
		logger.closer = mono_log_close_syslog;
		logger.dest   = (char *) dest;
	} else {
		logger.opener = mono_log_open_logfile;
		logger.writer = mono_log_write_logfile;
		logger.closer = mono_log_close_logfile;
		logger.dest   = (char *) dest;
	}
#endif

	mono_trace_set_log_handler_internal(&logger, NULL);
}

/**
 * mono_trace_set_logheader:
 * \param head Whether we want pid/date/time header on log messages
 * Sets the current logging header option.
 */
void 
mono_trace_set_logheader_string(const char *head)
{
	if (head == NULL) {
		mono_trace_log_header = FALSE;
	} else {
		mono_trace_log_header = TRUE;
	}
}

/**
 * mono_trace_push:
 * \param level Verbose level to set
 * \param mask Mask of visible message types.
 * Saves the current values of level and mask then calls \c mono_trace_set
 * with the specified new values.
 */
void 
mono_trace_push (GLogLevelFlags level, MonoTraceMask mask)
{
	if(level_stack == NULL)
		g_error("%s: cannot use mono_trace_push without calling mono_trace_init first.", __func__);
	else {
		MonoLogLevelEntry *entry = (MonoLogLevelEntry *) g_malloc(sizeof(MonoLogLevelEntry));
		entry->level	= mono_internal_current_level;
		entry->mask		= mono_internal_current_mask;

		g_queue_push_head (level_stack, (gpointer)entry);

		/* Set the new level and mask
		 */
		mono_internal_current_level = level;
		mono_internal_current_mask  = mask;
	}
}

/**
 * mono_trace_pop:
 *
 * Restores level and mask values saved from a previous call to mono_trace_push.
 */
void 
mono_trace_pop (void)
{
	if(level_stack == NULL)
		g_error("%s: cannot use mono_trace_pop without calling mono_trace_init first.", __func__);
	else {
		if(!g_queue_is_empty (level_stack)) {
			MonoLogLevelEntry *entry = (MonoLogLevelEntry*)g_queue_pop_head (level_stack);

			/*	Restore previous level and mask
			 */
			mono_internal_current_level = entry->level;
			mono_internal_current_mask  = entry->mask;

			g_free (entry);
		}
	}
}


void 
mono_trace_set_level_string (const char *value)
{
	int i = 0;
	const char *valid_vals[] = {"error", "critical", "warning", "message", "info", "debug", NULL};
	const GLogLevelFlags valid_ids[] = {G_LOG_LEVEL_ERROR, G_LOG_LEVEL_CRITICAL, G_LOG_LEVEL_WARNING,
										G_LOG_LEVEL_MESSAGE, G_LOG_LEVEL_INFO, G_LOG_LEVEL_DEBUG };

	if(!value)
		return;

	while(valid_vals[i]) {
		if(!strcmp(valid_vals[i], value)){
			mono_trace_set_level(valid_ids[i]);
			return;
		}
		i++;
	}

	if(*value)
		g_print("Unknown trace loglevel: %s\n", value);
}

void 
mono_trace_set_mask_string (const char *value)
{
	int i;
	const char *tok;
	guint32 flags = 0;

	static const struct { const char * const flag; const MonoTraceMask mask; } flag_mask_map[] = {
		{ "asm", MONO_TRACE_ASSEMBLY },
		{ "type", MONO_TRACE_TYPE },
		{ "dll", MONO_TRACE_DLLIMPORT },
		{ "gc", MONO_TRACE_GC },
		{ "cfg", MONO_TRACE_CONFIG },
		{ "aot", MONO_TRACE_AOT },
		{ "security", MONO_TRACE_SECURITY },
		{ "threadpool", MONO_TRACE_THREADPOOL },
		{ "io-threadpool", MONO_TRACE_IO_SELECTOR },
		{ "io-selector", MONO_TRACE_IO_SELECTOR },
		{ "io-layer-process", MONO_TRACE_IO_LAYER_PROCESS },
		{ "io-layer-socket", MONO_TRACE_IO_LAYER_SOCKET },
		{ "io-layer-file", MONO_TRACE_IO_LAYER_FILE },
		{ "io-layer-console", MONO_TRACE_IO_LAYER_FILE },
		{ "io-layer-pipe", MONO_TRACE_IO_LAYER_FILE },
		{ "io-layer-event", MONO_TRACE_IO_LAYER_EVENT },
		{ "io-layer-semaphore", MONO_TRACE_IO_LAYER_SEMAPHORE },
		{ "io-layer-mutex", MONO_TRACE_IO_LAYER_MUTEX },
		{ "io-layer-handle", MONO_TRACE_IO_LAYER_HANDLE },
		{ "io-layer", (MonoTraceMask)(MONO_TRACE_IO_LAYER_PROCESS
		               | MONO_TRACE_IO_LAYER_SOCKET
		               | MONO_TRACE_IO_LAYER_FILE
		               | MONO_TRACE_IO_LAYER_EVENT
		               | MONO_TRACE_IO_LAYER_SEMAPHORE
		               | MONO_TRACE_IO_LAYER_MUTEX
		               | MONO_TRACE_IO_LAYER_HANDLE) },
		{ "w32handle", MONO_TRACE_IO_LAYER_HANDLE },
		{ "tailcall", MONO_TRACE_TAILCALL },
		{ "profiler", MONO_TRACE_PROFILER },
		{ "all", (MonoTraceMask)~0 }, // FIXMEcxx there is a better way -- operator overloads of enums
		{ NULL, (MonoTraceMask)0 },
	};

	if(!value)
		return;

	tok = value;

	while (*tok) {
		if (*tok == ',') {
			tok++;
			continue;
		}
		for (i = 0; flag_mask_map[i].flag; i++) {
			size_t len = strlen (flag_mask_map[i].flag);
			if (strncmp (tok, flag_mask_map[i].flag, len) == 0 && (tok[len] == 0 || tok[len] == ',')) {
				flags |= flag_mask_map[i].mask;
				tok += len;
				break;
			}
		}
		if (!flag_mask_map[i].flag) {
			g_print("Unknown trace flag: %s\n", tok);
			break;
		}
	}

	mono_trace_set_mask ((MonoTraceMask) flags);
}

/*
 * mono_trace_is_traced:
 *
 *   Returns whenever a message with @level and @mask will be printed or not.
 */
gboolean
mono_trace_is_traced (GLogLevelFlags level, MonoTraceMask mask)
{
	return MONO_TRACE_IS_TRACED (level, mask);
}

/**
 * log_level_get_name
 * @log_level severity level
 *
 * Convert log level into a string for legacy log handlers
 */
static const char *
log_level_get_name (GLogLevelFlags log_level)
{
        switch (log_level & G_LOG_LEVEL_MASK) {
        case G_LOG_LEVEL_ERROR: return "error";
        case G_LOG_LEVEL_CRITICAL: return "critical";
        case G_LOG_LEVEL_WARNING: return "warning";
        case G_LOG_LEVEL_MESSAGE: return "message";
        case G_LOG_LEVEL_INFO: return "info";
        case G_LOG_LEVEL_DEBUG: return "debug";
        default: return "unknown";
        }
}

/**
 * callback_adapter
 * 
 *  @log_domain Message prefix
 *  @log_level Severity
 *  @message Message to be written
 *  @fatal Fatal flag - write then abort
 *  @user_data Argument passed to @callback
 *
 * This adapts the old callback writer exposed by MonoCallback to the newer method of
 * logging. We ignore the header request as legacy handlers never had headers.
 */
static void
callback_adapter (const char *domain, GLogLevelFlags level, mono_bool fatal, const char *message)
{
	UserSuppliedLoggerUserData *ll = (UserSuppliedLoggerUserData*)logCallback.user_data;

	ll->legacy_callback (domain, log_level_get_name(level), message, fatal, ll->user_data);
}

static void
eglib_log_adapter (const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data)
{
	UserSuppliedLoggerUserData *ll = (UserSuppliedLoggerUserData*)logCallback.user_data;

	ll->legacy_callback (log_domain, log_level_get_name (log_level), message, log_level & G_LOG_LEVEL_ERROR, ll->user_data);
}

/**
 * legacy_opener
 *
 * Dummy routine for older style loggers
 */
static void
legacy_opener(const char *path, void *user_data)
{
  /* nothing to do */
}

/**
 * legacy_closer
 *
 * Cleanup routine for older style loggers
 */
static void
legacy_closer(void)
{
	if (logCallback.user_data != NULL) {
		g_free (logCallback.user_data); /* This is a UserSuppliedLoggerUserData struct */
		logCallback.opener = NULL;	
		logCallback.writer = NULL;
		logCallback.closer = NULL;
		logCallback.user_data = NULL;
		logCallback.header = FALSE;
	}
}

/**
 *   mono_trace_set_log_handler:
 *  
 *  @callback The callback that will replace the default logging handler
 *  @user_data Argument passed to @callback
 * 
 * The log handler replaces the default runtime logger. All logging requests with be routed to it.
 * If the fatal argument in the callback is true, the callback must abort the current process. The runtime expects that
 * execution will not resume after a fatal error.
 */
void
mono_trace_set_log_handler (MonoLogCallback callback, void *user_data)
{
	g_assert (callback);

	if (logCallback.closer != NULL)
		logCallback.closer();
	UserSuppliedLoggerUserData *ll = (UserSuppliedLoggerUserData*)g_malloc (sizeof (UserSuppliedLoggerUserData));
	ll->legacy_callback = callback;
	ll->user_data = user_data;
	logCallback.opener = legacy_opener;
	logCallback.writer = callback_adapter;
	logCallback.closer = legacy_closer;
	logCallback.user_data = ll;
	logCallback.dest = NULL;

	g_log_set_default_handler (eglib_log_adapter, user_data);
}

static void
structured_log_adapter (const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data)
{
	logCallback.writer (log_domain, log_level, logCallback.header, message);
}

/**
 * mono_trace_set_log_handler_internal:
 * \param callback The callback that will replace the default logging handler
 * \param user_data Argument passed to \p callback
 * The log handler replaces the default runtime logger. All logging requests with be routed to it.
 * If the fatal argument in the callback is true, the callback must abort the current process. The runtime expects that
 * execution will not resume after a fatal error.
 */
void
mono_trace_set_log_handler_internal (MonoLogCallParm *callback, void *user_data)
{
	g_assert (callback);
	if (logCallback.closer != NULL)
		logCallback.closer();
	logCallback.opener = callback->opener;
	logCallback.writer = callback->writer;
	logCallback.closer = callback->closer;
	logCallback.header = mono_trace_log_header;
	logCallback.dest   = callback->dest;
	logCallback.opener (logCallback.dest, user_data);

	g_log_set_default_handler (structured_log_adapter, user_data);
}

static void
print_handler (const char *string)
{
	print_callback (string, TRUE);
}

static void
printerr_handler (const char *string)
{
	printerr_callback (string, FALSE);
}

/**
 * mono_trace_set_print_handler:
 * \param callback The callback that will replace the default runtime behavior for stdout output.
 * The print handler replaces the default runtime stdout output handler. This is used by free form output done by the runtime.
 */
void
mono_trace_set_print_handler (MonoPrintCallback callback)
{
	g_assert (callback);
	print_callback = callback;
	g_set_print_handler (print_handler);
}

/**
 * mono_trace_set_printerr_handler:
 * \param callback The callback that will replace the default runtime behavior for stderr output.
 * The print handler replaces the default runtime stderr output handler. This is used by free form output done by the runtime.
 */
void
mono_trace_set_printerr_handler (MonoPrintCallback callback)
{
	g_assert (callback);
	printerr_callback = callback;
	g_set_printerr_handler (printerr_handler);
}

static gchar
conv_ascii_char (gchar s)
{
	if (s < 0x20)
		return '.';
	if (s > 0x7e)
		return '.';
	return s;
}

/* No memfree because only called during crash */
void
mono_dump_mem (gpointer d, int len)
{
	guint8 *data = (guint8 *) d;

	for (int off = 0; off < len; off += 0x10) {
		g_async_safe_printf("%p  ", data + off);

		for (int i = 0; i < 0x10; i++) {
			if ((i + off) >= len) {
				g_async_safe_printf("%s", "   ");
			} else {
				g_async_safe_printf("%02x ", data [off + i]);
			}
		}

		g_async_safe_printf(" ");

		for (int i = 0; i < 0x10; i++) {
			if ((i + off) >= len) {
				g_async_safe_printf("%s", " ");
			} else {
				g_async_safe_printf("%c", conv_ascii_char (data [off + i]));
			}
		}

		g_async_safe_printf ("\n");
	}
}
