#include "config.h"

I2C_eeprom ee(0x50, EEPROM_SIZE_BYTES);

configData_t ConfigData;
persistenceData_t PersistenceData;
const uint16_t eeVersion = 1; // inc
boolean eeAvailable = false;

const uint16_t startofConfigData = 16;
const uint16_t startofPersistence = 16 + sizeof(ConfigData) + (sizeof(ConfigData) % 16);

boolean checkEEPROMavailable();

void InitEEPROM()
{
  ee.begin();
  checkEEPROMavailable();
}

void EEPROM_Process()
{
  switch (globals.requestEEAction)
  {
  case EE_CFG_SAVE:
    StoreConfig_EEPROM();
    globals.requestEEAction = EE_IDLE;
    Serial.println("Stored EEPROM CFG");
    break;
  case EE_CFG_LOAD:
    GetConfig_EEPROM();
    globals.requestEEAction = EE_IDLE;
    Serial.println("Loaded EEPROM CFG");
    break;
  case EE_CFG_FORMAT:
    FormatConfig_EEPROM();
    globals.requestEEAction = EE_IDLE;
    globals.systemStatus = sysStat_Shutdown;
    Serial.println("Formated EEPROM CFG");
    break;
  case EE_PDS_SAVE:
    StorePersistence_EEPROM();
    globals.requestEEAction = EE_IDLE;
    Serial.println("Stored EEPROM PDS");
    break;
  case EE_PDS_LOAD:
    GetPersistence_EEPROM();
    globals.requestEEAction = EE_IDLE;
    Serial.println("Loaded EEPROM PDS");
    break;
  case EE_PDS_FORMAT:
    FormatPersistence_EEPROM();
    globals.requestEEAction = EE_IDLE;
    Serial.println("Formated EEPROM PDS");
    break;
  case EE_FORMAT_ALL:
    FormatConfig_EEPROM();
    FormatPersistence_EEPROM();
    globals.requestEEAction = EE_IDLE;
    Serial.println("Formated EEPROM ALL");
    break;
  case EE_ALL_SAVE:
    StorePersistence_EEPROM();
    StoreConfig_EEPROM();
    globals.requestEEAction = EE_IDLE;
    Serial.println("Stored EEPROM ALL");
    break;
  case EE_IDLE:
  default:
    globals.requestEEAction = EE_IDLE;
  }
}

void StoreConfig_EEPROM()
{
  ConfigData.checksum = 0;
  ConfigData.checksum = Checksum_EEPROM((uint8_t *)&ConfigData, sizeof(ConfigData));
  if (!checkEEPROMavailable())
    return;

  ee.updateBlock(startofConfigData, (uint8_t *)&ConfigData, sizeof(ConfigData));
}

void GetConfig_EEPROM()
{
  if (!checkEEPROMavailable())
    return;

  ee.readBlock(startofConfigData, (uint8_t *)&ConfigData, sizeof(ConfigData));

  uint32_t checksum = ConfigData.checksum;
  ConfigData.checksum = 0;

  if (Checksum_EEPROM((uint8_t *)&ConfigData, sizeof(ConfigData)) != checksum)
  {
    MaintainDTC(DTC_EEPROM_CFG_BAD, DTC_CRITICAL, true);
  }
  ConfigData.checksum = checksum;

  uint32_t ConfigSanityCheckResult = ConfigSanityCheck(false);

  if (ConfigSanityCheckResult > 0)
  {
    MaintainDTC(DTC_EEPROM_CFG_SANITY, DTC_WARN, true, ConfigSanityCheckResult);
    globals.requestEEAction = EE_CFG_SAVE;
  }
}

void StorePersistence_EEPROM()
{
  if (PersistenceData.writeCycleCounter >= 0xFFF0)
    MovePersistencePage_EEPROM(false);
  else
    PersistenceData.writeCycleCounter++;

  PersistenceData.checksum = 0;
  PersistenceData.checksum = Checksum_EEPROM((uint8_t *)&PersistenceData, sizeof(PersistenceData));

  if (!checkEEPROMavailable())
    return;

  ee.updateBlock(globals.eePersistanceAdress, (uint8_t *)&PersistenceData, sizeof(PersistenceData));
}

