//-----------------------------------------------------------------------------
///
/// \brief  Functions for overloading libc time functions
/// 
///         The implementation for time functions like localtime() have quite a
///         big footprint (binary size). Its checking environment variables to 
///         determine timezone. This is just not available on stm32 biwak 
///         devices.
///
/// \date   20241114
/// \author Maximilian Seesslen <development@seesslen.net>
///
//-----------------------------------------------------------------------------


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


#include <biwak/rtc.hpp>
#include <lepto/time.h>       // macros
#include <sys/time.h>         // timezone
#include <stdio.h>            // printf


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


#if 0
   #define DUMPTIME(s)  printf("%s: Input: %d.%d.%d %d:%d:%d\n", s \
          , t->tm_year+1900, t->tm_mon+1, t->tm_mday \
          , t->tm_hour, t->tm_min, t->tm_sec);
#else
   #define DUMPTIME(s)
#endif

struct timezone tz{
   .tz_minuteswest= MIN_PER_HOUR * 1,
   .tz_dsttime=1,
};

int dayOfWeek (int y, int m, int d)
{
   return (d+=m<3?y--:y-2,23*m/9+d+4+y/4-y/100+y/400)%7;
}


// GCC 10 does not want it to be a constexpr
int dateCode(uint8_t day, uint8_t hour, uint8_t min, uint8_t sec)
{
   return( ((((((day << 8) | hour ) << 8 ) | min ) << 8 ) | sec ) );
   //return( (day << 24) | ( hour << 16 ) | ( min << 8 ) | ( sec ) );
}

// GCC 10 does not want it to be a constexpr
int dayOfMonth(char month) /* 0..11 */
{
   #if 0
      const char dom[]{  31,28,31,30,31,30,31, // Jul
                  31,30,31,30,31,       // Dez.
                };
      return( dom[month%13] ); // Overlap can happen in normalization
   #else
      // saves some bytes
      if(month>=12)
         month-=12;
      if(month>=7)
         month-=7;
      return(31-(month%2));
   #endif
}


// GCC 10 does not want it to be a constexpr
void normalize(struct tm* t)
{
   DUMPTIME("Normalize");
   
   if( t->tm_hour>=24 )
   {
      t->tm_hour-=24;
      t->tm_mday++;
   }
   if( t->tm_mday>dayOfMonth(t->tm_mon) )
   {
      t->tm_mday-=dayOfMonth(t->tm_mon);
      t->tm_mon++;
   }
   if(t->tm_mon+1>12)
   {
      t->tm_mon-=12;
      t->tm_year++;
   }
   DUMPTIME("Normalize out");
   return;
}


void utc2local(struct tm* t)
{
   t->tm_isdst=0;
   int lastSundayMarch=31-dayOfWeek( t->tm_year + 1900, 3, 31);
   int lastSundayOct=31-dayOfWeek( t->tm_year + 1900, 10, 31);
   
   if( ( t->tm_mon + 1  > 3 ) && ( t->tm_mon + 1 < 10 ) )
   {
      t->tm_isdst=1;
   }
   else
   {
      // tm_mon is [0...11]
      if( t->tm_mon + 1  == 3 )
      {
         if( dateCode( t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec ) >=
             dateCode( lastSundayMarch, 1, 0, 0 )
             )
         {
            t->tm_isdst=1;
         }
      }
      // tm_mon is [0...11]
      // MESZ switches from 3:00 to 2:00 MEZ. So MESZ ends at 2:00 MEZ, 1:00 UTC
      if( t->tm_mon + 1 == 10 )
      {
         if( dateCode( t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec ) <
             dateCode( lastSundayOct, 1, 0, 0 )
             )
         {
            t->tm_isdst=1;
         }
      }
   }
   DUMPTIME("Localtime");
   t->tm_hour += ( tz.tz_minuteswest / MIN_PER_HOUR ) + ( t->tm_isdst ? 1 : 0);

   normalize(t);
   return;
}


