#ifndef MISSION_CONTROLLER_CONTROLLERDEFINITIONS_THERMALCONTROLLERDEFINITIONS_H_
#define MISSION_CONTROLLER_CONTROLLERDEFINITIONS_THERMALCONTROLLERDEFINITIONS_H_

#include <fsfw/datapoollocal/LocalPoolVariable.h>
#include <fsfw/datapoollocal/StaticLocalDataSet.h>

#include "eive/eventSubsystemIds.h"
#include "mission/tcs/defs.h"

namespace tcsCtrl {

/**
 * NOP Limit: Hard limit for device, usually from datasheet. Device damage is possible lif NOP limit
 * is exceeded.
 * OP Limit: Soft limit. Device should be switched off or TCS controller should take action if the
 * limit is exceeded to avoid reaching NOP limit
 */
struct TempLimits {
  TempLimits(float nopLowerLimit, float opLowerLimit, float cutOffLimit, float opUpperLimit,
             float nopUpperLimit)
      : opLowerLimit(opLowerLimit),
        opUpperLimit(opUpperLimit),
        cutOffLimit(cutOffLimit),
        nopLowerLimit(nopLowerLimit),
        nopUpperLimit(nopUpperLimit) {}
  float opLowerLimit;
  float opUpperLimit;
  float cutOffLimit;
  float nopLowerLimit;
  float nopUpperLimit;
};

/**
 * Abstraction for the state of a single thermal component
 */
struct ThermalState {
  uint8_t noSensorAvailableCounter;
  // Which sensor is used for this component?
  uint8_t sensorIndex = 0;
  // Is heating on for that thermal module?
  bool heating = false;
  // Which switch is being used for heating the component
  heater::Switch heaterSwitch = heater::Switch::HEATER_NONE;
  // Heater start time and end times as UNIX seconds. Please note that these times will be updated
  // when a switch command is sent, with no guarantess that the heater actually went on.
  uint32_t heaterStartTime = 0;
  uint32_t heaterEndTime = 0;
};

/**
 * Abstraction for the state of a single heater.
 */
struct HeaterState {
  bool switchTransition = false;
  heater::SwitchState target = heater::SwitchState::OFF;
  uint8_t heaterSwitchControlCycles = 0;
  bool trackHeaterMaxBurnTime = false;
  Countdown heaterOnMaxBurnTime;
};

using HeaterSwitchStates = std::array<heater::SwitchState, heater::NUMBER_OF_SWITCHES>;

enum ThermalComponents : uint8_t {
  NONE = 0,
  ACS_BOARD = 1,
  MGT = 2,
  RW = 3,
  STR = 4,
  IF_BOARD = 5,
  TCS_BOARD = 6,
  OBC = 7,
  LEGACY_OBCIF_BOARD = 8,
  SBAND_TRANSCEIVER = 9,
  PCDUP60_BOARD = 10,
  PCDUACU = 11,
  PCDUPDU = 12,
  PLPCDU_BOARD = 13,
  PLOCMISSION_BOARD = 14,
  PLOCPROCESSING_BOARD = 15,
  DAC = 16,
  CAMERA = 17,
  DRO = 18,
  X8 = 19,
  HPA = 20,
  TX = 21,
  MPA = 22,
  SCEX_BOARD = 23,
  NUM_THERMAL_COMPONENTS
};

static const uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::TCS_CONTROLLER;
static constexpr Event NO_VALID_SENSOR_TEMPERATURE = MAKE_EVENT(0, severity::MEDIUM);
static constexpr Event NO_HEALTHY_HEATER_AVAILABLE = MAKE_EVENT(1, severity::MEDIUM);
static constexpr Event SYRLINKS_OVERHEATING = MAKE_EVENT(2, severity::HIGH);
static constexpr Event OBC_OVERHEATING = MAKE_EVENT(4, severity::HIGH);
static constexpr Event CAMERA_OVERHEATING = MAKE_EVENT(5, severity::HIGH);
static constexpr Event PCDU_SYSTEM_OVERHEATING = MAKE_EVENT(6, severity::HIGH);
static constexpr Event HEATER_NOT_OFF_FOR_OFF_MODE = MAKE_EVENT(7, severity::MEDIUM);
static constexpr Event MGT_OVERHEATING = MAKE_EVENT(8, severity::HIGH);
//! [EXPORT] : [COMMENT] P1: Module index. P2: Heater index
static constexpr Event TCS_SWITCHING_HEATER_ON = MAKE_EVENT(9, severity::INFO);
//! [EXPORT] : [COMMENT] P1: Module index. P2: Heater index
static constexpr Event TCS_SWITCHING_HEATER_OFF = MAKE_EVENT(10, severity::INFO);
//! [EXPORT] : [COMMENT] P1: Heater index. P2: Maximum burn time for heater.
static constexpr Event TCS_HEATER_MAX_BURN_TIME_REACHED = MAKE_EVENT(11, severity::MEDIUM);

enum SetId : uint32_t {
  SENSOR_TEMPERATURES = 0,
  DEVICE_TEMPERATURES = 1,
  SUS_TEMPERATURES = 2,
  COMPONENT_TEMPERATURES = 3,
  HEATER_SET = 4,
  TCS_CTRL_INFO = 5
};

enum PoolIds : lp_id_t {
  SENSOR_PLOC_HEATSPREADER,
  SENSOR_PLOC_MISSIONBOARD,
  SENSOR_4K_CAMERA,
  SENSOR_DAC_HEATSPREADER,
  SENSOR_STARTRACKER,
  SENSOR_RW1,
  SENSOR_DRO,
  SENSOR_SCEX,
  SENSOR_X8,
  SENSOR_HPA,
  SENSOR_TX_MODUL,
  SENSOR_MPA,
  SENSOR_ACU,
  SENSOR_PLPCDU_HEATSPREADER,
  SENSOR_TCS_BOARD,
  SENSOR_MAGNETTORQUER,
  SENSOR_TMP1075_TCS_0,
  SENSOR_TMP1075_TCS_1,
  SENSOR_TMP1075_PLPCDU_0,
  SENSOR_TMP1075_PLPCDU_1,
  SENSOR_TMP1075_IF_BOARD,

