#include <gba/gba.h>
#include <siirtc.h>
#include "global.h"
#include "main.h"

struct Time gTimeSinceBerryUpdate;
struct Time gRtcUTCTime;

static u16 sRtcProbeStatus;
static struct SiiRtcInfo sRtcInfoBuffer;
static u8 sRtcProbeCode;
static u16 sImeBak;
static struct SiiRtcInfo sRtcInfoWork;

const struct SiiRtcInfo sDefaultRTC = {
    .year = 0, // 2000
    .month = 1, // January
    .day = 1, // 01
    .dayOfWeek = 0,
    .hour = 0,
    .minute = 0,
    .second = 0,
    .status = 0,
    .alarmHour = 0,
    .alarmMinute = 0
};
const s32 sDaysPerMonth[] = {
    31,
    28,
    31,
    30,
    31,
    30,
    31,
    31,
    30,
    31,
    30,
    31
};

void rtc_get_status_and_datetime(struct SiiRtcInfo *);
u16 rtc_validate_datetime(struct SiiRtcInfo *);


void rtc_intr_disable(void)
{
    sImeBak = REG_IME;
    REG_IME = 0;
}

void rtc_intr_enable(void)
{
    REG_IME = sImeBak;
}

s32 bcd_to_hex(u8 a0)
{
    if (a0 >= 0xa0 || (a0 & 0xF) >= 10)
        return 0xFF;
    return ((a0 >> 4) & 0xF) * 10 + (a0 & 0xF);
}

bool8 is_leap_year(u8 year)
{
    if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
        return TRUE;
    return FALSE;
}

u16 rtc_count_days_parameterized(u8 year, u8 month, u8 day)
{
    u16 numDays = 0;
    s32 i;
    for (i = year - 1; i > 0; i--)
    {
        numDays += 365;
        if (is_leap_year(i) == TRUE)
            numDays++;
    }
    for (i = 0; i < month - 1; i++)
        numDays += sDaysPerMonth[i];
    if (month > MONTH_FEB && is_leap_year(year) == TRUE)
        numDays++;
    numDays += day;
    return numDays;
}

u16 rtc_count_days_from_info(struct SiiRtcInfo *info)
{
    return rtc_count_days_parameterized(bcd_to_hex(info->year), bcd_to_hex(info->month), bcd_to_hex(info->day));
}

static void rtc_probe_status(void)
{
    sRtcProbeStatus = 0;
    rtc_intr_disable();
    SiiRtcUnprotect();
    sRtcProbeCode = SiiRtcProbe();
    rtc_intr_enable();
    if ((sRtcProbeCode & 0xF) != 1)
        sRtcProbeStatus = 1;
    else
    {
        if (sRtcProbeCode & 0xF0)
            sRtcProbeStatus = 2;
        else
            sRtcProbeStatus = 0;
        rtc_get_status_and_datetime(&sRtcInfoBuffer);
        sRtcProbeStatus = rtc_validate_datetime(&sRtcInfoBuffer);
    }
}

u16 rtc_get_probe_status(void)
{
    return sRtcProbeStatus;
}

void sub_020106EC(struct SiiRtcInfo * info)
{
    if (sRtcProbeStatus & 0xFF0)
        *info = sDefaultRTC;
    else
        rtc_get_status_and_datetime(info);
}

void rtc_get_datetime(struct SiiRtcInfo * info)
{
    rtc_intr_disable();
    SiiRtcGetDateTime(info);
    rtc_intr_enable();
}

void rtc_get_status(struct SiiRtcInfo * info)
{
    rtc_intr_disable();
    SiiRtcGetStatus(info);
    rtc_intr_enable();
}

void rtc_get_status_and_datetime(struct SiiRtcInfo * info)
{
    rtc_get_status(info);
    rtc_get_datetime(info);
}

u16 rtc_validate_datetime(struct SiiRtcInfo * info)
{
    s32 year, month, day;
    u16 r4 = (info->status & SIIRTCINFO_POWER) ? 0x20 : 0;
    if (!(info->status & SIIRTCINFO_24HOUR))
        r4 |= 0x10;
    year = bcd_to_hex(info->year);
    if (year == 0xFF)
        r4 |= 0x40;
    month = bcd_to_hex(info->month);
    if (month == 0xFF || month == 0 || month > 12)
        r4 |= 0x80;
    day = bcd_to_hex(info->day);
    if (day == 0xFF)
        r4 |= 0x100;
    if (month == MONTH_FEB)
    {
        if (day > is_leap_year(year) + sDaysPerMonth[1])
            r4 |= 0x100;
    }
    else
    {
        if (day > sDaysPerMonth[month - 1])
            r4 |= 0x100;
    }
    day = bcd_to_hex(info->hour);
    if (day > 24)
        r4 |= 0x200;
    day = bcd_to_hex(info->minute);
    if (day > 60)
        r4 |= 0x400;
    day = bcd_to_hex(info->second);
    if (day > 60)
        r4 |= 0x800;
    return r4;
}

void rtc_reset(void)
{
    rtc_intr_disable();
    SiiRtcReset();
    rtc_intr_enable();
}

