#include "archinc.h"
#include "cli.h"
#include "strutil.h"
#include "ee24lc256.h"
#include "crc16.h"
#include "nvstore.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef CONFIG_INCLUDE_NVSTORE

#define NVSTORE_NAME_LEN (14)

//
// the config
//

typedef struct __attribute__ ((__packed__)) {
  unsigned char signature; // 0xcf to identify config structure
  unsigned char version;
  unsigned short firstItemPage;
  unsigned short numItemPages;
  unsigned short firstDataPage;
  unsigned short numDataPages;
  unsigned short configCrc16;
  } nvStoreConfig_t;

//
// the 'item' data structure tracks where the nv data is stored, 
// helps to implement the namespace, stores CRC data
//

typedef struct __attribute__ ((__packed__)) {
  unsigned char signature; // to identify 'item'
  char name[NVSTORE_NAME_LEN];
  unsigned short firstDataPage;
  unsigned short dataLen;
  unsigned short dataCrc16; // CRC16 of data
  unsigned short itemCrc16; // CRC16 of item
  } nvStoreItem_t;

//
// the 'data' header ensures that the signature byte is written
// and gives us a 'next' pointer so that data items can span
// more than one data page in the future
//
// 4-byte data header allows for 60 bytes of data per page
//

typedef struct __attribute__ ((__packed__)) {
  unsigned char signature; // 0xda to identify 'data'
  unsigned char filler;
  unsigned short nextDataPage; // one data page per item for now
  } nvStoreData_t;


// 
// data is stored as a byte array beginning at the offset and ending with 
// a two byte (little-endian) CRC16 of the data
//

#define NVSTORE_ATTRIB_INUSE (0x01)

#define NVSTORE_CFG_SIGNATURE  (0xCF)
#define NVSTORE_ITEM_SIGNATURE (0xA5)
#define NVSTORE_DATA_SIGNATURE (0xDA)

static unsigned int nvTotalNumPages;
static unsigned int pageSize;

static nvStoreConfig_t currentConfig;
static boolean nvstoreConfigured;

static int nvinitHandler(int argc, char * argv[], int flags);
static int nvshowHandler(int argc, char * argv[], int flags);
static int nvwriteHandler(int argc, char * argv[], int flags);
static int nvreadHandler(int argc, char * argv[], int flags);

static unsigned char pageCache[64];

static void clearPageCache(void)
{
  int i;
  for(i=0;i<64;i++)
    pageCache[i] = 0x00;
}

// read an NV item entry from NVRAM into the page cache
static int getNvItemEntry(int itemIndex)
{
  int i;
  int offset;
  int retVal;
  unsigned char *pEntry;
  unsigned char readSig;

  if (nvstoreConfigured == false)
    return -1;

  if (itemIndex < 1 || itemIndex > (currentConfig.numItemPages + 1))
    return -1;

  retVal = ee24lc256ReadByte(EEPAGE_TO_ADDR(itemIndex), &readSig);

  if ((retVal == -1) || (readSig != NVSTORE_ITEM_SIGNATURE))
    return -1;

  clearPageCache();
  return ee24lc256ReadPage((unsigned short)itemIndex, sizeof(nvStoreItem_t) , pageCache);
}


static int findNvItemEntry(char *pName)
{
  int i;
  nvStoreItem_t *pItem;
  int retVal = -1;
  unsigned short readCrc16;
  unsigned char readSig;

  if (nvstoreConfigured == false)
    return -1;

  for(i = 0; (i < currentConfig.numItemPages); i++) {
    retVal = ee24lc256ReadByte(EEPAGE_TO_ADDR(currentConfig.firstItemPage + i), &readSig);
    if (retVal == -1) {
      break;
      }
    if (readSig == NVSTORE_ITEM_SIGNATURE) {
      // read the item
      clearPageCache();
      retVal = ee24lc256ReadPage((unsigned short)(currentConfig.firstItemPage + i), 
                                   sizeof(nvStoreItem_t), pageCache);
      if (retVal == -1) {
        // Couldn't read the config page
        return -1;
        }

      // verify CRC16, excluding it in the calculation
      readCrc16 =  crc16Calc((char *)pageCache, sizeof(nvStoreItem_t) - 
                             sizeof(unsigned short));

      pItem = (nvStoreItem_t *)pageCache;

      if (readCrc16 == pItem->itemCrc16) {
        if (strutil_strncasecmp(pName, pItem->name, NVSTORE_NAME_LEN) == 0) {
          // we found it!
          retVal = (currentConfig.firstItemPage + i);
          break;
        }
      else {
        // hmmm, CRC16 failed on an item -- mark it as bad somehow??
        continue;
        }
      }
    }
  else {
    break; // stop reading once we encounter a non-signed item
    }
  } // for 

  return retVal;
}