  SUS_0_N_LOC_XFYFZM_PT_XF,
  SUS_6_R_LOC_XFYBZM_PT_XF,
  SUS_1_N_LOC_XBYFZM_PT_XB,
  SUS_7_R_LOC_XBYBZM_PT_XB,
  SUS_2_N_LOC_XFYBZB_PT_YB,
  SUS_8_R_LOC_XBYBZB_PT_YB,
  SUS_3_N_LOC_XFYBZF_PT_YF,
  SUS_9_R_LOC_XBYBZB_PT_YF,
  SUS_4_N_LOC_XMYFZF_PT_ZF,
  SUS_10_N_LOC_XMYBZF_PT_ZF,
  SUS_5_N_LOC_XFYMZB_PT_ZB,
  SUS_11_R_LOC_XBYMZB_PT_ZB,

  COMPONENT_RW,

  TEMP_Q7S,
  BATTERY_TEMP_1,
  BATTERY_TEMP_2,
  BATTERY_TEMP_3,
  BATTERY_TEMP_4,
  TEMP_RW1,
  TEMP_RW2,
  TEMP_RW3,
  TEMP_RW4,
  TEMP_STAR_TRACKER,
  TEMP_SYRLINKS_POWER_AMPLIFIER,
  TEMP_SYRLINKS_BASEBAND_BOARD,
  TEMP_MGT,
  TEMP_ACU,
  TEMP_PDU1,
  TEMP_PDU2,
  TEMP_1_P60DOCK,
  TEMP_2_P60DOCK,
  TEMP_GYRO_0_SIDE_A,
  TEMP_GYRO_1_SIDE_A,
  TEMP_GYRO_2_SIDE_B,
  TEMP_GYRO_3_SIDE_B,
  TEMP_MGM_0_SIDE_A,
  TEMP_MGM_2_SIDE_B,
  TEMP_ADC_PAYLOAD_PCDU,

  HEATER_SWITCH_LIST,
  HEATER_CURRENT,

  HEATER_ON_FOR_COMPONENT_VEC,
  SENSOR_USED_FOR_TCS_CTRL,
  HEATER_IDX_USED_FOR_TCS_CTRL,
  HEATER_START_TIME,
  HEATER_END_TIME
};

static const uint8_t ENTRIES_SENSOR_TEMPERATURE_SET = 25;
static const uint8_t ENTRIES_DEVICE_TEMPERATURE_SET = 25;
static const uint8_t ENTRIES_SUS_TEMPERATURE_SET = 12;

/**
 * @brief   This dataset can be used to store the collected temperatures of all temperature sensors
 */
class SensorTemperatures : public StaticLocalDataSet<ENTRIES_SENSOR_TEMPERATURE_SET> {
 public:
  explicit SensorTemperatures(HasLocalDataPoolIF* owner)
      : StaticLocalDataSet(owner, SENSOR_TEMPERATURES) {}

