/*
 * Copyright (c) 2015-2024 Thomas Kolb
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * 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 <stdio.h>
#include <malloc.h>

#include <time.h>
#include <sys/time.h>

#include <stdarg.h>

#include "logger.h"

// define constants
const char *LOGGER_STR_FATAL = "FATAL";
const char *LOGGER_STR_ERR   = "ERROR";
const char *LOGGER_STR_WARN  = "WARN ";
const char *LOGGER_STR_INFO  = "INFO ";
const char *LOGGER_STR_DEBUG = "DEBUG";
const char *LOGGER_STR_DUMP  = "DUMP ";

const char *LOGGER_COLOR_FATAL = "\033[1;31m";
const char *LOGGER_COLOR_ERR   = "\033[1;31m";
const char *LOGGER_COLOR_WARN  = "\033[1;33m";
const char *LOGGER_COLOR_INFO  = "\033[1;32m";
const char *LOGGER_COLOR_DEBUG = "\033[1m";
const char *LOGGER_COLOR_DUMP  = "\033[1;30m";
const char *LOGGER_COLOR_NONE  = "\033[0m";

// global variables
int logger_verbosity;
int logger_use_colors;

void logger_init(void)
{
	logger_verbosity = 2147483647;
	logger_use_colors = 1;
}

void logger_shutdown(void)
{
	// reserved for future use
}

void logger_enable_colors(int enable)
{
	logger_use_colors = enable;
}

void logger_set_verbosity(int verbosity)
{
	logger_verbosity = verbosity;
}

void logger_debug_message(const char *prefix, const char *fmt, va_list ap)
{
	/* Guess we need no more than 100 bytes. */
	int n, size = 100;
	char *p, *np;
	va_list internal_ap;

	if ((p = (char*)malloc(size)) == NULL) {
		fprintf(stderr, "[%s] FATAL: Cannot allocate string buffer while processing arguments.\n", LOGGER_STR_ERR);
		return;
	}

	while (1) {
		/* Try to print in the allocated space. */
		va_copy(internal_ap, ap);
		n = vsnprintf(p, size, fmt, internal_ap);
		va_end(internal_ap);

		/* If that worked, return the string. */
		if (n > -1 && n < size) {
			break;
		}

		/* Else try again with more space. */
		if (n > -1) {    /* glibc 2.1 */
			size = n+1; /* precisely what is needed */
		} else {         /* glibc 2.0 */
			size *= 2;  /* twice the old size */
		}

		if ((np = (char*)realloc (p, size)) == NULL) {
			free(p);
			fprintf(stderr, "[%s] FATAL: Cannot reallocate string buffer while processing arguments.\n", LOGGER_STR_ERR);
			return;
		} else {
			p = np;
		}
	}

	// fprintf is thread-safe, so this always results in clean log lines.
	// See flockfile(3).
	fprintf(stderr, "%s %s\n", prefix, p);

	free(p);
}

void logger_log(int level, const char *format, ...)
{
	va_list argptr;

	char timebuf[32];
	char timebuf2[32];

	char prefixbuf[64];
	const char *prefixcolor = "", *prefixtext = "";

	if(level > logger_verbosity) {
		return;
	}

	struct timeval tv;
	gettimeofday(&tv, NULL);
	strftime(timebuf, 32, "%Y-%M-%d %H:%M:%S.%%03d", localtime(&(tv.tv_sec)));
	snprintf(timebuf2, 32, timebuf, tv.tv_usec/1000);

	if(level >= LVL_DUMP) {
		if(logger_use_colors) {
			prefixcolor = LOGGER_COLOR_DUMP;
		}

		prefixtext = LOGGER_STR_DUMP;
	} else if(level >= LVL_DEBUG) {
		if(logger_use_colors) {
			prefixcolor = LOGGER_COLOR_DEBUG;
		}

		prefixtext = LOGGER_STR_DEBUG;
	} else if(level >= LVL_INFO) {
		if(logger_use_colors) {
			prefixcolor = LOGGER_COLOR_INFO;
		}

		prefixtext = LOGGER_STR_INFO;
	} else if(level >= LVL_WARN) {
		if(logger_use_colors) {
			prefixcolor = LOGGER_COLOR_WARN;
		}

		prefixtext = LOGGER_STR_WARN;
	} else if(level >= LVL_ERR) {
		if(logger_use_colors) {
			prefixcolor = LOGGER_COLOR_ERR;
		}

		prefixtext = LOGGER_STR_ERR;
	} else {
		if(logger_use_colors) {
			prefixcolor = LOGGER_COLOR_FATAL;
		}

		prefixtext = LOGGER_STR_FATAL;
	}

	if(logger_use_colors) {
		sprintf(prefixbuf, "%s [%s%s%s]", timebuf2, prefixcolor, prefixtext, LOGGER_COLOR_NONE);
	} else {
		sprintf(prefixbuf, "%s [%s]", timebuf2, prefixtext);
	}

	va_start(argptr, format);
	logger_debug_message(prefixbuf, format, argptr);
	va_end(argptr);
}