/*
 * 模块名称：System Clock — Wall Clock Time Maintenance
 * 模块功能：实现系统墙钟时间维护，包括 RTC 初始读取、HAL Tick 漂移维持
 * 适用平台：STM32F407ZGTx (Cortex-M4 FPU, 168MHz)
 * 作者：王建锋
 * 创建日期：2026-07-19
 * 修改记录：
 *   v1.0  2026-07-19  王建锋  创建初始版本
 */

#include "sys_clock.h"
#include "main.h"
#include <string.h>
#include <stdio.h>

#define DBG_TAG "[CLK]"
#include "dbg_log.h"

/* ============================================================
 * RTC 校时（开发期一次性）
 * 使能 RTC_DEFAULT_INIT_ENABLE 后：
 *   RTC_DEFAULT_INIT_FORCE=1：每次启动都写入默认时间（首次/覆盖校时用）
 *   RTC_DEFAULT_INIT_FORCE=0：仅当 RTC 时间无效（越界）时写入
 * 校时完成后建议将 FORCE 置 0（或注释 ENABLE），避免每次开机覆盖用户设的时间。
 * 修改日期/时间时同步更新下列宏。
 * ============================================================ */
#define RTC_DEFAULT_INIT_ENABLE   1
#define RTC_DEFAULT_INIT_FORCE    1
#define RTC_DEFAULT_YEAR    2026
#define RTC_DEFAULT_MONTH   8
#define RTC_DEFAULT_DAY     24
#define RTC_DEFAULT_HOUR    0
#define RTC_DEFAULT_MINUTE  0
#define RTC_DEFAULT_SECOND  0

/* ============================================================
 * 内部状态：系统墙钟
 * ============================================================ */

static volatile uint32_t s_base_tick = 0;  /* HAL_GetTick() 基准 */
static volatile uint32_t s_base_unix_secs = UINT32_MAX;

/* ============================================================
 * 内部辅助函数：Unix time ↔ sd2506_time_t 转换
 * ============================================================ */

/* 计算星期（Zeller 公式，0=Sunday），与下方 unix_to_sd2506 中算法一致 */
static uint8_t rtc_calc_week(uint16_t y, uint8_t m, uint8_t d)
{
    uint32_t wy = y;
    uint32_t wm = m;

    if (wm <= 2) {
        wy--;
        wm += 12u;
    }
    return (uint8_t)(((uint32_t)(wy % 100u + wy / 100u / 4u - wy / 100u / 15u) +
                      (uint32_t)((26u * (wm + 1u)) / 10u) + (uint32_t)(d % 100u)) % 7u);
}