  explicit SensorTemperatures(object_id_t objectId)
      : StaticLocalDataSet(sid_t(objectId, SENSOR_TEMPERATURES)) {}

  lp_var_t<float> plocHeatspreader =
      lp_var_t<float>(sid.objectId, PoolIds::SENSOR_PLOC_HEATSPREADER, this);
  lp_var_t<float> plocMissionboard =
      lp_var_t<float>(sid.objectId, PoolIds::SENSOR_PLOC_MISSIONBOARD, this);
  lp_var_t<float> payload4kCamera = lp_var_t<float>(sid.objectId, PoolIds::SENSOR_4K_CAMERA, this);
  lp_var_t<float> dacHeatspreader =
      lp_var_t<float>(sid.objectId, PoolIds::SENSOR_DAC_HEATSPREADER, this);
  lp_var_t<float> startracker = lp_var_t<float>(sid.objectId, PoolIds::SENSOR_STARTRACKER, this);
  lp_var_t<float> rw1 = lp_var_t<float>(sid.objectId, PoolIds::SENSOR_RW1, this);
  lp_var_t<float> scex = lp_var_t<float>(sid.objectId, PoolIds::SENSOR_SCEX, this);
  lp_var_t<float> eBandTx = lp_var_t<float>(sid.objectId, PoolIds::SENSOR_TX_MODUL, this);
  // E-Band module
  lp_var_t<float> dro = lp_var_t<float>(sid.objectId, PoolIds::SENSOR_DRO, this);
  lp_var_t<float> mpa = lp_var_t<float>(sid.objectId, PoolIds::SENSOR_MPA, this);
  lp_var_t<float> x8 = lp_var_t<float>(sid.objectId, PoolIds::SENSOR_X8, this);
  lp_var_t<float> hpa = lp_var_t<float>(sid.objectId, PoolIds::SENSOR_HPA, this);
  lp_var_t<float> acu = lp_var_t<float>(sid.objectId, PoolIds::SENSOR_ACU, this);
  lp_var_t<float> plpcduHeatspreader =
      lp_var_t<float>(sid.objectId, PoolIds::SENSOR_PLPCDU_HEATSPREADER, this);
  lp_var_t<float> tcsBoard = lp_var_t<float>(sid.objectId, PoolIds::SENSOR_TCS_BOARD, this);
  lp_var_t<float> mgt = lp_var_t<float>(sid.objectId, PoolIds::SENSOR_MAGNETTORQUER, this);
  lp_var_t<float> tmp1075Tcs0 = lp_var_t<float>(sid.objectId, PoolIds::SENSOR_TMP1075_TCS_0, this);
  lp_var_t<float> tmp1075Tcs1 = lp_var_t<float>(sid.objectId, PoolIds::SENSOR_TMP1075_TCS_1, this);
  lp_var_t<float> tmp1075PlPcdu0 =
      lp_var_t<float>(sid.objectId, PoolIds::SENSOR_TMP1075_PLPCDU_0, this);
  lp_var_t<float> tmp1075PlPcdu1 =
      lp_var_t<float>(sid.objectId, PoolIds::SENSOR_TMP1075_PLPCDU_1, this);
  lp_var_t<float> tmp1075IfBrd =
      lp_var_t<float>(sid.objectId, PoolIds::SENSOR_TMP1075_IF_BOARD, this);
};

/**
 * @brief   This dataset can be used to store the collected temperatures of all device temperature
 *          sensors
 */
class DeviceTemperatures : public StaticLocalDataSet<ENTRIES_DEVICE_TEMPERATURE_SET> {
 public:
  explicit DeviceTemperatures(HasLocalDataPoolIF* owner)
      : StaticLocalDataSet(owner, DEVICE_TEMPERATURES) {}

  explicit DeviceTemperatures(object_id_t objectId)
      : StaticLocalDataSet(sid_t(objectId, DEVICE_TEMPERATURES)) {}

