//----------------------------------------------------------------------------
///
/// \brief  Retain data
///
///         A defined data region is hold in e.g. flahs.
///         There are several storage areas (pages) to hold data redundant.
///         Currently the data area size must not exceed the page size of the
///         flash.
///
///         Each container has an counter. The oldest and the newest container
///         can be found by this counter. When a retain block is stored, its
///         stored in the oldest contianer.
///         When a retain block gets restored, the newest container is restored
///
///
/// \date   20160228
/// \author Maximilian Seesslen <mes@seesslen.net>
///
//----------------------------------------------------------------------------


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


//---Own------------------------------

// #define LEPTO_LOG_DEBUG 1

#include <biwak/retain.h>
#include <lepto/log.h>
#include <biwak/exception.h>     // bAssert
//#include <lepto/crc8.h>
#include <lepto/crc32.h>
#include <string.h>
#include <memory.h>
#include <stdio.h>
#include <stdint.h>
#include <lepto/print.h>


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


/// \brief  Le contructeur
///
CRetain::CRetain(CFlashX &_flash, int dataSize, int _containerCount/*=2*/ )
   :m_flash(_flash)
{
   m_pData=nullptr;
   m_dataSize=dataSize;
   m_pMirroredData=nullptr;
   m_versionMajor =
   m_versionMinor =
   m_counter=0;

   m_containerCount=_containerCount;

   if(m_dataSize)
   {
      acquireMemory();
   }
   
   return;
}

void CRetain::acquireMemory()
{
   lDebugAssert(!m_pMirroredData);

   #if BIWAK_MEMORY_MAPPED_FLASH
   if (!m_flash.isMemoryMapped())
   {
      m_pMirroredData=new char[m_dataSize];
   }
   #else
      m_pMirroredData=new char[m_dataSize];
   #endif

   #if 1
   int erasePageSize = m_flash.erasePageSize();
   int m_pages=m_dataSize / erasePageSize;
   while( ( m_totalSize = m_pages * erasePageSize ) < ( m_dataSize + (int)sizeof(header) ) )
   {
      m_pages++;
   }
   #endif

   for(int i1=0; i1<m_containerCount; i1++)
   {
      m_containerAddress[i1]=m_flash.acquireMemory( m_totalSize );
   }

   updateCycleTable();
}

void CRetain::setVersion(int versionMajor, int versionMinor)
{
   m_versionMajor=versionMajor;
   m_versionMinor=versionMinor;
   return;
}

int CRetain::getVersion() const
{
   return(m_versionMajor);
}

int CRetain::updateCycleTable()
{
   lDebug("Total size: %d ", m_totalSize);
   lDebug("Data size: %d", m_dataSize);
   //lDebug("pages: %d", m_pages);
   lDebug("header: %d", sizeof(header));

   for(int i1=0; i1<m_containerCount; i1++)
   {
      m_containerCounter[i1]=0;

      #if BIWAK_MEMORY_MAPPED_FLASH
      if( !m_flash.isMemoryMapped() )
      {
      #endif
         int sta=m_flash.readData(m_containerAddress[i1], m_pMirroredData, m_dataSize);
         if(sta<0)
         {
            lCritical("CNRF: %d", sta);
         }
      #if BIWAK_MEMORY_MAPPED_FLASH
      }
      else
      {
         m_pMirroredData=(void *)m_containerAddress[i1];
      }
      #endif
      m_flash.readData( ( m_containerAddress[i1]+m_totalSize)-sizeof(header)
                      , &header, sizeof(header) );
      lDebug( "Check container : 0x%x@0x%X: ", i1, m_containerAddress[i1]);
      
      if(header.versionMajor!=m_versionMajor)
      {
         lDebug( "wrong version: is %d vs. sh %d", header.versionMajor, m_versionMajor );
         continue;
      }
      
      crc32_t crc32=crc32Update( crc32Init(), m_pMirroredData, m_dataSize);
      crc32=crc32Update(crc32, &header, sizeof(header)-sizeof(header.dataCrc32) );
      crc32=crc32Finalize(crc32);
      if( crc32 != header.dataCrc32 )
      {
         lDebug ( LDS( "WCRC; cl %X vs st %X", "Wrong CRC; calculated %X vs. stored %X" )
                , crc32, header.dataCrc32 );
         // hexDump( m_pMirroredData, m_dataSize);
         // hexDump( &header, sizeof(header));
         continue;
      }
      else
      {
         m_containerCounter[i1]=header.counter;
         if( ! m_containerCounter[i1] )
         {
            lWarning( LDS( "VCZC", "Container counter is 0" ) );
         }
         lDebug ( "CRC correct; counter: %d", header.counter);
      }
   }
   return(0);
}


