/**
 * @file debug.c
 *
 * @brief Debugging functions.
 *
 * Copyright 2007-2008, Freie Universitaet Berlin. All rights reserved.
 * Please refer to the COPYING file for detailed licensing information.
 *
 * @author Georg Wittenburg
 *
 * $Id: debug.c 2683 2008-06-25 14:32:30Z wittenbu $
 */

#ifndef ARCH_WIN32
#include <execinfo.h>
#endif	// ARCH_WIN32
#include <stdarg.h>
#include <stdlib.h>
#include <time.h>

#include "../types.h"
#include "../vars.h"
#include "../interface.h"
#include "../interface_internal.h"
#include "debug.h"
#include "net.h"



#ifdef C_ENABLE_DEBUG

#ifndef ARCH_WIN32

/**
 * @brief Use color in output.
 */
#define DEBUG_USE_COLOR

#endif

/**
 * @brief Size of backtrace on FATAL errors.
 */
#define DEBUG_BACKTRACE_SIZE	64

/**
 * @brief Convert numeric debug level to a string.
 *
 * @param debug_level Numeric debug level.
 * @return A string representing the debug level.
 */
static const char* C_DEBUG_LEVEL_TO_STRING(int debug_level) {
	switch(debug_level) {
		case C_DEBUG_LEVEL_NONE:	C_printf("FATAL: Formatted output for debug level NONE requested.\n"); abort();
		case C_DEBUG_LEVEL_FATAL:	return "FATAL";
		case C_DEBUG_LEVEL_ERROR:	return "ERROR";
		case C_DEBUG_LEVEL_WARN:	return "WARN";
		case C_DEBUG_LEVEL_NOTE:	return "NOTE";
		case C_DEBUG_LEVEL_INFO:	return "INFO";
		case C_DEBUG_LEVEL_DEBUG:	return "DEBUG";
		case C_DEBUG_LEVEL_VERBOSE:	return "VERB";
		default: C_printf("FATAL: Unknown debug level 0x%X.\n", debug_level); abort(); return "NOT REACHED";
	}
}

#ifdef DEBUG_USE_COLOR
/**
 * @brief Return ASCII control sequence to highlight debugging output.
 *
 * @param debug_level Numeric debug level.
 * @return A string with a ASCII control sequence for appropriate highlighting.
 */
static const char* C_DEBUG_LEVEL_TO_COLOR(int debug_level) {
	switch(debug_level) {
		case C_DEBUG_LEVEL_NONE:	return "\033[0m";		// reset to default
		case C_DEBUG_LEVEL_FATAL:	return "\033[41m\033[1;31m";	// bright red on red
		case C_DEBUG_LEVEL_ERROR:	return "\033[40m\033[1;31m";	// bright red on black
		case C_DEBUG_LEVEL_WARN:	return "\033[40m\033[1;33m";	// yellow on black
		case C_DEBUG_LEVEL_NOTE:	return "\033[40m\033[1;32m";	// green on black
		case C_DEBUG_LEVEL_INFO:	return "\033[40m\033[1;34m";	// blue on black
		case C_DEBUG_LEVEL_DEBUG:	return "\033[40m\033[1;37m";	// light gray on black
		case C_DEBUG_LEVEL_VERBOSE:	return "\033[40m\033[1;30m";	// dark gray on black
		default: C_printf("FATAL: Unknown debug level 0x%X.\n", debug_level); abort(); return "NOT REACHED";
	}
}
#endif	// DEBUG_USE_COLOR

#endif	// C_ENABLE_DEBUG

/**
 * @brief Print formatted debuggging output.
 *
 * @param debug_level Numeric debug level of the output.
 * @param format A format string.
 * @param ... Parameters for the format string.
 */
void debug_printf(int debug_level, const char *format, ...) {
#ifdef ARCH_WIN32
	SYSTEMTIME time;
#endif	// ARCH_WIN32

#ifdef C_ENABLE_DEBUG
	if(debug_level == C_DEBUG_LEVEL_ERROR) {
		C_DEBUG_ERRORs++;
	} else if(debug_level == C_DEBUG_LEVEL_WARN) {
		C_DEBUG_WARNs++;
	}
	if(debug_level == C_DEBUG_LEVEL_NONE) {
		va_list argp;
		va_start(argp, format);
		C_vprintf(format, argp);
		va_end(argp);
	} else if(debug_level <= C_DEBUG_LEVEL) {
		va_list argp;

#ifdef DEBUG_USE_COLOR
		C_printf("%s", C_DEBUG_LEVEL_TO_COLOR(debug_level));
#endif	// DEBUG_USE_COLOR

#ifdef ARCH_LINUX
		time_t time = (time_t) C_time();
		char time_string[9];
		strftime(time_string, 9, "%T", localtime(&time));
		C_printf("[%s @ %s: %s]", time_string, addr_to_str(C_LOCAL_ADDR), C_DEBUG_LEVEL_TO_STRING(debug_level));
#elif defined(ARCH_WIN32)
		GetLocalTime(&time);
		C_printf("[%02d:%02d:%02d @ %s: %s]", time.wHour, time.wMinute, time.wSecond, addr_to_str(C_LOCAL_ADDR), C_DEBUG_LEVEL_TO_STRING(debug_level));
#else
		C_printf("[%.2f @ %s: %s]", C_time(), addr_to_str(C_LOCAL_ADDR), C_DEBUG_LEVEL_TO_STRING(debug_level));
#endif	// ARCH_*

#ifdef DEBUG_USE_COLOR
		C_printf("%s", C_DEBUG_LEVEL_TO_COLOR(C_DEBUG_LEVEL_NONE));
#endif	// DEBUG_USE_COLOR

		C_printf("\t");
		va_start(argp, format);
		C_vprintf(format, argp);
		va_end(argp);
		if(debug_level == C_DEBUG_LEVEL_FATAL) {
			C_printf("\n");
			C_printf("\t\t\t\t\\|/ ____ \\|/\n");
			C_printf("\t\t\t\t\"@'/ .. \\`@\"\n");
			C_printf("\t\t\t\t/_| \\__/ |_\\\n");
   			C_printf("\t\t\t\t   \\__U_/\n");
			C_printf("\n");

#ifndef ARCH_WIN32
			// Print backtrace.
			void *array[DEBUG_BACKTRACE_SIZE];
			size_t size = backtrace(array, DEBUG_BACKTRACE_SIZE);
			char **strings = backtrace_symbols(array, size);
			size_t i;
			C_printf("Backtrace of %zd stack frame(s):\n", size);
			for(i = 0; i < size; i++)
				C_printf("%s\n", strings[i]);
			free (strings);
			C_printf("\n");
#endif	// ARCH_WIN32
		}
	}
#endif	// C_ENABLE_DEBUG
}
