#ifndef MISSION_CONTROLLER_THERMALCONTROLLER_H_
#define MISSION_CONTROLLER_THERMALCONTROLLER_H_

#include <bsp_q7s/core/defs.h>
#include <fsfw/controller/ExtendedControllerBase.h>
#include <fsfw/devicehandlers/DeviceHandlerThermalSet.h>
#include <fsfw/timemanager/Countdown.h>
#include <fsfw_hal/devicehandlers/devicedefinitions/gyroL3gHelpers.h>
#include <fsfw_hal/devicehandlers/devicedefinitions/mgmLis3Helpers.h>
#include <mission/acs/gyroAdisHelpers.h>
#include <mission/acs/imtqHelpers.h>
#include <mission/acs/rwHelpers.h>
#include <mission/acs/str/strHelpers.h>
#include <mission/acs/susMax1227Helpers.h>
#include <mission/com/syrlinksDefs.h>
#include <mission/controller/tcsDefs.h>
#include <mission/payload/payloadPcduDefinitions.h>
#include <mission/power/bpxBattDefs.h>
#include <mission/power/gsDefs.h>
#include <mission/tcs/HeaterHandler.h>
#include <mission/tcs/Max31865Definitions.h>
#include <mission/tcs/Tmp1075Definitions.h>
#include <mission/utility/trace.h>

#include <atomic>
#include <list>
#include <optional>

class ThermalController : public ExtendedControllerBase {
 public:
  static constexpr uint8_t SUBMODE_NO_HEATER_CTRL = 1;

  static const uint16_t INVALID_TEMPERATURE = 999;
  static const uint8_t NUMBER_OF_SENSORS = 16;
  static constexpr int16_t SANITY_LIMIT_LOWER_TEMP = -80;
  static constexpr int16_t SANITY_LIMIT_UPPER_TEMP = 160;

  // 1 hour
  static constexpr uint32_t DEFAULT_MAX_HEATER_ON_DURATION_MS = 60 * 60 * 1000;
  static constexpr uint32_t MAX_HEATER_ON_DURATIONS_MS[8] = {// PLOC PROC board
                                                             DEFAULT_MAX_HEATER_ON_DURATION_MS,
                                                             // PCDU PDU
                                                             DEFAULT_MAX_HEATER_ON_DURATION_MS,
                                                             // ACS Board
                                                             DEFAULT_MAX_HEATER_ON_DURATION_MS,
                                                             // OBC Board
                                                             DEFAULT_MAX_HEATER_ON_DURATION_MS,
                                                             // Camera
                                                             DEFAULT_MAX_HEATER_ON_DURATION_MS,
                                                             // STR
                                                             DEFAULT_MAX_HEATER_ON_DURATION_MS,
                                                             // DRO
                                                             DEFAULT_MAX_HEATER_ON_DURATION_MS,
                                                             // S-Band
                                                             DEFAULT_MAX_HEATER_ON_DURATION_MS};

  ThermalController(object_id_t objectId, HeaterHandler& heater,
                    const std::atomic_bool& tcsBoardShortUnavailable, bool pollPcdu1Tmp);
  virtual ~ThermalController();

  ReturnValue_t initialize() override;

 protected:
  struct HeaterContext {
   public:
    HeaterContext(heater::Switch switchNr, heater::Switch redundantSwitchNr,
                  const tcsCtrl::TempLimits& tempLimit)
        : switchNr(switchNr), redSwitchNr(redundantSwitchNr), tempLimit(tempLimit) {}
    bool doHeaterHandling = true;
    heater::Switch switchNr;
    heater::SwitchState switchState = heater::SwitchState::OFF;
    heater::Switch redSwitchNr;
    const tcsCtrl::TempLimits& tempLimit;
  };

  void performThermalModuleCtrl(const tcsCtrl::HeaterSwitchStates& heaterSwitchStates);
  ReturnValue_t handleCommandMessage(CommandMessage* message) override;
  void performControlOperation() override;
  ReturnValue_t initializeLocalDataPool(localpool::DataPool& localDataPoolMap,
                                        LocalDataPoolManager& poolManager) override;
  LocalPoolDataSetBase* getDataSetHandle(sid_t sid) override;

  // Mode abstract functions
  ReturnValue_t checkModeCommand(Mode_t mode, Submode_t submode,
                                 uint32_t* msToReachTheMode) override;

