/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file    rtc.c
  * @brief   This file provides code for the configuration
  *          of the RTC instances.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "rtc.h"

/* USER CODE BEGIN 0 */
#define RTC_BKP_CONFIGURED_MARKER      0x32A5U
#define RTC_BKP_MARKER_REG             RTC_BKP_DR1
#define RTC_BKP_YEAR_MONTH_REG         RTC_BKP_DR2
#define RTC_BKP_DATE_WEEKDAY_REG       RTC_BKP_DR3

static uint8_t RTC_NormalizeYear(uint16_t year, uint8_t *normalized_year)
{
  if (year <= 99U)
  {
    *normalized_year = (uint8_t)year;
    return 1U;
  }

  if ((year >= 2000U) && (year <= 2099U))
  {
    *normalized_year = (uint8_t)(year - 2000U);
    return 1U;
  }

  return 0U;
}

static uint8_t RTC_IsLeapYearFull(uint16_t year)
{
  return (uint8_t)(((year % 4U) == 0U) && (((year % 100U) != 0U) || ((year % 400U) == 0U)));
}

static uint8_t RTC_DaysInMonth(uint16_t year, uint8_t month)
{
  static const uint8_t days_in_month[] = { 31U, 28U, 31U, 30U, 31U, 30U, 31U, 31U, 30U, 31U, 30U, 31U };

  if ((month == 0U) || (month > 12U))
  {
    return 0U;
  }

  if ((month == 2U) && RTC_IsLeapYearFull(year))
  {
    return 29U;
  }

  return days_in_month[month - 1U];
}

static uint8_t RTC_IsDateValid(uint16_t year, uint8_t month, uint8_t date)
{
  uint8_t normalized_year = 0U;
  uint16_t full_year = year;
  uint8_t max_day = 0U;

  if (RTC_NormalizeYear(year, &normalized_year) == 0U)
  {
    return 0U;
  }

  if (year <= 99U)
  {
    full_year = 2000U + year;
  }

  max_day = RTC_DaysInMonth(full_year, month);
  return (uint8_t)((date >= 1U) && (date <= max_day));
}

static void RTC_SaveDateToBackup(const RTC_DateTypeDef *date)
{
  uint32_t year_month = (((uint32_t)date->Year) << 8U) | date->Month;
  uint32_t date_weekday = (((uint32_t)date->Date) << 8U) | date->WeekDay;

  if ((HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_YEAR_MONTH_REG) != year_month) ||
      (HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DATE_WEEKDAY_REG) != date_weekday))
  {
    HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_YEAR_MONTH_REG, year_month);
    HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DATE_WEEKDAY_REG, date_weekday);
  }

  if (HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_MARKER_REG) != RTC_BKP_CONFIGURED_MARKER)
  {
    HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_MARKER_REG, RTC_BKP_CONFIGURED_MARKER);
  }
}

static uint8_t RTC_RestoreDateFromBackup(void)
{
  RTC_DateTypeDef restored_date = {0};
  uint32_t year_month = 0U;
  uint32_t date_weekday = 0U;

  if (HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_MARKER_REG) != RTC_BKP_CONFIGURED_MARKER)
  {
    return 0U;
  }

  year_month = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_YEAR_MONTH_REG);
  date_weekday = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DATE_WEEKDAY_REG);

  restored_date.Year = (uint8_t)((year_month >> 8U) & 0xFFU);
  restored_date.Month = (uint8_t)(year_month & 0xFFU);
  restored_date.Date = (uint8_t)((date_weekday >> 8U) & 0xFFU);
  restored_date.WeekDay = (uint8_t)(date_weekday & 0xFFU);

  if (RTC_IsDateValid(restored_date.Year, restored_date.Month, restored_date.Date) == 0U)
  {
    return 0U;
  }

  if (restored_date.WeekDay > RTC_WEEKDAY_SATURDAY)
  {
    restored_date.WeekDay = RTC_WEEKDAY_MONDAY;
  }

  hrtc.DateToUpdate = restored_date;
  return 1U;
}

/* USER CODE END 0 */

RTC_HandleTypeDef hrtc;

