/*
 * Logging utilities
 *
 *  (c) Adrian Smith 2012-2015, triode1@btinternet.com
 *  Philippe, philippe_44@outlook.com
 *
 * See LICENSE
 * 
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>

#ifdef _WIN32
#include <windows.h>
#else
#include <time.h>
#include <sys/time.h>
#endif

#include "cross_log.h"

static log_sink_fn g_log_sink = NULL;
static log_level* g_util_level_ref = NULL;
static log_level* g_raop_level_ref = NULL;

void cross_set_logger(log_sink_fn fn) {
	g_log_sink = fn;
}

void cross_set_levels(log_level util_level, log_level raop_level) {
	if (g_util_level_ref) *g_util_level_ref = util_level;
	if (g_raop_level_ref) *g_raop_level_ref = raop_level;
}

static const char* source_from_level(log_level* current) {
	extern log_level util_loglevel;
	extern log_level raop_loglevel;
	if (current == &raop_loglevel) return "raop";
	if (current == &util_loglevel) return "util";
	return "unknown";
}

// logging functions
const char *logtime(void) {
	static char buf[100];
#ifdef _WIN32
	SYSTEMTIME lt;
	GetLocalTime(&lt);
	sprintf(buf, "[%02d:%02d:%02d.%03d]", lt.wHour, lt.wMinute, lt.wSecond, lt.wMilliseconds);
#else
	struct timeval tv;
	gettimeofday(&tv, NULL);
	strftime(buf, sizeof(buf), "[%T.", localtime(&tv.tv_sec));
	sprintf(buf+strlen(buf), "%03ld]", (long)tv.tv_usec/1000);
#endif
	return buf;
}

/*---------------------------------------------------------------------------*/
void logprint_ex(log_level *current_level, const char *func, int line, const char *fmt, ...) {
	va_list args;
	if (!current_level) return;

	if (g_log_sink) {
		va_list args_copy;
		va_start(args, fmt);
		va_copy(args_copy, args);
		int needed = vsnprintf(NULL, 0, fmt, args_copy);
		va_end(args_copy);
		if (needed > 0) {
			size_t len = (size_t) needed + 64;
			char *msg = (char*) malloc(len + 1);
			if (msg) {
				int written = snprintf(msg, len, "%s %s:%d ", logtime(), func, line);
				if (written < 0) written = 0;
				vsnprintf(msg + written, len - (size_t)written, fmt, args);
				g_log_sink(source_from_level(current_level), msg, *current_level);
				free(msg);
				va_end(args);
				return;
			}
		}
		va_end(args);
		// fall through to stderr if allocation failed
	}

	va_start(args, fmt);
	fprintf(stderr, "%s %s:%d ", logtime(), func, line);
	vfprintf(stderr, fmt, args);
	va_end(args);
	fprintf(stderr, "\n");
	fflush(stderr);
}

/*---------------------------------------------------------------------------*/
void logdump(const char* data, size_t size) {
	size_t count = 0;
	while (size) {
		size_t col = size > 16 ? 16 : size;
		fprintf(stderr, "%04x  ", (unsigned int) count);
		for (size_t i = 0; i < col; i++) fprintf(stderr, "%02x ", (unsigned char) data[i]);
		for (size_t i = 0; i < col; i++) isprint((unsigned char) data[i]) ? fputc(data[i], stderr) : fputc(' ', stderr);
		fputc('\n', stderr);
		data += col; size -= col; count += col;
	}
}

/*---------------------------------------------------------------------------*/
log_level debug2level(char *level)
{
	if (!strcmp(level, "error")) return lERROR;
	if (!strcmp(level, "warn")) return lWARN;
	if (!strcmp(level, "info")) return lINFO;
	if (!strcmp(level, "debug")) return lDEBUG;
	if (!strcmp(level, "sdebug")) return lSDEBUG;
	return lWARN;
}

/*---------------------------------------------------------------------------*/
char *level2debug(log_level level)
{
	switch (level) {
	case lERROR: return "error";
	case lWARN: return "warn";
	case lINFO: return "info";
	case lDEBUG: return "debug";
	case lSDEBUG: return "debug";
	default: return "warn";
	}
}