 private:
  static const uint32_t INIT_DELAY = 1500;

  static const uint32_t TEMP_OFFSET = 5;

  enum class InternalState { STARTUP, INITIAL_DELAY, READY };

  InternalState internalState = InternalState::STARTUP;

  HeaterHandler& heaterHandler;

  bool pollPcdu1Tmp;
  tcsCtrl::SensorTemperatures sensorTemperatures;
  tcsCtrl::SusTemperatures susTemperatures;
  tcsCtrl::DeviceTemperatures deviceTemperatures;
  tcsCtrl::HeaterInfo heaterInfo;
  tcsCtrl::TcsCtrlInfo tcsCtrlInfo;

  DeviceHandlerThermalSet imtqThermalSet;

  // Temperature Sensors
  MAX31865::PrimarySet maxSet0PlocHspd;
  MAX31865::PrimarySet maxSet1PlocMissionBrd;
  MAX31865::PrimarySet maxSet2PlCam;
  MAX31865::PrimarySet maxSet3DacHspd;
  MAX31865::PrimarySet maxSet4Str;
  MAX31865::PrimarySet maxSet5Rw1MxMy;
  MAX31865::PrimarySet maxSet6Dro;
  MAX31865::PrimarySet maxSet7Scex;
  MAX31865::PrimarySet maxSet8X8;
  MAX31865::PrimarySet maxSet9Hpa;
  MAX31865::PrimarySet maxSet10EbandTx;
  MAX31865::PrimarySet maxSet11Mpa;
  MAX31865::PrimarySet maxSet31865Set12;
  MAX31865::PrimarySet maxSet13PlPcduHspd;
  MAX31865::PrimarySet maxSet14TcsBrd;
  MAX31865::PrimarySet maxSet15Imtq;

  TMP1075::Tmp1075Dataset tmp1075SetTcs0;
  TMP1075::Tmp1075Dataset tmp1075SetTcs1;
  TMP1075::Tmp1075Dataset tmp1075SetPlPcdu0;
  // damaged
  TMP1075::Tmp1075Dataset* tmp1075SetPlPcdu1;
  TMP1075::Tmp1075Dataset tmp1075SetIfBoard;

  // SUS
  susMax1227::SusDataset susSet0;
  susMax1227::SusDataset susSet1;
  susMax1227::SusDataset susSet2;
  susMax1227::SusDataset susSet3;
  susMax1227::SusDataset susSet4;
  susMax1227::SusDataset susSet5;
  susMax1227::SusDataset susSet6;
  susMax1227::SusDataset susSet7;
  susMax1227::SusDataset susSet8;
  susMax1227::SusDataset susSet9;
  susMax1227::SusDataset susSet10;
  susMax1227::SusDataset susSet11;

  // If the TCS board in unavailable, for example due to a recovery, skip
  // some TCS controller tasks to avoid unnecessary events.
  const std::atomic_bool& tcsBrdShortlyUnavailable = false;