static void unix_to_sd2506(uint32_t unix_secs, sd2506_time_t *p_sd_time)
{
    uint32_t y, m, d;
    uint8_t h, mi, s;
    int is_leap;

    /* 时分秒 */
    {
        uint64_t total = (uint64_t)unix_secs;
        s = (uint8_t)(total % 60uLL);
        total /= 60uLL;
        mi = (uint8_t)(total % 60uLL);
        total /= 60uLL;
        h = (uint8_t)(total % 24uL);
    }

    /* 年月日 */
    {
        uint64_t day_count = ((uint64_t)unix_secs / 86400uL);

        y = 1970u;
        while (day_count >= 365uLL) {
            uint64_t diy = 365uLL;
            if ((y % 4 == 0 && y % 100 != 0) || (y % 400 == 0)) {
                diy = 366uLL;
            }
            if (day_count < diy) break;
            day_count -= diy;
            y++;
        }

        is_leap = ((y % 4 == 0 && y % 100 != 0) || (y % 400 == 0));
        uint8_t mdays[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
        if (is_leap) mdays[1] = 29;

        m = 0;
        while (m < 12u) {
            if (day_count < (uint64_t)mdays[m]) break;
            day_count -= mdays[m];
            m++;
        }
        d = (uint32_t)(day_count + 1uL);

        /* 星期（Zeller's congruence，0=Sunday） */
        uint64_t wy = y;
        uint64_t wm = m;
        if (wm <= 2) {
            wy--;
            wm += 12uL;
        }
        uint8_t week = ((uint32_t)(wy % 100uLL + wy / 100uLL / 4uLL - wy / 100uLL / 15uLL) +
                       (uint32_t)(((uint64_t)26 * (wm + 1uL)) / 10uLL) +
                       (uint32_t)(d % 100uLL)) % 7uLL;

        /* SD2506 year 字段为 0~99（+2000） */
        uint16_t sd_year = y >= 2000 ? (y - 2000) : (y + 2000);

        p_sd_time->year   = sd_year;
        p_sd_time->month  = m + 1uL;    /* 月份从 0 → 1 */
        p_sd_time->day    = d;
        p_sd_time->hour   = h;
        p_sd_time->minute = mi;
        p_sd_time->second = s;
        p_sd_time->week   = week;
    }
}

/* ============================================================
 * 系统墙钟时间维护
 * ============================================================ */

int sys_clock_init(void)
{
    sd2506_time_t sd_time;
    uint32_t year, month, day, hour, minute, second;
    int64_t unix_secs = 0;
    int i;

    /* Step 1: 从 SD2506 RTC 读取初始时间
     * 注意：sd2506_get_time() 返回的 year 已是完整年份（如 2026，驱动内 +2000），
     * 此处不得再加 2000，否则年份虚增 → unix 秒数超 uint32 被截断回绕。 */
    sd2506_get_time(&sd_time);

    /* RTC 校时（可选）：时间无效时，或 FORCE=1 强制时，写入编译期默认时间 */
#if defined(RTC_DEFAULT_INIT_ENABLE)
    {
        int rtc_invalid = (sd_time.year < 2000 || sd_time.year > 2099 ||
                           sd_time.month < 1 || sd_time.month > 12 ||
                           sd_time.day   < 1 || sd_time.day   > 31);
#if (RTC_DEFAULT_INIT_FORCE == 1)
        rtc_invalid = 1;   /* 强制每次启动校时 */
#endif
        if (rtc_invalid) {
            sd2506_time_t init_t;
            init_t.year   = RTC_DEFAULT_YEAR;
            init_t.month  = RTC_DEFAULT_MONTH;
            init_t.day    = RTC_DEFAULT_DAY;
            init_t.hour   = RTC_DEFAULT_HOUR;
            init_t.minute = RTC_DEFAULT_MINUTE;
            init_t.second = RTC_DEFAULT_SECOND;
            init_t.week   = rtc_calc_week(init_t.year, init_t.month, init_t.day);

            if (sd2506_set_time(&init_t) == SD2506_OK) {
                DBG_INFO("sys_clock_init: RTC set %04d-%02d-%02d %02d:%02d:%02d",
                         init_t.year, init_t.month, init_t.day,
                         init_t.hour, init_t.minute, init_t.second);
                sd2506_get_time(&sd_time);   /* 重新读取校准后的时间 */
            } else {
                DBG_ERROR("sys_clock_init: RTC default write FAIL");
            }
        }
    }
#endif

    year   = (uint32_t)sd_time.year;
    month  = (uint32_t)sd_time.month;
    day    = (uint32_t)sd_time.day;
    hour   = (uint32_t)sd_time.hour;
    minute = (uint32_t)sd_time.minute;
    second = (uint32_t)sd_time.second;

    DBG_INFO("sys_clock_init: RTC initial time %04d-%02d-%02d %02d:%02d:%02d",
             year, month, day, hour, minute, second);

    /* Step 2: Unix epoch → seconds（简化算法，适用于 1970~2038） */
    {
        uint64_t total_days = 0;

        for (i = 1970; i < year; i++) {
            if ((i % 4 == 0 && i % 100 != 0) || (i % 400 == 0)) {
                total_days += 366uLL;
            } else {
                total_days += 365uLL;
            }
        }

        int is_leap = ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
        uint8_t mdays[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
        if (is_leap) mdays[1] = 29;

        for (i = 0; i < month - 1; i++) {
            total_days += mdays[i];
        }
        total_days += day - 1uL;

        unix_secs = (int64_t)(total_days * 86400LL) + hour * 3600LL + minute * 60LL + second;
    }

    /* Step 3: 设置系统墙钟基准 */
    s_base_tick = HAL_GetTick();
    s_base_unix_secs = (uint32_t)unix_secs;

    DBG_INFO("sys_clock_init: wall clock base = unix %lu, base_tick=%lu",
             (unsigned long)s_base_unix_secs, (unsigned long)s_base_tick);

    return 0;
}

uint32_t sys_clock_get(void)
{
    uint32_t now_tick = HAL_GetTick();
    int64_t unix_secs = (int64_t)s_base_unix_secs + (int64_t)((uint32_t)(now_tick - s_base_tick) / 1000uL);

    if (unix_secs < 0) {
        return UINT32_MAX; /* 未初始化 */
    }

    return (uint32_t)unix_secs;
}

char *sys_clock_get_str(char *buf, size_t buf_size)
{
    uint32_t unix_secs = sys_clock_get();
    if (unix_secs == UINT32_MAX) {
        snprintf(buf, buf_size, "----/--/-- --:--:--");
        return buf;
    }

    sd2506_time_t sd;
    unix_to_sd2506(unix_secs, &sd);

    snprintf(buf, buf_size, "%04d-%02d-%02d %02d:%02d:%02d",
             sd.year + 2000uL, sd.month, sd.day,
             sd.hour, sd.minute, sd.second);
    return buf;
}

int sys_clock_set(uint32_t unix_secs, const sd2506_time_t *sd_time)
{
    /* Step 1: 更新墙钟基准 */
    s_base_tick = HAL_GetTick();
    s_base_unix_secs = unix_secs;

    DBG_INFO("sys_clock_set: wall clock updated to %lu", (unsigned long)unix_secs);

    /* Step 2: 写回 SD2506 RTC（若提供了 sd_time） */
    if (sd_time != NULL) {
        sd2506_set_time(sd_time);
        DBG_INFO("sys_clock_set: SD2506 RTC written");
    }

    return 0;
}
