#include "bsp_rtc.h"
#include "bsp_flash.h"
#include "time.h"
// 用到了atoi
#include <stdlib.h>

#define LOG_TAG "bsp_rtc"   // 该模块对应的标签。不定义时，默认：NO_TAG
#define LOG_LVL LOG_LVL_DBG // 该模块对应的日志输出级别。不定义时，默认：调试级别
#include <ulog.h>

#define RTC_FIRST_INIT_VALUE (0x1319)

TsRtcDateTime RtcDateTime;

uint8_t const month_table[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

/**
 * @description: Judging whether it is a leap year.
 * @param {uint16_t} year
 * @return {*}  1 - Yes
 *              0 - No
 */
//  月份    1  2  3  4  5  6  7  8  9  10 11 12
//  闰年    31 29 31 30 31 30 31 31 30 31 30 31
//  非闰年  31 28 31 30 31 30 31 31 30 31 30 31
static uint8_t Is_LeapYear(u16 year)
{
    if (year % 4 == 0)
    {
        if (year % 100 == 0)
        {
            if (year % 400 == 0)
                return 1;
            else
                return 0;
        }
        else
            return 1;
    }
    else
        return 0;
}

/**
 * @description: Get the current day of the week.
 *              输入公历日期得到星期(起始时间为: 公元0年3月1日开始, 输入往后的任何日期, 都可以获取正确的星期)
 *              使用 基姆拉尔森计算公式 计算, 原理说明见此贴:
 *              https://www.cnblogs.com/fengbohello/p/3264300.html
 * @param {uint16_t} year
 * @param {uint8_t} month
 * @param {uint8_t} day
 * @return {*}
 */
uint8_t RTC_GetWeek(uint16_t year, uint8_t month, uint8_t day)
{
    uint8_t week = 0;

    if (month < 3)
    {
        month += 12;
        --year;
    }

    week = (day + 1 + 2 * month + 3 * (month + 1) / 5 + year + (year >> 2) - year / 100 + year / 400) % 7;
    return week;
}

/**
 * @description: 将年月日时分秒转换成秒钟数
 *   @note      以1970年1月1日为基准, 1970年1月1日, 0时0分0秒, 表示第0秒钟
 *              最大表示到2105年, 因为uint32_t最大表示136年的秒钟数(不包括闰年)!
 *              本代码参考只linux mktime函数, 原理说明见此贴:
 *              http://www.openedv.com/thread-63389-1-1.html
 * @param {uint16_t} syear
 * @param {uint8_t} smon
 * @param {uint8_t} sday
 * @param {uint8_t} hour
 * @param {uint8_t} min
 * @param {uint8_t} sec
 * @return {*} 转换后的秒钟数
 */
uint32_t DateTime2Seconds(uint16_t syear, uint8_t smon, uint8_t sday, uint8_t hour, uint8_t min, uint8_t sec)
{
    ASSERT((syear > 1970 || syear < 2099));

    uint32_t y, m, d, x, t;
    signed char monx = smon; /* 将月份转换成带符号的值, 方便后面运算 */

    if (0 >= (monx -= 2)) /* 1..12 -> 11,12,1..10 */
    {
        monx += 12; /* Puts Feb last since it has leap day */
        syear -= 1;
    }

    y = (syear - 1) * 365 + syear / 4 - syear / 100 + syear / 400; /* 公元元年1到现在的闰年数 */
    m = 367 * monx / 12 - 30 + 59;
    d = sday - 1;
    x = y + m + d - 719162;                      /* 减去公元元年到1970年的天数 */
    t = ((x * 24 + hour) * 60 + min) * 60 + sec; /* 总秒钟数 */
    return t;
}

uint32_t RTC_Time2TimeStamp(void)
{
    RTC_GetTime();
    uint32_t time_stamp = DateTime2Seconds(RtcDateTime.year, RtcDateTime.month, RtcDateTime.day, RtcDateTime.hour, RtcDateTime.minute, RtcDateTime.second) - (8 * 3600);

    return time_stamp;
}

void Seconds2DateTime(uint32_t seconds, TsRtcDateTime *pDateTime)
{
    uint16_t daycnt    = 0;
    uint32_t timecount = 0;
    uint32_t temp      = 0;
    uint16_t temp1     = 0;

    timecount = seconds;
    temp      = timecount / 86400;
    if (daycnt != temp)
    {
        daycnt = temp;
        temp1  = 1970;
        while (temp >= 365)
        {
            if (Is_LeapYear(temp1))
            {
                if (temp >= 366)
                    temp -= 366;
                else
                {
                    temp1++;
                    break;
                }
            }
            else
                temp -= 365;
            temp1++;
        }
        pDateTime->year = temp1;
        temp1           = 0;
        while (temp >= 28)
        {
            if (Is_LeapYear(pDateTime->year) && temp1 == 1)
            {
                if (temp >= 29)
                    temp -= 29;
                else
                    break;
            }
            else
            {
                if (temp >= month_table[temp1])
                    temp -= month_table[temp1];
                else
                    break;
            }
            temp1++;
        }
        pDateTime->month = temp1 + 1;
        pDateTime->day   = temp + 1;
    }
    temp              = timecount % 86400;
    pDateTime->hour   = temp / 3600;
    pDateTime->minute = (temp % 3600) / 60;
    pDateTime->second = (temp % 3600) % 60;
    pDateTime->week   = RTC_GetWeek(pDateTime->year, pDateTime->month, pDateTime->day);
}

/**
 * @description: 设置时间, 包括年月日时分秒
 *   @note      以1970年1月1日为基准, 往后累加时间
 *              合法年份范围为: 1970 ~ 2105年
                HAL默认为年份起点为2000年
 * @param {uint16_t} syear
 * @param {uint8_t} smon
 * @param {uint8_t} sday
 * @param {uint8_t} hour
 * @param {uint8_t} min
 * @param {uint8_t} sec
 * @return {*}
 */
void RTC_SetTime(rt_uint16_t syear, rt_uint8_t smon, rt_uint8_t sday, rt_uint8_t hour, rt_uint8_t min, rt_uint8_t sec)
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
    PWR_BackupAccessCmd(ENABLE);

    RTC_SetCounter(DateTime2Seconds(syear, smon, sday, hour, min, sec));
    RTC_WaitForLastTask();
}