// Saves 156 bytes versus picolibc implementation in release build, origin implementation
// Saves 316 bytes when converting time_t to uint32_t.
// Saves 288 bytes with offset loop
struct tm *bGmtime (const time_t *__timer)
{
   static struct tm t;
   const uint32_t SEKUNDEN_PRO_TAG   =  86400ul; /*  24* 60 * 60 */
   const uint32_t TAGE_IM_GEMEINJAHR =    365ul; /* kein Schaltjahr */
   uint32_t TagN=0;
   
   // Offset loop
   while( (*__timer) - ( (uint64_t)TagN * SEKUNDEN_PRO_TAG) > ( 0x7FFFFFFF - ( 12 * SEKUNDEN_PRO_TAG ) ) )
   {
      // in32_t has a range of ca. 68 ignoring leap years.
      TagN+= ( TAGE_IM_GEMEINJAHR * 60 );
   }
   
   uint32_t _timer=( (*__timer) - (TagN*SEKUNDEN_PRO_TAG) );
   //_timer += tz.tz_minuteswest * SEC_PER_MIN;
   
   const uint32_t TAGE_IN_4_JAHREN   =   1461ul; /*   4*365 +   1 */
   const uint32_t TAGE_IN_100_JAHREN =  36524ul; /* 100*365 +  25 - 1 */
   const uint32_t TAGE_IN_400_JAHREN = 146097ul; /* 400*365 + 100 - 4 + 1 */
   const uint32_t TAGN_AD_1970_01_01 = 719468ul; /* Tagnummer bezogen auf den 1. Maerz des Jahres "Null" */
   
   TagN += ( _timer / SEKUNDEN_PRO_TAG);
   TagN+=TAGN_AD_1970_01_01;
   unsigned long int Sekunden_seit_Mitternacht = _timer % SEKUNDEN_PRO_TAG;
   unsigned long int temp;
   
   /* Schaltjahrregel des Gregorianischen Kalenders:
       Jedes durch 100 teilbare Jahr ist kein Schaltjahr, es sei denn, es ist durch 400 teilbar. */
   temp = 4 * (TagN + TAGE_IN_100_JAHREN + 1) / TAGE_IN_400_JAHREN - 1;
   t.tm_year = 100 * temp;
   TagN -= TAGE_IN_100_JAHREN * temp + temp / 4;
   
   /* Schaltjahrregel des Julianischen Kalenders:
       Jedes durch 4 teilbare Jahr ist ein Schaltjahr. */
   temp = 4 * (TagN + TAGE_IM_GEMEINJAHR + 1) / TAGE_IN_4_JAHREN - 1;
   t.tm_year += temp;
   TagN -= TAGE_IM_GEMEINJAHR * temp + temp / 4;
   
   /* TagN enthaelt jetzt nur noch die Tage des errechneten Jahres bezogen auf den 1. Maerz. */
   t.tm_mon = (5 * TagN + 2) / 153;
   t.tm_mday = TagN - (t.tm_mon * 153 + 2) / 5 + 1;
   /*  153 = 31+30+31+30+31 Tage fuer die 5 Monate von Maerz bis Juli
        153 = 31+30+31+30+31 Tage fuer die 5 Monate von August bis Dezember
              31+28          Tage fuer Januar und Februar (siehe unten)
        +2: Justierung der Rundung
        +1: Der erste Tag im Monat ist 1 (und nicht 0).
    */
   
   t.tm_mon += 3; /* vom Jahr, das am 1. Maerz beginnt auf unser normales Jahr umrechnen: */
   
   t.tm_hour  = Sekunden_seit_Mitternacht / 3600;
   t.tm_min  = Sekunden_seit_Mitternacht % 3600 / 60;
   t.tm_sec = Sekunden_seit_Mitternacht        % 60;
   
   t.tm_year -= 1900;
   t.tm_mon -= 1;
   
   normalize(&t);
   
   return(&t);
}


struct tm *bLocaltime (const time_t *_timer)
{
   struct tm* t = bGmtime( _timer );
   utc2local(t);
   return(t);
}


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