#ifndef LINUX_DEVICES_DEVICEDEFINITIONS_PAYLOADPCDUDEFINITIONS_H_
#define LINUX_DEVICES_DEVICEDEFINITIONS_PAYLOADPCDUDEFINITIONS_H_

#include <fsfw/datapoollocal/StaticLocalDataSet.h>
#include <fsfw/devicehandlers/DeviceHandlerIF.h>
#include <mission/memory/NvmParameterBase.h>
#include <mission/tcs/max1227.h>

#include <cstddef>
#include <filesystem>
#include <nlohmann/json.hpp>

#include "OBSWConfig.h"

namespace plpcdu {

using namespace max1227;

enum PlPcduAdcChannels : uint8_t {
  U_BAT_DIV_6 = 0,
  // According to schematic, will be 2.2158V for Vneg = +0V and 0.2446V for Vneg = -6V
  // Full Forumula: V_neg = V_post - (R1 + R2) / R1 * (V_pos - V_out) with R1 being 27.4k
  // and R2 being 49.9k. FB = Feedback
  U_NEG_V_FB = 1,
  I_HPA = 2,
  U_HPA_DIV_6 = 3,
  I_MPA = 4,
  U_MPA_DIV_6 = 5,
  I_TX = 6,
  U_TX_DIV_6 = 7,
  I_X8 = 8,
  U_X8_DIV_6 = 9,
  I_DRO = 10,
  U_DRO_DIV_6 = 11,
  NUM_CHANNELS = 12
};

enum PlPcduParamIds : uint8_t {
  NEG_V_LOWER_BOUND = 0,
  NEG_V_UPPER_BOUND = 1,
  DRO_U_LOWER_BOUND = 2,
  DRO_U_UPPER_BOUND = 3,
  DRO_I_UPPER_BOUND = 4,
  X8_U_LOWER_BOUND = 5,
  X8_U_UPPER_BOUND = 6,
  X8_I_UPPER_BOUND = 7,
  TX_U_LOWER_BOUND = 8,
  TX_U_UPPER_BOUND = 9,
  TX_I_UPPER_BOUND = 10,
  MPA_U_LOWER_BOUND = 11,
  MPA_U_UPPER_BOUND = 12,
  MPA_I_UPPER_BOUND = 13,
  HPA_U_LOWER_BOUND = 14,
  HPA_U_UPPER_BOUND = 15,
  HPA_I_UPPER_BOUND = 16,

  SSR_TO_DRO_WAIT_TIME = 17,
  DRO_TO_X8_WAIT_TIME = 18,
  X8_TO_TX_WAIT_TIME = 19,
  TX_TO_MPA_WAIT_TIME = 20,
  MPA_TO_HPA_WAIT_TIME = 21,