// refresh RtcDateTime
/**
 * @description: 该函数不直接返回时间, 时间数据保存在RtcDateTime结构体里面
 * @return {*}
 */
void RTC_GetTime(void)
{
    uint16_t daycnt    = 0;
    uint32_t timecount = 0;
    uint32_t temp      = 0;
    uint16_t temp1     = 0;

    timecount = RTC_GetCounter();
    temp      = timecount / 86400;
    if (daycnt != temp)
    {
        daycnt = temp;
        temp1  = 1970;
        while (temp >= 365)
        {
            if (Is_LeapYear(temp1))
            {
                if (temp >= 366)
                    temp -= 366;
                else
                {
                    temp1++;
                    break;
                }
            }
            else
                temp -= 365;
            temp1++;
        }
        RtcDateTime.year = temp1;
        temp1            = 0;
        while (temp >= 28)
        {
            if (Is_LeapYear(RtcDateTime.year) && temp1 == 1)
            {
                if (temp >= 29)
                    temp -= 29;
                else
                    break;
            }
            else
            {
                if (temp >= month_table[temp1])
                    temp -= month_table[temp1];
                else
                    break;
            }
            temp1++;
        }
        RtcDateTime.month = temp1 + 1;
        RtcDateTime.day   = temp + 1;
    }
    temp               = timecount % 86400;
    RtcDateTime.hour   = temp / 3600;
    RtcDateTime.minute = (temp % 3600) / 60;
    RtcDateTime.second = (temp % 3600) % 60;
    RtcDateTime.week   = RTC_GetWeek(RtcDateTime.year, RtcDateTime.month, RtcDateTime.day);
}