void rtc_sub_time_from_datetime(struct SiiRtcInfo * datetime, struct Time * dest, struct Time * timediff)
{
    u16 r4 = rtc_count_days_from_info(datetime);
    dest->seconds = bcd_to_hex(datetime->second) - timediff->seconds;
    dest->minutes = bcd_to_hex(datetime->minute) - timediff->minutes;
    dest->hours = bcd_to_hex(datetime->hour) - timediff->hours;
    dest->days = r4 - timediff->days;
    if (dest->seconds < 0)
    {
        dest->seconds += 60;
        dest->minutes--;
    }
    if (dest->minutes < 0)
    {
        dest->minutes += 60;
        dest->hours--;
    }
    if (dest->hours < 0)
    {
        dest->hours += 24;
        dest->days--;
    }
}

void rtc_sub_time_from_time(struct Time * dest, struct Time * diff, struct Time * src)
{
    dest->seconds = src->seconds - diff->seconds;
    dest->minutes = src->minutes - diff->minutes;
    dest->hours = src->hours - diff->hours;
    dest->days = src->days - diff->days;
    if (dest->seconds < 0)
    {
        dest->seconds += 60;
        dest->minutes--;
    }
    if (dest->minutes < 0)
    {
        dest->minutes += 60;
        dest->hours--;
    }
    if (dest->hours < 0)
    {
        dest->hours += 24;
        dest->days--;
    }
}

bool32 rtc_maincb_is_rtc_working(void)
{
    rtc_probe_status();
    if (rtc_get_probe_status() & 0xFF0)
        return FALSE;
    return TRUE;
}

void rtc_set_datetime(struct SiiRtcInfo * info)
{
    vu16 imeBak = REG_IME;
    REG_IME = 0;
    SiiRtcSetDateTime(info);
    REG_IME = imeBak;
}

bool32 rtc_maincb_is_time_since_last_berry_update_positive(u8 * a0)
{
    rtc_get_status_and_datetime(&sRtcInfoWork);
    *a0 = bcd_to_hex(sRtcInfoWork.year);
    rtc_sub_time_from_datetime(&sRtcInfoWork, &gRtcUTCTime, LocalTimeOffset);
    rtc_sub_time_from_time(&gTimeSinceBerryUpdate, LastBerryTreeUpdate, &gRtcUTCTime);
    if (gTimeSinceBerryUpdate.days * 1440 + gTimeSinceBerryUpdate.hours * 60 + gTimeSinceBerryUpdate.minutes >= 0)
        return TRUE;
    return FALSE;
}

u32 hex_to_bcd(u8 a0)
{
    u32 r4;
    if (a0 > 99)
        return 0xFF;
    r4 = Div(a0, 10) << 4;
    r4 |= Mod(a0, 10);
    return r4;
}

void sii_rtc_inc(u8 * a0)
{
    *a0 = hex_to_bcd(bcd_to_hex(*a0) + 1);
}

void sii_rtc_inc_month(struct SiiRtcInfo * a0)
{
    sii_rtc_inc(&a0->month);
    if (bcd_to_hex(a0->month) > 12)
    {
        sii_rtc_inc(&a0->year);
        a0->month = MONTH_JAN;
    }
}

void sii_rtc_inc_day(struct SiiRtcInfo * a0)
{
    sii_rtc_inc(&a0->day);
    if (bcd_to_hex(a0->day) > sDaysPerMonth[bcd_to_hex(a0->month) - 1])
    {
        if (!is_leap_year(bcd_to_hex(a0->year)) || bcd_to_hex(a0->month) != MONTH_FEB || bcd_to_hex(a0->day) != 29)
        {
            a0->day = 1;
            sii_rtc_inc_month(a0);
        }
    }
}

bool32 rtc_is_past_feb_28_2000(struct SiiRtcInfo * a0)
{
    if (bcd_to_hex(a0->year) == 0)
    {
        if (bcd_to_hex(a0->month) == MONTH_JAN)
            return FALSE;
        if (bcd_to_hex(a0->month) > MONTH_FEB)
            return TRUE;
        if (bcd_to_hex(a0->day) == 29)
            return TRUE;
        return FALSE;
    }
    if (bcd_to_hex(a0->year) == 1)
        return TRUE;
    return FALSE;
}

void rtc_maincb_fix_date(void)
{
    rtc_get_status_and_datetime(&sRtcInfoWork);
    if (bcd_to_hex(sRtcInfoWork.year) == 0 || bcd_to_hex(sRtcInfoWork.year) == 1)
    {
        if (bcd_to_hex(sRtcInfoWork.year) == 1)
        {
            sRtcInfoWork.year = 2;
            sRtcInfoWork.month = MONTH_JAN;
            sRtcInfoWork.day = 2;
            rtc_set_datetime(&sRtcInfoWork);
        }
        else
        {
            if (rtc_is_past_feb_28_2000(&sRtcInfoWork) == TRUE)
            {
                sii_rtc_inc_day(&sRtcInfoWork);
                sii_rtc_inc(&sRtcInfoWork.year);
            }
            else
            {
                sii_rtc_inc(&sRtcInfoWork.year);
            }
            rtc_set_datetime(&sRtcInfoWork);
        }
    }
}
