#include "arch/Timer.h"
#include "Log.h"
#include "arch/Serial.h"
#include "boot/bootboot.h"
#include <luna/TypeTraits.h>

static struct timespec s_monotonic_clock = { 0, 0 };
static struct timespec s_realtime_clock;

static inline constexpr bool isleap(u32 year)
{
    return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
}

static constexpr u32 make_yday(u32 year, u32 month)
{
    constexpr u16 upto[12] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };

    u32 yd = upto[month - 1];
    if (month > 2 && isleap(year)) yd++;
    return yd;
}

// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_16
static constexpr u64 broken_down_to_unix(u64 year, u64 yday, u64 hour, u64 min, u64 sec)
{
    return sec + min * 60 + hour * 3600 + yday * 86400 + (year - 70) * 31536000 + ((year - 69) / 4) * 86400 -
           ((year - 1) / 100) * 86400 + ((year + 299) / 400) * 86400;
}

// The bootloader encodes the date and time in Binary-Coded Decimal (BCD), which represents decimal digits using
// hexadecimal digits. For example, BCD 0x22 is 22 in decimal.
// https://gitlab.com/bztsrc/bootboot/-/blob/master/bootboot_spec_1st_ed.pdf, page 15.
static inline constexpr u32 bcd_number_to_decimal(u32 num)
{
    return ((num >> 4) * 10) + (num & 0xf);
}

static u64 bootloader_time_to_unix(const u8 boottime[8])
{
    const u32 year = bcd_number_to_decimal(boottime[0]) * 100 + bcd_number_to_decimal(boottime[1]);
    const u32 month = bcd_number_to_decimal(boottime[2]);
    const u32 day = bcd_number_to_decimal(boottime[3]);
    const u32 hour = bcd_number_to_decimal(boottime[4]);
    const u32 minute = bcd_number_to_decimal(boottime[5]);
    const u32 second = bcd_number_to_decimal(boottime[6]);
    // "The last byte can store 1/100th second precision, but in lack of support on most platforms, it is 0x00".
    // Therefore, let's not rely on it.
    kinfoln("Current time: %.2d/%.2d/%d %.2d:%.2d:%.2d UTC", day, month, year, hour, minute, second);
    return broken_down_to_unix(year - 1900, make_yday(year, month) + (day - 1), hour, minute, second);
}

extern const BOOTBOOT bootboot;

namespace Timer
{
    static struct timespec s_interval = { .tv_sec = 0, .tv_nsec = ARCH_TIMER_RESOLUTION * 1000 };

    void tick()
    {
        timespecadd(&s_monotonic_clock, &s_interval, &s_monotonic_clock);
        timespecadd(&s_realtime_clock, &s_interval, &s_realtime_clock);
    }

    usize ticks_ms()
    {
        return (s_monotonic_clock.tv_sec * 1000) + (s_monotonic_clock.tv_nsec / 1'000'000);
    }

    struct timespec* monotonic_clock()
    {
        return &s_monotonic_clock;
    }

    struct timespec* realtime_clock()
    {
        return &s_realtime_clock;
    }

    void init()
    {
        s_realtime_clock.tv_sec = bootloader_time_to_unix(bootboot.datetime);
        s_realtime_clock.tv_nsec = 0;
        arch_init();
    }
}

bool should_invoke_scheduler()
{
    return (s_realtime_clock.tv_nsec % 1'000'000) == 0;
}