  lp_var_t<float> tempQ7s = lp_var_t<float>(objects::CORE_CONTROLLER, core::PoolIds::TEMPERATURE);
  lp_var_t<int16_t> battTemp1 = lp_var_t<int16_t>(objects::BPX_BATT_HANDLER, bpxBat::BATT_TEMP_1);
  lp_var_t<int16_t> battTemp2 = lp_var_t<int16_t>(objects::BPX_BATT_HANDLER, bpxBat::BATT_TEMP_2);
  lp_var_t<int16_t> battTemp3 = lp_var_t<int16_t>(objects::BPX_BATT_HANDLER, bpxBat::BATT_TEMP_3);
  lp_var_t<int16_t> battTemp4 = lp_var_t<int16_t>(objects::BPX_BATT_HANDLER, bpxBat::BATT_TEMP_4);
  lp_var_t<int32_t> tempRw1 = lp_var_t<int32_t>(objects::RW1, rws::TEMPERATURE_C);
  lp_var_t<int32_t> tempRw2 = lp_var_t<int32_t>(objects::RW2, rws::TEMPERATURE_C);
  lp_var_t<int32_t> tempRw3 = lp_var_t<int32_t>(objects::RW3, rws::TEMPERATURE_C);
  lp_var_t<int32_t> tempRw4 = lp_var_t<int32_t>(objects::RW4, rws::TEMPERATURE_C);
  lp_var_t<float> tempStartracker =
      lp_var_t<float>(objects::STAR_TRACKER, startracker::MCU_TEMPERATURE);
  lp_var_t<float> tempSyrlinksPowerAmplifier =
      lp_var_t<float>(objects::SYRLINKS_HANDLER, syrlinks::TEMP_POWER_AMPLIFIER);
  lp_var_t<float> tempSyrlinksBasebandBoard =
      lp_var_t<float>(objects::SYRLINKS_HANDLER, syrlinks::TEMP_BASEBAND_BOARD);
  lp_var_t<int16_t> tempMgt = lp_var_t<int16_t>(objects::IMTQ_HANDLER, imtq::MCU_TEMPERATURE);
  lp_vec_t<float, 3> tempAcu =
      lp_vec_t<float, 3>(objects::ACU_HANDLER, ACU::pool::ACU_TEMPERATURES);
  lp_var_t<float> tempPdu1 = lp_var_t<float>(objects::PDU1_HANDLER, PDU::pool::PDU_TEMPERATURE);
  lp_var_t<float> tempPdu2 = lp_var_t<float>(objects::PDU2_HANDLER, PDU::pool::PDU_TEMPERATURE);
  lp_var_t<float> temp1P60dock =
      lp_var_t<float>(objects::P60DOCK_HANDLER, P60Dock::pool::P60DOCK_TEMPERATURE_1);
  lp_var_t<float> temp2P60dock =
      lp_var_t<float>(objects::P60DOCK_HANDLER, P60Dock::pool::P60DOCK_TEMPERATURE_2);
  lp_var_t<float> tempGyro0 = lp_var_t<float>(objects::GYRO_0_ADIS_HANDLER, adis1650x::TEMPERATURE);
  lp_var_t<float> tempGyro1 = lp_var_t<float>(objects::GYRO_1_L3G_HANDLER, l3gd20h::TEMPERATURE);
  lp_var_t<float> tempGyro2 = lp_var_t<float>(objects::GYRO_2_ADIS_HANDLER, adis1650x::TEMPERATURE);
  lp_var_t<float> tempGyro3 = lp_var_t<float>(objects::GYRO_3_L3G_HANDLER, l3gd20h::TEMPERATURE);
  lp_var_t<float> tempMgm0 =
      lp_var_t<float>(objects::MGM_0_LIS3_HANDLER, mgmLis3::TEMPERATURE_CELCIUS);
  lp_var_t<float> tempMgm2 =
      lp_var_t<float>(objects::MGM_2_LIS3_HANDLER, mgmLis3::TEMPERATURE_CELCIUS);
  lp_var_t<float> tempAdcPayloadPcdu = lp_var_t<float>(objects::PLPCDU_HANDLER, plpcdu::TEMP);
  lp_vec_t<int16_t, 9> currentVecPdu2 =
      lp_vec_t<int16_t, 9>(gp_id_t(objects::PDU2_HANDLER, PDU::pool::PDU_CURRENTS));

