//-----------------------------------------------------------------------------
///
/// \brief  Class for RTC
///
/// \date   20121107
/// \author Maximilian Seesslen <development@seesslen.net>
///
//-----------------------------------------------------------------------------


//---Includes------------------------------------------------------------------


#include <biwak/rtc.hpp>
#include <biwak/rcc.h>
#include <biwak/biwak.h>            // assert
#include <HALWrapper/stm32_rcc.h>
#include <HALWrapper/stm32_pwr.h>
#include <HALWrapper/stm32_rtc.h>
#include <HALWrapper/stm32xx_rtc_sup.h>
#include <HALWrapper/stm32_rcc.h>
#include <lepto/time.h>             // Time Macros
#include <lepto/log.h>
#include <string.h>                 // memset
#include <biwak/exception.h>
#include <cassert>
#include <biwak/nvic.h>
#include <biwak/handler.h>
#include <sys/time.h>         // timezone

// #include <sys/time.h>

#if defined BIWAK_USE_LSE
   #define RCC_RTCCLKSOURCE_LSI_LSE RCC_RTCCLKSOURCE_LSE
#else
   #define RCC_RTCCLKSOURCE_LSI_LSE RCC_RTCCLKSOURCE_LSI
#endif

//---Implementation------------------------------------------------------------

CRtc *interruptRtc1=nullptr;
extern CRtc*  libcBiwakRtc __attribute(( alias("interruptRtc1") ));

CRtc::CRtc( )
{
   m_pHandler=new RTC_HandleTypeDef{
       .Instance=RTC
   };
   status=-1;
   interruptRtc1=this;
   init();

   #if defined( RTC_WAKEUP_SUPPORT )
      HAL_RTCEx_DeactivateWakeUpTimer(m_pHandler);
      // NVIC_EnableIRQ( RTC_Alarm_IRQn );
      // interrupt();

      #pragma GCC diagnostic push
      #pragma GCC diagnostic ignored "-Wvolatile"

      #if ! defined( PWR_FLAG_WU )
         #define PWR_FLAG_WU PWR_FLAG_WUFI
      #endif

      /* Check and Clear the Wakeup flag */
      if(__HAL_PWR_GET_FLAG(PWR_FLAG_WU) != RESET)
      {
         __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
      }

      /* Check if the system was resumed from StandBy mode */
      if(__HAL_PWR_GET_FLAG(PWR_FLAG_SB) != RESET)
      {
         /* Clear StandBy flag */
         __HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);
      }

      #pragma GCC diagnostic pop

   #endif
}

CRtc::~CRtc()
{
   HAL_RTC_DeInit( m_pHandler );
}

int CRtc::init()
{
   HAL_StatusTypeDef status;
   
   if(isValid())
   {
      #if defined ( STM32F103xx )
         syncDateRead();
      #endif
      return(0);
   }
   
   enableLsiLse();
   
   rccRtcClockEnable(m_pHandler->Instance);

   HAL_PWR_EnableBkUpAccess();

   // Backup-Registers can only be wtritten if unit was reseted
   // This will reset the RTC when running RTC on LSE
   //__HAL_RCC_BACKUPRESET_FORCE();
   //__HAL_RCC_BACKUPRESET_RELEASE();

   #if !defined(STM32F1)
      m_pHandler->Init.HourFormat = RTC_HOURFORMAT_24;
      m_pHandler->Init.SynchPrediv = (32768/128)-1;
      m_pHandler->Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
      m_pHandler->Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
      #if defined( RTC_OUTPUT_REMAP_NONE )
      m_pHandler->Init.OutPutRemap = RTC_OUTPUT_REMAP_NONE;
      #if defined( RTC_OUTPUT_PULLUP_NONE )
      m_pHandler->Init.OutPutPullUp = RTC_OUTPUT_PULLUP_NONE;
      #endif // ? RTC_OUTPUT_PULLUP_NONE
      #endif // ? RTC_OUTPUT_REMAP_NONE
   #endif

   #if defined(STM32F0) || defined(STM32F4) || defined(STM32F3) \
      || defined(STM32L1) || defined(STM32L4) || defined(STM32L0) \
      || defined(STM32G0) || defined(STM32G4)
      m_pHandler->Init.AsynchPrediv = 0x7F;
   #else
      m_pHandler->Init.AsynchPrediv = RTC_AUTO_1_SECOND;
   #endif

   #if !defined(STM32F1)
      m_pHandler->Init.OutPut = RTC_OUTPUT_DISABLE;
   #else
      m_pHandler->Init.OutPut = RTC_OUTPUTSOURCE_NONE;
   #endif

   status=HAL_RTC_Init(m_pHandler);
   #if defined(STM32F303x8) || defined(STM32F103xx)
      // Nucleo STM32F303k8 does not work yet; to be fixed
   #else
      //lAssert( status == HAL_OK );
      if( status != HAL_OK )
      {
         lCritical("Could not init RTC: %d", status);
      }
   #endif
   
   #if defined ( STM32F103xx )
   if(isValid())
   {
      syncDateRead();
   }
   #endif

   #if defined(STM32L0) || defined( STM32F0 )
      setNVInterrupt( RTC_IRQn, true, NVI_DEFAULT_PRIORITY );
   #elif defined( STM32G0 )
         setNVInterrupt( RTC_TAMP_IRQn, true, NVI_DEFAULT_PRIORITY );
   #else
      setNVInterrupt( RTC_Alarm_IRQn, true, NVI_DEFAULT_PRIORITY );
   #endif

   return( (int)status );
}

