//----------------------------------------------------------------------------
///
/// \brief support for I2C-RTC MCP7940N
///
/// https://www.mouser.de/datasheet/2/268/25010A-71550.pdf
///
/// \author Maximilian Seesslen
///
//----------------------------------------------------------------------------


#include <fosh/command.h>
#include <stdio.h>
#include <time.h>       // time.h, localtime, strftime
#include <string.h>     // strncpy
#include <sys/time.h>   // settimeofday
#include <stdlib.h>      // atoi

#ifdef STM32
   //#include <arena/platform.h>
   #include <biwak/rtc.hpp>
   #include <biwak/i2c_slave.h>
#endif

#include <biwak/i2c_slave.h>
#include <biwak/rtc.hpp>

#include <lepto/log.h>


const char REG_RTCSEC     = 0x00;  // Register Address: Time Second
const char REG_RTCMIN     = 0x01;  // Register Address: Time Minute
const char REG_RTCHOUR    = 0x02;  // Register Address: Time Hour
const char REG_RTCWKDAY   = 0x03;  // Register Address: Date Day of Week
const char REG_RTCDATE    = 0x04;  // Register Address: Date Day
const char REG_RTCMTH     = 0x05;  // Register Address: Date Month
const char REG_RTCYEAR    = 0x06;  // Register Address: Date Year
const char REG_CONTROL    = 0x07;  // Register Address: RTC Feature Control
const char REG_OSCTRIM    = 0x08;  // Register Address: Oscillator Digital Trim
#if 0 // unuded yet
const char REG_PWRDNMIN   = 0x18;  // Register Address: Power Failure Time Minute
const char REG_PWRDNHR    = 0x19;  // Register Address: Power Failure Time Hour
const char REG_PWRUPMIN   = 0x1C;  // Register Address: Power Restore Time Minute
const char REG_PWRUPHR    = 0x1D;  // Register Address: Power Restore Time Hour
#endif

const char REG_YEARMSK    = 0xFF;
const char REG_MONTHMSK   = 0x1F;
const char REG_DAYMSK     = 0x3F;
const char REG_SECMSK     = 0x7F;
const char REG_MINMSK     = 0x7F;
const char REG_HOURMSK    = 0x3F;

// 20211205-125400

class CCommandHwclock: public CCommand
{
   private:
      CI2cSlave &m_slave;
      CRtc &m_rtc;

   public:
      CCommandHwclock(const char *_name, CI2cSlave &slave, CRtc &rtc)
         :CCommand( _name, "hwclock (-w|-r): store/restore time and date from/to i2c rtc" )
         ,m_slave(slave)
         ,m_rtc(rtc)
      {
         char start;
         int sta;
         sta=m_slave.i2cReadData(0, &start, 1);
         lAssert( sta == 0 );

         if( ! (start&0x80) )
         {
            lWarning("RTC was stopped");
         }
         else
         {
            hwToRtc();
         }
      }
      void enable() const
      {
         char    trimVal       = 0x45;    // The amount of clock cycles to add/subtract.  See datasheet
         char    twelveHour    = 0x00;    // 0 = 24 Hour Clock Mode     / 1 = 12 Hour Clock Mode
         char    crsTrim       = 0x00;    // 0 = Disable Course Trim    / 1 = Enable Course Trim
         char    batEnable     = 0x01;    // 0 = Disable Battery Backup / 1 = Enable Battery Backup
         char    startClock    = 0x01;    // 0 = Start Oscillator       / 1 = Stop Oscillator
         char zero=0x00;
         int sta;

         sta=m_slave.i2cWriteData(REG_CONTROL, &zero, 1);       // Ensure the Control register starts as 0x00
         lAssert( sta == 0 );

         sta=m_slave.i2cWriteData(REG_OSCTRIM, &trimVal, 1);    // Clock Cycle Trim Value - see datasheet
         lAssert( sta == 0 );

         modifyBit(REG_RTCHOUR,  twelveHour,  6);          // 12 Hour Clock:      0 = NO / 1 = YES
         modifyBit(REG_CONTROL,  crsTrim,     2);          // Course Trim Enable: 0 = NO / 1 = YES
         modifyBit(REG_RTCWKDAY, batEnable,   3);          // Battery Enable:     0 = NO / 1 = YES
         modifyBit(REG_RTCSEC,   startClock,  7);          // Start Oscillator:   0 = NO / 1 = YES
      }
      virtual int exec(int argc, const char *argv[]) const;
      void modifyBit(int reg, char value, char bit) const
      {
         char data;
         int sta;
         sta=m_slave.i2cReadData(reg, &data, 1);
         lAssert( sta == 0 );
         if(value)
         {
            data|=(1<<bit);
         }else
         {
            data&=~(1<<bit);
         }
         sta=m_slave.i2cWriteData(reg,  &data, 1);
         lAssert( sta == 0 );
      }
      int getDate() const;
      static int bcd2dec(int val)
      {
         return( ( ( val >> 4) * 10) + (val & 0xF) );
      }
      static int dec2bcd(int val)
      {
         return( ( ( val / 10 ) << 4) + ( val % 10 ) );
      }