// returns first free item entry
static int findFreeNvItemEntry(void)
{
  int i;
  int retVal = -1;
  unsigned char readSig;

  if (nvstoreConfigured == false)
    return -1;

  for(i = 0; (i < currentConfig.numItemPages); i++) {
    retVal = ee24lc256ReadByte(EEPAGE_TO_ADDR(currentConfig.firstItemPage + i), &readSig);
    if (retVal == -1) {
      break;
      }
    if (readSig != NVSTORE_ITEM_SIGNATURE) {
      retVal = (currentConfig.firstItemPage + i);
      break;
      }
    }

  return retVal;
}

static int findFreeNvDataEntry(void)
{
  int i;
  int retVal = -1;
  unsigned char readSig;

  if (nvstoreConfigured == false)
    return -1;

  for(i = 0; (i < currentConfig.numDataPages); i++) {
    retVal = ee24lc256ReadByte(EEPAGE_TO_ADDR(currentConfig.firstDataPage + i), &readSig);
    if (retVal == -1) {
      break;
      }
    if (readSig != NVSTORE_DATA_SIGNATURE) {
      retVal = (currentConfig.firstDataPage + i);
      break;
      }
    }

  return retVal;
}

int nvstoreInit(void)
{
  int retVal = -1;
  unsigned char readSig;
  unsigned short readCrc16;

  // always make the init command available
  cliRegisterCommand("nvinit", nvinitHandler, "nvinit - [numItems] initialize nv store");

  /*
   * Read the config page -- if it's valid, get the info from 
   * there, else use info passed in to create a config page
   */

  retVal = ee24lc256ReadByte(EEPAGE_TO_ADDR(0), &readSig);

  // can't read the eeprom
  if (retVal == -1)
    return -1;

  if (readSig == NVSTORE_CFG_SIGNATURE) {
    // read the entire config page
    retVal = ee24lc256ReadPage((unsigned short)0, 
                               sizeof(nvStoreConfig_t),
                               (unsigned char *)(&currentConfig)
                               );
    if (retVal == -1) {
      // Couldn't read the config page
      return -2;
      }

    // verify CRC16, excluding it in the calculation
    readCrc16 =  crc16Calc((char *)(&currentConfig), (sizeof(nvStoreConfig_t) - 
                           sizeof(unsigned short)));

    if (readCrc16 == currentConfig.configCrc16) {
      // CRC matched -- we're good to go
      nvstoreConfigured = true;
      retVal = 0;
      }
    else {
      // do something else like write a new config??
      return -3;
      }
    } // config signature valid

  cliRegisterCommand("nvshow", nvshowHandler, "nvshow - show an nv item by name");
  cliRegisterCommand("nvwrite", nvwriteHandler, "nvwrite [name] [string] write to nv");
  cliRegisterCommand("nvread", nvreadHandler, "nvread [name] read from nv");

  return retVal;
}

// returns 0 if an item exists, -1 otherwise
int nvstoreLookup(char *pName)
{
  return ((findNvItemEntry(pName) == -1) ? -1 : 0);
}