CRetain::~CRetain()
{
   // Unlikely to get destructed. But may happen when single function checks
   // eeprom values from eeprom.
}


/// \brief  initializes header for the first time
void CRetain::initHeader()
{
   // data has to be set!
   bAssert( m_dataSize && m_versionMajor, 1 );

   header.magic='R';
   header.versionMajor=m_versionMajor;
   header.versionMinor=m_versionMinor;
   header.counter=m_counter;
   header.len=m_dataSize;
   header.dataCrc32=0;

   return;
}


/// \brief  Updates the values of the header
///
///         CRC82 is calculated.
void CRetain::updateHeader()
{
   // Check if header is valid
   lDebugAssert( m_versionMajor == header.versionMajor );
   
   header.dataCrc32=crc32Update(crc32Init(), m_pData, m_dataSize);
   header.dataCrc32=crc32Update(header.dataCrc32,&header, sizeof(header)-sizeof(header.dataCrc32));
   header.dataCrc32=crc32Finalize(header.dataCrc32);
   lDebug ( LDS("UCRC: 0x%X", "Updated CRC: 0x%X"), header.dataCrc32 );
   return;
}


/// \brief  Specify how the retain memory is mapped to aplpications ram
///
///         Application should define some retain structure (word aligned),
///         instanciate it and use setData() to tell biwak where it is located.
///         Whenever the layout of the structure changes you should increase the
///         layout version to let biwak know that the old data is not valid any
///         more.
void CRetain::setData(void *data, int dataSize, int layoutVersionMajor, int layoutVersionMinor)
{
   lDebugAssert(!m_dataSize);

   m_pData=data;
   m_dataSize=dataSize;
   m_versionMajor=layoutVersionMajor;
   m_versionMinor=layoutVersionMinor;

   acquireMemory();

   return;
}


/// \brief  Restore data from Flash to Ram
///
///         All Containers are checked and the one with hioghes store counter
///         wins (if Crc32c and id are correct of course).
///
/// \return Number of region or <0 if not successfull
int CRetain::restore()
{
   int container=findNewest();
   int sta;

   if(container<0)
   {
      // could not find container
      lWarning ( LDS("CNFC", "Could not find newest container" ) );
      return(container);
   }

   sta = restore(container);
   if(sta)
   {
      lDebug ( "Could not restore container: %d", sta );
      return(-sta);
   }
   else
   {
      lDebug ( "Restored container %d", container );
   }

   return(container);
}

/// \brief  Restore an concrete container
int CRetain::restore(int container)
{
   bAssert( ( container >= 0 ) && ( container < 2 ), 1 );
   int sta=m_flash.readData(m_containerAddress[container]+m_totalSize-sizeof(header)
                   , &header, sizeof(header) );

   bAssert( sta==0, 1 );
   bAssert( m_versionMajor == header.versionMajor, 1 );

   m_versionMajor=header.versionMajor;
   m_versionMinor=header.versionMinor;
   m_counter=header.counter;
   m_dataSize=header.len;
   sta = m_flash.readData( m_containerAddress[container], m_pData, m_dataSize );

   return(sta);
}