/* RTC init function */
void MX_RTC_Init(void)
{

  /* USER CODE BEGIN RTC_Init 0 */

  /* USER CODE END RTC_Init 0 */

  RTC_TimeTypeDef sTime = {0};
  RTC_DateTypeDef DateToUpdate = {0};

  /* USER CODE BEGIN RTC_Init 1 */
  uint8_t rtc_backup_ready = 0U;

  /* USER CODE END RTC_Init 1 */

  /** Initialize RTC Only
  */
  hrtc.Instance = RTC;
  hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND;
  hrtc.Init.OutPut = RTC_OUTPUTSOURCE_SECOND;
  if (HAL_RTC_Init(&hrtc) != HAL_OK)
  {
    Error_Handler();
  }

  /* USER CODE BEGIN Check_RTC_BKUP */
  rtc_backup_ready = RTC_RestoreDateFromBackup();

  /* USER CODE END Check_RTC_BKUP */

  /** Initialize RTC and set the Time and Date
  */
  if (rtc_backup_ready == 0U)
  {
    sTime.Hours = 0U;
    sTime.Minutes = 0U;
    sTime.Seconds = 0U;

    if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK)
    {
      Error_Handler();
    }
    DateToUpdate.WeekDay = RTC_WEEKDAY_MONDAY;
    DateToUpdate.Month = RTC_MONTH_JANUARY;
    DateToUpdate.Date = 1U;
    DateToUpdate.Year = 0U;

    if (HAL_RTC_SetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BIN) != HAL_OK)
    {
      Error_Handler();
    }

    RTC_SaveDateToBackup(&DateToUpdate);
  }
  /* USER CODE BEGIN RTC_Init 2 */

  /* USER CODE END RTC_Init 2 */

}

void HAL_RTC_MspInit(RTC_HandleTypeDef* rtcHandle)
{

  if(rtcHandle->Instance==RTC)
  {
  /* USER CODE BEGIN RTC_MspInit 0 */

  /* USER CODE END RTC_MspInit 0 */
    HAL_PWR_EnableBkUpAccess();
    /* Enable BKP CLK enable for backup registers */
    __HAL_RCC_BKP_CLK_ENABLE();
    /* RTC clock enable */
    __HAL_RCC_RTC_ENABLE();

    /* RTC interrupt Init */
    HAL_NVIC_SetPriority(RTC_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(RTC_IRQn);
  /* USER CODE BEGIN RTC_MspInit 1 */

  /* USER CODE END RTC_MspInit 1 */
  }
}

void HAL_RTC_MspDeInit(RTC_HandleTypeDef* rtcHandle)
{

  if(rtcHandle->Instance==RTC)
  {
  /* USER CODE BEGIN RTC_MspDeInit 0 */

  /* USER CODE END RTC_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_RTC_DISABLE();

    /* RTC interrupt Deinit */
    HAL_NVIC_DisableIRQ(RTC_IRQn);
  /* USER CODE BEGIN RTC_MspDeInit 1 */

  /* USER CODE END RTC_MspDeInit 1 */
  }
}

/* USER CODE BEGIN 1 */
uint8_t RTC_CalendarIsValid(const RTC_CalendarTypeDef *calendar)
{
  if (calendar == NULL)
  {
    return 0U;
  }

  return (uint8_t)((calendar->hours <= 23U) &&
                   (calendar->minutes <= 59U) &&
                   (calendar->seconds <= 59U) &&
                   RTC_IsDateValid(calendar->year, calendar->month, calendar->date));
}

HAL_StatusTypeDef RTC_CalendarGet(RTC_CalendarTypeDef *calendar)
{
  RTC_TimeTypeDef time = {0};
  RTC_DateTypeDef date = {0};

  if (calendar == NULL)
  {
    return HAL_ERROR;
  }

  if (HAL_RTC_GetDate(&hrtc, &date, RTC_FORMAT_BIN) != HAL_OK)
  {
    return HAL_ERROR;
  }

  if (HAL_RTC_GetTime(&hrtc, &time, RTC_FORMAT_BIN) != HAL_OK)
  {
    return HAL_ERROR;
  }

  RTC_SaveDateToBackup(&date);

  calendar->hours = time.Hours;
  calendar->minutes = time.Minutes;
  calendar->seconds = time.Seconds;
  calendar->date = date.Date;
  calendar->month = date.Month;
  calendar->year = 2000U + date.Year;
  calendar->weekday = date.WeekDay;

  return HAL_OK;
}

HAL_StatusTypeDef RTC_CalendarSet(const RTC_CalendarTypeDef *calendar)
{
  RTC_TimeTypeDef time = {0};
  RTC_DateTypeDef date = {0};
  uint8_t normalized_year = 0U;

  if ((calendar == NULL) || (RTC_CalendarIsValid(calendar) == 0U))
  {
    return HAL_ERROR;
  }

  if (RTC_NormalizeYear(calendar->year, &normalized_year) == 0U)
  {
    return HAL_ERROR;
  }

  time.Hours = (uint8_t)calendar->hours;
  time.Minutes = (uint8_t)calendar->minutes;
  time.Seconds = (uint8_t)calendar->seconds;

  date.Year = normalized_year;
  date.Month = (uint8_t)calendar->month;
  date.Date = (uint8_t)calendar->date;
  date.WeekDay = RTC_WEEKDAY_MONDAY;

  if (HAL_RTC_SetTime(&hrtc, &time, RTC_FORMAT_BIN) != HAL_OK)
  {
    return HAL_ERROR;
  }

  if (HAL_RTC_SetDate(&hrtc, &date, RTC_FORMAT_BIN) != HAL_OK)
  {
    return HAL_ERROR;
  }

  RTC_SaveDateToBackup(&date);

  return HAL_OK;
}

/* USER CODE END 1 */
