/*
 * Settings.c
 *
 *  Created on: 29 Sep 2016
 *      Author: Ralim
 *
 *      This file holds the users settings and saves / restores them to the
 * devices flash
 */

#include "Settings.h"
#include "BSP.h"
#include "Setup.h"
#include "configuration.h"
#include <string.h> // for memset
bool sanitiseSettings();

#ifdef POW_QC_20V
#define QC_VOLTAGE_MAX 220
#else
#define QC_VOLTAGE_MAX 140
#endif

/*
 * This struct must be a multiple of 2 bytes as it is saved / restored from
 * flash in uint16_t chunks
 */
typedef struct {
  uint16_t versionMarker;
  uint16_t length; // Length of valid bytes following
  uint16_t settingsValues[SettingsOptionsLength];
  // used to make this nicely "good enough" aligned to 32 bytes to make driver code trivial
  uint32_t padding;

} systemSettingsType;

//~1024 is common programming size, setting threshold to be lower so we have warning
static_assert(sizeof(systemSettingsType) < 512);
// char (*__kaboom)[sizeof(systemSettingsType)] = 1; // Uncomment to print size at compile time
volatile systemSettingsType systemSettings;

// For every setting we need to store the min/max/increment values
typedef struct {
  const uint16_t min;          // Inclusive minimum value
  const uint16_t max;          // Inclusive maximum value
  const uint16_t increment;    // Standard increment
  const uint16_t defaultValue; // Default vaue after reset
} SettingConstants;

