/*
 * lb_rtc.c
 *
 * This source file contains a set of functions for handling
 * date and time calculations and conversions in the context of the lbRTC system.
 * The lbRTC system is designed to manage timekeeping and manipulation of date-times.
 *
 * Functions in this file include (but are not limited to):
 *
 * - lbRTC_HardTime: Retrieve the hardware time.
 * - lbRTC_IsAbnormal: Check if the lbRTC system is in an abnormal state.
 * - lbRTC_Sampling: Perform a sampling operation on the lbRTC system.
 * - lbRTC_SetTime: Set the current time in the lbRTC system.
 * - lbRTC_GetTime: Get the current time from the lbRTC system.
 * - lbRTC_GetDaysByMonth: Get the number of days in a given month and year.
 * - lbRTC_IsEqualDate: Check if two dates are equal.
 * - lbRTC_IsEqualTime: Check if two times are equal.
 * - lbRTC_IsOverTime: Check if one time is greater than another time.
 * - lbRTC_IsOverRTC: Check if the given time is greater than the current time.
 * - lbRTC_IntervalTime: Calculate the interval between two times.
 * - lbRTC_GetIntervalDays: Calculate the interval between two times in days.
 * - lbRTC_GetIntervalDays2: Calculate the interval between two times (date only) in days.
 * - lbRTC_Add_YY: Add years to a time.
 * - lbRTC_Add_MM: Add months to a time.
 * - lbRTC_Add_DD: Add days to a time.
 * - lbRTC_Add_hh: Add hours to a time.
 * - lbRTC_Add_mm: Add minutes to a time.
 * - lbRTC_Add_ss: Add seconds to a time.
 * - lbRTC_Add_Date: Add a time duration to a time.
 * - lbRTC_Sub_YY: Subtract years from a time.
 * - lbRTC_Sub_MM: Subtract months from a time.
 * - lbRTC_Sub_DD: Subtract days from a time.
 * - lbRTC_Sub_hh: Subtract hours from a time.
 * - lbRTC_Sub_mm: Subtract minutes from a time.
 * - lbRTC_Sub_ss: Subtract seconds from a time.
 * - lbRTC_Week: Calculate the day of the week for a given date.
 * - lbRTC_TimeCopy: Copy one time structure to another.
 * - lbRTC_IsValidTime: Check if the given time is valid.
 * - lbRTC_time_c_save_data_check: Check if the given time from save data is valid.
 * - lbRTC_Weekly_day: Calculate the day of the week for a given date, considering weeks and the desired day of the week.
 */

#include "lb_rtc.h"

#include "m_common_data.h"
#include "m_lib.h"
#include "types.h"
#include "dolphin/os.h"

static BOOL l_lbRTC_isInitial = TRUE;
static lbRTC_time_c l_lbRTC_Time;
static BOOL l_lbRTC_IsSampled;

/**
 * @brief Get the current hardware time in ticks.
 *
 * This function retrieves the current hardware time using the OSGetTime()
 * function and returns it as OSTime.
 *
 * @return OSTime representing the current hardware time in ticks.
 */
static OSTime lbRTC_GetHardTime() {
  return OSGetTime();
}

/**
 * @brief Wrapper function for lbRTC_GetHardTime().
 *
 * This function is an external wrapper for the lbRTC_GetHardTime() function.
 * It retrieves the current hardware time and returns it as OSTime.
 *
 * @return OSTime representing the current hardware time in ticks.
 */
extern OSTime lbRTC_HardTime() {
  return lbRTC_GetHardTime();
}

/**
 * @brief Convert OSCalendarTime to lbRTC_time_c structure.
 *
 * This function converts the given OSCalendarTime structure into
 * an lbRTC_time_c structure by copying the relevant fields.
 *
 * @param calendar_time Pointer to the source OSCalendarTime structure.
 * @param datetime Pointer to the destination lbRTC_time_c structure.
 */
static void lbRTC_CalenderTimeToRTCTime(const OSCalendarTime* calendar_time, lbRTC_time_c* datetime) {
  datetime->sec = calendar_time->sec;
  datetime->min = calendar_time->min;
  datetime->hour = calendar_time->hour;
  datetime->day = calendar_time->mday;
  datetime->weekday = calendar_time->wday;
  datetime->month = calendar_time->mon + 1;
  datetime->year = calendar_time->year;
}

/* @unused ? OSTime lbRTC_NowHardRtcTime() */

/* @unused ? OSTime lbRTC_SavedHardRtcTime() */

/**
 * @brief Convert lbRTC_time_c to ticks.
 *
 * This function converts the given lbRTC_time_c structure to ticks (OSTime)
 * using OSCalendarTimeToTicks() function.
 *
 * @param time Pointer to the lbRTC_time_c structure to be converted.
 * @return OSTime representing the given lbRTC_time_c in ticks.
 */
