// vim:fileencoding=utf-8:foldmethod=marker

#include "log.h"
#include "../base/strings/str8/str8.h"
#include "../common/aliases/aliases.h"
#include "../common/assert/assert.h"
#include "../common/misc/misc_utils.h"
#include "../os/file/file.h"
#include <time.h>

#define MIN_LOG_MSG_LENGTH 32
#define TIME_BUF_CAPACITY 70

wp_intern WpStr8RO L_BRACKET         = wpStr8LitRo("[");
wp_intern WpStr8RO R_BRACKET_SPACE   = wpStr8LitRo("] ");
wp_intern WpStr8RO R_BRACKET_NEWLINE = wpStr8LitRo("]\n");

typedef struct {
	WpFile *outlog;
	WpFile *errlog;
	WpLogLevel level;

	wpMiscUtilsReservePadding(2 * sizeof(WpFile *) + sizeof(WpLogLevel));
} LogConfig;

wp_intern LogConfig LOG_CONFIG = {
	.level = WP_LOG_LEVEL_DEBUG,
};
wp_intern WpStr8RO LOG_LEVEL_STRINGS[COUNT_LOG_LEVEL] = {
	[WP_LOG_LEVEL_FATAL]    = wpStr8LitRoInitialiserList("fatal    "),
	[WP_LOG_LEVEL_CRITICAL] = wpStr8LitRoInitialiserList("critical "),
	[WP_LOG_LEVEL_ERROR]    = wpStr8LitRoInitialiserList("error    "),
	[WP_LOG_LEVEL_WARNING]  = wpStr8LitRoInitialiserList("warning  "),
	[WP_LOG_LEVEL_INFO]     = wpStr8LitRoInitialiserList("info     "),
	[WP_LOG_LEVEL_DEBUG]    = wpStr8LitRoInitialiserList("debug    "),
};

wp_intern void _get_current_time_string(WpStr8 *dst);
wp_intern void _write_log_line(WpFile *fp, const WpLogger *logger, WpStr8 msg, WpLogLevel level);

void wpLogSetLevel(WpLogLevel level) {
	LOG_CONFIG.level = level;
}

void wpLogConfigure(WpFile *outlog, WpFile *errlog, WpLogLevel level) {
	LOG_CONFIG.outlog = outlog;
	LOG_CONFIG.errlog = errlog;
	LOG_CONFIG.level  = level;
}

WpLogger wpLogMakeLogger(WpStr8 name) {
	return (WpLogger){ .name = name };
}

void wpLogDebug(const WpLogger *logger, WpStr8 msg) {
	wpDebugAssert(logger != NULL, "`logger` should not be NULL");
	if (LOG_CONFIG.level < WP_LOG_LEVEL_DEBUG) { return; }

	WpFile *fp = LOG_CONFIG.outlog != NULL ? LOG_CONFIG.outlog : wpFileStdout();
	_write_log_line(fp, logger, msg, WP_LOG_LEVEL_DEBUG);
}

void wpLogInfo(const WpLogger *logger, WpStr8 msg) {
	wpDebugAssert(logger != NULL, "`logger` should not be NULL");
	if (LOG_CONFIG.level < WP_LOG_LEVEL_INFO) { return; }

	WpFile *fp = LOG_CONFIG.outlog != NULL ? LOG_CONFIG.outlog : wpFileStdout();
	_write_log_line(fp, logger, msg, WP_LOG_LEVEL_INFO);
}

void wpLogWarning(const WpLogger *logger, WpStr8 msg) {
	wpDebugAssert(logger != NULL, "`logger` should not be NULL");
	if (LOG_CONFIG.level < WP_LOG_LEVEL_WARNING) { return; }

	WpFile *fp = LOG_CONFIG.outlog != NULL ? LOG_CONFIG.outlog : wpFileStdout();
	_write_log_line(fp, logger, msg, WP_LOG_LEVEL_WARNING);
}

void wpLogError(const WpLogger *logger, WpStr8 msg) {
	wpDebugAssert(logger != NULL, "`logger` should not be NULL");
	if (LOG_CONFIG.level < WP_LOG_LEVEL_ERROR) { return; }

	WpFile *fp = LOG_CONFIG.errlog != NULL ? LOG_CONFIG.errlog : wpFileStderr();
	_write_log_line(fp, logger, msg, WP_LOG_LEVEL_ERROR);
}

void wpLogCritical(const WpLogger *logger, WpStr8 msg) {
	wpDebugAssert(logger != NULL, "`logger` should not be NULL");
	if (LOG_CONFIG.level < WP_LOG_LEVEL_CRITICAL) { return; }

	WpFile *fp = LOG_CONFIG.errlog != NULL ? LOG_CONFIG.errlog : wpFileStderr();
	_write_log_line(fp, logger, msg, WP_LOG_LEVEL_CRITICAL);
}

void wpLogFatal(const WpLogger *logger, WpStr8 msg) {
	wpDebugAssert(logger != NULL, "`logger` should not be NULL");
	if (LOG_CONFIG.level < WP_LOG_LEVEL_FATAL) { return; }

	WpFile *fp = LOG_CONFIG.errlog != NULL ? LOG_CONFIG.errlog : wpFileStderr();
	_write_log_line(fp, logger, msg, WP_LOG_LEVEL_FATAL);
}

wp_intern void _get_current_time_string(WpStr8 *dst) {
	// TODO (Abdelrahman): Replace with proper date/time utilities
	char buf[TIME_BUF_CAPACITY];
	time_t now = time(NULL);
	struct tm utc;
	gmtime_r(&now, &utc);
	strftime(buf, sizeof(buf), "%FT%TZ ", &utc);
	wpStr8CopyCstrCapped(dst, buf);
}

wp_intern void _write_log_line(WpFile *fp, const WpLogger *logger, WpStr8 msg, WpLogLevel level) {
	WpStr8 padding     = wpStr8Buf(MIN_LOG_MSG_LENGTH);
	u32 padding_size = msg.size < MIN_LOG_MSG_LENGTH ? MIN_LOG_MSG_LENGTH - msg.size + 1 : 0;
	wpStr8Format(&padding, "%-*s", padding_size, " ");

	WpStr8 time_str = wpStr8Buf(TIME_BUF_CAPACITY);
	_get_current_time_string(&time_str);

	WpStr8RO **strings = wpArray(
		WpStr8RO *,
		&time_str,
		&L_BRACKET,
		&LOG_LEVEL_STRINGS[level],
		&R_BRACKET_SPACE,
		&msg,
		&padding,
		&L_BRACKET,
		&logger->name,
		&R_BRACKET_NEWLINE
	);

	for (u64 i = 0; i < wpArrayCount(strings); ++i) {
		wpFileWriteStr8(strings[i], fp);
	}
}
