#include "log.h"

BOOL HAS_COLOUR = FALSE;

char _log_prelude[32];
char* log_prelude() {
    time_t rawtime;
    struct tm* timeinfo;

    time(&rawtime);
    timeinfo = localtime(&rawtime);

    strftime(_log_prelude, 32, "[%Y/%m/%d %H:%M:%S] ", timeinfo);
    return _log_prelude;
}

HANDLE LOG_FILE = NULL;
VOID trace_hook(char* output);
CRITICAL_SECTION logger_lock;

int _do_log(const char* caller, const char* level, const char* format, va_list args, bool toStdout) {
    EnterCriticalSection(&logger_lock);

    int len = snprintf(NULL, 0, "%s%s:%s:", log_prelude(), level, caller) + vsnprintf(NULL, 0, format, args);
    char* buf = (char*)malloc(len + 2);
    if (!buf) {
        LeaveCriticalSection(&logger_lock);
        return 0;
    }

    int wrote_a = snprintf(buf, len, "%s%s:%s:", log_prelude(), level, caller);
    int wrote_b = vsnprintf(buf + wrote_a, len - wrote_a + 1, format, args);
    buf[len] = '\n';
    buf[len + 1] = '\0';

    // No +1 here to not get the \n
    if (toStdout) WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), buf, len, NULL, NULL);
#ifdef LOG_TO_FILE
    if (LOG_FILE && LOG_FILE != INVALID_HANDLE_VALUE) WriteFile(LOG_FILE, buf, len + 1, NULL, NULL);
#endif

    free(buf);

    LeaveCriticalSection(&logger_lock);
    return wrote_b;
}
int vlog_misc(const char* caller, const char* format, va_list args) {
#ifdef LOG_MISC
    if (HAS_COLOUR) WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), "\033[90m", 5, NULL, NULL);
    int ret = _do_log(caller, "M", format, args, true);
    puts(HAS_COLOUR ? "\033[0m" : "");
    return ret;
#else
    return _do_log(caller, "M", format, args, false);
#endif
}
int log_misc(const char* caller, const char* format, ...) {
    va_list args;
    va_start(args, format);
    int ret = vlog_misc(caller, format, args);
    va_end(args);
    return ret;
}
int vlog_info(const char* caller, const char* format, va_list args) {
#ifdef LOG_INFO
    if (HAS_COLOUR) WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), "\033[97m", 5, NULL, NULL);
    int ret = _do_log(caller, "I", format, args, true);
    puts(HAS_COLOUR ? "\033[0m" : "");
    return ret;
#else
    return _do_log(caller, "I", format, args, false);
#endif
}
int log_info(const char* caller, const char* format, ...) {
    va_list args;
    va_start(args, format);
    int ret = vlog_info(caller, format, args);
    va_end(args);
    return ret;
}
int vlog_warning(const char* caller, const char* format, va_list args) {
#ifdef LOG_WARNING
    if (HAS_COLOUR) WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), "\033[33m", 5, NULL, NULL);
    int ret = _do_log(caller, "W", format, args, true);
    puts(HAS_COLOUR ? "\033[0m" : "");
    return ret;
#else
    return _do_log(caller, "W", format, args, false);
#endif
}
int log_warning(const char* caller, const char* format, ...) {
    va_list args;
    va_start(args, format);
    int ret = vlog_warning(caller, format, args);
    va_end(args);
    return ret;
}
int vlog_error(const char* caller, const char* format, va_list args) {
#ifdef LOG_ERROR
    if (HAS_COLOUR) WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), "\033[91m", 5, NULL, NULL);
    int ret = _do_log(caller, "E", format, args, true);
    puts(HAS_COLOUR ? "\033[0m" : "");
    return ret;
#else
    return _do_log(caller, "E", format, args, false);
#endif
}
int log_error(const char* caller, const char* format, ...) {
    va_list args;
    va_start(args, format);
    int ret = vlog_error(caller, format, args);
    va_end(args);
    return ret;
}

// char format_buf[1024];  // Will do. We guard against overflow in
// Fake[f]printf int WINAPIV Fakeprintf(const char* _Format, ...) { 	int
// flen = strlen(_Format); 	if (flen == strcspn(_Format, "\n") + 1 && flen
// < (sizeof format_buf)) { 		strcpy_s(format_buf, flen, _Format);
// format_buf[flen - 1] = 0; 		_Format = format_buf;
// 	}
// 	va_list args;
// 	va_start(args, _Format);
// 	int ret = vlog_info("printf", _Format, args);
// 	va_end(args);
// 	return ret;
// };
// int WINAPIV Fakefprintf(FILE* _File, const char* _Format, ...) {
// 	int flen = strlen(_Format);
// 	if (flen == strcspn(_Format, "\n") + 1 && flen < (sizeof format_buf))
// { 		strcpy_s(format_buf, flen, _Format); 		format_buf[flen
// - 1] = 0; 		_Format = format_buf;
// 	}
// 	va_list args;
// 	va_start(args, _Format);
// 	int ret = vlog_error("fprintf", _Format, args);
// 	va_end(args);
// 	return ret;
// };

// int WINAPIV Fakefprintf_s(FILE* _Stream, const char* _Format, ...) {
// 	va_list args;
// 	va_start(args, _Format);
// 	int ret = vlog_error("fprintf_s", _Format, args);
// 	va_end(args);
// 	return ret;
// };

static VOID(WINAPI* TrueOutputDebugStringA)(LPCSTR lpOutputString);
VOID WINAPI FakeOutputDebugStringA(LPCSTR lpOutputString) { log_info("debug", "%s", lpOutputString); }

VOID trace_hook(char* output) {
    output[strcspn(output, "\n")] = 0;
    log_error("trace", output);
}

void setup_logging() {
    // Enable colour in CMD
    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
    DWORD dwMode = 0;
    if (GetConsoleMode(hConsole, &dwMode))
        HAS_COLOUR = SetConsoleMode(hConsole, dwMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);

    InitializeCriticalSection(&logger_lock);

    if (LOG_FILE == NULL)
        LOG_FILE = CreateFileA("log.txt", GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, NULL);
}