void CRtc::enableLsiLse()
{
   // Only needed once

   RCC_PeriphCLKInitTypeDef PeriphClkInitStruct{0};

   #pragma GCC diagnostic push
   #pragma GCC diagnostic ignored "-Wvolatile"

   __HAL_RCC_PWR_CLK_ENABLE();

   #pragma GCC diagnostic pop

   HAL_PWR_EnableBkUpAccess();

   PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_RTC;
   PeriphClkInitStruct.RTCClockSelection = RCC_RTCCLKSOURCE_LSI_LSE;
   if(HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
   {
      // "Could not config RCCEx"
      exception( );
   }
   
}

void CRtc::getTime( int &hour, int &min, int &sec /*, uint8_t &subsec*/) const
{
   RTC_TimeTypeDef RTC_TimeStruct;

   HAL_RTC_GetTime(m_pHandler, &RTC_TimeStruct, RTC_FORMAT_BIN );
   
   #if defined ( STM32F103xx )
   syncDateWrite();
   #endif

   hour=RTC_TimeStruct.Hours;
   min=RTC_TimeStruct.Minutes;
   sec=RTC_TimeStruct.Seconds;
   //subsec=RTC_TimeStruct.SubSeconds;

   // stm32f1 has no DR register?
   #if !defined(STM32F1)
   (void)m_pHandler->Instance->DR;
   #endif

   return;
}

void CRtc::getDate( int &year, int &month, int &day) const
{
   RTC_DateTypeDef RTC_DateStruct;
   HAL_StatusTypeDef sta __attribute ((unused));

   sta=HAL_RTC_GetDate(m_pHandler, &RTC_DateStruct, RTC_FORMAT_BIN );
   bDebugAssert( sta == HAL_OK, sta );
   
   #if defined ( STM32F103xx )
   syncDateWrite();
   #endif

   year=RTC_DateStruct.Year+2000;
   month=RTC_DateStruct.Month;
   day=(uint16_t)RTC_DateStruct.Date;

   return;
}

void CRtc::getTime( int &hour, int &min, int &sec, int &msec)  const
{
   RTC_TimeTypeDef RTC_TimeStruct;
   HAL_RTC_GetTime( m_pHandler, &RTC_TimeStruct, RTC_FORMAT_BIN );
   
   #if defined ( STM32F103xx )
   syncDateWrite();
   #endif

   hour=RTC_TimeStruct.Hours;
   min=RTC_TimeStruct.Minutes;
   sec=RTC_TimeStruct.Seconds;
   #if ! ( defined(STM32F1) || defined(STM32L1) )
      msec=(1000*(0xFF-RTC_TimeStruct.SubSeconds))/0x100;
   #else
      msec=0;
   #endif

   // STM32F1 has no DR register?
   #if !defined(STM32F1)
   (void)m_pHandler->Instance->DR;
   #endif
   return;
}

int CRtc::setTime( int8_t hour, int8_t min, int8_t sec)
{
   RTC_TimeTypeDef RTC_TimeStruct;

   RTC_TimeStruct.Hours=hour;
   RTC_TimeStruct.Minutes=min;
   RTC_TimeStruct.Seconds=sec;

   #if ! ( defined(STM32F1) || defined(STM32L1) )
   RTC_TimeStruct.SubSeconds=0;
   #endif

   #if ! defined(STM32F1)
   RTC_TimeStruct.TimeFormat=RTC_HOURFORMAT_24;
   RTC_TimeStruct.DayLightSaving=RTC_DAYLIGHTSAVING_NONE;
   RTC_TimeStruct.StoreOperation=RTC_STOREOPERATION_RESET;
   #endif
   
   // At least STM32L4: has to be called before every 'set' access
   HAL_PWR_EnableBkUpAccess();
   int ret=HAL_RTC_SetTime( m_pHandler, &RTC_TimeStruct, RTC_FORMAT_BIN);
   
   #if defined ( STM32F103xx )
   syncDateWrite();
   #endif

   return( ret );
}

/// \brief  Adjust current time relatively to current time
void CRtc::setRelativeTime(int relHour, int relMin, int relSec)
{
   int hour, min, sec;

   getTime(hour, min, sec);

   hour= (hour + relHour) % 24;
   min = (min + relMin ) % 60;
   sec = (sec + relSec ) % 60;

   if(hour < 0)
      hour=24+hour;

   if(min < 0)
      min=60+min;

   if(sec < 0)
      sec=60+sec;

   //lHint << "Hour"
   setTime(hour, min, sec);

   return;
}

int CRtc::setDate( int16_t year, int8_t month, int8_t day)
{
   RTC_DateTypeDef RTC_DateStruct;

   RTC_DateStruct.Year=year-2000;
   RTC_DateStruct.Month=month;
   RTC_DateStruct.Date=day;
   RTC_DateStruct.WeekDay=1;     // Hopefully not used

   #ifdef STM32F1
   // STM32F1 has no 'valid'-register. We use a backup register to detect if
   // date is valid or invalid
   LL_RTC_BKP_SetRegister(BKP, RTC_BKP_DR1, 0xAFFE);
   static int val=LL_RTC_BKP_GetRegister(BKP, RTC_BKP_DR1);
   bAssert( val == 0xAFFE, val);
   #endif
   
   // At least STM32L4: has to be called before every 'set' access
   HAL_PWR_EnableBkUpAccess();
   int ret=HAL_RTC_SetDate( m_pHandler, &RTC_DateStruct, RTC_FORMAT_BIN);
   
   #if defined ( STM32F103xx )
   syncDateWrite();
   #endif

   return( ret );
}

uint32_t CRtc::getMSec()
{
    //uint8_t hour, min, sec, subsec;
    //uint32_t shift;
    RTC_TimeTypeDef ts;

    //getTime(hour, min, sec);
    //RTC_SupGetTime(&hour, &min, &sec, &subsec, &shift );
    //RTC_GetTime(&hour, &min, &sec, &subsec, &shift );
    HAL_RTC_GetTime(m_pHandler, &ts, RTC_FORMAT_BIN);
    
    #if defined ( STM32F103xx )
    syncDateWrite();
   #endif

    return( getMSec( ts.Hours, ts.Minutes, ts.Seconds
                 #if ! ( defined(STM32F1) || defined(STM32L1) )
                        , ts.SubSeconds
                 #else
                        , 0
                 #endif
                     ) );
}

uint32_t CRtc::getSec()
{
   RTC_TimeTypeDef sTime;

   HAL_RTC_GetTime( m_pHandler, &sTime, RTC_FORMAT_BIN);
   
   #if defined ( STM32F103xx )
   syncDateWrite();
   #endif

   #if !defined(STM32F1)
   (void)m_pHandler->Instance->DR;
   #endif

   return( getSec( sTime.Hours, sTime.Minutes, sTime.Seconds ) );
}

uint32_t CRtc::getSec( uint8_t hour, uint8_t min, uint8_t sec) /* static */
{
    return(
          (hour * SEC_PER_HOUR )
         +(min * SEC_PER_MIN)
         + sec
        );
};


time_t CRtc::getSecondsSinceEpoch( ) const
{
   int year, month, day, hour, minute, second, usecond;
   constexpr const short dayOffset[12]
      {0,31,59,90,120,151,181,212,243,273,304,334};
      
   getTime( hour, minute, second, usecond);
   getDate( year, month, day);
   
  int leapYear = ((year-1)-1968)/4
                  - ((year-1)-1900)/100
                  + ((year-1)-1600)/400;

  int daysSince1970 = (year-1970)*365 + leapYear
                      + dayOffset[month-1] + day-1;

   if ( (month>2) && (year%4==0 && (year%100!=0 || year%400==0)) )
   {
      daysSince1970 += 1;
   }
   
   return second + 60 * (time_t)( minute + 60 * (hour + ( 24 * daysSince1970 ) ) );
}


uint32_t CRtc::getMSec( uint8_t hour, uint8_t min, uint8_t sec, /* static */
                        uint8_t subsec )
{
    // Subsecond 0xff..00-inc second-0xff..0x00
    return( ( ( ( ( ( (
          hour
        * MIN_PER_HOUR ) + min )
        * SEC_PER_MIN ) + sec )
        * SUBSEC_PER_SEC ) + ( 0xFF - subsec ) )
        * MSEC_PER_SUBSEC
        );
}

bool CRtc::isValid()
{
   #if defined(STM32G4) || defined(STM32G0)
      return( m_pHandler->Instance->ICSR & RTC_ICSR_INITS);
   #elif !defined(STM32F1)
      return( m_pHandler->Instance->ISR & RTC_ISR_INITS );
   #else
      // STM32F1 does not have the valid register.
      // Better solution would be using an backup register but we just check
      // plausability of date at the moment.
      // If the year offset is less than 5 years (added to 2000) we assume date
      // is not valid.
      //RTC_DateTypeDef sDate;
      //HAL_RTC_GetDate(m_pHandler, &sDate, RTC_FORMAT_BIN);
      return(LL_RTC_BKP_GetRegister(BKP, RTC_BKP_DR1) == 0xAFFE);
      //return( sDate.Year > 5 ? true : false);
   #endif
}

uint32_t CRtc::waitSecondSwitch()
{
   uint32_t startSecond=getSec();
   uint32_t switchingSecond;

   while( (switchingSecond=getSec())==startSecond )
   {

   }
   return(switchingSecond);
}

#if defined ( STM32F103xx )

void CRtc::syncDateRead()
{
   uint32_t data=LL_RTC_BKP_GetRegister(BKP, RTC_BKP_DR2);
   uint32_t data2=LL_RTC_BKP_GetRegister(BKP, RTC_BKP_DR3);

   RTC_DateTypeDef date;
   date.Year = data & 0xFF;
   date.Month = ( data >> 8 ) & 0xFF;
   date.Date = data2 & 0xFF;

   HAL_RTC_SetDate(m_pHandler, &date, RTC_FORMAT_BIN);
}

void CRtc::syncDateWrite() const
{
   uint32_t data, data2;

   data= m_pHandler->DateToUpdate.Year |
         ( (uint32_t) m_pHandler->DateToUpdate.Month << 8);
   data2 = m_pHandler->DateToUpdate.Date;

   LL_RTC_BKP_SetRegister(BKP, RTC_BKP_DR2, data);
   LL_RTC_BKP_SetRegister(BKP, RTC_BKP_DR3, data2);

   data=LL_RTC_BKP_GetRegister(BKP, RTC_BKP_DR2);
}

#endif

void CRtc::setAlarm()
{
   RTC_AlarmTypeDef alarm;
   HAL_StatusTypeDef status;

   // return;

   //__HAL_RTC_ALARM_CLEAR_FLAG( m_pHandler, RTC_FLAG_ALRAF);
   //__HAL_RTC_ALARM_CLEAR_FLAG( m_pHandler, RTC_FLAG_ALRBF);

   status = HAL_RTC_GetTime(m_pHandler, &alarm.AlarmTime, RTC_FORMAT_BIN);
   lAssert( status == HAL_OK );
   lDebug("%d:%d:%d", alarm.AlarmTime.Hours, alarm.AlarmTime.Minutes, alarm.AlarmTime.Seconds);

   #if defined RTC_ALARMMASK_MINUTES
      alarm.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_DATE;
      alarm.AlarmDateWeekDay = RTC_WEEKDAY_MONDAY;
      // alarm.AlarmMask = RTC_ALARMMASK_SECONDS | RTC_ALARMMASK_MINUTES;
      alarm.AlarmMask = RTC_ALARMMASK_MINUTES;
      // alarm.AlarmMask = RTC_ALARMMASK_ALL;
   #endif

   alarm.Alarm = RTC_ALARM_A;
   alarm.AlarmTime.Seconds+=5;
   //alarm.AlarmTime.Minutes+=30;

   while( alarm.AlarmTime.Seconds>=60 )
   {
      alarm.AlarmTime.Minutes++;
      alarm.AlarmTime.Seconds-=60;
   }

   status = HAL_RTC_SetAlarm_IT(m_pHandler, &alarm, RTC_FORMAT_BIN);
   lAssert( status == HAL_OK );
   return;
}

void CRtc::interrupt()
{
   HAL_RTC_AlarmIRQHandler( m_pHandler );
}

IRQ_Start

IRQ_Funcion_imp( RTC_Alarm_IRQHandler )
{
   interruptRtc1->interrupt();
}

IRQ_End

void HAL_RTCEx_AlarmBEventCallback(RTC_HandleTypeDef *hrtc)
{
   // Foo;
}


//---fin-----------------------------------------------------------------------