  // TempLimits
  tcsCtrl::TempLimits acsBoardLimits = tcsCtrl::TempLimits(-40.0, -40.0, 80.0, 85.0, 85.0);
  tcsCtrl::TempLimits mgtLimits = tcsCtrl::TempLimits(-40.0, -40.0, 65.0, 70.0, 70.0);
  tcsCtrl::TempLimits rwLimits = tcsCtrl::TempLimits(-40.0, -40.0, 80.0, 85.0, 85.0);
  tcsCtrl::TempLimits strLimits = tcsCtrl::TempLimits(-30.0, -20.0, 65.0, 70.0, 80.0);
  tcsCtrl::TempLimits ifBoardLimits = tcsCtrl::TempLimits(-65.0, -40.0, 80.0, 85.0, 150.0);
  tcsCtrl::TempLimits tcsBoardLimits = tcsCtrl::TempLimits(-60.0, -40.0, 80.0, 85.0, 130.0);
  tcsCtrl::TempLimits obcLimits = tcsCtrl::TempLimits(-40.0, -40.0, 80.0, 85.0, 85.0);
  tcsCtrl::TempLimits obcIfBoardLimits = tcsCtrl::TempLimits(-65.0, -40.0, 80.0, 85.0, 125.0);
  tcsCtrl::TempLimits sBandTransceiverLimits = tcsCtrl::TempLimits(-40.0, -25.0, 35.0, 40.0, 65.0);
  tcsCtrl::TempLimits pcduP60BoardLimits = tcsCtrl::TempLimits(-35.0, -35.0, 80.0, 85.0, 85.0);
  tcsCtrl::TempLimits pcduAcuLimits = tcsCtrl::TempLimits(-35.0, -35.0, 80.0, 85.0, 85.0);
  tcsCtrl::TempLimits pcduPduLimits = tcsCtrl::TempLimits(-35.0, -35.0, 80.0, 85.0, 85.0);
  tcsCtrl::TempLimits plPcduBoardLimits = tcsCtrl::TempLimits(-55.0, -40.0, 80.0, 85.0, 125.0);
  tcsCtrl::TempLimits plocMissionBoardLimits = tcsCtrl::TempLimits(-30.0, -10.0, 40.0, 45.0, 60);
  tcsCtrl::TempLimits plocProcessingBoardLimits =
      tcsCtrl::TempLimits(-30.0, -10.0, 40.0, 45.0, 60.0);
  tcsCtrl::TempLimits dacLimits = tcsCtrl::TempLimits(-65.0, -40.0, 113.0, 118.0, 150.0);
  tcsCtrl::TempLimits cameraLimits = tcsCtrl::TempLimits(-40.0, -30.0, 60.0, 65.0, 85.0);
  tcsCtrl::TempLimits droLimits = tcsCtrl::TempLimits(-40.0, -30.0, 75.0, 80.0, 90.0);
  tcsCtrl::TempLimits x8Limits = tcsCtrl::TempLimits(-40.0, -30.0, 75.0, 80.0, 90.0);
  tcsCtrl::TempLimits hpaLimits = tcsCtrl::TempLimits(-40.0, -30.0, 75.0, 80.0, 90.0);
  tcsCtrl::TempLimits txLimits = tcsCtrl::TempLimits(-40.0, -30.0, 75.0, 80.0, 90.0);
  tcsCtrl::TempLimits mpaLimits = tcsCtrl::TempLimits(-40.0, -30.0, 75.0, 80.0, 90.0);
  tcsCtrl::TempLimits scexBoardLimits = tcsCtrl::TempLimits(-60.0, -40.0, 80.0, 85.0, 150.0);

  struct CtrlContext {
    double sensorTemp = INVALID_TEMPERATURE;
    uint8_t currentSensorIndex = 0;
    tcsCtrl::ThermalComponents thermalComponent = tcsCtrl::NONE;
    bool redSwitchNrInUse = false;
    bool componentAboveCutOffLimit = false;
    bool componentAboveUpperLimit = false;
    Event overHeatEventToTrigger;
  } ctrlCtx;

  MessageQueueId_t camId = MessageQueueIF::NO_QUEUE;

  struct TooHotFlags {
    bool eBandTooHotFlag = false;
    bool camTooHotOneShotFlag = false;
    bool scexTooHotFlag = false;
    bool plocTooHotFlag = false;
    bool pcduSystemTooHotFlag = false;
    bool syrlinksTooHotFlag = false;
    bool obcTooHotFlag = false;
    bool mgtTooHotFlag = false;
    bool strTooHotFlag = false;
    bool rwTooHotFlag = false;
  } tooHotFlags;

  bool transitionWhenHeatersOff = false;
  uint32_t transitionWhenHeatersOffCycles = 0;
  Mode_t targetMode = MODE_OFF;
  Submode_t targetSubmode = SUBMODE_NONE;
  uint32_t cycles = 0;
  std::array<tcsCtrl::ThermalState, tcsCtrl::NUM_THERMAL_COMPONENTS> thermalStates{};
  std::array<tcsCtrl::HeaterState, heater::NUMBER_OF_SWITCHES> heaterStates{};

  // Initial delay to make sure all pool variables have been initialized their owners.
  // Also, wait for system initialization to complete.
  Countdown initialCountdown = Countdown(INIT_DELAY);

#if OBSW_THREAD_TRACING == 1
  uint32_t opCounter = 0;
#endif

  std::array<std::pair<bool, double>, 5> sensors;
  uint8_t numSensors = 0;