static void RTC_NVIC_Config(void)
{
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel                   = RTC_IRQn; // RTC全局中断
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;        // 先占优先级1位,从优先级3位
    NVIC_InitStructure.NVIC_IRQChannelSubPriority        = 6;        // 先占优先级0位,从优先级4位
    NVIC_InitStructure.NVIC_IRQChannelCmd                = ENABLE;   // 使能该通道中断
    NVIC_Init(&NVIC_InitStructure);                                  // 根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
}

static uint8_t RTC_FirstInit(void)
{
    uint8_t temp = 0;

    RCC_LSEConfig(RCC_LSE_ON);
    while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET && temp < 250)
    {
        temp++;
        rt_thread_mdelay(5);
    }
    if (temp >= 250)
    {
        LOG_E("RTC_FirstInit: LSE not ready");
        return 1;
    }

    RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
    RCC_RTCCLKCmd(ENABLE);
    RTC_WaitForLastTask();
    RTC_WaitForSynchro();
    RTC_WaitForLastTask();
    RTC_SetPrescaler(32767);
    RTC_WaitForLastTask();

    RTC_SetTime(2025, 1, 17, 14, 31, 21);
    RTC_WaitForLastTask();

    /* Is it the first configuration */
    BKP_WriteBackupRegister(BKP_DR1, RTC_FIRST_INIT_VALUE);

    return 0;
}

int BSP_RTC_Init(void)
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
    PWR_BackupAccessCmd(ENABLE);

    LOG_D("RCC_GetFlagStatus(RCC_FLAG_LSERDY): %d", RCC_GetFlagStatus(RCC_FLAG_LSERDY));
    /* Is it the first configuration */
    if (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET || BKP_ReadBackupRegister(BKP_DR1) != RTC_FIRST_INIT_VALUE)
    {
        RTC_FirstInit();
    }
    else
    {
        // 上电/掉电复位标志
        if (RCC_GetFlagStatus(RCC_FLAG_PORRST) != RESET)
        {
            LOG_D("上电复位");
        }
        // 软件复位
        if (RCC_GetFlagStatus(RCC_FLAG_SFTRST) != RESET)
        {
            LOG_D("软件复位");
        }
        // 独立看门狗复位
        if (RCC_GetFlagStatus(RCC_FLAG_IWDGRST) != RESET)
        {
            LOG_D("独立看门狗复位");
        }
        // 窗口看门狗复位
        if (RCC_GetFlagStatus(RCC_FLAG_WWDGRST) != RESET)
        {
            LOG_D("窗口看门狗复位");
        }
        // 低功耗复位
        if (RCC_GetFlagStatus(RCC_FLAG_LPWRRST) != RESET)
        {
            LOG_D("低功耗复位");
        }

        // 虽然RTC模块不需要重新配置，且掉电后依靠后备电池依然运行
        // 但是每次上电后，还是要使能RTCCLK
        RCC_RTCCLKCmd(ENABLE); // 使能RTCCLK
        RTC_WaitForLastTask();
        RTC_WaitForSynchro(); // 等待RTC时钟与APB1时钟同步

        RTC_WaitForLastTask();
    }
    RTC_NVIC_Config();
    RTC_GetTime();
    LOG_D("%4d-%02d-%02d, %02d:%02d:%02d", RtcDateTime.year, RtcDateTime.month, RtcDateTime.day,
          RtcDateTime.hour, RtcDateTime.minute, RtcDateTime.second);
    LOG_I("BSP_RTC_Init!");

    return 0;
}
// INIT_PREV_EXPORT(BSP_RTC_Init);

#if 1
void RTC_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void RTC_IRQHandler(void)
{
    GET_INT_SP();
    /* enter interrupt */
    rt_interrupt_enter();

    if (RTC_GetITStatus(RTC_IT_SEC) != RESET) // 秒钟中断
    {
        RTC_ClearITPendingBit(RTC_IT_SEC | RTC_IT_OW); // 清秒中断
        // RTC_GetTime();                                 // 更新时间
        // LOG_D("%4d-%02d-%02d, %02d:%02d:%02d", RtcDateTime.year, RtcDateTime.month, RtcDateTime.day,
        //       RtcDateTime.hour, RtcDateTime.minute, RtcDateTime.second);
    }

    rt_interrupt_leave();
    FREE_INT_SP();
}
#endif