static OSTime lbRTC_RTCTimeToTicks(const lbRTC_time_c* time)  {
  OSCalendarTime ctime;
  int i;
  int month = time->month;

  // Copy the lbRTC_time_c structure fields to OSCalendarTime fields
  ctime.sec = time->sec;
  ctime.min = time->min;
  ctime.hour = time->hour;
  ctime.mday = time->day;
  ctime.mon = month - 1;
  ctime.year = time->year;
  ctime.wday = month;
  ctime.yday = 0;
        
  // Get calendar year-day to start of current month
  for (i = 1; i < month; i++) {
    ctime.yday += lbRTC_GetDaysByMonth(time->year, (lbRTC_month_t)i);
  }

  ctime.yday += time->day;
  ctime.yday -= 1;
  ctime.msec = 0;
  ctime.usec = 0;

  // Convert the OSCalendarTime structure to ticks and return it
  return OSCalendarTimeToTicks(&ctime);
}

/**
 * @brief Get the current game time as an lbRTC_time_c structure.
 *
 * This function calculates the current game time by getting the hardware time,
 * adding the saved time delta, converting the result to an OSCalendarTime
 * structure, and finally converting it to an lbRTC_time_c structure.
 *
 * @param time Pointer to the lbRTC_time_c structure that will hold the game time.
 */
static void lbRTC_GetGameTime(lbRTC_time_c* time) {
  OSCalendarTime ctime;
  OSTime t = lbRTC_GetHardTime();
  t += Save_Get(time_delta);

  OSTicksToCalendarTime(t, &ctime);
  lbRTC_CalenderTimeToRTCTime(&ctime, time);
}

/* @unused ? void lbRTC_GetSaveExpectGameTime(lbRTC_time_c* time) */

/**
 * @brief Initialize lbRTC module.
 *
 * This function initializes the lbRTC module by setting l_lbRTC_isInitial
 * to FALSE if it is TRUE.
 *
 * @return 0 after initialization.
 */
static int lbRTC_Initial() {
  if (l_lbRTC_isInitial == TRUE) {
    l_lbRTC_isInitial = FALSE;
  }

  return 0;
}

/**
 * @brief Check if the lbRTC module is initialized and get the game time.
 *
 * This function initializes the lbRTC module if it is not already
 * initialized, sets l_lbRTC_isInitial to TRUE, and gets the current game
 * time using lbRTC_GetGameTime().
 *
 * @param time Pointer to an lbRTC_time_c structure that will hold the game time.
 * @return 0 after checking and getting the game time.
 */
static int lbRTC_IsOki(lbRTC_time_c* time) {
  lbRTC_Initial();
  l_lbRTC_isInitial = TRUE;
  lbRTC_GetGameTime(time);
  return 0;
}

/**
 * @brief Check if the lbRTC module is in an abnormal state.
 *
 * This function checks if the lbRTC module is in an abnormal state by
 * comparing the current game time with the previously sampled game time.
 * It is assumed that a lot of debug code is missing in this function.
 *
 * @return 0 if the lbRTC module is in a normal state, non-zero otherwise.
 */
extern int lbRTC_IsAbnormal() {
  /* Lots of debug code missing here */
  lbRTC_time_c time;

  int res = lbRTC_IsOki(&time);
  if (res == 0 && l_lbRTC_IsSampled == TRUE) {
    if (time.sec + time.min * lbRTC_SECONDS_PER_MINUTE !=
        l_lbRTC_Time.sec + l_lbRTC_Time.min * lbRTC_SECONDS_PER_MINUTE) {
      return res;
    }
  }

  return res;
}

/**
 * @brief Sample the game time using lbRTC_IsOki().
 *
 * This function samples the game time by checking if the lbRTC module
 * is in a normal state using lbRTC_IsOki() and sets l_lbRTC_IsSampled to
 * TRUE if it is not already TRUE.
 */
extern void lbRTC_Sampling() {
  lbRTC_time_c time;

  if (lbRTC_IsOki(&time) == 0 && l_lbRTC_IsSampled == FALSE) {
    l_lbRTC_Time.sec = time.sec;
    l_lbRTC_Time.min = time.min;
    /* Likely debug here */
    l_lbRTC_IsSampled = TRUE;
  }
}

/**
 * @brief Set the game time.
 *
 * This function sets the game time based on the given lbRTC_time_c structure.
 * If the RTC feature is enabled and not crashed, it updates the time delta.
 * Otherwise, it copies the given time to the appropriate rtc_time location.
 *
 * @param time Pointer to the lbRTC_time_c structure containing the time to be set.
 */