static const SettingConstants settingsConstants[(int)SettingsOptions::SettingsOptionsLength] = {
    //{min,max,increment,default}
    {MIN_TEMP_C, MAX_TEMP_F, 5, 320},                                      // SolderingTemp
    {MIN_TEMP_C, MAX_TEMP_F, 5, 150},                                      // SleepTemp
    {0, 15, 1, SLEEP_TIME},                                                // SleepTime
    {0, 4, 1, CUT_OUT_SETTING},                                            // MinDCVoltageCells
    {24, 38, 1, RECOM_VOL_CELL},                                           // MinVoltageCells
    {90, QC_VOLTAGE_MAX, 2, 90},                                           // QCIdealVoltage
    {0, 2, 1, ORIENTATION_MODE},                                           // OrientationMode
    {0, 9, 1, SENSITIVITY},                                                // Sensitivity
    {0, 1, 1, ANIMATION_LOOP},                                             // AnimationLoop
    {0, settingOffSpeed_t::MAX_VALUE - 1, 1, ANIMATION_SPEED},             // AnimationSpeed
    {0, 3, 1, AUTO_START_MODE},                                            // AutoStartMode
    {0, 60, 1, SHUTDOWN_TIME},                                             // ShutdownTime
    {0, 1, 1, COOLING_TEMP_BLINK},                                         // CoolingTempBlink
    {0, 1, 1, DETAILED_IDLE},                                              // DetailedIDLE
    {0, 1, 1, DETAILED_SOLDERING},                                         // DetailedSoldering
    {0, 1, 1, TEMPERATURE_INF},                                            // TemperatureInF
    {0, 1, 1, DESCRIPTION_SCROLL_SPEED},                                   // DescriptionScrollSpeed
    {0, 2, 1, LOCKING_MODE},                                               // LockingMode
    {0, 99, 1, POWER_PULSE_DEFAULT},                                       // KeepAwakePulse
    {1, POWER_PULSE_WAIT_MAX, 1, POWER_PULSE_WAIT_DEFAULT},                // KeepAwakePulseWait
    {1, POWER_PULSE_DURATION_MAX, 1, POWER_PULSE_DURATION_DEFAULT},        // KeepAwakePulseDuration
    {360, 900, 1, VOLTAGE_DIV},                                            // VoltageDiv
    {0, MAX_TEMP_F, 10, BOOST_TEMP},                                       // BoostTemp
    {MIN_CALIBRATION_OFFSET, 2500, 1, CALIBRATION_OFFSET},                 // CalibrationOffset
    {0, MAX_POWER_LIMIT, POWER_LIMIT_STEPS, POWER_LIMIT},                  // PowerLimit
    {0, 1, 1, REVERSE_BUTTON_TEMP_CHANGE},                                 // ReverseButtonTempChangeEnabled
    {5, TEMP_CHANGE_LONG_STEP_MAX, 5, TEMP_CHANGE_LONG_STEP},              // TempChangeLongStep
    {1, TEMP_CHANGE_SHORT_STEP_MAX, 1, TEMP_CHANGE_SHORT_STEP},            // TempChangeShortStep
    {0, 9, 1, 7},                                                          // HallEffectSensitivity
    {0, 9, 1, 0},                                                          // AccelMissingWarningCounter
    {0, 9, 1, 0},                                                          // PDMissingWarningCounter
    {0, 0xFFFF, 0, 41431 /*EN*/},                                          // UILanguage
    {0, 50, 1, 20},                                                        // PDNegTimeout
    {0, 1, 1, 0},                                                          // OLEDInversion
    {MIN_BRIGHTNESS, MAX_BRIGHTNESS, BRIGHTNESS_STEP, DEFAULT_BRIGHTNESS}, // OLEDBrightness
    {0, 5, 1, 1},                                                          // LOGOTime
    {0, 1, 1, 0},                                                          // CalibrateCJC
    {0, 1, 1, 1},                                                          // BluetoothLE
    {0, 1, 1, 1},                                                          // PDVpdo
    {1, 5, 1, 4},                                                          // ProfilePhases
    {MIN_TEMP_C, MAX_TEMP_F, 5, 90},                                       // ProfilePreheatTemp
    {1, 10, 1, 1},                                                         // ProfilePreheatSpeed
    {MIN_TEMP_C, MAX_TEMP_F, 5, 130},                                      // ProfilePhase1Temp
    {10, 180, 5, 90},                                                      // ProfilePhase1Duration
    {MIN_TEMP_C, MAX_TEMP_F, 5, 140},                                      // ProfilePhase2Temp
    {10, 180, 5, 30},                                                      // ProfilePhase2Duration
    {MIN_TEMP_C, MAX_TEMP_F, 5, 165},                                      // ProfilePhase3Temp
    {10, 180, 5, 30},                                                      // ProfilePhase3Duration
    {MIN_TEMP_C, MAX_TEMP_F, 5, 140},                                      // ProfilePhase4Temp
    {10, 180, 5, 30},                                                      // ProfilePhase4Duration
    {MIN_TEMP_C, MAX_TEMP_F, 5, 90},                                       // ProfilePhase5Temp
    {10, 180, 5, 30},                                                      // ProfilePhase5Duration
    {1, 10, 1, 2},                                                         // ProfileCooldownSpeed
};
static_assert((sizeof(settingsConstants) / sizeof(SettingConstants)) == ((int)SettingsOptions::SettingsOptionsLength));

void saveSettings() { flash_save_buffer((uint8_t *)&systemSettings, sizeof(systemSettingsType)); }

bool loadSettings() {
  // We read the flash
  flash_read_buffer((uint8_t *)&systemSettings, sizeof(systemSettingsType));
  // Then ensure all values are valid
  return sanitiseSettings();
}

bool sanitiseSettings() {
  // For all settings, need to ensure settings are in a valid range
  // First for any not know about due to array growth, reset them and update the length value
  bool dirty = false;
  if (systemSettings.versionMarker != 0x55AA) {
    memset((void *)&systemSettings, 0xFF, sizeof(systemSettings));
    systemSettings.versionMarker = 0x55AA;
    dirty                        = true;
  }
  if (systemSettings.padding != 0xFFFFFFFF) {
    systemSettings.padding = 0xFFFFFFFF; // Force padding to 0xFFFFFFFF so that rolling forwards / back should be easier
    dirty                  = true;
  }
  if (systemSettings.length < (int)SettingsOptions::SettingsOptionsLength) {
    dirty = true;
    for (int i = systemSettings.length; i < (int)SettingsOptions::SettingsOptionsLength; i++) {
      systemSettings.settingsValues[i] = 0xFFFF; // Ensure its as if it was erased
    }
    systemSettings.length = (int)SettingsOptions::SettingsOptionsLength;
  }
  for (int i = 0; i < (int)SettingsOptions::SettingsOptionsLength; i++) {
    // Check min max for all settings, if outside the range, move to default
    if (systemSettings.settingsValues[i] < settingsConstants[i].min || systemSettings.settingsValues[i] > settingsConstants[i].max) {
      systemSettings.settingsValues[i] = settingsConstants[i].defaultValue;
      dirty                            = true;
    }
  }
  if (dirty) {
    saveSettings();
  }
  return dirty;
}
void resetSettings() {
  memset((void *)&systemSettings, 0xFF, sizeof(systemSettingsType));
  sanitiseSettings();
  saveSettings(); // Save defaults
}