// 将时间戳转换为 TsRtcDateTime 结构体
void Timestamp_To_Rtc_DateTime(time_t timestamp, TsRtcDateTime *rtc_dt)
{
    struct tm *tm_info;
    time_t local_timestamp = timestamp + 8 * 3600; // 北京时间比UTC时间快8小时

    tm_info = localtime(&local_timestamp);
    if (tm_info != NULL)
    {
        rtc_dt->year   = tm_info->tm_year + 1900;
        rtc_dt->month  = tm_info->tm_mon + 1;
        rtc_dt->day    = tm_info->tm_mday;
        rtc_dt->hour   = tm_info->tm_hour;
        rtc_dt->minute = tm_info->tm_min;
        rtc_dt->second = tm_info->tm_sec;
        rtc_dt->week   = RTC_GetWeek(rtc_dt->year, rtc_dt->month, rtc_dt->day);

        // rtc_dt->week   = tm_info->tm_wday + 1; // tm_wday 的范围是0-6，对应星期日到星期六
    }
    else
    {
        // 处理时间转换失败的情况
        rtc_dt->year   = 0;
        rtc_dt->month  = 0;
        rtc_dt->day    = 0;
        rtc_dt->hour   = 0;
        rtc_dt->minute = 0;
        rtc_dt->second = 0;
        rtc_dt->week   = 0;
    }
}

#ifdef TEST_ENABLE

void RTC_ShowTime(void)
{
    RTC_GetTime();
    LOG_I("RTC Timer : %04d-%02d-%02d  %02d:%02d:%02d week[%d]", RtcDateTime.year, RtcDateTime.month, RtcDateTime.day,
          RtcDateTime.hour, RtcDateTime.minute, RtcDateTime.second, RtcDateTime.week);
}
MSH_CMD_EXPORT(RTC_ShowTime, "get rtc timer");

static void RTC_Cfg_Time(int argc, char **argv)
{
    if (argc == 7)
    {
        /*年 月 日 时 分 秒*/
        RTC_SetTime((rt_uint16_t)(atoi(argv[1])), (rt_uint8_t)(atoi(argv[2])), (rt_uint8_t)(atoi(argv[3])),
                    (rt_uint8_t)(atoi(argv[4])), (rt_uint8_t)(atoi(argv[5])), (rt_uint8_t)(atoi(argv[6])));
        RTC_ShowTime();
    }
    else
    {
        LOG_E("RTC_Cfg_Time      --use [y m d h m s]");
    }
}
MSH_CMD_EXPORT(RTC_Cfg_Time, "y m d h m s");

void RTC_Control(int argc, char **argv)
{
    if (argc == 2)
    {
        int flag = atoi(argv[1]);
        if (flag == 0)
        {
            RTC_ITConfig(RTC_IT_SEC, DISABLE);
            RTC_WaitForLastTask();
        }
        else if (flag == 1)
        {
            RTC_ITConfig(RTC_IT_SEC, ENABLE);
            RTC_WaitForLastTask();
        }
        else if (flag == 2)
        {
            RTC_GetTime();
            LOG_D("%4d-%02d-%02d, week:%d, %02d:%02d:%02d", RtcDateTime.year, RtcDateTime.month, RtcDateTime.day,
                  RtcDateTime.week, RtcDateTime.hour, RtcDateTime.minute, RtcDateTime.second);
        }
    }
    else
    {
        LOG_E("Show rtc time    --use _cmd_ [0:关IT/1:开IT/2:show rtc time(once)]");
    }
}
MSH_CMD_EXPORT(RTC_Control, "0, 1(1s)show time: 2-show time once");

#endif // TSET_BSP_RTC
