#include "rtc_calendar.h"

//Conditions for judging year as leap year
#define LEAP_YEAR_CONDITION(year)     (year % 400 == 0 || (year % 4 == 0 && year % 100 != 0))

//Abbreviation of week
const char *weekday[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sta"};

//Time and date structure
volatile rtc_time_typedef time_struct;
volatile rtc_date_typedef date_struct;

//Alarm clock test output
AT(.com_rodata.isr)
const char alarm_tip[] = "\n----------------->> alarm <<--------------------\n\n";

//Service interruption: alarm clock interruption
AT(.com_text.isr)
void rtc_isr(void)
{
    if (rtc_get_flag(RTC_FLAG_ALM) != RESET) {
        rtc_clear_flag(RTC_FLAG_ALM);
        printf(alarm_tip);
    }
}

// Set the alarm clock time (24H) and turn on the interrupt at the same time
void set_alarm(uint8_t hour, uint8_t minute)
{
    uint32_t current_timestamp;
    uint32_t alarm_timestamp;
    uint32_t timestamp_offet;

    // check the time valid.
    if (hour > 23 || minute > 59) {
        return;
    }

    current_timestamp = rtc_get_counter();

    // get timestamp only including date.
    alarm_timestamp = current_timestamp - (current_timestamp % 86400);
    timestamp_offet = hour * 3600 + minute * 60;

    // time zone time conversion to UTC.
    if (TIME_ZONE < 13) {
        alarm_timestamp -= 3600 * TIME_ZONE;
    } else if (TIME_ZONE < 25) {
        alarm_timestamp += 3600 * TIME_ZONE;
    }

    // if the current time has exceeded the time point, set the alarm for tomorrow.
    if ((alarm_timestamp + timestamp_offet) <= current_timestamp) {
        alarm_timestamp += 86400;
    }
    alarm_timestamp += timestamp_offet;

    rtc_set_alarm(alarm_timestamp);
    rtc_pic_config(rtc_isr, 0, RTC_IT_ALM, ENABLE);
}

// Set date information
void set_date(uint16_t year, uint8_t mon, uint8_t day)
{
    uint32_t current_timestamp;
    uint32_t new_timestamp;
    uint32_t timestamp_offset;
    uint32_t tmp_day;
    u8 i;

    // check the year and month valid.
    if (year < 1970 || year > 2100 ||
        mon < 1 || mon > 12) {
        return;
    }

    // check the day valid.
    switch (mon) {
        case 4:
        case 6:
        case 9:
        case 11:
            if (day < 1 || day > 30) {
                return;
            }
            break;
        case 2:
            if (day < 1 || day > 29) {
                return;
            } else if (!LEAP_YEAR_CONDITION(year) && (day == 29)) {
                return;
            }
            break;
        default:
            if (day < 1 || day > 31) {
                return;
            }
    }

    // convert date to timestamp.
    current_timestamp = rtc_get_counter();
    timestamp_offset = current_timestamp % 86400;
    tmp_day = day - 1;

    for (i = 1; i < 13; i++) {
        if (i == mon) {
            break;
        }
        switch (i) {
            case 4:
            case 6:
            case 9:
            case 11:
                tmp_day += 30;
                break;
            case 2:
                if (LEAP_YEAR_CONDITION(year)) {
                    tmp_day += 29;
                } else {
                    tmp_day += 28;
                }
                break;
            default:
                tmp_day += 31;
        }
    }

    while (year > 1970) {
        year--;
        if (LEAP_YEAR_CONDITION(year)) {
            tmp_day += 366;
        } else {
            tmp_day += 365;
        }
    }

    // set the timestamp to register.
    new_timestamp = tmp_day * 86400 + timestamp_offset;
    rtc_set_counter(new_timestamp);
}

// Set time information (24H)
void set_time(uint8_t hour, uint8_t minute, uint8_t second)
{
    uint32_t current_timestamp;
    uint32_t new_timestamp;
    uint32_t timestamp_offet;

    // check the time valid.
    if (hour > 23 || minute > 59 || second > 59) {
        return;
    }

    // modify the timestamp according to param.
    current_timestamp = rtc_get_counter();
    new_timestamp = current_timestamp - (current_timestamp % 86400);
    timestamp_offet = hour * 3600 + minute * 60 + second;
    new_timestamp += timestamp_offet;

    // can't set the time before (UTC)1970/1/1 00:00:00
    if (new_timestamp < 3600 * TIME_ZONE) {
        return;
    }

    // time zone time conversion to UTC.
    if (TIME_ZONE < 13) {
        new_timestamp -= 3600 * TIME_ZONE;
    } else if (TIME_ZONE < 25) {
        new_timestamp += 3600 * TIME_ZONE;
    }

    // set the time to register.
    rtc_set_counter(new_timestamp);
}

// Get alarm clock time (24H)
void get_alarm(void)
{
    uint32_t alarm_timestamp;

    // get the timestamp from register.
    alarm_timestamp = rtc_get_alarm();

    // UTC to time zone time.
    if (TIME_ZONE < 13) {
        alarm_timestamp += 3600 * TIME_ZONE;
    } else if (TIME_ZONE < 25) {
        alarm_timestamp -= 3600 * TIME_ZONE;
    }

    // write the time info to struct.
    time_struct.hour = alarm_timestamp % 86400 / 3600;
    time_struct.minute = alarm_timestamp % 86400 % 3600 / 60;
    time_struct.second = 0;
}

// get date
void get_date(void)
{
    u32 now_unix;
    u16 year = 1970, day = 0, tmp_day = 0;
    u8 mon = 0;

    now_unix = rtc_get_counter();
    if (TIME_ZONE < 13) {
        now_unix += 3600 * TIME_ZONE;
    } else if (TIME_ZONE < 25) {
        now_unix -= 3600 * (TIME_ZONE - 12);
    }

    day = now_unix / 86400;

    while (day >= 365) {
        if (LEAP_YEAR_CONDITION(year)) {
            if (day <= 365) {
                break;
            }
            day -= 366;
        } else {
            day -= 365;
        }
        year++;
    }

    for (mon = 1; mon < 13; mon++) {
        tmp_day = day;
        switch (mon) {
            case 1:
            case 3:
            case 5:
            case 7:
            case 8:
            case 10:
            case 12:
                day -= 31;
                break;
            case 4:
            case 6:
            case 9:
            case 11:
                day -= 30;
                break;
            case 2:
                if (LEAP_YEAR_CONDITION(year)) {
                    day -= 29;
                } else {
                    day -= 28;
                }
                break;
        }
        if (day > 365) {
            day = tmp_day + 1;
            break;
        }
    }

    date_struct.year = year;
    date_struct.month = mon;
    date_struct.day = day;

    if (mon == 1 || mon == 2) {
        mon += 12;
        year -= 1;
    }
    date_struct.weekday = (day + 2 * mon + 3 * (mon + 1) / 5 + year +
                           year / 4 - year / 100 + year / 400 + 1) % 7;
}

// Acquisition time (24H)
void get_time(void)
{
    u32 now_unix;

    now_unix = rtc_get_counter();
    if (TIME_ZONE < 13) {
        now_unix += 3600 * TIME_ZONE;
    } else if (TIME_ZONE < 25) {
        now_unix -= 3600 * (TIME_ZONE - 12);
    }

    time_struct.hour = (now_unix % 86400) / 3600;
    time_struct.minute = (now_unix % 86400) % 3600 / 60;
    time_struct.second = (now_unix % 86400) %60;
}

//Initialize
void calendar_config(void)
{
    rtc_clk_typedef clk_struct;

    clock_gate0_cmd(CLOCK_GATE0_RTCC, ENABLE);

    clk_struct.external_enable = DISABLE;
    clk_struct.clk_value_flag = RTC_32K_CLK_MODE;
    clk_struct.clk_source_sel = RTC_X24MDIV8_SOURCE;//RTC_RC2M_SOURCE;//

    rtc_clk_config(&clk_struct);
}

void rtc_calendar_init(void)
{
    calendar_config();

//   Set time
    set_date(2024, 04, 26);
    set_time(9, 48, 50);

    //If the time point has passed, it will be postponed by one day
    set_alarm(9, 50);

    get_alarm();
    printf("\n------> set alarm: %02d:%02d:%02d\n\n", time_struct.hour, time_struct.minute, time_struct.second);
}

//Test function
void rtc_calendar_example(void)
{
    static u8 time_fresh_flag = 0;


    get_date();
    get_time();

    if (time_fresh_flag != time_struct.second) {
        time_fresh_flag = time_struct.second;
        printf("===> now time: %d-%02d-%02d %02d:%02d:%02d  %s\n",
               date_struct.year, date_struct.month, date_struct.day,
               time_struct.hour, time_struct.minute, time_struct.second,
               weekday[date_struct.weekday]);
    }
}
