/*
 * Copyright (c) 2001-2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 *
 * This software is available to you under a choice of one of two
 * licenses.  You may choose to be licensed under the terms of the GNU
 * General Public License (GPL) Version 2, available from the file
 * COPYING in the main directory of this source tree, or the
 * BSD license below:
 *
 *     Redistribution and use in source and binary forms, with or
 *     without modification, are permitted provided that the following
 *     conditions are met:
 *
 *      - Redistributions of source code must retain the above
 *        copyright notice, this list of conditions and the following
 *        disclaimer.
 *
 *      - Redistributions in binary form must reproduce the above
 *        copyright notice, this list of conditions and the following
 *        disclaimer in the documentation and/or other materials
 *        provided with the distribution.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */


#include "vlogger.h"

#include <sys/types.h>
#include <sys/syscall.h>
#include <execinfo.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>

#include "utils/bullseye.h"
#include "vma/util/utils.h"
#include "vma/util/sys_vars.h"

#define VLOG_DEFAULT_MODULE_NAME "VMA"
#define VMA_LOG_CB_ENV_VAR "VMA_LOG_CB_FUNC_PTR"

char         g_vlogger_module_name[VLOG_MODULE_MAX_LEN] = VLOG_DEFAULT_MODULE_NAME;
int          g_vlogger_fd = -1;
FILE*        g_vlogger_file = NULL;
vlog_levels_t g_vlogger_level = VLOG_DEFAULT;
vlog_levels_t* g_p_vlogger_level = NULL;
uint8_t      g_vlogger_details = 0;
uint8_t*     g_p_vlogger_details = NULL;
uint32_t     g_vlogger_usec_on_startup = 0;
bool         g_vlogger_log_in_colors = MCE_DEFAULT_LOG_COLORS;
vma_log_cb_t g_vlogger_cb = NULL;

namespace log_level
{
	typedef struct {
		vlog_levels_t level;
		const char *  output_name;
		const char *  output_color;
		const char ** input_names;
	} level_names;

	static const char *log_names_none[]    = {"none", NULL};
	static const char *log_names_panic[]   = {"panic", "0", NULL};
	static const char *log_names_error[]   = {"error", "1", NULL};
	static const char *log_names_warn[]    = {"warn",  "warning", "2", NULL};
	static const char *log_names_info[]    = {"info",  "information", "3", NULL};
	static const char *log_names_details[] = {"details", NULL};
	static const char *log_names_debug[]   = {"debug", "4", NULL};
	static const char *log_names_fine[]    = {"fine",  "func", "5", NULL};
	static const char *log_names_finer[]   = {"finer", "func+", "funcall", "func_all", "func-all", "6", NULL};
	static const char *log_names_all[]     = {"all", NULL};

	// must be by order because "to_str" relies on that!
	static const level_names levels[] = {
			{VLOG_NONE,    "NONE",    "\e[0;31m" /*Red*/,     (const char ** )log_names_none},
			{VLOG_PANIC,   "PANIC",   "\e[0;31m" /*Red*/,     (const char ** )log_names_panic},
			{VLOG_ERROR,   "ERROR",   "\e[0;31m" /*Red*/,     (const char ** )log_names_error},
			{VLOG_WARNING, "WARNING", "\e[2;35m" /*Magenta*/, (const char ** )log_names_warn},
			{VLOG_INFO,    "INFO",    "\e[0m"    /*Default*/, (const char ** )log_names_info},
			{VLOG_DETAILS, "DETAILS", "\e[0m"    /*Default*/, (const char ** )log_names_details},
			{VLOG_DEBUG,   "DEBUG",   "\e[0m"    /*Default*/, (const char ** )log_names_debug},
			{VLOG_FINE,    "FINE",    "\e[2m"    /*Grey*/,    (const char ** )log_names_fine},
			{VLOG_FINER,   "FINER",   "\e[2m"    /*Grey*/,    (const char ** )log_names_finer},
			{VLOG_ALL,     "ALL",     "\e[2m"    /*Grey*/,    (const char ** )log_names_all},
	};