extern void lbRTC_SetTime(lbRTC_time_c* time) {
  if (Common_Get(time.rtc_enabled) == TRUE && !Common_Get(time.rtc_crashed)) {
    Save_Set(time_delta, lbRTC_RTCTimeToTicks(time) - lbRTC_GetHardTime());
  }
  else {
    lbRTC_TimeCopy(Common_GetPointer(time.rtc_time), time);
  }
}

/**
 * @brief Get the game time.
 *
 * This function gets the game time and stores it in the provided lbRTC_time_c
 * structure. If the RTC feature is enabled and not crashed, it retrieves the
 * game time using lbRTC_GetGameTime(). Otherwise, it copies the rtc_time
 * to the provided structure.
 *
 * @param time Pointer to the lbRTC_time_c structure that will hold the game time.
 */
extern void lbRTC_GetTime(lbRTC_time_c* time) {
  if (Common_Get(time.rtc_enabled) == TRUE && !Common_Get(time.rtc_crashed)) {
    lbRTC_GetGameTime(time);
  }
  else {
    lbRTC_TimeCopy(time, Common_GetPointer(time.rtc_time));
  }
}

/**
 * @brief Get the number of days in a given month for a specific year.
 *
 * This function returns the number of days in a given month for a specific
 * year, taking into account leap years.
 *
 * @param year The year in which the month occurs.
 * @param month The month for which the number of days is requested.
 * @return lbRTC_day_t representing the number of days in the given month.
 */
extern lbRTC_day_t lbRTC_GetDaysByMonth(lbRTC_year_t year, lbRTC_month_t month) {
  static const lbRTC_day_t days_month[2][lbRTC_MONTHS_MAX + 1] = {
    // Regular year
    {
      0,
      31, 28, 31, 30, 31, 30,
      31, 31, 30, 31, 30, 31
    },

    // Leap year
    {
      0,
      31, 29, 31, 30, 31, 30,
      31, 31, 30, 31, 30, 31
    }
  };

  int year_type = lbRTC_IS_LEAPYEAR(year) == TRUE ? 1 : 0;
  return days_month[year_type][month];
}

typedef union {
  int raw;
  lbRTC_ymd_c ymd;
} ymd_u;

/**
 * @brief Compare two dates to determine if they are equal, lesser, or greater.
 *
 * This function compares two given dates (year, month, day) and returns the
 * result as an RTC_EQUALITY enumeration value (lbRTC_EQUAL, lbRTC_LESS,
 * or lbRTC_OVER).
 *
 * @param y0 Year of the first date.
 * @param m0 Month of the first date.
 * @param d0 Day of the first date.
 * @param y1 Year of the second date.
 * @param m1 Month of the second date.
 * @param d1 Day of the second date.
 * @return RTC_EQUALITY value representing the comparison result.
 */
extern int lbRTC_IsEqualDate(
  lbRTC_year_t y0, lbRTC_month_t m0, lbRTC_day_t d0,
  lbRTC_year_t y1, lbRTC_month_t m1, lbRTC_day_t d1
) {
  ymd_u ymd0, ymd1;
  int res;

  ymd0.ymd.year = y0;
  ymd0.ymd.month = m0;
  ymd0.ymd.day = d0;
    
  ymd1.ymd.year = y1;
  ymd1.ymd.month = m1;
  ymd1.ymd.day = d1;

  res = ymd0.raw - ymd1.raw;
  if (res == 0) {
    return lbRTC_EQUAL;
  }

  if (res < 0) {
    return lbRTC_LESS;
  }
  return lbRTC_OVER;
}

/**
 * @brief Check if two lbRTC_time_c structures are equal based on specified flags.
 *
 * This function checks if two lbRTC_time_c structures are equal by comparing
 * their time components according to the specified flags (seconds, minutes, hours,
 * weekdays, days, months, years).
 *
 * @param t0 Pointer to the first lbRTC_time_c structure.
 * @param t1 Pointer to the second lbRTC_time_c structure.
 * @param flags Bitmask of lbRTC_check_t flags specifying which components to compare.
 * @return TRUE if all specified components are equal, FALSE otherwise.
 */
extern int lbRTC_IsEqualTime(const lbRTC_time_c* t0, const lbRTC_time_c* t1, int flags) {
  int equal = 0;

  if (flags & lbRTC_CHECK_SECONDS) {
    if (t0->sec == t1->sec)  {
      equal |= lbRTC_CHECK_SECONDS;
    }
  }

  if (flags & lbRTC_CHECK_MINUTES) {
    if (t0->min == t1->min) {
      equal |= lbRTC_CHECK_MINUTES;
    }
  }

  if (flags & lbRTC_CHECK_HOURS) {
    if (t0->hour == t1->hour) {
      equal |= lbRTC_CHECK_HOURS;
    }
  }

  if (flags & lbRTC_CHECK_WEEKDAYS) {
    if (t0->weekday == t1->weekday) {
      equal |= lbRTC_CHECK_WEEKDAYS;
    }
  }

  if (flags & lbRTC_CHECK_DAYS) {
    if (t0->day == t1->day) {
      equal |= lbRTC_CHECK_DAYS;
    }
  }

  if (flags & lbRTC_CHECK_MONTHS) {
    if (t0->month == t1->month) {
      equal |= lbRTC_CHECK_MONTHS;
    }
  }

  if (flags & lbRTC_CHECK_YEARS) {
    if (t0->year == t1->year) {
      equal |= lbRTC_CHECK_YEARS;
    }
  }

  return (equal & flags) == flags;
}

