/** @file
 * EEPROM Storage updates.
 */
/** Store and load various configs to/from EEPROM considering the the data format versions of various SW generations.
 * This routine is used for doing any data conversions that are required during firmware changes.
 * This prevents users getting difference reports in TS when such a data change occurs.
 * It also can be used for setting good values when there are variables that move locations in the ini.
 * When a user skips multiple firmware versions at a time, this will roll through the updates 1 at a time.
 * The doUpdates() uses may lower level routines from Arduino EEPROM library and storage.ino to carry out EEPROM storage tasks.
 */
#include "globals.h"
#include "storage.h"
#include "sensors.h"
#include "updates.h"
#include "pages.h"
#include EEPROM_LIB_H //This is defined in the board .h files

void doUpdates(void)
{
  #define CURRENT_DATA_VERSION    23
  //Only the latest update for small flash devices must be retained
   #ifndef SMALL_FLASH_MODE

  //May 2017 firmware introduced a -40 offset on the ignition table. Update that table to +40
  if(readEEPROMVersion() == 2)
  {
    auto table_it = ignitionTable.values.begin();
    while (!table_it.at_end())
    {
      auto row = *table_it;
      while (!row.at_end())
      {
        *row = *row + 40;
        ++row;
      }      
      ++table_it;
    }
    writeAllConfig();
    storeEEPROMVersion(3);
  }
  //June 2017 required the forced addition of some CAN values to avoid weird errors
  if(readEEPROMVersion() == 3)
  {
    configPage9.speeduino_tsCanId = 0;
    configPage9.true_address = 256;
    configPage9.realtime_base_address = 336;

    //There was a bad value in the May base tune for the spark duration setting, fix it here if it's a problem
    if(configPage4.sparkDur == 255) { configPage4.sparkDur = 10; }

    writeAllConfig();
    storeEEPROMVersion(4);
  }
  //July 2017 adds a cranking enrichment curve in place of the single value. This converts that single value to the curve
  if(readEEPROMVersion() == 4)
  {
    //Some default values for the bins (Doesn't matter too much here as the values against them will all be identical)
    configPage10.crankingEnrichBins[0] = 0;
    configPage10.crankingEnrichBins[1] = 40;
    configPage10.crankingEnrichBins[2] = 70;
    configPage10.crankingEnrichBins[3] = 100;

    configPage10.crankingEnrichValues[0] = 100 + configPage2.crankingPct;
    configPage10.crankingEnrichValues[1] = 100 + configPage2.crankingPct;
    configPage10.crankingEnrichValues[2] = 100 + configPage2.crankingPct;
    configPage10.crankingEnrichValues[3] = 100 + configPage2.crankingPct;

    writeAllConfig();
    storeEEPROMVersion(5);
  }
  //September 2017 had a major change to increase the minimum table size to 128. This required multiple pieces of data being moved around
  if(readEEPROMVersion() == 5)
  {
    //Data after page 4 has to move back 128 bytes
    for(int x=0; x < 1152; x++)
    {
      int endMem = EEPROM_CONFIG10_END - x;
      int startMem = endMem - 128; //
      byte currentVal = EEPROM.read(startMem);
      EEPROM.update(endMem, currentVal);
    }
    //The remaining data only has to move back 64 bytes
    for(int x=0; x < 352; x++)
    {
      int endMem = EEPROM_CONFIG10_END - 1152 - x;
      int startMem = endMem - 64; //
      byte currentVal = EEPROM.read(startMem);
      EEPROM.update(endMem, currentVal);
    }

    storeEEPROMVersion(6);
    loadConfig(); //Reload the config after changing everything in EEPROM
  }
  //November 2017 added the staging table that comes after boost and vvt in the EEPROM. This required multiple pieces of data being moved around
  if(readEEPROMVersion() == 6)
  {
    //Data after page 8 has to move back 82 bytes
    for(int x=0; x < 529; x++)
    {
      int endMem = EEPROM_CONFIG10_END - x;
      int startMem = endMem - 82; //
      byte currentVal = EEPROM.read(startMem);
      EEPROM.update(endMem, currentVal);
    }

    storeEEPROMVersion(7);
    loadConfig(); //Reload the config after changing everything in EEPROM
  }

  if (readEEPROMVersion() == 7) {
    //Convert whatever flex fuel settings are there into the new tables

    configPage10.flexBoostBins[0] = 0;
    configPage10.flexBoostAdj[0]  = (int8_t)configPage2.aeColdPct;

    configPage10.flexFuelBins[0] = 0;
    configPage10.flexFuelAdj[0]  = configPage2.idleUpPin;

    configPage10.flexAdvBins[0] = 0;
    configPage10.flexAdvAdj[0]  = configPage2.aeTaperMin;

    for (uint8_t x = 1; x < 6; x++)
    {
      uint8_t pct = x * 20;
      configPage10.flexBoostBins[x] = pct;
      configPage10.flexFuelBins[x] = pct;
      configPage10.flexAdvBins[x] = pct;

      int16_t boostAdder = (((configPage2.aeColdTaperMin - (int8_t)configPage2.aeColdPct) * pct) / 100) + (int8_t)configPage2.aeColdPct;
      configPage10.flexBoostAdj[x] = boostAdder;

      uint8_t fuelAdder = (((configPage2.idleUpAdder - configPage2.idleUpPin) * pct) / 100) + configPage2.idleUpPin;
      configPage10.flexFuelAdj[x] = fuelAdder;

      uint8_t advanceAdder = (((configPage2.aeTaperMax - configPage2.aeTaperMin) * pct) / 100) + configPage2.aeTaperMin;
      configPage10.flexAdvAdj[x] = advanceAdder;
    }

    writeAllConfig();
    storeEEPROMVersion(8);
  }

  if (readEEPROMVersion() == 8)
  {
    //May 2018 adds separate load sources for fuel and ignition. Copy the existing load algorithm into Both
    configPage2.fuelAlgorithm = configPage2.legacyMAP; //Was configPage2.unused2_38c
    configPage2.ignAlgorithm = configPage2.legacyMAP; //Was configPage2.unused2_38c

    //Add option back in for open or closed loop boost. For all current configs to use closed
    configPage4.boostType = 1;

    writeAllConfig();
    storeEEPROMVersion(9);
  }

  if(readEEPROMVersion() == 9)
  {
    //October 2018 set default values for all the aux in variables (These were introduced in Aug, but no defaults were set then)
    //All aux channels set to Off
    for (byte AuxinChan = 0; AuxinChan <16 ; AuxinChan++)
    {
      configPage9.caninput_sel[AuxinChan] = 0;
    }

    //Ability to change the analog filter values was added. Set default values for these:
    configPage4.ADCFILTER_TPS  = ADCFILTER_TPS_DEFAULT;
    configPage4.ADCFILTER_CLT  = ADCFILTER_CLT_DEFAULT;
    configPage4.ADCFILTER_IAT  = ADCFILTER_IAT_DEFAULT;
    configPage4.ADCFILTER_O2   = ADCFILTER_O2_DEFAULT;
    configPage4.ADCFILTER_BAT  = ADCFILTER_BAT_DEFAULT;
    configPage4.ADCFILTER_MAP  = ADCFILTER_MAP_DEFAULT;
    configPage4.ADCFILTER_BARO = ADCFILTER_BARO_DEFAULT;

    writeAllConfig();
    storeEEPROMVersion(10);
  }

  if(readEEPROMVersion() == 10)
  {
    //May 2019 version adds the use of a 2D table for the priming pulse rather than a single value.
    //This sets all the values in the 2D table to be the same as the previous single value
    configPage2.primePulse[0] = configPage2.aeColdTaperMax / 5; //New priming pulse values are in the range 0-127.5 rather than 0-25.5 so they must be divided by 5
    configPage2.primePulse[1] = configPage2.aeColdTaperMax / 5; //New priming pulse values are in the range 0-127.5 rather than 0-25.5 so they must be divided by 5
    configPage2.primePulse[2] = configPage2.aeColdTaperMax / 5; //New priming pulse values are in the range 0-127.5 rather than 0-25.5 so they must be divided by 5
    configPage2.primePulse[3] = configPage2.aeColdTaperMax / 5; //New priming pulse values are in the range 0-127.5 rather than 0-25.5 so they must be divided by 5
    //Set some sane default temperatures for this table
    configPage2.primeBins[0] = 0;
    configPage2.primeBins[1] = 40;
    configPage2.primeBins[2] = 70;
    configPage2.primeBins[3] = 100;

    //Also added is coolant based ASE for both duration and amount
    //All the adder amounts are set to what the single value was previously
    configPage2.asePct[0] = configPage2.aeColdTaperMin;
    configPage2.asePct[1] = configPage2.aeColdTaperMin;
    configPage2.asePct[2] = configPage2.aeColdTaperMin;
    configPage2.asePct[3] = configPage2.aeColdTaperMin;
    //ASE duration is set to 10s for all coolant values
    configPage2.aseCount[0] = 10;
    configPage2.aseCount[1] = 10;
    configPage2.aseCount[2] = 10;
    configPage2.aseCount[3] = 10;
    //Finally the coolant bins for the above are set to sane values (Remembering these are offset values)
    configPage2.aseBins[0] = 0;
    configPage2.aseBins[1] = 20;
    configPage2.aseBins[2] = 60;
    configPage2.aseBins[3] = 80;

    //Coolant based ignition advance was added also. Set sane values
    configPage4.cltAdvBins[0] = 0;
    configPage4.cltAdvBins[1] = 30;
    configPage4.cltAdvBins[2] = 60;
    configPage4.cltAdvBins[3] = 70;
    configPage4.cltAdvBins[4] = 85;
    configPage4.cltAdvBins[5] = 100;
    configPage4.cltAdvValues[0] = 0;
    configPage4.cltAdvValues[1] = 0;
    configPage4.cltAdvValues[2] = 0;
    configPage4.cltAdvValues[3] = 0;
    configPage4.cltAdvValues[4] = 0;
    configPage4.cltAdvValues[5] = 0;


    //March 19 added a tacho pulse duration that could default to stupidly high values. Check if this is the case and fix it if found. 6ms is the maximum allowed value
    if(configPage2.tachoDuration > 6) { configPage2.tachoDuration = 3; }

    //MAP based AE was introduced, force the AE mode to be TPS for all existing tunes
    configPage2.aeMode = AE_MODE_TPS;
    configPage2.maeThresh = configPage2.taeThresh;
    //Set some sane values for the MAP AE curve
    configPage4.maeRates[0] = 75;
    configPage4.maeRates[1] = 75;
    configPage4.maeRates[2] = 75;
    configPage4.maeRates[3] = 75;
    configPage4.maeBins[0] = 7;
    configPage4.maeBins[1] = 12;
    configPage4.maeBins[2] = 20;
    configPage4.maeBins[3] = 40;

    //The 2nd fuel table was added. To prevent issues, force it to be disabled.
    configPage10.fuel2Mode = 0;


    writeAllConfig();
    storeEEPROMVersion(11);
  }

  if(readEEPROMVersion() == 11)
  {
    //Sep 2019
    //A battery calibration offset value was introduced. Set default value to 0
    configPage4.batVoltCorrect = 0;

    //An option was added to select the older method of performing MAP reads with the pullup resistor active
    configPage2.legacyMAP = 0;

    //Secondary fuel table was added for switching. Make sure it's all turned off initially
    configPage10.fuel2Mode = 0;
    configPage10.fuel2SwitchVariable = 0; //Set switch variable to RPM
    configPage10.fuel2SwitchValue = 7000; //7000 RPM switch point is safe

    writeAllConfig();
    storeEEPROMVersion(12);
  }

  if(readEEPROMVersion() == 12)
  {
    //Nov 2019
    //New option to only apply voltage correction to dead time. Set existing tunes to use old method
    configPage2.battVCorMode = BATTV_COR_MODE_WHOLE;

    //Manual baro correction curve was added. Give it some default values (All baro readings set to 100%)
    configPage4.baroFuelBins[0] = 80;
    configPage4.baroFuelBins[1] = 85;
    configPage4.baroFuelBins[2] = 90;
    configPage4.baroFuelBins[3] = 95;
    configPage4.baroFuelBins[4] = 100;
    configPage4.baroFuelBins[5] = 105;
    configPage4.baroFuelBins[6] = 110;
    configPage4.baroFuelBins[7] = 115;

    configPage4.baroFuelValues[0] = 100;
    configPage4.baroFuelValues[1] = 100;
    configPage4.baroFuelValues[2] = 100;
    configPage4.baroFuelValues[3] = 100;
    configPage4.baroFuelValues[4] = 100;
    configPage4.baroFuelValues[5] = 100;
    configPage4.baroFuelValues[6] = 100;
    configPage4.baroFuelValues[7] = 100;

    //Idle advance curve was added. Add default values
    configPage2.idleAdvEnabled = 0; //Turn this off by default
    configPage2.idleAdvTPS = 5; //Active below 5% tps
    configPage2.idleAdvRPM = 20; //Active below 2000 RPM
    configPage4.idleAdvBins[0] = 30;
    configPage4.idleAdvBins[1] = 40;
    configPage4.idleAdvBins[2] = 50;
    configPage4.idleAdvBins[3] = 60;
    configPage4.idleAdvBins[4] = 70;
    configPage4.idleAdvBins[5] = 80;
    configPage4.idleAdvValues[0] = 15; //These values offset by 15, so this is just making this equal to 0
    configPage4.idleAdvValues[1] = 15;
    configPage4.idleAdvValues[2] = 15;
    configPage4.idleAdvValues[3] = 15;
    configPage4.idleAdvValues[4] = 15;
    configPage4.idleAdvValues[5] = 15;

    writeAllConfig();
    storeEEPROMVersion(13);
  }

  if(readEEPROMVersion() == 13)
  {
    //202005
    //Cranking enrichment range 0..1275% instead of older 0.255, so need to divide old values by 5
    configPage10.crankingEnrichValues[0] = configPage10.crankingEnrichValues[0] / 5;
    configPage10.crankingEnrichValues[1] = configPage10.crankingEnrichValues[1] / 5;
    configPage10.crankingEnrichValues[2] = configPage10.crankingEnrichValues[2] / 5;
    configPage10.crankingEnrichValues[3] = configPage10.crankingEnrichValues[3] / 5;

    //Added the injector timing curve
    //Set all the values to be the same as the first one. 
    configPage2.injAng[0] = configPage2.injAng[0]; //Obviously not needed, but here for completeness
    configPage2.injAng[1] = configPage2.injAng[0];
    configPage2.injAng[2] = configPage2.injAng[0];
    configPage2.injAng[3] = configPage2.injAng[0];
    //The RPMs are divided by 100
    configPage2.injAngRPM[0] = 5;
    configPage2.injAngRPM[1] = 25;
    configPage2.injAngRPM[2] = 45;
    configPage2.injAngRPM[3] = 65;

    //Introduced a DFCO delay option. Default it to 0
    configPage2.dfcoDelay = 0;
    //Introduced a minimum temperature for DFCO. Default it to 40C
    configPage2.dfcoMinCLT = 80; //CALIBRATION_TEMPERATURE_OFFSET is 40

    //Update flex fuel ignition config values for 40 degrees offset
    for (int i=0; i<6; i++)
    {
      configPage10.flexAdvAdj[i] += 40;
    }
    
    //AE cold modifier added. Default to sane values
    configPage2.aeColdPct = 100;
    configPage2.aeColdTaperMin = 40;
    configPage2.aeColdTaperMax = 100;

    //New PID resolution, old resolution was 100% for each increase, 100% now is stored as 32
    if(configPage6.idleKP >= 8) { configPage6.idleKP = 255; }
    else { configPage6.idleKP = configPage6.idleKP<<5; }
    if(configPage6.idleKI >= 8) { configPage6.idleKI = 255; }
    else { configPage6.idleKI = configPage6.idleKI<<5; }
    if(configPage6.idleKD >= 8) { configPage6.idleKD = 255; }
    else { configPage6.idleKD = configPage6.idleKD<<5; }
    if(configPage10.vvtCLKP >= 8) { configPage10.vvtCLKP = 255; }
    else { configPage10.vvtCLKP = configPage10.vvtCLKP<<5; }
    if(configPage10.vvtCLKI >= 8) { configPage10.vvtCLKI = 255; }
    else { configPage10.vvtCLKI = configPage10.vvtCLKI<<5; }
    if(configPage10.vvtCLKD >= 8) { configPage10.vvtCLKD = 255; }
    else { configPage10.vvtCLKD = configPage10.vvtCLKD<<5; }

    //Cranking enrichment to run taper added. Default it to 0,1 secs
    configPage10.crankingEnrichTaper = 1;
    
    //ASE to run taper added. Default it to 0,1 secs
    configPage2.aseTaperTime = 1;

    // there is now option for fixed and relative timing retard for soft limit. This sets the soft limiter to the old fixed timing mode.
    configPage2.SoftLimitMode = SOFT_LIMIT_FIXED;

    //VSS was added for testing, disable it by default
    configPage2.vssMode = 0;

    writeAllConfig();
    storeEEPROMVersion(14);

  }

  if(readEEPROMVersion() == 14)
  {
    //202008

    //MAJOR update to move the coolant, IAT and O2 calibrations to 2D tables
    int y;
    for(int x=0; x<(CALIBRATION_TABLE_SIZE/16); x++) //Each calibration table is 512 bytes long
    {
      y = EEPROM_CALIBRATION_CLT_OLD + (x * 16);
      cltCalibration_values[x] = EEPROM.read(y);
      cltCalibration_bins[x] = (x * 32);

      y = EEPROM_CALIBRATION_IAT_OLD + (x * 16);
      iatCalibration_values[x] = EEPROM.read(y);
      iatCalibration_bins[x] = (x * 32);

      y = EEPROM_CALIBRATION_O2_OLD + (x * 16);
      o2Calibration_values[x] = EEPROM.read(y);
      o2Calibration_bins[x] = (x * 32);
    }
    writeCalibration();

    //Oil and fuel pressure inputs were introduced. Disable them both by default
    configPage10.oilPressureProtEnbl = false;
    configPage10.oilPressureEnable = false;
    configPage10.fuelPressureEnable = false;
    
    //wmi
    configPage10.wmiEnabled = 0;
    configPage10.wmiMode = 0;
    configPage10.wmiOffset = 0;
    configPage10.wmiIndicatorEnabled = 0;
    configPage10.wmiEmptyEnabled = 0;
    configPage10.wmiAdvEnabled = 0;
    for(int i=0; i<6; i++)
    {
      configPage10.wmiAdvBins[i] = i*100/2;
      configPage10.wmiAdvAdj[i] = OFFSET_IGNITION;
    }

    //Programmable outputs added. Set all to disabled
    configPage13.outputPin[0] = 0;
    configPage13.outputPin[1] = 0;
    configPage13.outputPin[2] = 0;
    configPage13.outputPin[3] = 0;
    configPage13.outputPin[4] = 0;
    configPage13.outputPin[5] = 0;
    configPage13.outputPin[6] = 0;
    configPage13.outputPin[7] = 0;

    //New multiply MAP option added. Set new option to be the same as old
    configPage2.multiplyMAP = configPage2.crkngAddCLTAdv;
    //New AE option added to allow for PW added in addition to existing PW multiply
    configPage2.aeApplyMode = 0; //Set the AE mode to Multiply

    //Injector priming delay added
    configPage2.primingDelay = 0;
    //ASE taper time added
    configPage2.aseTaperTime = 10; //1 second taper

    writeAllConfig();
    storeEEPROMVersion(15);
  }

  if(readEEPROMVersion() == 15)
  {
    //202012
    configPage10.spark2Mode = 0; //Disable 2nd spark table

    writeAllConfig();
    storeEEPROMVersion(16);
  }

  //Move this #endif to only do latest updates to safe ROM space on small devices.
  #endif
  if(readEEPROMVersion() == 16)
  {
    //Fix for wrong placed page 13
    for(int x=EEPROM_CONFIG14_END; x>=EEPROM_CONFIG13_START; x--)
    {
      EEPROM.update(x, EEPROM.read(x-112));
    }

    configPage6.iacPWMrun = false; // just in case. This should be false anyways, but sill.
    configPage2.useDwellMap = 0; //Dwell map added, use old fixed value as default

    writeAllConfig();
    storeEEPROMVersion(17);
  }

  if(readEEPROMVersion() == 17)
  {
    //VVT stuff has now 0.5 accuracy, so shift values in vvt table by one.
    auto table_it = vvtTable.values.begin();
    while (!table_it.at_end())
    {
      auto row = *table_it;
      while (!row.at_end())
      {
        *row = *row << 1;
        ++row;
      }      
      ++table_it;
    }

    configPage10.vvtCLholdDuty = configPage10.vvtCLholdDuty << 1;
    configPage10.vvtCLminDuty = configPage10.vvtCLminDuty << 1;
    configPage10.vvtCLmaxDuty = configPage10.vvtCLmaxDuty << 1;

    //VVT2 added, so default values and disable it
    configPage10.vvt2Enabled = 0;
    configPage4.vvt2PWMdir = 0;
    configPage10.TrigEdgeThrd = 0;

    //Old use as On/Off selection is removed, so change VVT mode to On/Off based on that
    if(configPage6.tachoMode == 1) { configPage6.vvtMode = VVT_MODE_ONOFF; }

    //Closed loop VVT improvements. Set safety limits to max/min working values and filter to minimum.
    configPage10.vvtCLMinAng = 0;
    configPage10.vvtCLMaxAng = 200;
    configPage4.ANGLEFILTER_VVT = 0;

    configPage2.idleAdvDelay *= 2; //Increased resolution to 0.5 second
    
    //RPM switch point added for map sample method. Set to 0 to not affect existing tunes.
    configPage2.mapSwitchPoint = 0;

    configPage9.boostByGearEnabled = 0;

    //Added possibility to set minimum programmable output time
    configPage13.outputTimeLimit[0] = 0;
    configPage13.outputTimeLimit[1] = 0;
    configPage13.outputTimeLimit[2] = 0;
    configPage13.outputTimeLimit[3] = 0;
    configPage13.outputTimeLimit[4] = 0;
    configPage13.outputTimeLimit[5] = 0;
    configPage13.outputTimeLimit[6] = 0;
    configPage13.outputTimeLimit[7] = 0;

    writeAllConfig();
    storeEEPROMVersion(18);
  }

  if(readEEPROMVersion() == 18)
  {
    //202202
    configPage2.fanEnable = configPage6.fanUnused; // PWM Fan mode added, but take the previous setting of Fan in use.

    //TPS resolution increased to 0.5%
    //configPage2.taeThresh *= 2;
    configPage2.idleAdvTPS *= 2;
    configPage2.iacTPSlimit *= 2;
    configPage4.floodClear *= 2;
    configPage4.dfcoTPSThresh *= 2;
    configPage6.egoTPSMax *= 2;
    configPage10.lnchCtrlTPS *= 2;
    configPage10.wmiTPS *= 2;
    configPage10.n2o_minTPS *= 2;
    if(configPage10.fuel2SwitchVariable == FUEL2_CONDITION_TPS) { configPage10.fuel2SwitchValue *= 2; }
    if(configPage10.spark2SwitchVariable == SPARK2_CONDITION_TPS) { configPage10.spark2SwitchVariable *= 2; }

    // Each table Y axis need to be updated as well if TPS is the source
    if(configPage2.fuelAlgorithm == LOAD_SOURCE_TPS)
    {
      multiplyTableLoad(&fuelTable,  fuelTable.type_key,  4);
      multiplyTableLoad(&afrTable,   afrTable.type_key,   4);
      multiplyTableLoad(&trim1Table, trim1Table.type_key, 4);
      multiplyTableLoad(&trim2Table, trim2Table.type_key, 4);
      multiplyTableLoad(&trim3Table, trim3Table.type_key, 4);
      multiplyTableLoad(&trim4Table, trim4Table.type_key, 4);
      multiplyTableLoad(&trim5Table, trim5Table.type_key, 4);
      multiplyTableLoad(&trim6Table, trim6Table.type_key, 4);
      multiplyTableLoad(&trim7Table, trim7Table.type_key, 4);
      multiplyTableLoad(&trim8Table, trim8Table.type_key, 4);
      if(configPage4.sparkMode == IGN_MODE_ROTARY)
      { 
        for(uint8_t x = 0; x < 8; x++)
        {
          configPage10.rotarySplitBins[x] *= 2;
        }
      }
    }
    if(configPage2.ignAlgorithm == LOAD_SOURCE_TPS) { multiplyTableLoad(&ignitionTable, ignitionTable.type_key, 4); }
    if(configPage10.fuel2Algorithm == LOAD_SOURCE_TPS) { multiplyTableLoad(&fuelTable2, fuelTable2.type_key, 4); }
    if(configPage10.spark2Algorithm == LOAD_SOURCE_TPS) { multiplyTableLoad(&ignitionTable2, ignitionTable2.type_key, 4); }
    multiplyTableLoad(&boostTable, boostTable.type_key, 2); // Boost table used 1.0 previously, so it only needs a 2x multiplier

    if(configPage6.vvtLoadSource == VVT_LOAD_TPS)
    {
      //NOTE: The VVT tables all had 1.0 as the multiply value rather than 2.0 used in all other tables. For this reason they only need to be multiplied by 2 when updating
      multiplyTableLoad(&vvtTable, vvtTable.type_key, 2);
      multiplyTableLoad(&vvt2Table, vvt2Table.type_key, 2);
    }
    else
    {
      //NOTE: The VVT tables all had 1.0 as the multiply value rather than 2.0 used in all other tables. For this reason they need to be divided by 2 when updating
      divideTableLoad(&vvtTable, vvtTable.type_key, 2);
      divideTableLoad(&vvt2Table, vvt2Table.type_key, 2);
    }


    configPage4.vvtDelay = 0;
    configPage4.vvtMinClt = 0;

    //Set SD logging related settings to zero.
    configPage13.onboard_log_csv_separator = 0;
    configPage13.onboard_log_file_style = 0;
    configPage13.onboard_log_file_rate = 0;
    configPage13.onboard_log_filenaming = 0;
    configPage13.onboard_log_storage = 0;
    configPage13.onboard_log_trigger_boot = 0;
    configPage13.onboard_log_trigger_RPM = 0;
    configPage13.onboard_log_trigger_prot = 0;
    configPage13.onboard_log_trigger_Vbat = 0;
    configPage13.onboard_log_trigger_Epin = 0;
    configPage13.onboard_log_tr1_duration = 0;
    configPage13.onboard_log_tr2_thr_on = 0;
    configPage13.onboard_log_tr2_thr_off = 0;
    configPage13.onboard_log_tr3_thr_RPM = 0;
    configPage13.onboard_log_tr3_thr_MAP = 0;
    configPage13.onboard_log_tr3_thr_Oil = 0;
    configPage13.onboard_log_tr3_thr_AFR = 0;
    configPage13.onboard_log_tr4_thr_on = 0;
    configPage13.onboard_log_tr4_thr_off = 0;
    configPage13.onboard_log_tr5_Epin_pin = 0;

    writeAllConfig();
    storeEEPROMVersion(19);
  }
  
  if(readEEPROMVersion() == 19)
  {
    //202207

    //Option added to select injector pairing on 4 cylinder engines
    if( configPage4.inj4cylPairing > INJ_PAIR_14_23 ) { configPage4.inj4cylPairing = 0; } //Check valid value
    if( configPage2.nCylinders == 4 )
    {
      if ( configPage2.injLayout == INJ_SEQUENTIAL ) { configPage4.inj4cylPairing = INJ_PAIR_13_24; } //Since #478 engine will always start in semi, make the sequence right for the majority of inlie 4 engines
      else { configPage4.inj4cylPairing = INJ_PAIR_14_23; } //Force setting to use the default mode from previous FW versions. This is to prevent issues on any setups that have been wired accordingly
    }

    configPage9.hardRevMode = 1; //Set hard rev limiter to Fixed mode
    configPage6.tachoMode = 0;

    //CAN broadcast introduced
    configPage2.canBMWCluster = 0;
    configPage2.canVAGCluster = 0;
    
    configPage15.boostDCWhenDisabled = 0;
    configPage15.boostControlEnable = EN_BOOST_CONTROL_BARO;
    
    //Fill the boostTableLookupDuty with all 50% duty cycle. This is the same as the hardcoded 50% DC that had been used before.
    //This makes the boostcontrol fully backwards compatible.  
    auto table_it = boostTableLookupDuty.values.begin();
    while (!table_it.at_end())
    {
      auto row = *table_it;
      while (!row.at_end())
      {
        *row = 50*2;
        ++row;
      }      
      ++table_it;
    }

    //Set some sensible values at the RPM axis
    auto table_X = boostTableLookupDuty.axisX.begin();
    uint16_t i = 0;
    while (!table_X.at_end())
    {
      ++i;
      *table_X = 1000+(500*i);
      ++table_X;
    }

    //Set some sensible values at the boosttarget axis
    auto table_Y = boostTableLookupDuty.axisY.begin();
    i = 0;
    while (!table_Y.at_end())
    {
      ++i;
      *table_Y = (120 + 10*i);
      ++table_Y;
    }

    //AFR Protection added, add default values
    configPage9.afrProtectEnabled = 0; //Disable by default
    configPage9.afrProtectMinMAP = 90; //Is divided by 2, vlue represents 180kPa
    configPage9.afrProtectMinRPM = 40; //4000 RPM min
    configPage9.afrProtectMinTPS = 160; //80% TPS min
    configPage9.afrProtectDeviation = 14; //1.4 AFR deviation    
    
    writeAllConfig();
    storeEEPROMVersion(20);
  }

  if(readEEPROMVersion() == 20)
  {
    //202305
    configPage2.taeMinChange = 4; //Default is 2% minimum change to match prior behaviour. (4 = 2% account for 0.5 resolution)
    configPage2.maeMinChange = 2; //Default is 2% minimum change to match prior behaviour.

    configPage2.decelAmount = 100; //Default decel fuel amount is 100%, so no change in fueling in decel as before.
    //full status structure has been changed. Update programmable outputs settings to match.
    for (uint8_t y = 0; y < sizeof(configPage13.outputPin); y++)
    {
      if ((configPage13.firstDataIn[y] > 22) && (configPage13.firstDataIn[y] < 240)) {configPage13.firstDataIn[y]++;}
      if ((configPage13.firstDataIn[y] > 92) && (configPage13.firstDataIn[y] < 240)) {configPage13.firstDataIn[y]++;}
      if ((configPage13.secondDataIn[y] > 22) && (configPage13.secondDataIn[y] < 240)) {configPage13.secondDataIn[y]++;}
      if ((configPage13.secondDataIn[y] > 92) && (configPage13.secondDataIn[y] < 240)) {configPage13.secondDataIn[y]++;}
    }
    
    //AC Control (configPage15)
    //Set A/C default values - these line up with the ini file defaults
    configPage15.airConEnable = 0;

    //Oil Pressure protection delay added. Set to 0 to match existing behaviour
    configPage10.oilPressureProtTime = 0;

    //Option to power stepper motor constantly was added. Default to previous behaviour
    configPage9.iacStepperPower = 0;

    writeAllConfig();
    storeEEPROMVersion(21);
  }

  if(readEEPROMVersion() == 21)
  {
    //202310

    //Rolling cut curve added. Default values
    configPage15.rollingProtRPMDelta[0]   = -30;
    configPage15.rollingProtRPMDelta[1]   = -20;
    configPage15.rollingProtRPMDelta[2]   = -10;
    configPage15.rollingProtRPMDelta[3]   = -5;
    configPage15.rollingProtCutPercent[0] = 50;
    configPage15.rollingProtCutPercent[1] = 65;
    configPage15.rollingProtCutPercent[2] = 80;
    configPage15.rollingProtCutPercent[3] = 95;

    //DFCO Hyster was multipled by 2 to allow a range of 0-500. Existing values must be halved
    configPage4.dfcoHyster = configPage4.dfcoHyster / 2;

    writeAllConfig();
    storeEEPROMVersion(22);
  }

  if(readEEPROMVersion() == 22)
  {
    //202311-dev
    
    if( configPage10.wmiMode >= WMI_MODE_OPENLOOP ) { multiplyTableValue(wmiMapPage, 2); } //Increased PWM resolution from 0-100 to 0-200 to match VVT

    //Default values for pulsed hw test modes
    configPage13.hwTestInjDuration = 8;
    configPage13.hwTestIgnDuration = 4;

    //DFCO taper default values (Feature disabled by default)
    configPage9.dfcoTaperEnable = 0; //Disable
    configPage9.dfcoTaperTime = 10; //1 second
    configPage9.dfcoTaperFuel = 100; //Don't scale fuel
    configPage9.dfcoTaperAdvance = 20; //Reduce 20deg until full fuel cut
    
    //EGO MAP Limits
    configPage9.egoMAPMax = 255, // 255 will be 510 kpa
    configPage9.egoMAPMin = 0,  // 0 will be 0 kpa

    writeAllConfig();
    storeEEPROMVersion(23);
  }
  
  //Final check is always for 255 and 0 (Brand new arduino)
  if( (readEEPROMVersion() == 0) || (readEEPROMVersion() == 255) )
  {
    configPage9.true_address = 0x200;
    
    //Programmable outputs added. Set all to disabled
    configPage13.outputPin[0] = 0;
    configPage13.outputPin[1] = 0;
    configPage13.outputPin[2] = 0;
    configPage13.outputPin[3] = 0;
    configPage13.outputPin[4] = 0;
    configPage13.outputPin[5] = 0;
    configPage13.outputPin[6] = 0;
    configPage13.outputPin[7] = 0;

    configPage4.FILTER_FLEX = FILTER_FLEX_DEFAULT;

    storeEEPROMVersion(CURRENT_DATA_VERSION);
  }

  //Check to see if someone has downgraded versions:
  if( readEEPROMVersion() > CURRENT_DATA_VERSION ) { storeEEPROMVersion(CURRENT_DATA_VERSION); }
}

void multiplyTableLoad(void *pTable, table_type_t key, uint8_t multiplier)
{
  auto y_it = y_begin(pTable, key);
  while(!y_it.at_end())
  {
    *y_it = *y_it * multiplier; 
    ++y_it;
  }
}

void divideTableLoad(void *pTable, table_type_t key, uint8_t divisor)
{
  auto y_it = y_begin(pTable, key);
  while(!y_it.at_end())
  {
    *y_it = *y_it / divisor; //Previous TS scale was 2.0, now is 0.5, 4x increase
    ++y_it;
  }
}

void multiplyTableValue(uint8_t pageNum, uint8_t multiplier)
{
  uint16_t count = getPageSize(pageNum);
  for (uint16_t i = 0; i < count; i++)
  {
    setPageValue(pageNum, i, (uint8_t)(getPageValue(pageNum, i) * multiplier));
  }
}

void divideTableValue(uint8_t pageNum, uint8_t divisor)
{
  uint16_t count = getPageSize(pageNum);
  for (uint16_t i = 0; i < count; i++)
  {
    setPageValue(pageNum, i, (uint8_t)(getPageValue(pageNum, i) / divisor));
  }
}