	// convert str to vlog_levels_t; upon error - returns the given 'def_value'
	vlog_levels_t from_str(const char* str, vlog_levels_t def_value)
	{
		size_t num_levels = sizeof(levels) / sizeof(levels[0]);
		for (size_t i = 0; i < num_levels; ++i) {
			const char ** input_name = levels[i].input_names;
			while (*input_name) {
				if (strcasecmp(str, *input_name) == 0) {
					/* Set maximum accessible logging level in case
					 * a user requests level that is reduced during compilation
					 * or requested one if the level is in valid range
					 */
					if (levels[i].level <= VMA_MAX_DEFINED_LOG_LEVEL) {
						return levels[i].level;
					}
					def_value = (vlog_levels_t)(VMA_MAX_DEFINED_LOG_LEVEL);
					vlog_printf(VLOG_WARNING, "VMA trace level set to max level %s\n", to_str(def_value));
					return def_value;
				}
				input_name++;
			}
		}

		return def_value; // not found. use given def_value
	}

	// convert int to vlog_levels_t; upon error - returns the given 'def_value'
	vlog_levels_t from_int(const int int_log, vlog_levels_t def_value)
	{
		if (int_log >= VLOG_NONE && int_log <= VLOG_ALL) {
			return static_cast<vlog_levels_t>(int_log);
		}
		return def_value; // not found. use given def_value
	}

	const char * to_str(vlog_levels_t level)
	{
		static int base = VLOG_NONE;
		return levels[level - base].output_name;
	}

	const char * get_color(vlog_levels_t level)
	{
		static int base = VLOG_NONE;
		return levels[level - base].output_color;
	}
}

pid_t gettid(void)
{
	return syscall(__NR_gettid);
}

#if _BullseyeCoverage
    #pragma BullseyeCoverage off
#endif

// Credit for the C++ de-mangler go to: http://tombarta.wordpress.com/2008/08/01/c-stack-traces-with-gcc/
#include <cxxabi.h>
void printf_backtrace(void)
{
	char **backtrace_strings;
	void* backtrace_addrs[10];
	int backtrace_depth = backtrace(backtrace_addrs, 10);
	printf("[tid: %d] ------ printf_backtrace ------ \n", gettid());
	backtrace_strings = backtrace_symbols(backtrace_addrs, backtrace_depth);
	for (int i = 1; i < backtrace_depth; i++) {
#if 0
		printf("[%d] %p: %s\n", i, backtrace_addrs[i], backtrace_strings[i]);
#else
		size_t sz = 1024; // just a guess, template names will go much wider
		char *function = NULL;
		char *begin = 0, *end = 0;
		// find the parentheses and address offset surrounding the mangled name
		for (char *j = backtrace_strings[i]; *j; ++j) {
			if (*j == '(') {
				begin = j;
			}
			else if (*j == '+') {
				end = j;
			}
		}
		if (begin && end) {
			*begin++ = '\0';
			*end = '\0';
			// found our mangled name, now in [begin, end)

			int status;
			function = abi::__cxa_demangle(begin, NULL, &sz, &status);
			if (NULL == function) {
				// demangling failed, just pretend it's a C function with no args
				function = static_cast<char*>(malloc(sz));
				if (function) {
					status = snprintf(function, sz - 1, "%s()", begin);
					if (status > 0) {
						function[status] = '\0';
					} else {
						function[0] = '\0';
					}
				}
			}
			//	        fprintf(out, "    %s:%s\n", stack.backtrace_strings[i], function);
			printf("[%d] %p: %s:%s\n", i, backtrace_addrs[i], backtrace_strings[i], (function ? function : "n/a"));
			if (function) {
				free(function);
			}
		}
		else
		{
			// didn't find the mangled name, just print the whole line
			printf("[%d] %p: %s\n", i, backtrace_addrs[i], backtrace_strings[i]);
		}
#endif
	}
	free(backtrace_strings);
}

#if _BullseyeCoverage
    #pragma BullseyeCoverage on
#endif

////////////////////////////////////////////////////////////////////////////////
// NOTE: this function matches 'bool vma_log_set_cb_func(vma_log_cb_t log_cb)' that
// we gave customers; hence, you must not change our side without considering their side
static vma_log_cb_t vma_log_get_cb_func()
{
	vma_log_cb_t log_cb = NULL;
	const char* const CB_STR = getenv(VMA_LOG_CB_ENV_VAR);
	if (!CB_STR || !*CB_STR) return NULL;

	if (1 != sscanf(CB_STR, "%p", &log_cb)) return NULL;
	return log_cb;
}