/**
 * @brief Determine if a given time is greater (over) another time.
 *
 * This function compares two lbRTC_time_c structures to determine if the first
 * time (t0) is greater (over) the second time (t1). Returns lbRTC_LESS if the first
 * time is lesser than or equal to the second time, and lbRTC_OVER if the first time
 * is greater than the second time.
 *
 * @param t0 Pointer to the first lbRTC_time_c structure.
 * @param t1 Pointer to the second lbRTC_time_c structure.
 * @return lbRTC_LESS if t0 is lesser than or equal to t1, lbRTC_OVER if t0 is greater than t1.
 */
extern int lbRTC_IsOverTime(const lbRTC_time_c* t0, const lbRTC_time_c* t1) {
  if (t1->year < t0->year) {
    return lbRTC_LESS;
  }

  if (t1->year == t0->year) {
    if (t1->month >= t0->month) {
      if (t1->month == t0->month) {
        if (t1->day >= t0->day) {
          if (t1->day == t0->day) {
            if (t1->hour >= t0->hour) {
              if (t1->hour == t0->hour) {
                if (t1->min >= t0->min) {
                  if (t1->min == t0->min) {
                    if (t1->sec < t0->sec) {
                      return lbRTC_LESS;
                    }
                  }
                } else {
                  return lbRTC_LESS;
                }
              }
            } else {
              return lbRTC_LESS;
            }
          }
        } else {
          return lbRTC_LESS;
        }
      }
    } else {
      return lbRTC_LESS;
    }
  }

  return lbRTC_OVER;
}

/**
 * @fabricated
 * 
 * @brief Check if the current RTC time is equal to the given time based on specified flags.
 *
 * This function compares the current RTC time with the given time and returns TRUE
 * if the specified components are equal, based on the check_flags provided.
 *
 * @param time Pointer to the lbRTC_time_c structure representing the time to compare.
 * @param check_flags Bitmask of lbRTC_check_t flags specifying which components to compare.
 * @return TRUE if all specified components are equal, FALSE otherwise.
 */
/*
extern int lbRTC_IsJustAtRTC(const lbRTC_time_c* time, int check_flags) {
  lbRTC_time_c rtc_time;
  
  lbRTC_GetTime(&rtc_time);
  return lbRTC_IsEqualTime(time, &rtc_time, check_flags);
}
*/

/**
 * @brief Check if the given time is greater (over) the current RTC time.
 *
 * This function compares the given time with the current RTC time and returns
 * TRUE if the given time is greater (over) the current RTC time, and FALSE otherwise.
 *
 * @param time Pointer to the lbRTC_time_c structure representing the time to compare.
 * @return TRUE if the given time is greater than the current RTC time, FALSE otherwise.
 */
extern int lbRTC_IsOverRTC(const lbRTC_time_c* time) {
  lbRTC_time_c rtc_time;
  
  lbRTC_GetTime(&rtc_time);
  return lbRTC_IsOverTime(time, &rtc_time) == lbRTC_OVER;
}

/* @unused extern int lbRTC_IsOverWeekRTC(lbRTC_time_c* time) */

/**
 * @brief Calculate the interval of time between two given lbRTC_time_c structures in minutes.
 *
 * This function calculates the interval of time between two given lbRTC_time_c
 * structures. It returns the interval in minutes as an integer. If time0 is earlier than
 * time1, the interval will be negative.
 *
 * @note This function is intended to be used internally by lbRTC_IntervalTime().
 *
 * @param time0 Pointer to the first lbRTC_time_c structure.
 * @param time1 Pointer to the second lbRTC_time_c structure.
 * @return Number of minutes between the two given times, negative if time0 is earlier than time1.
 */
static int lbRTC_IntervalTime_sub(const lbRTC_time_c* time0, const lbRTC_time_c* time1) {
  OSTime osTime1 = lbRTC_RTCTimeToTicks(time1);
  OSTime osTime0 = lbRTC_RTCTimeToTicks(time0);
  
  return OSTicksToSeconds(osTime0 - osTime1) / lbRTC_SECONDS_PER_MINUTE;
}