void GetPersistence_EEPROM()
{
  if (!checkEEPROMavailable())
    return;

  ee.readBlock(0, (uint8_t *)&globals.eePersistanceAdress, sizeof(globals.eePersistanceAdress));
  // if we got the StartAdress of Persistance and it's out of Range - we Reset it and store defaults
  // otherwise we Read from eeprom and check if everything is correct
  if (globals.eePersistanceAdress < startofPersistence || globals.eePersistanceAdress > ee.getDeviceSize())
  {
    MovePersistencePage_EEPROM(true);
    FormatPersistence_EEPROM();
    MaintainDTC(DTC_EEPROM_PDSADRESS_BAD, DTC_CRITICAL, true);
  }
  else
  {
    ee.readBlock(globals.eePersistanceAdress, (uint8_t *)&PersistenceData, sizeof(PersistenceData));

    uint32_t checksum = PersistenceData.checksum;
    PersistenceData.checksum = 0;

    if (Checksum_EEPROM((uint8_t *)&PersistenceData, sizeof(PersistenceData)) != checksum)
    {
      MaintainDTC(DTC_EEPROM_PDS_BAD, DTC_CRITICAL, true);
    }
    PersistenceData.checksum = checksum;
  }
}

void FormatConfig_EEPROM()
{
  Serial.println("Formatting Config-Partition");
  ConfigData = ConfigData_defaults;
  ConfigData.EEPROM_Version = eeVersion;
  StoreConfig_EEPROM();
}

void FormatPersistence_EEPROM()
{
  Serial.println("Formatting Persistance-Partition");
  memset(&PersistenceData, 0, sizeof(PersistenceData));
  StorePersistence_EEPROM();
}

void MovePersistencePage_EEPROM(boolean reset)
{
  if (!checkEEPROMavailable())
    return;

  globals.eePersistanceAdress = +sizeof(PersistenceData);
  PersistenceData.writeCycleCounter = 0;

  // check if we reached the End of the EEPROM and Startover at the beginning
  if ((globals.eePersistanceAdress + sizeof(PersistenceData)) > ee.getDeviceSize() || reset)
  {
    globals.eePersistanceAdress = startofPersistence;
  }

  ee.updateBlock(0, (uint8_t *)&globals.eePersistanceAdress, sizeof(globals.eePersistanceAdress));
}

uint32_t Checksum_EEPROM(uint8_t const *data, size_t len)
{
  if (data == NULL)
    return 0;
  uint32_t crc, mask;
  crc = 0xFFFFFFFF;

  while (len--)
  {
    crc ^= *data++;
    for (uint8_t k = 0; k < 8; k++)
    {
      mask = -(crc & 1);
      crc = (crc >> 1) ^ (0xEDB88320 & mask);
    }
  }
  return ~crc;
}

void dumpEEPROM(uint16_t memoryAddress, uint16_t length)
{
#define BLOCK_TO_LENGTH 16

  if (!checkEEPROMavailable())
    return;

  char ascii_buf[BLOCK_TO_LENGTH + 1];
  sprintf(ascii_buf, "%*s", BLOCK_TO_LENGTH, "ASCII");
  Serial.print(PSTR("\nAddress "));
  for (int x = 0; x < BLOCK_TO_LENGTH; x++)
    Serial.printf("%3d", x);

  memoryAddress = memoryAddress / BLOCK_TO_LENGTH * BLOCK_TO_LENGTH;
  length = (length + BLOCK_TO_LENGTH - 1) / BLOCK_TO_LENGTH * BLOCK_TO_LENGTH;

  for (unsigned int i = 0; i < length; i++)
  {
    int blockpoint = memoryAddress % BLOCK_TO_LENGTH;
    if (blockpoint == 0)
    {
      ascii_buf[BLOCK_TO_LENGTH] = 0;
      Serial.printf("  %s", ascii_buf);
      Serial.printf("\n0x%05X:", memoryAddress);
    }
    ascii_buf[blockpoint] = ee.readByte(memoryAddress);
    Serial.printf(" %02X", ascii_buf[blockpoint]);
    if (ascii_buf[blockpoint] < 0x20 || ascii_buf[blockpoint] > 0x7E)
      ascii_buf[blockpoint] = '.';
    memoryAddress++;
  }
  Serial.println();
}

boolean checkEEPROMavailable()
{
  if (!ee.isConnected())
  {
    MaintainDTC(DTC_NO_EEPROM_FOUND, DTC_CRITICAL, true);
    globals.systemStatus = sysStat_Error;
    return false;
  }
  MaintainDTC(DTC_NO_EEPROM_FOUND, DTC_CRITICAL, false);
  return true;
}

uint32_t ConfigSanityCheck(bool autocorrect)
{
  uint32_t setting_reset_bits = 0;

  if ((ConfigData.batteryType != BATTERY_LIPO_2S) || (ConfigData.batteryType != BATTERY_LIPO_3S))
  {
    setting_reset_bits = setting_reset_bits | (1 << 0);
    if (autocorrect)
      ConfigData.batteryType = ConfigData_defaults.batteryType;
  }

  return setting_reset_bits;
}