  lp_var_t<float> q7s = lp_var_t<float>(sid.objectId, PoolIds::TEMP_Q7S, this);
  lp_var_t<int16_t> batteryTemp1 = lp_var_t<int16_t>(sid.objectId, PoolIds::BATTERY_TEMP_1, this);
  lp_var_t<int16_t> batteryTemp2 = lp_var_t<int16_t>(sid.objectId, PoolIds::BATTERY_TEMP_2, this);
  lp_var_t<int16_t> batteryTemp3 = lp_var_t<int16_t>(sid.objectId, PoolIds::BATTERY_TEMP_3, this);
  lp_var_t<int16_t> batteryTemp4 = lp_var_t<int16_t>(sid.objectId, PoolIds::BATTERY_TEMP_4, this);
  lp_var_t<int32_t> rw1 = lp_var_t<int32_t>(sid.objectId, PoolIds::TEMP_RW1, this);
  lp_var_t<int32_t> rw2 = lp_var_t<int32_t>(sid.objectId, PoolIds::TEMP_RW2, this);
  lp_var_t<int32_t> rw3 = lp_var_t<int32_t>(sid.objectId, PoolIds::TEMP_RW3, this);
  lp_var_t<int32_t> rw4 = lp_var_t<int32_t>(sid.objectId, PoolIds::TEMP_RW4, this);
  lp_var_t<float> startracker = lp_var_t<float>(sid.objectId, PoolIds::TEMP_STAR_TRACKER, this);
  lp_var_t<float> syrlinksPowerAmplifier =
      lp_var_t<float>(sid.objectId, PoolIds::TEMP_SYRLINKS_POWER_AMPLIFIER, this);
  lp_var_t<float> syrlinksBasebandBoard =
      lp_var_t<float>(sid.objectId, PoolIds::TEMP_SYRLINKS_BASEBAND_BOARD, this);
  lp_var_t<int16_t> mgt = lp_var_t<int16_t>(sid.objectId, PoolIds::TEMP_MGT, this);
  lp_vec_t<float, 3> acu = lp_vec_t<float, 3>(sid.objectId, PoolIds::TEMP_ACU, this);
  lp_var_t<float> pdu1 = lp_var_t<float>(sid.objectId, PoolIds::TEMP_PDU1, this);
  lp_var_t<float> pdu2 = lp_var_t<float>(sid.objectId, PoolIds::TEMP_PDU2, this);
  lp_var_t<float> temp1P60dock = lp_var_t<float>(sid.objectId, PoolIds::TEMP_1_P60DOCK, this);
  lp_var_t<float> temp2P60dock = lp_var_t<float>(sid.objectId, PoolIds::TEMP_2_P60DOCK, this);
  lp_var_t<float> gyro0SideA = lp_var_t<float>(sid.objectId, PoolIds::TEMP_GYRO_0_SIDE_A, this);
  lp_var_t<float> gyro1SideA = lp_var_t<float>(sid.objectId, PoolIds::TEMP_GYRO_1_SIDE_A, this);
  lp_var_t<float> gyro2SideB = lp_var_t<float>(sid.objectId, PoolIds::TEMP_GYRO_2_SIDE_B, this);
  lp_var_t<float> gyro3SideB = lp_var_t<float>(sid.objectId, PoolIds::TEMP_GYRO_3_SIDE_B, this);
  lp_var_t<float> mgm0SideA = lp_var_t<float>(sid.objectId, PoolIds::TEMP_MGM_0_SIDE_A, this);
  lp_var_t<float> mgm2SideB = lp_var_t<float>(sid.objectId, PoolIds::TEMP_MGM_2_SIDE_B, this);
  lp_var_t<float> adcPayloadPcdu =
      lp_var_t<float>(sid.objectId, PoolIds::TEMP_ADC_PAYLOAD_PCDU, this);
};

/**
 * @brief   This dataset can be used to store the collected temperatures of all SUS temperature
 * sensors
 */
class SusTemperatures : public StaticLocalDataSet<ENTRIES_SUS_TEMPERATURE_SET> {
 public:
  explicit SusTemperatures(HasLocalDataPoolIF* owner)
      : StaticLocalDataSet(owner, SUS_TEMPERATURES) {}

  explicit SusTemperatures(object_id_t objectId)
      : StaticLocalDataSet(sid_t(objectId, SUS_TEMPERATURES)) {}