/**
 * @brief Calculate the interval of time between two given lbRTC_time_c structures in minutes.
 *
 * This function calculates the interval of time between two given lbRTC_time_c
 * structures. It returns the interval in minutes as an integer. If time0 is earlier than
 * time1, the interval will be negative.
 *
 * @param time0 Pointer to the first lbRTC_time_c structure.
 * @param time1 Pointer to the second lbRTC_time_c structure.
 * @return Number of minutes between the two given times, negative if time0 is earlier than time1.
 */
extern int lbRTC_IntervalTime(const lbRTC_time_c* time0, const lbRTC_time_c* time1) {
  return lbRTC_IntervalTime_sub(time0, time1);
}

/**
 * @brief Calculate the number of days between two dates.
 *
 * This function calculates the number of days between two given lbRTC_time_c
 * structures. It returns the interval in days as an integer.
 *
 * @param t0 Pointer to the lbRTC_time_c structure representing the first date.
 * @param t1 Pointer to the lbRTC_time_c structure representing the second date.
 * @return Number of days between the two given dates.
 */
extern int lbRTC_GetIntervalDays(const lbRTC_time_c* t0, const lbRTC_time_c* t1) {
  static const int total_days[2][lbRTC_MONTHS_MAX + 1] = {
    // Standard year
    {
      0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365
    },
    // Leap year (flawed leap year calculation)
    {
      0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366
    }
  };

  int year_leap_period = (t1->year - t0->year) / 4; /* Total 'leap years' (missing extra not divisible by 100, except when divisible by 400 rule) */
  int extra_years = (t1->year - t0->year) % 4; /* Non-leap year remainder */
  int less_leap = (t0->year % 4) == 0; /* Is the lesser year a leap year? */
  int over_leap = (t1->year % 4) == 0; /* Is the greater year a leap year? */
  int leap_add = ((4 - (t0->year % 4)) % 4) < extra_years; /* Add leap day when leap day occurs during 'extra years' */

  int days;

  if (t0->year > t1->year) {
    return 0;
  }
  else {
    if (t0->year == t1->year) {
      if (t0->month > t1->month) {
        return 0;
      }
      else {
        if (t0->month == t1->month) {
          if (t0->day > t1->day) {
            return 0;
          }
          else {
            if (t0->day == t1->day) {
              if (t0->hour > t1->hour) {
                return 0;
              }
              else {
                if (t0->hour == t1->hour) {
                  if (t0->min > t1->min) {
                    return 0;
                  }
                }
              }
            }
          }
        }
      }
    }
  }

  /***
   * Every four years (incorrect) has 365 * 3 + 366 * 1 days (1461).
   * The remaining years will be 365 * years, plus 1 if leap year occurs in range.
   ***/
  days = year_leap_period * 1461 + extra_years * 365 + leap_add;

  /* Add up through t1 day */
  days += t1->day - 1;
  days += total_days[over_leap][t1->month - 1];

  /* Remove through t0 day */
  days -= t0->day - 1;
  days -= total_days[less_leap][t0->month - 1];

  return days;
}

/**
 * @brief Calculate the number of days between two dates using lbRTC_ymd_c structures.
 *
 * This function calculates the number of days between two given lbRTC_ymd_c
 * structures. It returns the interval in days as an integer. If ymd0 is greater than
 * ymd1, the interval will be negative.
 *
 * @param ymd0 Pointer to the first lbRTC_ymd_c structure.
 * @param ymd1 Pointer to the second lbRTC_ymd_c structure.
 * @return Number of days between the two given dates, negative if ymd0 is greater than ymd1.
 */
extern int lbRTC_GetIntervalDays2(lbRTC_ymd_c* ymd0, lbRTC_ymd_c* ymd1) {
  lbRTC_time_c t0, t1;
  int equality;

  mTM_ymd_2_time(&t0, ymd0);
  mTM_ymd_2_time(&t1, ymd1);

  equality = lbRTC_IsEqualDate(t0.year, t0.month, t0.day, t1.year, t1.month, t1.day);
  if (equality > lbRTC_EQUAL) {
    return lbRTC_GetIntervalDays(&t1, &t0) * -1;
  }
  if (equality < lbRTC_EQUAL) {
    return lbRTC_GetIntervalDays(&t0, &t1);
  }

  return 0;
}

/**
 * @brief Add a specified number of years to an lbRTC_time_c structure.
 *
 * @param time Pointer to an lbRTC_time_c structure.
 * @param year Integer value representing the number of years to add.
 */
extern void lbRTC_Add_YY(lbRTC_time_c* time, int year) {
  time->year += (lbRTC_year_t)year;
}

/**
 * @brief Add a specified number of months to an lbRTC_time_c structure.
 *
 * @param time Pointer to an lbRTC_time_c structure.
 * @param month Integer value representing the number of months to add.
 */