void setSettingValue(const enum SettingsOptions option, const uint16_t newValue) {
  const auto constants                       = settingsConstants[(int)option];
  uint16_t constrainedValue = newValue;
  if (constrainedValue < constants.min) {
    // If less than min, constrain
    constrainedValue = constants.min;
  }
  else if (constrainedValue > constants.max) {
    // If hit max, constrain
    constrainedValue = constants.max;
  }
  systemSettings.settingsValues[(int)option] = constrainedValue;
}
// Lookup wrapper for ease of use (with typing)
uint16_t getSettingValue(const enum SettingsOptions option) { return systemSettings.settingsValues[(int)option]; }

// Increment by the step size to the next value. If past the end wrap to the minimum
// Returns true if we are on the _last_ value
bool nextSettingValue(const enum SettingsOptions option) {
  const auto constants = settingsConstants[(int)option];
  if (systemSettings.settingsValues[(int)option] == (constants.max)) {
    // Already at max, wrap to the start
    systemSettings.settingsValues[(int)option] = constants.min;
  } else if (systemSettings.settingsValues[(int)option] >= (constants.max - constants.increment)) {
    // If within one increment of the end, constrain to the end
    systemSettings.settingsValues[(int)option] = constants.max;
  } else {
    // Otherwise increment
    systemSettings.settingsValues[(int)option] += constants.increment;
  }
  // Return if we are at the max
  return constants.max == systemSettings.settingsValues[(int)option];
}

// Step backwards on the settings item
// Return true if we are at the end (min)
bool prevSettingValue(const enum SettingsOptions option) {
  const auto constants = settingsConstants[(int)option];
  if (systemSettings.settingsValues[(int)option] == (constants.min)) {
    // Already at min, wrap to the max
    systemSettings.settingsValues[(int)option] = constants.max;
  } else if (systemSettings.settingsValues[(int)option] <= (constants.min + constants.increment)) {
    // If within one increment of the start, constrain to the start
    systemSettings.settingsValues[(int)option] = constants.min;
  } else {
    // Otherwise decrement
    systemSettings.settingsValues[(int)option] -= constants.increment;
  }
  // Return if we are at the min
  return constants.min == systemSettings.settingsValues[(int)option];
}
uint16_t lookupHallEffectThreshold() {
  // Return the threshold above which the hall effect sensor is "activated"
  // We want this to be roughly exponentially mapped from 0-1000
  switch (getSettingValue(SettingsOptions::HallEffectSensitivity)) {
  case 0:
    return 0;
  case 1:
    return 1000;
  case 2:
    return 750;
  case 3:
    return 500;
  case 4:
    return 250;
  case 5:
    return 150;
  case 6:
    return 100;
  case 7:
    return 75;
  case 8:
    return 50;
  case 9:
    return 25;
  default:
    return 0; // Off
  }
}
// Lookup function for cutoff setting -> X10 voltage
/*
 * 0=DC
 * 1=3S
 * 2=4S
 * 3=5S
 * 4=6S
 */
uint8_t lookupVoltageLevel() {
  auto minVoltageOnCell    = getSettingValue(SettingsOptions::MinDCVoltageCells);
  auto minVoltageCellCount = getSettingValue(SettingsOptions::MinVoltageCells);
  if (minVoltageOnCell == 0)
    return 90; // 9V since iron does not function effectively below this
  else
    return (minVoltageOnCell * minVoltageCellCount) + (minVoltageCellCount * 2);
}