int nvstoreWrite(char * pName, unsigned char *pWriteData, unsigned char length)
{
  int itemEntry, dataEntry, i;
  nvStoreData_t * pDataEntry;
  nvStoreItem_t * pItemEntry;
  unsigned char * pCache;
  unsigned short dataCrc16;
  int retVal = -1;

  if (nvstoreConfigured == false)
    return -1;

  if (length > (64 - sizeof(nvStoreData_t)))
    return -2;

  // first, try to find the item
  if ((itemEntry = findNvItemEntry(pName)) > 0) {
    clearPageCache();
    pItemEntry = (nvStoreItem_t *)pageCache;
    retVal = ee24lc256ReadPage((unsigned short)itemEntry, 
                               sizeof(nvStoreItem_t),
                               pageCache
                               );
    // verify item page CRC
    dataCrc16 = crc16Calc((char *)pageCache, 
                          (sizeof(nvStoreItem_t) - sizeof(unsigned short)) 
                          );

    if (dataCrc16 != pItemEntry->itemCrc16)
      return -3;

    // get the data page
    dataEntry = pItemEntry->firstDataPage;

    clearPageCache();

    // modify the data page
    pDataEntry = (nvStoreData_t *)(pageCache);
    pDataEntry->signature = NVSTORE_DATA_SIGNATURE;
    pDataEntry->nextDataPage = 0;

    // populate the updated data portion in cache
    pCache = (unsigned char *)(pageCache + sizeof(nvStoreData_t));
    for (i=0; i<length; i++) {
      *pCache++ = *pWriteData++;
      }

    // calculate CRC16 on data for new data entry
    dataCrc16 = crc16Calc((char *)pageCache, (length + sizeof(nvStoreData_t)));

    // write the entire page to eeprom
    retVal = ee24lc256WritePage((unsigned short)dataEntry, 
                                (length + sizeof(nvStoreData_t)),
                                pageCache);

    if (retVal == -1) {
      return -4;
      }

    // read back the item entry
    clearPageCache();
    retVal = ee24lc256ReadPage((unsigned short)itemEntry, 
                               sizeof(nvStoreItem_t),
                               pageCache
                               );
    if (retVal == -1) {
      return -5;
      }

    pItemEntry = (nvStoreItem_t *)pageCache;
    // update the length, data CRC, and recalc the item CRC
    pItemEntry->dataLen = length;
    pItemEntry->dataCrc16 = dataCrc16;
    pItemEntry->itemCrc16 = crc16Calc((char *)pageCache, 
                                      (sizeof(nvStoreItem_t) - sizeof(unsigned short))
                                      );


    // update the original item
    retVal = ee24lc256WritePage((unsigned short)itemEntry, 
                                sizeof(nvStoreItem_t),
                                pageCache);
    }
  else {
    // create a new item
    if ((itemEntry = findFreeNvItemEntry()) != -1) {
      if ((dataEntry = findFreeNvDataEntry()) != -1) {

        clearPageCache();

        // populate the data entry
        pDataEntry = (nvStoreData_t *)(pageCache);
        pDataEntry->signature = NVSTORE_DATA_SIGNATURE;
        pDataEntry->nextDataPage = 0;

        // write the data portion
        pCache = (unsigned char *)(pageCache + sizeof(nvStoreData_t));
        for (i=0; i<length; i++) {
          *pCache++ = *pWriteData++;
          }

        // calculate CRC16 on data for new item entry
        dataCrc16 = crc16Calc((char *)pageCache, (length + sizeof(nvStoreData_t)));

         // write the entire page to eeprom
        retVal = ee24lc256WritePage((unsigned short)dataEntry, 
                                    (length + sizeof(nvStoreData_t)),
                                    pageCache);

        if (retVal == -1) {
          return -6;
          }

        /*
         * populate the item entry
         */

        clearPageCache();

        pItemEntry = (nvStoreItem_t *)(pageCache);
        pItemEntry->signature = NVSTORE_ITEM_SIGNATURE;
        strncpy(pItemEntry->name, pName, NVSTORE_NAME_LEN);
        pItemEntry->firstDataPage = (unsigned short)dataEntry ;
        pItemEntry->dataLen = length;
        pItemEntry->dataCrc16 = dataCrc16;
        pItemEntry->itemCrc16 = crc16Calc((char *)pageCache, 
                                          (sizeof(nvStoreItem_t) - sizeof(unsigned short))
                                          );

        // write the entire page to eeprom
        retVal = ee24lc256WritePage((unsigned short)itemEntry, 
                                    sizeof(nvStoreItem_t),
                                    pageCache);

        if (retVal == -1) {
          return -7;
          }
        } // data
      } // item
    }

  return 0;
}

int nvstoreRead(char *pName, unsigned char *pReadData, unsigned char *pLength)
{
  int itemEntry, dataEntry, i ;
  nvStoreItem_t * pItemEntry;
  unsigned short dataCrc16;
  unsigned short calcCrc16;
  unsigned short dataReadLen;
  unsigned char *pCache;
  int retVal;

  if (nvstoreConfigured == false)
    return -1;

  if ((itemEntry = findNvItemEntry(pName)) != -1) {
    clearPageCache();
    pItemEntry = (nvStoreItem_t *)pageCache;
    retVal = ee24lc256ReadPage((unsigned short)itemEntry, 
                               sizeof(nvStoreItem_t),
                               pageCache
                               );
    if (retVal == -1) {
      return -1;
      }

    // verify checksum
    dataCrc16 = crc16Calc((char *)pageCache, 
                          (sizeof(nvStoreItem_t) - sizeof(unsigned short)) 
                          );

    if (dataCrc16 != pItemEntry->itemCrc16) {
      return -2;
      }

    // save the data CRC and get the data page
    dataReadLen = pItemEntry->dataLen;
    dataCrc16 = pItemEntry->dataCrc16;
    dataEntry = pItemEntry->firstDataPage;

    clearPageCache();

    retVal = ee24lc256ReadPage((unsigned short)dataEntry, 
                               (dataReadLen + sizeof(nvStoreData_t)),
                               pageCache
                               );
    if (retVal == -1) {
      return -3;
      }

    // verify data checksum
    calcCrc16 = crc16Calc((char *)pageCache , 
                          (dataReadLen + sizeof(nvStoreData_t))
                          );

    if (calcCrc16 != dataCrc16) {
      return -4;
      }

    // copy data to pReadData
    pCache = (unsigned char *)(pageCache + sizeof(nvStoreData_t));
    for(i=0; i<dataReadLen; i++) {
      *pReadData++ = *pCache++;
      }

    // give the caller the number of bytes copied
    *pLength = dataReadLen;
    }

  return 0;
}