  lp_var_t<float> sus_0_n_loc_xfyfzm_pt_xf =
      lp_var_t<float>(sid.objectId, PoolIds::SUS_0_N_LOC_XFYFZM_PT_XF, this);
  lp_var_t<float> sus_6_r_loc_xfybzm_pt_xf =
      lp_var_t<float>(sid.objectId, PoolIds::SUS_6_R_LOC_XFYBZM_PT_XF, this);
  lp_var_t<float> sus_1_n_loc_xbyfzm_pt_xb =
      lp_var_t<float>(sid.objectId, PoolIds::SUS_1_N_LOC_XBYFZM_PT_XB, this);
  lp_var_t<float> sus_7_r_loc_xbybzm_pt_xb =
      lp_var_t<float>(sid.objectId, PoolIds::SUS_7_R_LOC_XBYBZM_PT_XB, this);
  lp_var_t<float> sus_2_n_loc_xfybzb_pt_yb =
      lp_var_t<float>(sid.objectId, PoolIds::SUS_2_N_LOC_XFYBZB_PT_YB, this);
  lp_var_t<float> sus_8_r_loc_xbybzb_pt_yb =
      lp_var_t<float>(sid.objectId, PoolIds::SUS_8_R_LOC_XBYBZB_PT_YB, this);
  lp_var_t<float> sus_3_n_loc_xfybzf_pt_yf =
      lp_var_t<float>(sid.objectId, PoolIds::SUS_3_N_LOC_XFYBZF_PT_YF, this);
  lp_var_t<float> sus_9_r_loc_xbybzb_pt_yf =
      lp_var_t<float>(sid.objectId, PoolIds::SUS_9_R_LOC_XBYBZB_PT_YF, this);
  lp_var_t<float> sus_4_n_loc_xmyfzf_pt_zf =
      lp_var_t<float>(sid.objectId, PoolIds::SUS_4_N_LOC_XMYFZF_PT_ZF, this);
  lp_var_t<float> sus_10_n_loc_xmybzf_pt_zf =
      lp_var_t<float>(sid.objectId, PoolIds::SUS_10_N_LOC_XMYBZF_PT_ZF, this);
  lp_var_t<float> sus_5_n_loc_xfymzb_pt_zb =
      lp_var_t<float>(sid.objectId, PoolIds::SUS_5_N_LOC_XFYMZB_PT_ZB, this);
  lp_var_t<float> sus_11_r_loc_xbymzb_pt_zb =
      lp_var_t<float>(sid.objectId, PoolIds::SUS_11_R_LOC_XBYMZB_PT_ZB, this);
};

class HeaterInfo : public StaticLocalDataSet<3> {
 public:
  HeaterInfo(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, HEATER_SET) {}
  HeaterInfo(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, HEATER_SET)) {}

  lp_vec_t<uint8_t, heater::NUMBER_OF_SWITCHES> heaterSwitchState =
      lp_vec_t<uint8_t, heater::NUMBER_OF_SWITCHES>(sid.objectId, PoolIds::HEATER_SWITCH_LIST,
                                                    this);
  lp_var_t<int16_t> heaterCurrent = lp_var_t<int16_t>(sid.objectId, PoolIds::HEATER_CURRENT, this);
};

class TcsCtrlInfo : public StaticLocalDataSet<6> {
 public:
  explicit TcsCtrlInfo(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, TCS_CTRL_INFO) {}

  explicit TcsCtrlInfo(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, TCS_CTRL_INFO)) {}

  lp_vec_t<uint8_t, tcsCtrl::NUM_THERMAL_COMPONENTS> heatingOnVec =
      lp_vec_t<uint8_t, tcsCtrl::NUM_THERMAL_COMPONENTS>(
          sid.objectId, PoolIds::HEATER_ON_FOR_COMPONENT_VEC, this);
  lp_vec_t<uint8_t, tcsCtrl::NUM_THERMAL_COMPONENTS> sensorIdxUsedForTcsCtrl =
      lp_vec_t<uint8_t, tcsCtrl::NUM_THERMAL_COMPONENTS>(sid.objectId,
                                                         PoolIds::SENSOR_USED_FOR_TCS_CTRL, this);
  lp_vec_t<uint8_t, tcsCtrl::NUM_THERMAL_COMPONENTS> heaterSwitchIdx =
      lp_vec_t<uint8_t, tcsCtrl::NUM_THERMAL_COMPONENTS>(
          sid.objectId, PoolIds::HEATER_IDX_USED_FOR_TCS_CTRL, this);
  lp_vec_t<uint32_t, tcsCtrl::NUM_THERMAL_COMPONENTS> heaterStartTimes =
      lp_vec_t<uint32_t, tcsCtrl::NUM_THERMAL_COMPONENTS>(sid.objectId, PoolIds::HEATER_START_TIME,
                                                          this);
  lp_vec_t<uint32_t, tcsCtrl::NUM_THERMAL_COMPONENTS> heaterEndTimes =
      lp_vec_t<uint32_t, tcsCtrl::NUM_THERMAL_COMPONENTS>(sid.objectId, PoolIds::HEATER_END_TIME,
                                                          this);
};

}  // namespace tcsCtrl

#endif /* MISSION_CONTROLLER_CONTROLLERDEFINITIONS_THERMALCONTROLLERDEFINITIONS_H_ */