extern void lbRTC_Add_MM(lbRTC_time_c* time, int month) {
  int current_mo = time->month;
  current_mo += month;
  if (current_mo > lbRTC_DECEMBER) {
    lbRTC_Add_YY(time, current_mo / lbRTC_MONTHS_MAX);
    current_mo %= lbRTC_MONTHS_MAX;
  }

  time->month = (lbRTC_month_t)current_mo;
}

/**
 * @brief Add a specified number of days to an lbRTC_time_c structure.
 *
 * @param time Pointer to an lbRTC_time_c structure.
 * @param day Integer value representing the number of days to add.
 */
extern void lbRTC_Add_DD(lbRTC_time_c* time, int day) {
  int month_days = lbRTC_GetDaysByMonth(time->year, (lbRTC_month_t)time->month);
  int days = time->day;
  days += day;

  /* This could lead to a bug if adding days rolls over more than one month. */
  if (days > month_days) {
    days -= month_days;
    lbRTC_Add_MM(time, 1);
  }

  time->day = (lbRTC_day_t)days;
}

/**
 * @brief Add a specified number of hours to an lbRTC_time_c structure.
 *
 * @param time Pointer to an lbRTC_time_c structure.
 * @param hour Integer value representing the number of hours to add.
 */
extern void lbRTC_Add_hh(lbRTC_time_c* time, int hour) {
  int curr_hr = time->hour;

  curr_hr += hour;
  if (curr_hr >= lbRTC_HOURS_PER_DAY) {
    lbRTC_Add_DD(time, curr_hr / lbRTC_HOURS_PER_DAY);
    curr_hr %= lbRTC_HOURS_PER_DAY;
  }

  time->hour = (lbRTC_hour_t)curr_hr;
}

/**
 * @brief Add a specified number of minutes to an lbRTC_time_c structure.
 *
 * @param time Pointer to an lbRTC_time_c structure.
 * @param min Integer value representing the number of minutes to add.
 */
extern void lbRTC_Add_mm(lbRTC_time_c* time, int min) {
  int curr_min = time->min;

  curr_min += min;
  if (curr_min >= lbRTC_MINUTES_PER_HOUR) {
    lbRTC_Add_hh(time, curr_min / lbRTC_MINUTES_PER_HOUR);
    curr_min %= lbRTC_MINUTES_PER_HOUR;
  }

  time->min = (lbRTC_min_t)curr_min;
}

/**
 * @brief Add a specified number of seconds to an lbRTC_time_c structure.
 *
 * @param time Pointer to an lbRTC_time_c structure.
 * @param sec Integer value representing the number of seconds to add.
 */
extern void lbRTC_Add_ss(lbRTC_time_c* time, int sec) {
  int curr_sec = time->sec;

  curr_sec += sec;
  if (curr_sec >= lbRTC_SECONDS_PER_MINUTE) {
    lbRTC_Add_mm(time, curr_sec / lbRTC_SECONDS_PER_MINUTE);
    curr_sec %= lbRTC_SECONDS_PER_MINUTE;
  }

  time->sec = (lbRTC_sec_t)curr_sec;
}

/**
 * @brief Add the values of an lbRTC_time_c structure to another lbRTC_time_c structure.
 *
 * This function adds the values of one lbRTC_time_c structure (add_time) to another
 * lbRTC_time_c structure (time). The values are added in the order of least to most
 * significant time unit: seconds, minutes, hours, days, months, years.
 *
 * @param time Pointer to an lbRTC_time_c structure, which will be modified.
 * @param add_time Pointer to an lbRTC_time_c structure containing the values to add.
 */
extern void lbRTC_Add_Date(lbRTC_time_c* time, const lbRTC_time_c* add_time) {
  lbRTC_Add_ss(time, add_time->sec);
  lbRTC_Add_mm(time, add_time->min);
  lbRTC_Add_hh(time, add_time->hour);
  lbRTC_Add_DD(time, add_time->day);
  lbRTC_Add_MM(time, add_time->month);
  lbRTC_Add_YY(time, add_time->year);
}

/**
 * @brief Subtract a specified number of years from an lbRTC_time_c structure.
 *
 * @param time Pointer to an lbRTC_time_c structure.
 * @param year Integer value representing the number of years to subtract.
 */
extern void lbRTC_Sub_YY(lbRTC_time_c* time, int year) {
  time->year -= (lbRTC_year_t)year;
}

/**
 * @brief Subtract a specified number of months from an lbRTC_time_c structure.
 *
 * @param time Pointer to an lbRTC_time_c structure.
 * @param month Integer value representing the number of months to subtract.
 */
