#include <hilbert/kernel/timer.hpp>

namespace hilbert::kernel::timer {

  struct sleeping_thread {
    uint64_t sleeping_until;
    application::thread *thread;
  };

  //sorted ascending by sleeping_until
  static utility::list<sleeping_thread> *sleeping_threads;

  uint64_t current_time;

  //output is
  //   seconds     | (minutes <<  8) | (hours << 16) |
  //  (days << 24) | ( months << 32) | (years << 40)
  extern "C" uint64_t get_time_from_rtc();

  //index is (year % 4) * 12 + month - 1;
  //output is days from january 1st 2000 to [month] 1st [year]
  static uint16_t month_table[] {
       0,   31,   60,   91,  121,  152,
     182,  213,  244,  274,  305,  335,
     366,  397,  425,  456,  486,  517,
     547,  578,  609,  639,  670,  700,
     731,  762,  790,  821,  851,  882,
     912,  943,  974, 1004, 1035, 1065,
    1096, 1127, 1155, 1186, 1216, 1247,
    1277, 1308, 1339, 1369, 1400, 1430
  };

  //index is (year % 4) * 12 + month - 1;
  //output is days in that month
  static uint8_t month_table_2[] = {
    31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
    31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
    31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
    31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
  };

  void clamp(uint8_t &value, uint8_t min, uint8_t max) {
    if (value < min)
      value = min;
    else if (value > max)
      value = max;
  }

  extern "C" void enable_rtc_interrupts();

  void init_timer() {

    sleeping_threads = new utility::list<sleeping_thread>();

    uint64_t rtc_time = get_time_from_rtc();

    uint8_t   years =  rtc_time >> 40;
    uint8_t  months = (rtc_time >> 32) & 0xff;
    uint8_t    days = (rtc_time >> 24) & 0xff;
    uint8_t   hours = (rtc_time >> 16) & 0xff;
    uint8_t minutes = (rtc_time >>  8) & 0xff;
    uint8_t seconds =  rtc_time        & 0xff;

    uint8_t month_table_index =
      (years % 4) * 12 + months - 1;

    clamp(  years, 0, 99);
    clamp( months, 1, 12);
    clamp(   days, 1, month_table_2[month_table_index]);
    clamp(  hours, 0, 23);
    clamp(minutes, 0, 59);
    clamp(seconds, 0, 59);

    current_time  = 1461 * (years / 4);
    current_time += month_table[month_table_index];
    current_time += days - 1;
    current_time *= 86400;
    current_time += hours * 3600;
    current_time += minutes * 60;
    current_time += seconds;
    current_time *= 1024;

    enable_rtc_interrupts();

  }

  void register_sleeping_thread(
    uint64_t sleeping_until, application::thread *thread) {

    auto *after = sleeping_threads->first;
    while (after && after->value.sleeping_until < sleeping_until)
      after = after->next;

    sleeping_threads->insert_before(
      { .sleeping_until = sleeping_until, .thread = thread }, after);

  }

  extern "C" void acknowledge_rtc_interrupt();

  extern "C" void on_rtc_interrupt() {

    ++current_time;

    while (sleeping_threads->first &&
           sleeping_threads->first->value.sleeping_until <=
             current_time) {
      application::paused_threads->insert(
        sleeping_threads->first->value.thread);
      sleeping_threads->remove(sleeping_threads->first);
    }

    acknowledge_rtc_interrupt();

  }

}