  INJECT_SSR_TO_DRO_FAILURE = 30,
  INJECT_DRO_TO_X8_FAILURE = 31,
  INJECT_X8_TO_TX_FAILURE = 32,
  INJECT_TX_TO_MPA_FAILURE = 33,
  INJECT_MPA_TO_HPA_FAILURE = 34,
  INJECT_ALL_ON_FAILURE = 35
};

static std::map<PlPcduParamIds, std::string> PARAM_KEY_MAP = {
    {NEG_V_LOWER_BOUND, "negVoltLowerBound"}, {NEG_V_UPPER_BOUND, "negVoltUpperBound"},
    {DRO_U_LOWER_BOUND, "droVoltLowerBound"}, {DRO_U_UPPER_BOUND, "droVoltUpperBound"},
    {DRO_I_UPPER_BOUND, "droCurrUpperBound"}, {X8_U_LOWER_BOUND, "x8VoltLowerBound"},
    {X8_U_UPPER_BOUND, "x8VoltUpperBound"},   {X8_I_UPPER_BOUND, "x8CurrUpperBound"},
    {TX_U_LOWER_BOUND, "txVoltLowerBound"},   {TX_U_UPPER_BOUND, "txVoltUpperBound"},
    {TX_I_UPPER_BOUND, "txCurrUpperBound"},   {MPA_U_LOWER_BOUND, "mpaVoltLowerBound"},
    {MPA_U_UPPER_BOUND, "mpaVoltUpperBound"}, {MPA_I_UPPER_BOUND, "mpaCurrUpperBound"},
    {HPA_U_LOWER_BOUND, "hpaVoltLowerBound"}, {HPA_U_UPPER_BOUND, "hpaVoltUpperBound"},
    {HPA_I_UPPER_BOUND, "hpaCurrUpperBound"}, {SSR_TO_DRO_WAIT_TIME, "ssrToDroWait"},
    {DRO_TO_X8_WAIT_TIME, "droToX8Wait"},     {X8_TO_TX_WAIT_TIME, "x8ToTxWait"},
    {TX_TO_MPA_WAIT_TIME, "txToMpaWait"},     {MPA_TO_HPA_WAIT_TIME, "mpaToHpaWait"},
};

enum PlPcduPoolIds : uint32_t { CHANNEL_VEC = 0, PROCESSED_VEC = 1, TEMP = 2 };

static constexpr size_t MAX_ADC_REPLY_SIZE = 64;

static constexpr DeviceCommandId_t READ_CMD = 0;
static constexpr DeviceCommandId_t SETUP_CMD = 1;
static constexpr DeviceCommandId_t READ_TEMP_EXT = 2;
static constexpr DeviceCommandId_t READ_WITH_TEMP_EXT = 3;

enum NormalSubmodeBits {
  SOLID_STATE_RELAYS_ADC_ON = 0,
  DRO_ON = 1,
  X8_ON = 2,
  TX_ON = 3,
  MPA_ON = 4,
  HPA_ON = 5
};

static constexpr Submode_t ALL_OFF_SUBMODE = 0;
static constexpr Submode_t ALL_ON_SUBMODE = (1 << HPA_ON) | (1 << MPA_ON) | (1 << TX_ON) |
                                            (1 << X8_ON) | (1 << DRO_ON) |
                                            (1 << SOLID_STATE_RELAYS_ADC_ON);

// 12 ADC values * 2 + trailing zero
static constexpr size_t ADC_REPLY_SIZE = 25;
// Conversion byte + 24 * zero
static constexpr size_t TEMP_REPLY_SIZE = 25;

static constexpr uint8_t SETUP_BYTE =
    max1227::buildSetupByte(ClkSel::EXT_CONV_EXT_TIMED, RefSel::INT_REF_NO_WAKEUP, DiffSel::NONE_0);

static constexpr uint32_t ADC_SET_ID = READ_CMD;
static constexpr uint8_t CHANNELS_NUM = 12;
static constexpr uint8_t CHANNEL_N = CHANNELS_NUM - 1;
// Store temperature as well
static constexpr size_t DATASET_ENTRIES = CHANNELS_NUM + 1;

static constexpr uint8_t VOLTAGE_DIV = 6;
// 12-bit ADC: 2 to the power of 12 minus 1
static constexpr uint16_t MAX122X_BIT = 4095;
static constexpr float MAX122X_VREF = 2.5;
static constexpr float GAIN_INA169 = 100.0;
static constexpr float R_SHUNT_HPA = 0.008;
static constexpr float R_SHUNT_MPA = 0.015;
static constexpr float R_SHUNT_TX = 0.05;
static constexpr float R_SHUNT_X8 = 0.015;
static constexpr float R_SHUNT_DRO = 0.22;
static constexpr float V_POS = 3.3;
static constexpr float VOLTAGE_DIV_U_NEG = (49.9 + 27.4) / 27.4;
static constexpr float MAX122X_SCALE = MAX122X_VREF / MAX122X_BIT;
static constexpr float SCALE_VOLTAGE = MAX122X_SCALE * VOLTAGE_DIV;
static constexpr float SCALE_CURRENT_HPA = MAX122X_SCALE / (GAIN_INA169 * R_SHUNT_HPA);
static constexpr float SCALE_CURRENT_MPA = MAX122X_SCALE / (GAIN_INA169 * R_SHUNT_MPA);
static constexpr float SCALE_CURRENT_TX = MAX122X_SCALE / (GAIN_INA169 * R_SHUNT_TX);
static constexpr float SCALE_CURRENT_X8 = MAX122X_SCALE / (GAIN_INA169 * R_SHUNT_X8);
static constexpr float SCALE_CURRENT_DRO = MAX122X_SCALE / (GAIN_INA169 * R_SHUNT_DRO);

// TODO: Make these configurable parameters using a JSON file
// Upper bound of currents in milliamperes [mA]
static constexpr double DFT_NEG_V_LOWER_BOUND = -6.5;
static constexpr double DFT_NEG_V_UPPER_BOUND = -2.7;

static constexpr double DFT_DRO_U_LOWER_BOUND = 5.0;
static constexpr double DFT_DRO_U_UPPER_BOUND = 7.0;
// Max Current DRO + Max Current Neg V | 40 + 15
static constexpr double DFT_DRO_I_UPPER_BOUND = 55.0;

static constexpr double DFT_X8_U_LOWER_BOUND = 2.6;
static constexpr double DFT_X8_U_UPPER_BOUND = 4.0;
static constexpr double DFT_X8_I_UPPER_BOUND = 100.0;

static constexpr double DFT_TX_U_LOWER_BOUND = 2.6;
static constexpr double DFT_TX_U_UPPER_BOUND = 4.0;
static constexpr double DFT_TX_I_UPPER_BOUND = 250.0;

static constexpr double DFT_MPA_U_LOWER_BOUND = 2.6;
static constexpr double DFT_MPA_U_UPPER_BOUND = 4.0;
static constexpr double DFT_MPA_I_UPPER_BOUND = 650.0;

static constexpr double DFT_HPA_U_LOWER_BOUND = 9.4;
static constexpr double DFT_HPA_U_UPPER_BOUND = 11.0;
static constexpr double DFT_HPA_I_UPPER_BOUND = 3800.0;

// Wait time in floating point seconds
static constexpr double DFT_SSR_TO_DRO_WAIT_TIME = 5.0;
static constexpr double DFT_DRO_TO_X8_WAIT_TIME = 905.0;
static constexpr double DFT_X8_TO_TX_WAIT_TIME = 5.0;
static constexpr double DFT_TX_TO_MPA_WAIT_TIME = 5.0;
static constexpr double DFT_MPA_TO_HPA_WAIT_TIME = 5.0;

/**
 * The current of the processed values is calculated and stored as a milliamperes [mA].
 * The voltages are stored as Volt values.
 */
class PlPcduAdcSet : public StaticLocalDataSet<DATASET_ENTRIES> {
 public:
  PlPcduAdcSet(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, ADC_SET_ID) {}