extern void lbRTC_Sub_MM(lbRTC_time_c* time, int month) {
  int mo = time->month - month;
  if (mo < lbRTC_JANUARY) {
    int t_mo = mo;
    int sub_year;

    if (mo == 0) {
      mo = lbRTC_DECEMBER;
      sub_year = 1;
    }
    else {
      t_mo = ABS(mo);
      sub_year = t_mo / lbRTC_MONTHS_MAX + 1; /* Years to subtract */
      mo = lbRTC_MONTHS_MAX - (t_mo % lbRTC_MONTHS_MAX); /* Final month we end on */
    }

    lbRTC_Sub_YY(time, sub_year);
  }

  time->month = (lbRTC_month_t)mo;
}

/**
 * @brief Subtract a specified number of days from an lbRTC_time_c structure.
 *
 * @param time Pointer to an lbRTC_time_c structure.
 * @param days Integer value representing the number of days to subtract.
 */
extern void lbRTC_Sub_DD(lbRTC_time_c* time, int days) {
  int day = time->day;
  int month_days;

  if (time->month == lbRTC_JANUARY) {
    month_days = lbRTC_GetDaysByMonth(time->year, (lbRTC_month_t)lbRTC_DECEMBER);
  }
  else {
    month_days = lbRTC_GetDaysByMonth(time->year, (lbRTC_month_t)(time->month - 1));
  }

  day -= days;

  /* Check if day rolled back to previous month */
  if (day <= 0) {
    if (day == 0) {
      day = month_days; /* Landed on last day of the month */
    }
    else {
      day += month_days; /* Bring day positive */
    }

    /* Another 'bug' here. If more than 1 month of days is subtracted, month will be wrong */
    lbRTC_Sub_MM(time, 1);
  }

  time->day = (lbRTC_day_t)day;
}

/**
 * @brief Subtract a specified number of hours from an lbRTC_time_c structure.
 *
 * @param time Pointer to an lbRTC_time_c structure.
 * @param hour Integer value representing the number of hours to subtract.
 */
extern void lbRTC_Sub_hh(lbRTC_time_c* time, int hour) {
  int h = time->hour;
  h -= hour;

  if (h < 0) {
    int temp_h = h;
    int sub_days;

    temp_h = ABS(h);
    sub_days = temp_h / lbRTC_HOURS_PER_DAY + 1;
    h = lbRTC_HOURS_PER_DAY - (temp_h % lbRTC_HOURS_PER_DAY);

    /* Check if we're rolling over the day */
    if (h == lbRTC_HOURS_PER_DAY) {
      h = 0;
      sub_days--;
    }

    lbRTC_Sub_DD(time, sub_days);
  }

  time->hour = (lbRTC_hour_t)h;
}

/**
 * @brief Subtract a specified number of minutes from an lbRTC_time_c structure.
 *
 * @param time Pointer to an lbRTC_time_c structure.
 * @param min Integer value representing the number of minutes to subtract.
 */
extern void lbRTC_Sub_mm(lbRTC_time_c* time, int min) {
  int t_min = time->min;
  t_min -= min;

  if (t_min < 0) {
    int temp_min = t_min;
    int sub_hours;

    temp_min = ABS(t_min);
    sub_hours = temp_min / lbRTC_MINUTES_PER_HOUR + 1;
    t_min = lbRTC_MINUTES_PER_HOUR - (temp_min % lbRTC_MINUTES_PER_HOUR);

    /* Check if we're rolling over the hour */
    if (t_min == lbRTC_MINUTES_PER_HOUR) {
      t_min = 0;
      sub_hours--;
    }

    lbRTC_Sub_hh(time, sub_hours);
  }

  time->min = (lbRTC_min_t)t_min;
}

/**
 * @brief Subtract a specified number of seconds from an lbRTC_time_c structure.
 *
 * @param time Pointer to an lbRTC_time_c structure.
 * @param sec Integer value representing the number of seconds to subtract.
 */
extern void lbRTC_Sub_ss(lbRTC_time_c* time, int sec) {
  int t_sec = time->sec;
  t_sec -= sec;

  if (t_sec < 0) {
    int temp_sec = t_sec;
    int sub_mins;

    temp_sec = ABS(t_sec);
    sub_mins = temp_sec / lbRTC_SECONDS_PER_MINUTE + 1;
    t_sec = lbRTC_SECONDS_PER_MINUTE - (temp_sec % lbRTC_SECONDS_PER_MINUTE);

    /* Check if we're rolling over the minute */
    if (t_sec == lbRTC_SECONDS_PER_MINUTE) {
      t_sec = 0;
      sub_mins--;
    }

    lbRTC_Sub_mm(time, sub_mins);
  }

  time->sec = (lbRTC_sec_t)t_sec;
}

/**
 * @fabricated
 * 
 * @brief Subtract the values of an lbRTC_time_c structure from another lbRTC_time_c structure.
 *
 * This function subtracts the values of one lbRTC_time_c structure (sub_time) from another
 * lbRTC_time_c structure (time). The values are subtracted in the order of least to most
 * significant time unit: seconds, minutes, hours, days, months, years.
 *
 * @param time Pointer to an lbRTC_time_c structure, which will be modified.
 * @param sub_time Pointer to an lbRTC_time_c structure containing the values to subtract.
 */