static int nvinitHandler(int argc, char * argv[], int flags)
{
  unsigned short numItems;
  unsigned char writeData;
  char * pOutBuf;
  int bufLen, retVal, i;
  unsigned char readSig;
  unsigned short readCrc16;

  if (argc != 2)
    return -1;

  pOutBuf = clicharGetOutputBuffer(&bufLen);

  if (ee24lc256GetDeviceInfo(&nvTotalNumPages, &pageSize) != 0)
    return -1;

  nvTotalNumPages -= 1; // take away 1 for config page (0)

  /*
   * Read the config page -- if it's valid, get the info from 
   * there, else use info passed in to create a config page
   */

  retVal = ee24lc256ReadByte(EEPAGE_TO_ADDR(0), &readSig);

  // can't read the eeprom
  if (retVal == -1)
    return -1;

  numItems = (unsigned short)strtol(argv[1], NULL, 10);

  if (readSig != NVSTORE_CFG_SIGNATURE) {
    //
    // no valid config page, so create one
    //
    if (numItems > (nvTotalNumPages/2))
      return -1;

    currentConfig.signature = NVSTORE_CFG_SIGNATURE;
    currentConfig.version = 0x01;
    currentConfig.firstItemPage = 0x01;
    currentConfig.numItemPages = numItems;
    currentConfig.firstDataPage = (0x01 + numItems);
    currentConfig.numDataPages = numItems;
    currentConfig.configCrc16 = crc16Calc((char *)&currentConfig, 
                                          sizeof(nvStoreConfig_t) - 
                                          sizeof(unsigned short));

    retVal = ee24lc256WritePage((unsigned short)0, 
                                sizeof(nvStoreConfig_t), 
                                (unsigned char *)(&currentConfig));

    if (retVal == -1) {
      return -1;
      }

    // initialize 'item' descriptor page signatures to 'unused'
    for(i=0;i<currentConfig.numItemPages;i++) {
      ee24lc256WriteByte(EEPAGE_TO_ADDR(currentConfig.firstItemPage + i), 
                         (unsigned char)0xff);
      }

    // initialize 'data' descriptor page signatures to 'unused'
    for(i=0;i<currentConfig.numDataPages;i++) {
      ee24lc256WriteByte(EEPAGE_TO_ADDR(currentConfig.firstDataPage + i), 
                         (unsigned char)0xff);
      }    
    } 

  return retVal;
}

static int nvshowHandler(int argc, char * argv[], int flags)
{
  int bufLen;
  char * pOutBuf;

  pOutBuf = clicharGetOutputBuffer(&bufLen);

  sprintf(pOutBuf, "%s: sig[%02x] ver[%02x] firstItemPg[%u] numItem[%u]\r\n",
    argv[0], currentConfig.signature, currentConfig.version, 
    currentConfig.firstItemPage, currentConfig.numItemPages);

  clicharSendOutputBuffer(pOutBuf, bufLen);

  sprintf(pOutBuf, "%s: firstDataPg[%u] numData[%u] configCrc16[%04x]\r\n",
    argv[0], currentConfig.firstDataPage, currentConfig.numDataPages, 
    currentConfig.configCrc16);

  clicharSendOutputBuffer(pOutBuf, bufLen);

  return 0;
}

static int nvwriteHandler(int argc, char * argv[], int flags)
{
  int bufLen;
  char * pOutBuf;

  if (argc != 3)
    return -1;

  pOutBuf = clicharGetOutputBuffer(&bufLen);

  sprintf(pOutBuf, "%s: writing [%s] length [%d] name[%s]\r\n",
    argv[0], argv[2], strlen(argv[2]), argv[1]);

  clicharSendOutputBuffer(pOutBuf, bufLen);

  return nvstoreWrite(argv[1], argv[2], strlen(argv[2]));
}

static int nvreadHandler(int argc, char * argv[], int flags)
{
  int bufLen;
  char * pOutBuf;
  unsigned char readLen;
  int retVal;

  if (argc != 2)
    return -1;

  pOutBuf = clicharGetOutputBuffer(&bufLen);

  retVal = nvstoreRead(argv[1], pageCache, &readLen);

  if (retVal != 0) {
    sprintf(pOutBuf, "%s: ERROR %d reading name[%s]\r\n",
    argv[0], retVal, argv[1]);
    clicharSendOutputBuffer(pOutBuf, bufLen);
    }
  else {
   strutilHexdump((char *)pageCache, readLen);
   }

  return retVal;
}

#endif /* CONFIG_INCLUDE_NVSTORE */