void vlog_start(const char* log_module_name, vlog_levels_t log_level, const char* log_filename, int log_details, bool log_in_colors)
{
	g_vlogger_file = stderr;

	g_vlogger_cb = vma_log_get_cb_func();

	strncpy(g_vlogger_module_name, log_module_name, sizeof(g_vlogger_module_name) - 1);
	g_vlogger_module_name[sizeof(g_vlogger_module_name) - 1] = '\0';

	vlog_get_usec_since_start();

	char local_log_filename[255];
	if (log_filename != NULL && strcmp(log_filename,"")) {
		sprintf(local_log_filename, "%s", log_filename);
		g_vlogger_fd = open(local_log_filename, O_WRONLY|O_CREAT|O_TRUNC, 0644);
		if (g_vlogger_fd < 0) {
			vlog_printf(VLOG_PANIC, "Failed to open logfile: %s\n",local_log_filename);
			exit(1);
		}
		g_vlogger_file = fdopen(g_vlogger_fd, "w");

		BULLSEYE_EXCLUDE_BLOCK_START
		if (g_vlogger_file == NULL) {
			g_vlogger_file = stderr;
			vlog_printf(VLOG_PANIC, "Failed to open logfile: %s\n",local_log_filename);
			exit(1);
		}
		BULLSEYE_EXCLUDE_BLOCK_END
	}

	g_vlogger_level = log_level;
	g_p_vlogger_level = &g_vlogger_level;
	g_vlogger_details = log_details;
	g_p_vlogger_details = &g_vlogger_details;


	int file_fd = fileno(g_vlogger_file);
	if (file_fd >= 0 && isatty(file_fd) && log_in_colors)
		g_vlogger_log_in_colors = log_in_colors;
}

void vlog_stop(void)
{
	// Closing logger

	// Allow only really extreme (PANIC) logs to go out
	g_vlogger_level = VLOG_PANIC;
	
	//set default module name
	strcpy(g_vlogger_module_name, VLOG_DEFAULT_MODULE_NAME);

	// Close output stream
	if(g_vlogger_file && g_vlogger_file != stderr)
		fclose(g_vlogger_file);

	//fix for using LD_PRELOAD with LBM. Unset the pointer given by the parent process, so a child could get his own pointer without issues.
	unsetenv(VMA_LOG_CB_ENV_VAR);
}

void vlog_output(vlog_levels_t log_level, const char* fmt , ... )
{
	int len = 0;
	char buf[VLOGGER_STR_SIZE];

	// Format header

	// Set color scheme
	if (g_vlogger_log_in_colors)
		len += snprintf(buf+len, VLOGGER_STR_SIZE-len-1, "%s", log_level::get_color(log_level));

	switch (g_vlogger_details) {
	case 3: // Time
		len += snprintf(buf+len, VLOGGER_STR_SIZE-len-1, " Time: %9.3f", ((float)vlog_get_usec_since_start())/1000); // fallthrough
	case 2: // Pid
		len += snprintf(buf+len, VLOGGER_STR_SIZE-len-1, " Pid: %5u", getpid()); // fallthrough
	case 1: // Tid
		len += snprintf(buf+len, VLOGGER_STR_SIZE-len-1, " Tid: %5u", gettid()); // fallthrough
	case 0: // Func
	default:
		len += snprintf(buf+len, VLOGGER_STR_SIZE-len-1, " %s %s: ", g_vlogger_module_name, log_level::to_str(log_level));
	}

	if (len < 0) {
		return ;
	}
	buf[len+1] = '\0';

	// Format body
	va_list ap;
	va_start(ap, fmt);
	if (fmt != NULL)
		len += vsnprintf(buf+len, VLOGGER_STR_SIZE-len, fmt, ap);
	va_end(ap);

	// Reset color scheme
	if (g_vlogger_log_in_colors) {
		// Save enough room for color code termination and EOL
	        if (len > VLOGGER_STR_SIZE - VLOGGER_STR_TERMINATION_SIZE)
	                len = VLOGGER_STR_SIZE - VLOGGER_STR_TERMINATION_SIZE - 1;

		len = snprintf(buf + len, VLOGGER_STR_TERMINATION_SIZE, VLOGGER_STR_COLOR_TERMINATION_STR);
		if (len < 0) {
			return ;
		}
	}

	if (g_vlogger_cb)
	{
		g_vlogger_cb(log_level, buf);
	}
	else if (g_vlogger_file)
	{
		// Print out
		fprintf(g_vlogger_file, "%s", buf);
		fflush(g_vlogger_file);
	}
	else {
		printf("%s", buf);
	}
}