  PoolEntry<float> tmp1075Tcs0 = PoolEntry<float>({10.0});
  PoolEntry<float> tmp1075Tcs1 = PoolEntry<float>({10.0});
  PoolEntry<float> tmp1075PlPcdu0 = PoolEntry<float>({10.0});
  PoolEntry<float> tmp1075PlPcdu1 = PoolEntry<float>({10.0});
  PoolEntry<float> tmp1075IfBrd = PoolEntry<float>({10.0});
  PoolEntry<uint8_t> heaterSwitchStates = PoolEntry<uint8_t>(heater::NUMBER_OF_SWITCHES);
  PoolEntry<int16_t> heaterCurrent = PoolEntry<int16_t>();

  PoolEntry<uint8_t> tcsCtrlHeaterOn = PoolEntry<uint8_t>(tcsCtrl::NUM_THERMAL_COMPONENTS);
  PoolEntry<uint8_t> tcsCtrlSensorIdx = PoolEntry<uint8_t>(tcsCtrl::NUM_THERMAL_COMPONENTS);
  PoolEntry<uint8_t> tcsCtrlHeaterIdx = PoolEntry<uint8_t>(tcsCtrl::NUM_THERMAL_COMPONENTS);
  PoolEntry<uint32_t> tcsCtrlStartTimes = PoolEntry<uint32_t>(tcsCtrl::NUM_THERMAL_COMPONENTS);
  PoolEntry<uint32_t> tcsCtrlEndTimes = PoolEntry<uint32_t>(tcsCtrl::NUM_THERMAL_COMPONENTS);

  static constexpr dur_millis_t MUTEX_TIMEOUT = 50;

  void startTransition(Mode_t mode, Submode_t submode) override;

  bool heaterCtrlAllowed() const;
  void resetThermalStates();

  void resetSensorsArray();
  void copySensors();
  void copySus();
  void copyDevices();

  void ctrlComponentTemperature(HeaterContext& heaterContext);
  void checkLimitsAndCtrlHeater(HeaterContext& heaterContext);
  bool heaterCtrlCheckUpperLimits(HeaterContext& heaterContext);
  void heaterCtrlTempTooHighHandler(HeaterContext& heaterContext, const char* whatLimit);

  bool chooseHeater(heater::Switch& switchNr, heater::Switch redSwitchNr);
  bool selectAndReadSensorTemp(HeaterContext& htrCtx);

  void heaterSwitchHelperAllOff();
  void heaterSwitchHelper(heater::Switch switchNr, heater::SwitchState state,
                          std::optional<unsigned> componentIdx);

  void ctrlAcsBoard();
  void ctrlMgt();
  void ctrlRw();
  void ctrlStr();
  void ctrlIfBoard();
  void ctrlTcsBoard();
  void ctrlObc();
  void ctrlSBandTransceiver();
  void ctrlPcduP60Board();
  void ctrlPcduAcu();
  void ctrlPcduPdu();
  void ctrlPlPcduBoard();
  void ctrlPlocMissionBoard();
  void ctrlPlocProcessingBoard();
  void ctrlDac();
  void ctrlCameraBody();
  void ctrlDro();
  void ctrlX8();
  void ctrlHpa();
  void ctrlTx();
  void ctrlMpa();
  void ctrlScexBoard();

  /**
   * The transition of heaters might take some time. As long as a transition is
   * going on, the TCS controller works in a reduced form. This function takes care
   * of tracking transition and capturing their completion.
   * @param currentHeaterStates
   */
  void heaterTransitionControl(const tcsCtrl::HeaterSwitchStates& currentHeaterStates);
  /**
   * Control tasks to prevent heaters being on for prolonged periods. Ideally, this
   * should never happen, but this task prevents bugs from causing heaters to stay on
   * for a long time, which draws a lot of power.
   * @param currentHeaterStates
   */
  void heaterMaxDurationControl(const tcsCtrl::HeaterSwitchStates& currentHeaterStates);
  void crossCheckHeaterStateOfComponentsWhenHeaterGoesOff(heater::Switch switchIdx);
  void setMode(Mode_t mode, Submode_t submode);
  uint32_t tempFloatToU32() const;
  bool tooHotHandler(object_id_t object, bool& oneShotFlag);
  void tooHotHandlerWhichClearsOneShotFlag(object_id_t object, bool& oneShotFlag);
};

#endif /* MISSION_CONTROLLER_THERMALCONTROLLER_H_ */