#if 0
   /*
   currentContainer=-1;
   SRetainHeader *retainHeader;

   lAssert(m_dataSize!=0);

   // Iterate all containers and check for the most recent one
   for(int i1=0; i1<containerCount; i1++)
   {
      //const char *data="Hello World";
      //uint32_t crc32=calcCrc32(0, data, strlen(data));
      //retainHeader=containerAddress[i1];
      if( retainHeader->id != RETAIN_HEADER_ID )
      {
         lWarn << "#" << i1 << ":ID wrong(" << retainHeader->id << ")";
         continue;
      }
      if( ( retainHeader->dataSize != m_dataSize )
       || ( retainHeader->dataLayout != dataLayout ) )
      {
         lWarn << "#" << i1 << ":size/layout wrong(" << retainHeader->dataSize << "/" << m_dataSize << ")";
         continue;
      }
      if( retainHeader->dataCrc32 != calcCrc32( 0, retainHeader->dataPtr, retainHeader->dataSize) )
      {
         lWarn << "#" << i1 << ":CRC wrong(" << retainHeader->dataCrc32 << ")";
         continue;
      }
      if( retainHeader->storeCounter >= header.storeCounter)
      {
         memcpy( &header, retainHeader, sizeof(header));
         currentContainer=i1;
         header.restoreCounter++;
      }
   }

   if(currentContainer>=0)
   {
      //retainHeader=containerAddress[ currentContainer ];
      memcpy( m_pData, retainHeader->dataPtr, retainHeader->dataSize );
   }
   */

   return(0);
}

#endif


int CRetain::findNewest()
{
   int container=0;
   uint32_t counter=m_containerCounter[0];

   for(int i1=1; i1<m_containerCount; i1++)
   {
      /*
      if(!m_containerCounter[i1])
      {
         continue;
      }
      */
      if( isNewer(m_containerCounter[i1], counter) )
      {
         counter=m_containerCounter[i1];
         container=i1;
      }
   }

   // Assmue zero is not a valid counter
   if(!counter)
   {
      container=ERetainError::noValidCounter;
   }

   return(container);
}

/// \brief  find the oldest container that can be overwritten
///
///         An invalid container is always older than any other.
int CRetain::findOldest()
{
   int container=0;
   uint32_t counter=m_containerCounter[0];

   for(int i1=1; i1<m_containerCount; i1++)
   {
      if( isOlder(m_containerCounter[i1], counter ) )
      {
         counter=m_containerCounter[i1];
         container=i1;
      }
   }

   return(container);
}

/// \brief  Store data from Ram to Flash
///
/// \return Number of region or <0 if not successfull
int CRetain::store()
{
   int sta=0;

   // Just store Container in the "next"; if there is no current container
   // (e.g. could not restore any container) start with 0.
   int newContainer=findOldest();

   bAssert(newContainer>=0, 1);

   m_counter++;
   // Avoid 0-counter which means 'invalid'
   if( !m_counter )
   {
      m_counter++;
   }

   initHeader();
   updateHeader();
   m_containerCounter[newContainer]=m_counter;

   // On flashes that dont need to be erased the page size will probably smaller
   // then the retain data size.
   lDebug( "Storing flash at %d/%d", newContainer, m_containerAddress[newContainer]);

   #if ! defined (BIWAK_I2C_RETAIN_ONLY)
   m_flash.erasePage( m_containerAddress[newContainer] );
   #endif
   
   sta=m_flash.writeData( m_containerAddress[newContainer] , m_pData, m_dataSize );
   if(!sta)
   {
      sta=m_flash.writeData(
               ( m_containerAddress[newContainer]+m_totalSize)-sizeof(header)
                              , (char *)&header, sizeof(header) );
   }

#if 0
   char *ptr=(char *)( ( (uint32_t)containerAddress[newContainer]+m_totalSize)-sizeof(header) );
   for(int i1=0; i1<sizeof(header); i1++)
   {
      printf("[%02X] ", ptr[i1]);CACHE STRING "tcb3" FORCE
   }
#endif

   if(sta)
   {
      lCritical ("STA: %d", sta);
   }

   return(sta?-1:newContainer);
}

/// \brief  Destroy the headers of all containers on flash
int CRetain::destroyAll()
{
   int sta;

   memset((void *)&header, 0, sizeof(header));
   for(int i1=0; i1<m_containerCount; i1++)
   {
      sta=m_flash.writeData( ( m_containerAddress[i1]+m_totalSize)-sizeof(header) , (char *)&header, sizeof(header) );
      if(sta)
      {
         lCritical("STA: %d", sta);
         return(sta);
      }
   }
   return(0);
}

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