/*
extern void lbRTC_Sub_Date(lbRTC_time_c* time, const lbRTC_time_c* sub_time) {
  lbRTC_Sub_ss(time, sub_time->sec);
  lbRTC_Sub_mm(time, sub_time->min);
  lbRTC_Sub_hh(time, sub_time->hour);
  lbRTC_Sub_DD(time, sub_time->day);
  lbRTC_Sub_MM(time, sub_time->month);
  lbRTC_Sub_YY(time, sub_time->year);
}
*/

/**
 * @brief Calculate the day of the week for a given date.
 *
 * @param year Year value.
 * @param month Month value.
 * @param day Day value.
 * @return The day of the week as an lbRTC_weekday_t value.
 */
extern lbRTC_weekday_t lbRTC_Week(lbRTC_year_t year, lbRTC_month_t month, lbRTC_day_t day) {
  /* 00:00:00 @ January 1st, 1901 */
  static const lbRTC_time_c a_time = {
    0, 0, 0,
    1, 0, 1,
    1901
  };

  /* This initialization is required lmao */
  lbRTC_time_c b_time = {
    0, 0, 0,
    0, 0, 0,
    0000
  };

  b_time.year = year;
  b_time.month = month;
  b_time.day = day;

  return (lbRTC_weekday_t)((lbRTC_GetIntervalDays(&a_time, &b_time) + 2) % lbRTC_WEEK);
}

/**
 * @brief Copy the values of one lbRTC_time_c structure to another.
 *
 * @param dst Pointer to the destination lbRTC_time_c structure.
 * @param src Pointer to the source lbRTC_time_c structure.
 */
extern void lbRTC_TimeCopy(lbRTC_time_c* dst, const lbRTC_time_c* src) {
  *dst = *src;
}

/**
 * @brief Check if the given time is valid.
 *
 * @param time Pointer to an lbRTC_time_c structure.
 * @return Non-zero (TRUE) if the time is valid, zero (FALSE) otherwise.
 */
extern int lbRTC_IsValidTime(const lbRTC_time_c* time) {
  static const u8 day_tbl[] = {
    00,
    31, 28, 31, 30, 31, 30,
    31, 31, 30, 31, 30, 31
  };

  int res;

  if (
    (time->year < lbRTC_YEAR_MIN ||  time->year > lbRTC_YEAR_MAX) ||
    (time->month == lbRTC_MONTHS_BEGIN || time->month > lbRTC_MONTHS_MAX) ||
    (time->day == 0) ||
    (time->hour > 23) ||
    (time->min > 59) ||
    (time->sec > 59)
  ) {
    res = FALSE;
  }
  else {
    if (time->day == 29 && time->month == 2) {
      res = lbRTC_IS_LEAPYEAR(time->year);
    }
    else if (time->day > day_tbl[time->month]) {
      res = FALSE;
    }
    else {
      res = TRUE;
    }
  }

  return res;
}

/**
 * @brief Check if the given time is valid for save data.
 *
 * @param time Pointer to an lbRTC_time_c structure.
 * @return Non-zero (TRUE) if the time is invalid, zero (FALSE) otherwise.
 */
extern int lbRTC_time_c_save_data_check(const lbRTC_time_c* time) {
  int res = FALSE;

  if (
    (time->sec < 60) &&
    (time->min < 60) &&
    (time->hour < 24) &&
    (time->day >= 1 && time->day <= 31) &&
    (time->weekday < 7) &&
    (time->month >= 1 && time->month <= 12) &&
    (time->year >= GAME_YEAR_MIN + 1 && time->year <= GAME_YEAR_MAX - 1)
  ) {
      res = TRUE;
    }

  return !res;
}

/**
 * @brief Calculate the day of the month for a given week and weekday.
 *
 * @param year Year value.
 * @param month Month value.
 * @param weeks Integer representing the week number in the month.
 * @param weekday Integer representing the day of the week.
 * @return The day of the month as an integer.
 */
extern int lbRTC_Weekly_day(lbRTC_year_t year, lbRTC_month_t month, int weeks, int weekday) {
  int t_weekday;
  
  int month_first_weekday = lbRTC_Week(year, month, 1);
  lbRTC_day_t month_days = lbRTC_GetDaysByMonth(year, month);

  weeks--;
  t_weekday = (int)(((weekday - month_first_weekday) + lbRTC_WEEK) % lbRTC_WEEK) + 1;

  while (weeks > 0) {
    t_weekday += 7;
    if (t_weekday > month_days) {
      t_weekday -= 7;
      break;
    }

    weeks--;
  }

  return t_weekday;
}