  PlPcduAdcSet(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, ADC_SET_ID)) {}

  lp_vec_t<uint16_t, 12> channels = lp_vec_t<uint16_t, 12>(sid.objectId, CHANNEL_VEC, this);
  lp_vec_t<float, 12> processed = lp_vec_t<float, 12>(sid.objectId, PROCESSED_VEC, this);
  lp_var_t<float> tempC = lp_var_t<float>(sid.objectId, TEMP, this);
};

class PlPcduParameter : public NVMParameterBase {
 public:
  PlPcduParameter() : NVMParameterBase(""), mountPrefix("") {
    using namespace plpcdu;
    // Initialize with default values
    resetValues();
  }

  ReturnValue_t initialize(std::string mountPrefix) {
    setFullName(mountPrefix + "/conf/plpcdu.json");
    ReturnValue_t result = readJsonFile();
    if (result != returnvalue::OK) {
      // File does not exist or reading JSON failed for various reason. Rewrite the JSON file
#if OBSW_VERBOSE_LEVEL >= 1
      sif::info << "Creating PL PCDU JSON file at " << getFullName() << std::endl;
#endif
      resetValues();
      writeJsonFile();
    }
    return returnvalue::OK;
  }
  void resetValues() {
    insertValue(PARAM_KEY_MAP[SSR_TO_DRO_WAIT_TIME], DFT_SSR_TO_DRO_WAIT_TIME);
    insertValue(PARAM_KEY_MAP[DRO_TO_X8_WAIT_TIME], DFT_DRO_TO_X8_WAIT_TIME);
    insertValue(PARAM_KEY_MAP[X8_TO_TX_WAIT_TIME], DFT_X8_TO_TX_WAIT_TIME);
    insertValue(PARAM_KEY_MAP[TX_TO_MPA_WAIT_TIME], DFT_TX_TO_MPA_WAIT_TIME);
    insertValue(PARAM_KEY_MAP[MPA_TO_HPA_WAIT_TIME], DFT_MPA_TO_HPA_WAIT_TIME);
    insertValue(PARAM_KEY_MAP[NEG_V_LOWER_BOUND], DFT_NEG_V_LOWER_BOUND);
    insertValue(PARAM_KEY_MAP[NEG_V_UPPER_BOUND], DFT_NEG_V_UPPER_BOUND);
    insertValue(PARAM_KEY_MAP[DRO_U_LOWER_BOUND], DFT_DRO_U_LOWER_BOUND);
    insertValue(PARAM_KEY_MAP[DRO_U_UPPER_BOUND], DFT_DRO_U_UPPER_BOUND);
    insertValue(PARAM_KEY_MAP[DRO_I_UPPER_BOUND], DFT_DRO_I_UPPER_BOUND);
    insertValue(PARAM_KEY_MAP[X8_U_LOWER_BOUND], DFT_X8_U_LOWER_BOUND);
    insertValue(PARAM_KEY_MAP[X8_U_UPPER_BOUND], DFT_X8_U_UPPER_BOUND);
    insertValue(PARAM_KEY_MAP[X8_I_UPPER_BOUND], DFT_X8_I_UPPER_BOUND);
    insertValue(PARAM_KEY_MAP[TX_U_LOWER_BOUND], DFT_TX_U_LOWER_BOUND);
    insertValue(PARAM_KEY_MAP[TX_U_UPPER_BOUND], DFT_TX_U_UPPER_BOUND);
    insertValue(PARAM_KEY_MAP[TX_I_UPPER_BOUND], DFT_TX_I_UPPER_BOUND);
    insertValue(PARAM_KEY_MAP[MPA_U_LOWER_BOUND], DFT_MPA_U_LOWER_BOUND);
    insertValue(PARAM_KEY_MAP[MPA_U_UPPER_BOUND], DFT_MPA_U_UPPER_BOUND);
    insertValue(PARAM_KEY_MAP[MPA_I_UPPER_BOUND], DFT_MPA_I_UPPER_BOUND);
    insertValue(PARAM_KEY_MAP[HPA_U_LOWER_BOUND], DFT_HPA_U_LOWER_BOUND);
    insertValue(PARAM_KEY_MAP[HPA_U_UPPER_BOUND], DFT_HPA_U_UPPER_BOUND);
    insertValue(PARAM_KEY_MAP[HPA_I_UPPER_BOUND], DFT_HPA_I_UPPER_BOUND);
  }

 private:
  std::string mountPrefix;
};

}  // namespace plpcdu

#endif /* LINUX_DEVICES_DEVICEDEFINITIONS_PAYLOADPCDUDEFINITIONS_H_ */