/*
 * Copyright (c) 2001-2018 Mellanox Technologies, Ltd. 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.
 */


#ifndef VLOGGER_H
#define VLOGGER_H

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <iostream>
#include <fstream>
#include <time.h>
#include <stdarg.h>
#include <errno.h>
#include <stdio.h>
#include <stdint.h>
#include "utils/bullseye.h"
#include "utils/rdtsc.h"

#define TO_STR(a) TOSTR_HELPER(a)
#define TOSTR_HELPER(a) #a

#undef  MODULE_HDR
#define MODULE_HDR	 	MODULE_NAME "%d:%s() "

#undef  MODULE_HDR_INFO
#define MODULE_HDR_INFO 	MODULE_NAME "[%p]:%d:%s() "

#undef	MODULE_HDR_ENTRY
#define MODULE_HDR_ENTRY	"ENTER: "

#undef	MODULE_HDR_EXIT
#define MODULE_HDR_EXIT 	"EXIT: "

#undef	__INFO__
#define __INFO__		this

#define VLOG_PRINTF(     log_level, log_fmt, log_args...) 	vlog_printf(log_level, MODULE_HDR       log_fmt "\n",           __LINE__, __FUNCTION__, ##log_args)
#define VLOG_PRINTF_INFO(log_level, log_fmt, log_args...) 	vlog_printf(log_level, MODULE_HDR_INFO  log_fmt "\n", __INFO__, __LINE__, __FUNCTION__, ##log_args)
#define VLOG_PRINTF_INFO_ONCE_THEN_ALWAYS(log_level_once, log_level, log_fmt,log_args...) \
	do { \
		static vlog_levels_t ___log_level = log_level_once; \
		VLOG_PRINTF_INFO(___log_level, log_fmt, ##log_args); \
		___log_level = log_level; \
	} while (0)

#define VLOG_PRINTF_ONCE_THEN_ALWAYS(log_level_once, log_level, log_fmt,log_args...) \
	do { \
		static vlog_levels_t ___log_level = log_level_once; \
		VLOG_PRINTF(___log_level, log_fmt, ##log_args); \
		___log_level = log_level; \
	} while (0)

#define VLOG_PRINTF_ONCE_THEN_DEBUG(log_level_once, log_fmt,log_args...) \
	do { \
		static vlog_levels_t ___log_level = log_level_once; \
		vlog_printf(___log_level, log_fmt, ##log_args); \
		___log_level = VLOG_DEBUG; \
	} while (0)

#define VLOG_PRINTF_ENTRY(log_level, log_fmt, log_args...)	vlog_printf(log_level, MODULE_HDR_ENTRY "%s(" log_fmt ")\n", __FUNCTION__, ##log_args)
#define VLOG_PRINTF_EXIT( log_level, log_fmt, log_args...)	vlog_printf(log_level, MODULE_HDR_EXIT  "%s() " log_fmt "\n", __FUNCTION__, ##log_args)


#define __log_panic(log_fmt, log_args...)        do { VLOG_PRINTF(VLOG_PANIC, log_fmt, ##log_args); throw; } while (0)
#define __log_err(log_fmt, log_args...)          do { VLOG_PRINTF(VLOG_ERROR, log_fmt, ##log_args); } while (0)
#define __log_warn(log_fmt, log_args...)         do { VLOG_PRINTF(VLOG_WARNING, log_fmt, ##log_args); } while (0)
#define __log_info(log_fmt, log_args...)         do { VLOG_PRINTF(VLOG_INFO, log_fmt, ##log_args); } while (0)

#if (VMA_MAX_DEFINED_LOG_LEVEL < DEFINED_VLOG_DETAILS)
#define __log_details(log_fmt, log_args...)      ((void)0)
#else
#define __log_details(log_fmt, log_args...)      do { if (g_vlogger_level >= VLOG_DETAILS) 	VLOG_PRINTF(VLOG_DETAILS, log_fmt, ##log_args); } while (0)
#endif

#if (VMA_MAX_DEFINED_LOG_LEVEL < DEFINED_VLOG_DEBUG)
#define __log_dbg(log_fmt, log_args...)          ((void)0)
#else
#define __log_dbg(log_fmt, log_args...)          do { if (g_vlogger_level >= VLOG_DEBUG) 	VLOG_PRINTF(VLOG_DEBUG, log_fmt, ##log_args); } while (0)
#endif

#if (VMA_MAX_DEFINED_LOG_LEVEL < DEFINED_VLOG_FINE)
#define __log_fine(log_fmt, log_args...)         ((void)0)
#else
#define __log_fine(log_fmt, log_args...)         do { if (g_vlogger_level >= VLOG_FINE) 		VLOG_PRINTF(VLOG_FINE, log_fmt, ##log_args); } while (0)
#endif

#if (VMA_MAX_DEFINED_LOG_LEVEL < DEFINED_VLOG_FINER)
#define __log_finer(log_fmt, log_args...)        ((void)0)
#else
#define __log_finer(log_fmt, log_args...)        do { if (g_vlogger_level >= VLOG_FINER) 	VLOG_PRINTF(VLOG_FINER, log_fmt, ##log_args); } while (0)
#endif /* VMA_MAX_DEFINED_LOG_LEVEL */

#define __log_info_panic(log_fmt, log_args...)   do { VLOG_PRINTF_INFO(VLOG_PANIC, log_fmt, ##log_args); throw; } while (0)
#define __log_info_err(log_fmt, log_args...)     do { VLOG_PRINTF_INFO(VLOG_ERROR, log_fmt, ##log_args); } while (0)
#define __log_info_warn(log_fmt, log_args...)    do { VLOG_PRINTF_INFO(VLOG_WARNING, log_fmt, ##log_args); } while (0)
#define __log_info_info(log_fmt, log_args...)    do { VLOG_PRINTF_INFO(VLOG_INFO, log_fmt, ##log_args); } while (0)

#if (VMA_MAX_DEFINED_LOG_LEVEL < DEFINED_VLOG_DETAILS)
#define __log_info_details(log_fmt, log_args...) ((void)0)
#else
#define __log_info_details(log_fmt, log_args...) do { if (g_vlogger_level >= VLOG_DETAILS) 	VLOG_PRINTF_INFO(VLOG_DETAILS, log_fmt, ##log_args); } while (0)
#endif

#if (VMA_MAX_DEFINED_LOG_LEVEL < DEFINED_VLOG_DEBUG)
#define __log_info_dbg(log_fmt, log_args...)     ((void)0)
#else
#define __log_info_dbg(log_fmt, log_args...)     do { if (g_vlogger_level >= VLOG_DEBUG) 	VLOG_PRINTF_INFO(VLOG_DEBUG, log_fmt, ##log_args); } while (0)
#endif

#if (VMA_MAX_DEFINED_LOG_LEVEL < DEFINED_VLOG_FINE)
#define __log_info_fine(log_fmt, log_args...)    ((void)0)
#else
#define __log_info_fine(log_fmt, log_args...)    do { if (g_vlogger_level >= VLOG_FINE) 	VLOG_PRINTF_INFO(VLOG_FINE, log_fmt, ##log_args); } while (0)
#endif

#if (VMA_MAX_DEFINED_LOG_LEVEL < DEFINED_VLOG_FINER)
#define __log_info_finer(log_fmt, log_args...)   ((void)0)
#else
#define __log_info_finer(log_fmt, log_args...)   do { if (g_vlogger_level >= VLOG_FINER) 	VLOG_PRINTF_INFO(VLOG_FINER, log_fmt, ##log_args); } while (0)
#endif /* VMA_MAX_DEFINED_LOG_LEVEL */

#if (VMA_MAX_DEFINED_LOG_LEVEL < DEFINED_VLOG_DEBUG)
#define __log_entry_dbg(log_fmt, log_args...)    ((void)0)
#else
#define __log_entry_dbg(log_fmt, log_args...)    do { if (g_vlogger_level >= VLOG_DEBUG) 	VLOG_PRINTF_ENTRY(VLOG_DEBUG, log_fmt, ##log_args); } while (0)
#endif

#if (VMA_MAX_DEFINED_LOG_LEVEL < DEFINED_VLOG_FINE)
#define __log_entry_fine(log_fmt, log_args...)   ((void)0)
#else
#define __log_entry_fine(log_fmt, log_args...)   do { if (g_vlogger_level >= VLOG_FINE)		VLOG_PRINTF_ENTRY(VLOG_FINE, log_fmt, ##log_args); } while (0)
#endif

#if (VMA_MAX_DEFINED_LOG_LEVEL < DEFINED_VLOG_FINER)
#define __log_entry_finer(log_fmt, log_args...)  ((void)0)
#else
#define __log_entry_finer(log_fmt, log_args...)  do { if (g_vlogger_level >= VLOG_FINER) 	VLOG_PRINTF_ENTRY(VLOG_FINER, log_fmt, ##log_args); } while (0)
#endif

#if (VMA_MAX_DEFINED_LOG_LEVEL < DEFINED_VLOG_DEBUG)
#define __log_exit_dbg(log_fmt, log_args...)     ((void)0)
#else
#define __log_exit_dbg(log_fmt, log_args...)     do { if (g_vlogger_level >= VLOG_DEBUG) 	VLOG_PRINTF_EXIT(VLOG_DEBUG, log_fmt, ##log_args); } while (0)
#endif

#if (VMA_MAX_DEFINED_LOG_LEVEL < DEFINED_VLOG_FINE)
#define __log_exit_fine(log_fmt, log_args...)    ((void)0)
#else
#define __log_exit_fine(log_fmt, log_args...)    do { if (g_vlogger_level >= VLOG_FINE)		VLOG_PRINTF_EXIT(VLOG_FINE, log_fmt, ##log_args); } while (0)
#endif

#if (VMA_MAX_DEFINED_LOG_LEVEL < DEFINED_VLOG_FINER)
#define __log_exit_finer(log_fmt, log_args...)   ((void)0)
#else
#define __log_exit_finer(log_fmt, log_args...)   do { if (g_vlogger_level >= VLOG_FINER) 	VLOG_PRINTF_EXIT(VLOG_FINER, log_fmt, ##log_args); } while (0)
#endif /* VMA_MAX_DEFINED_LOG_LEVEL */

// deprecated functions - only exist for Backward Compatibility.  Please avoid using them!
#define __log_func(...)          __log_fine(__VA_ARGS__)
#define __log_funcall(...)       __log_finer(__VA_ARGS__)
#define __log_info_func(...)     __log_info_fine(__VA_ARGS__)
#define __log_info_funcall(...)  __log_info_finer(__VA_ARGS__)
#define __log_entry_func(...)    __log_entry_fine(__VA_ARGS__)
#define __log_entry_funcall(...) __log_entry_finer(__VA_ARGS__)
#define __log_exit_func(...)     __log_exit_fine(__VA_ARGS__)
#define __log_exit_funcall(...)  __log_exit_finer(__VA_ARGS__)

#ifdef __cplusplus
extern "C" {
#endif //__cplusplus

typedef enum {
	VLOG_INIT	= DEFINED_VLOG_INIT,
	VLOG_NONE	= DEFINED_VLOG_NONE,
	VLOG_PANIC	= DEFINED_VLOG_PANIC,
	VLOG_ERROR	= DEFINED_VLOG_ERROR,
	VLOG_WARNING	= DEFINED_VLOG_WARNING,
	VLOG_INFO	= DEFINED_VLOG_INFO, VLOG_DEFAULT = VLOG_INFO,
	VLOG_DETAILS	= DEFINED_VLOG_DETAILS,
	VLOG_DEBUG	= DEFINED_VLOG_DEBUG,
	VLOG_FINE	= DEFINED_VLOG_FINE, VLOG_FUNC = VLOG_FINE,
	VLOG_FINER	= DEFINED_VLOG_FINER, VLOG_FUNC_ALL = VLOG_FINER,
	VLOG_ALL	= DEFINED_VLOG_ALL /* last element */
} vlog_levels_t;

namespace log_level {
	// 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 = VLOG_DEFAULT);

	// 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 = VLOG_DEFAULT);

	const char * to_str(vlog_levels_t level);
	const char * get_color(vlog_levels_t level);
}


#define VLOG_SINCE_YEAR 	1900
#define VLOG_MODULE_MAX_LEN 	10

#define VLOGGER_STR_COLOR_TERMINATION_STR     "\e[0m"
#define VLOGGER_STR_TERMINATION_SIZE          6

typedef void (*vma_log_cb_t)(int log_level, const char* str);

extern char         g_vlogger_module_name[VLOG_MODULE_MAX_LEN];
extern FILE*        g_vlogger_file;
extern int          g_vlogger_fd;
extern vlog_levels_t      g_vlogger_level;
extern vlog_levels_t*     g_p_vlogger_level;
extern uint8_t      g_vlogger_details;
extern uint8_t*     g_p_vlogger_details;
extern uint32_t     g_vlogger_usec_on_startup;
extern bool         g_vlogger_log_in_colors;
extern vma_log_cb_t g_vlogger_cb;

#define vlog_func_enter()       vlog_printf(VLOG_FINE,"ENTER %s\n", __PRETTY_FUNCTION__);
#define vlog_func_exit()        vlog_printf(VLOG_FINE,"EXIT %s\n",__PRETTY_FUNCTION__);

#define vlog_func_all_enter()   vlog_printf(VLOG_FINER,"ENTER %s\n", __PRETTY_FUNCTION__);
#define vlog_func_all_exit()    vlog_printf(VLOG_FINER,"EXIT %s\n",__PRETTY_FUNCTION__);

pid_t gettid(void); // Check vlogger.cpp for implementation

void printf_backtrace(void);

void vlog_start(const char* log_module_name, vlog_levels_t log_level = VLOG_DEFAULT, const char* log_filename = NULL, int log_details = 0, bool colored_log = true);
void vlog_stop(void);

class LogDuration {
	static const tscval_t TSC_RATE_PER_USEC;

	const char * m_label;
	vlog_levels_t m_log_level;
	tscval_t m_startTime;
	tscval_t m_lastPrint;
	unsigned m_printCounter;
public:
	LogDuration(const char * label = "", vlog_levels_t log_level = VLOG_DEBUG);
	void print();
	~LogDuration(); // no need for virtual
};


static inline uint32_t vlog_get_usec_since_start()
{
	struct timespec ts_now;

	BULLSEYE_EXCLUDE_BLOCK_START
	if (gettime(&ts_now)) {
		printf("%s() gettime() Returned with Error (errno=%d %m)\n", __func__, errno);
		return (uint32_t)-1;
	}
	BULLSEYE_EXCLUDE_BLOCK_END

	if (!g_vlogger_usec_on_startup) {
		g_vlogger_usec_on_startup = ts_to_usec(&ts_now);
	}

	return (ts_to_usec(&ts_now) - g_vlogger_usec_on_startup);
}

#define VLOGGER_STR_SIZE    512

static inline void vlog_printf(vlog_levels_t log_level, const char* fmt , ... )
{
	if (g_vlogger_level < log_level)
		return;

	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);
	}

/*
	if (log_level <= VLOG_WARNING) {
		printf_backtrace();
	}
*/
}

#if _BullseyeCoverage
    #pragma BullseyeCoverage off
#endif

static inline void vlog_print_buffer(vlog_levels_t log_level, const char* msg_header, const char* msg_tail, const char* buf_user, int buf_len)
{
	if (g_vlogger_level < log_level)
		return;

	int len = 0;
	char buf[VLOGGER_STR_SIZE];

	// Format header
	if (g_vlogger_level >= VLOG_DEBUG) {
		//vlog_time(log_level, log_msg);
		len = snprintf(buf, sizeof(buf)-1, " Tid: %11lx : %s %s: ",
			       pthread_self(), g_vlogger_module_name, log_level::to_str(log_level));
	} else {
		len = snprintf(buf, sizeof(buf)-1, "%s %s: ",
			       g_vlogger_module_name, log_level::to_str(log_level));
	}
	if (len < 0) {
		return ;
	}
	buf[len+1] = '\0';


	if (msg_header)
		len += snprintf(buf+len, VLOGGER_STR_SIZE-len-1, "%s", msg_header);

	for (int c = 0; c < buf_len && len < (VLOGGER_STR_SIZE-1-6); c++) {
		len += sprintf(buf+len, "%2.2X ", (unsigned char)buf_user[c]);
		if ((c % 8) == 7) 
			len += sprintf(buf+len, " ");
	}

	if (msg_tail)
		len += snprintf(buf+len, VLOGGER_STR_SIZE-len-1, "%s", msg_tail);

	buf[len+1] = '\0';

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

}

#if _BullseyeCoverage
    #pragma BullseyeCoverage on
#endif

#ifdef __cplusplus
};
#endif //__cplusplus

#endif // VLOGGER_H