      int getTime(int &hour, int &minute, int &sec) const;
      int getDate(int &year, int &month, int &day) const;
      int setTime(int hour, int minute, int sec) const;
      int setDate(int year, int month, int day) const;
      void hwToRtc() const;
      void rtcToHw() const;
      int dump() const;
};


int CCommandHwclock::exec(int argc, const char *argv[]) const /* virtual */
{

   if(argc>1)
   {
      if(! strcmp(argv[1], "-w") )
      {
         rtcToHw();
      }
      else
      if(! strcmp(argv[1], "-r") )
      {
         hwToRtc();
      }
      else
      {
         printf("Syntax error!");
      }
   }

   getDate();

   return( 0 );
}

int CCommandHwclock::getDate(int &year, int &month, int &day) const
{
   int val;

   m_slave.i2cReadData(REG_RTCYEAR, &val, 1);
   year=bcd2dec( val& 0xFF ) + 2000;

   m_slave.i2cReadData(REG_RTCMTH, &val, 1);
   month=bcd2dec( val& 0x1F );

   m_slave.i2cReadData(REG_RTCDATE, &val, 1);
   day=bcd2dec( val& 0x3F );

   return(0);
}

int CCommandHwclock::getTime(int &hour, int &minute, int &sec) const
{
   int val;

   m_slave.i2cReadData(REG_RTCSEC, &val, 1);
   sec=bcd2dec( val& 0x7F );

   m_slave.i2cReadData(REG_RTCMIN, &val, 1);
   minute=bcd2dec( val& 0x7F );

   m_slave.i2cReadData(REG_RTCHOUR, &val, 1);
   hour=bcd2dec( val& 0x3F );

   return(0);
}

int CCommandHwclock::setDate(int year, int month, int day) const
{
   int val;

   m_slave.i2cReadData(REG_RTCYEAR, &val, 1);
   val&=~REG_YEARMSK;
   val|=dec2bcd(year-2000);
   m_slave.i2cWriteData(REG_RTCYEAR, &val, 1);

   m_slave.i2cReadData(REG_RTCMTH, &val, 1);
   val&=~REG_MONTHMSK;
   val|=dec2bcd(month);
   m_slave.i2cWriteData(REG_RTCMTH, &val, 1);

   m_slave.i2cReadData(REG_RTCDATE, &val, 1);
   val&=~REG_DAYMSK;
   val|=dec2bcd(day);
   m_slave.i2cWriteData(REG_RTCDATE, &val, 1);
   enable();

   return(0);
}

int CCommandHwclock::setTime(int hour, int minute, int sec) const
{
   int val;

   m_slave.i2cReadData(REG_RTCSEC, &val, 1);
   val&=~REG_SECMSK;
   val|=dec2bcd(sec);
   m_slave.i2cWriteData(REG_RTCSEC, &val, 1);

   m_slave.i2cReadData(REG_RTCMIN, &val, 1);
   val&=~REG_MINMSK;
   val|=dec2bcd(minute);
   m_slave.i2cWriteData(REG_RTCMIN, &val, 1);

   m_slave.i2cReadData(REG_RTCHOUR, &val, 1);
   val&=~REG_HOURMSK;
   val|=dec2bcd(hour);
   m_slave.i2cWriteData(REG_RTCHOUR, &val, 1);
   enable();

   return(0);
}

int CCommandHwclock::dump() const
{
   char seconds;
   int sta;

   for(int i1=0; i1<0xA; i1++)
   {
      sta=m_slave.i2cReadData(i1, &seconds, 1);
      printf("data 0x%02X: 0x%02X (%d)\n", i1, seconds, sta);
   }
   return(0);
}

int CCommandHwclock::getDate() const
{
   int val;

   m_slave.i2cReadData(REG_RTCSEC, &val, 1);
   int sec=bcd2dec( val& 0x7F );
   m_slave.i2cReadData(REG_RTCMIN, &val, 1);
   int min=bcd2dec( val& 0x7F );
   m_slave.i2cReadData(REG_RTCHOUR, &val, 1);
   int hour=bcd2dec( val& 0x3F );

   m_slave.i2cReadData(REG_RTCYEAR, &val, 1);
   int year=bcd2dec( val& 0xFF ) + 2000;
   m_slave.i2cReadData(REG_RTCMTH, &val, 1);
   int month=bcd2dec( val& 0x1F );
   m_slave.i2cReadData(REG_RTCDATE, &val, 1);
   int day=bcd2dec( val& 0x3F );

   printf("Time: %d:%d:%d\n", hour, min, sec);
   printf("Date: %d.%d.%d\n", day, month, year);

   return(0);
}

void CCommandHwclock::hwToRtc() const
{
   int year, month, day;
   int hour, min, sec;
   getDate(year, month, day);
   getTime(hour, min, sec);
   m_rtc.setDate(year, month, day);
   m_rtc.setTime(hour, min, sec);
}

void CCommandHwclock::rtcToHw() const
{
   int year, month, day;
   int hour, min, sec;
   m_rtc.getDate(year, month, day);
   m_rtc.getTime(hour, min, sec);
   setDate(year, month, day);
   setTime(hour, min, sec);
}

const CCommand *createInstance_hwclock(CI2cSlave &slave, CRtc &rtc)
{
   return( new CCommandHwclock("hwclock", slave, rtc) );
}


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