#include "Log.h"
#include "arch/CPU.h"
#include "arch/Serial.h"
#include "arch/Timer.h"
#include "video/TextConsole.h"
#include <luna/Format.h>

static bool g_debug_enabled = true;
static bool g_serial_enabled = true;
static bool g_text_console_enabled = false;

static constexpr u32 BLACK = 0xff000000;
static constexpr u32 WHITE = 0xffffffff;
static constexpr u32 YELLOW = 0xffffff00;
static constexpr u32 RED = 0xffff0000;

static char log_level_letters[] = { 'D', 'I', 'W', 'E' }; // D for debug, I for info, W for warning, E for error
static const char* ansi_color_codes_per_log_level[] = { "37", "37", "33", "31" }; // 37 is white, 33 yellow, 31 red

static void log_serial(LogLevel level, const char* format, va_list origin)
{
    va_list ap;
    va_copy(ap, origin);

    Serial::printf("\x1b[%sm"
                   "%c"
                   "\x1b[0m ",
                   ansi_color_codes_per_log_level[(int)level], log_level_letters[(int)level]);

    Serial::printf("%4zu.%.3zu ", Timer::ticks(), Timer::ticks_ms() - (Timer::ticks() * 1000));

    // FIXME: We do this manually because of a lack of vprintf() in both Serial and TextConsole.
    pure_cstyle_format(
        format, [](char c, void*) { Serial::putchar((u8)c); }, nullptr, ap);

    Serial::putchar('\n');

    va_end(ap);
}

static void log_text_console(LogLevel level, const char* format, va_list origin)
{
    va_list ap;
    va_copy(ap, origin);

    const u32 original_foreground = TextConsole::foreground();
    const u32 original_background = TextConsole::background();

    TextConsole::set_background(BLACK);

    if (level == LogLevel::Warn) TextConsole::set_foreground(YELLOW);
    else if (level == LogLevel::Error)
        TextConsole::set_foreground(RED);
    else
        TextConsole::set_foreground(WHITE);

    // FIXME: Same as above.
    auto rc = cstyle_format(
        format, [](char c, void*) -> Result<void> { return TextConsole::putchar(c); }, nullptr, ap);

    if (rc.has_error()) { TextConsole::wprint(L"Invalid UTF-8 in log message"); }

    TextConsole::putwchar(L'\n');

    TextConsole::set_background(original_background);

    TextConsole::set_foreground(original_foreground);

    va_end(ap);
}

void vlog(LogLevel level, const char* format, va_list ap)
{
    if (!g_debug_enabled && level == LogLevel::Debug) return;

    if (g_serial_enabled) log_serial(level, format, ap);
    if (g_text_console_enabled) log_text_console(level, format, ap);
}

void log(LogLevel level, const char* format, ...)
{
    va_list ap;
    va_start(ap, format);

    vlog(level, format, ap);

    va_end(ap);
}

// for luna/DebugLog.h
void debug_log_impl(const char* format, va_list ap)
{
    vlog(LogLevel::Debug, format, ap);
}

void setup_log(bool enable_debug, bool enable_serial, bool enable_text_console)
{
    g_debug_enabled = enable_debug;
    g_serial_enabled = enable_serial;
    g_text_console_enabled = enable_text_console;
}

bool log_debug_enabled()
{
    return g_debug_enabled;
}

bool log_serial_enabled()
{
    return g_serial_enabled;
}

bool log_text_console_enabled()
{
    return g_text_console_enabled;
}

static bool g_check_already_failed = false;

[[noreturn]] bool __check_failed(const char* file, const char* line, const char* func, const char* expr)
{
    CPU::disable_interrupts();
    if (!g_check_already_failed)
    { // Avoid endlessly failing when trying to report a failed check.
        g_check_already_failed = true;
        kerrorln("ERROR: Check failed at %s:%s, in %s: %s", file, line, func, expr);
        CPU::print_stack_trace();
    }
    CPU::efficient_halt();
}