#ifndef MISSION_DEVICES_SOLARARRAYDEPLOYMENT_H_
#define MISSION_DEVICES_SOLARARRAYDEPLOYMENT_H_

#include <fsfw/globalfunctions/PeriodicOperationDivider.h>

#include <unordered_map>

#include "devices/powerSwitcherList.h"
#include "eive/definitions.h"
#include "events/subsystemIdRanges.h"
#include "fsfw/action/HasActionsIF.h"
#include "fsfw/devicehandlers/CookieIF.h"
#include "fsfw/devicehandlers/DeviceHandlerIF.h"
#include "fsfw/objectmanager/SystemObject.h"
#include "fsfw/power/PowerSwitchIF.h"
#include "fsfw/returnvalues/returnvalue.h"
#include "fsfw/serialize/SerialLinkedListAdapter.h"
#include "fsfw/tasks/ExecutableObjectIF.h"
#include "fsfw/timemanager/Countdown.h"
#include "fsfw_hal/common/gpio/GpioIF.h"
#include "mission/memory/SdCardMountedIF.h"
#include "mission/trace.h"
#include "returnvalues/classIds.h"

enum DeploymentChannels : uint8_t { SA_1 = 1, SA_2 = 2 };

class ManualDeploymentCommand : public SerialLinkedListAdapter<SerializeIF> {
 public:
  ManualDeploymentCommand() { setLinks(); }

  void setLinks() {
    setStart(&burnTimeSecs);
    burnTimeSecs.setNext(&switchIntervalMs);
    switchIntervalMs.setNext(&initChannel);
    initChannel.setNext(&dryRun);
  }

  uint32_t getBurnTimeSecs() const { return burnTimeSecs.entry; }

  uint32_t getSwitchIntervalMs() const { return switchIntervalMs.entry; };

  uint8_t getInitChannel() const { return initChannel.entry; };

  bool isDryRun() const { return dryRun.entry; }

 private:
  SerializeElement<uint32_t> burnTimeSecs;
  SerializeElement<uint32_t> switchIntervalMs;
  SerializeElement<uint8_t> initChannel;
  SerializeElement<uint8_t> dryRun;
};

/**
 * @brief	This class is used to control the solar array deployment.
 *
 * @author	J. Meier
 */
class SolarArrayDeploymentHandler : public ExecutableObjectIF,
                                    public SystemObject,
                                    public HasActionsIF {
 public:
  //! Manual deployment of the solar arrays. Burn time, channel switch interval, initial
  //! burn channel and dry run flag are supplied as parameters. There are following cases to
  //! consider.
  //!
  //! - Channel switch interval greater or equal to burn time: Only burn one channel. The init
  //!   burn channel parameter can be used to select which channel is burned.
  //! - Channel switch interval half of burn time: Burn each channel for half of the burn time.
  //!
  //! The dry run flag can be used to avoid actually toggling IO pins and only test the
  //! application logic.
  static constexpr DeviceCommandId_t DEPLOY_SOLAR_ARRAYS_MANUALLY = 0x05;
  static constexpr DeviceCommandId_t SWITCH_OFF_DEPLOYMENT = 0x06;

  static constexpr uint32_t FIRST_BURN_START_TIME = config::SA_DEPL_INIT_BUFFER_SECS;
  static constexpr uint32_t FIRST_BURN_END_TIME =
      FIRST_BURN_START_TIME + config::SA_DEPL_BURN_TIME_SECS;
  static constexpr uint32_t WAIT_START_TIME = FIRST_BURN_END_TIME;
  static constexpr uint32_t WAIT_END_TIME = WAIT_START_TIME + config::SA_DEPL_WAIT_TIME_SECS;
  static constexpr uint32_t SECOND_BURN_START_TIME = WAIT_END_TIME;
  static constexpr uint32_t SECOND_BURN_END_TIME =
      SECOND_BURN_START_TIME + config::SA_DEPL_WAIT_TIME_SECS;

  static constexpr char SD_0_DEPL_FILE[] = "/mnt/sd0/conf/deployment";
  static constexpr char SD_1_DEPL_FILE[] = "/mnt/sd1/conf/deployment";
  static constexpr char SD_0_DEPLY_INFO[] = "/mnt/sd0/conf/deployment_info.txt";
  static constexpr char SD_1_DEPLY_INFO[] = "/mnt/sd1/conf/deployment_info.txt";

  static constexpr char PHASE_INIT_STR[] = "init";
  static constexpr char PHASE_FIRST_BURN_STR[] = "first_burn";
  static constexpr char PHASE_WAIT_STR[] = "wait";
  static constexpr char PHASE_SECOND_BURN_STR[] = "second_burn";
  static constexpr char PHASE_DONE[] = "done";
  /**
   * @brief constructor
   *
   * @param setObjectId   The object id of the SolarArrayDeploymentHandler.
   * @param gpioDriverId  The id of the gpio com if.
   * @param gpioCookie    GpioCookie holding information about the gpios used to switch the
   *                      transistors.
   * @param mainLineSwitcherObjectId  The object id of the object responsible for switching
   *                                  the 8V power source. This is normally the PCDU.
   * @param mainLineSwitch    The id of the main line switch. This is defined in
   *                          powerSwitcherList.h.
   * @param deplSA1   gpioId of the GPIO controlling the deployment 1 transistor.
   * @param deplSA2   gpioId of the GPIO controlling the deployment 2 transistor.
   * @param burnTimeMs  Time duration the power will be applied to the burn wires.
   */
  SolarArrayDeploymentHandler(object_id_t setObjectId, GpioIF& gpio,
                              PowerSwitchIF& mainLineSwitcher, pcdu::Switches mainLineSwitch,
                              gpioId_t deplSA1, gpioId_t deplSA2, SdCardMountedIF& sdcMountedIF);

  virtual ~SolarArrayDeploymentHandler();

  virtual ReturnValue_t performOperation(uint8_t operationCode = 0) override;

  virtual MessageQueueId_t getCommandQueue() const override;
  virtual ReturnValue_t executeAction(ActionId_t actionId, MessageQueueId_t commandedBy,
                                      const uint8_t* data, size_t size) override;
  virtual ReturnValue_t initialize() override;

 private:
  enum AutonomousDeplState { INIT, FIRST_BURN, WAIT, SECOND_BURN, DONE };

  enum StateMachine {
    IDLE,
    MAIN_POWER_ON,
    MAIN_POWER_OFF,
    WAIT_MAIN_POWER_ON,
    WAIT_MAIN_POWER_OFF,
    SWITCH_DEPL_GPIOS,
    BURNING
  };

  struct FsmInfo {
    // Not required anymore
    // DeploymentChannels channel;
    bool dryRun;
    bool alternationDummy = false;
    uint8_t initChannel = 0;
    uint32_t burnCountdownMs = config::SA_DEPL_MAX_BURN_TIME;
  };

  static const uint8_t INTERFACE_ID = CLASS_ID::SA_DEPL_HANDLER;
  static const ReturnValue_t COMMAND_NOT_SUPPORTED = MAKE_RETURN_CODE(0xA0);
  static const ReturnValue_t DEPLOYMENT_ALREADY_EXECUTING = MAKE_RETURN_CODE(0xA1);
  static const ReturnValue_t MAIN_SWITCH_TIMEOUT_FAILURE = MAKE_RETURN_CODE(0xA2);
  static const ReturnValue_t SWITCHING_DEPL_SA1_FAILED = MAKE_RETURN_CODE(0xA3);
  static const ReturnValue_t SWITCHING_DEPL_SA2_FAILED = MAKE_RETURN_CODE(0xA4);

  static const uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::SA_DEPL_HANDLER;

  //! [EXPORT] : [COMMENT] P1: Burn duration in milliseconds, P2: Dry run flag
  static constexpr Event BURN_PHASE_START = event::makeEvent(SUBSYSTEM_ID, 0, severity::INFO);
  //! [EXPORT] : [COMMENT] P1: Burn duration in milliseconds, P2: Dry run flag
  static constexpr Event BURN_PHASE_DONE = event::makeEvent(SUBSYSTEM_ID, 1, severity::INFO);
  static constexpr Event MAIN_SWITCH_ON_TIMEOUT = event::makeEvent(SUBSYSTEM_ID, 2, severity::LOW);
  static constexpr Event MAIN_SWITCH_OFF_TIMEOUT = event::makeEvent(SUBSYSTEM_ID, 3, severity::LOW);
  static constexpr Event DEPL_SA1_GPIO_SWTICH_ON_FAILED =
      event::makeEvent(SUBSYSTEM_ID, 4, severity::HIGH);
  static constexpr Event DEPL_SA2_GPIO_SWTICH_ON_FAILED =
      event::makeEvent(SUBSYSTEM_ID, 5, severity::HIGH);
  static constexpr Event DEPL_SA1_GPIO_SWTICH_OFF_FAILED =
      event::makeEvent(SUBSYSTEM_ID, 6, severity::HIGH);
  static constexpr Event DEPL_SA2_GPIO_SWTICH_OFF_FAILED =
      event::makeEvent(SUBSYSTEM_ID, 7, severity::HIGH);
  static constexpr Event AUTONOMOUS_DEPLOYMENT_COMPLETED =
      event::makeEvent(SUBSYSTEM_ID, 8, severity::INFO);

  FsmInfo fsmInfo;
  StateMachine stateMachine = IDLE;
  bool actionActive = false;
  bool firstAutonomousCycle = true;
  ActionId_t activeCmd = HasActionsIF::INVALID_ACTION_ID;
  std::optional<uint64_t> initUptime;
#if OBSW_THREAD_TRACING == 1
  uint32_t opCounter = 0;
#endif
  PeriodicOperationDivider opDivider = PeriodicOperationDivider(5);
  uint8_t retryCounter = 3;

  bool startFsmOn(uint32_t burnCountdownSecs, uint32_t channelAlternationIntervalMs,
                  uint8_t initChannel, bool dryRun);
  void startFsmOff();

  void finishFsm(ReturnValue_t resultForActionHelper);

  ReturnValue_t performAutonomousDepl(sd::SdCard sdCard, bool dryRun);
  bool dryRunStringInFile(const char* filename);
  bool autonomousDeplForFile(sd::SdCard sdCard, const char* filename, bool dryRun);
  /**
   * This countdown is used to check if the PCDU sets the 8V line on in the intended time.
   */
  Countdown mainSwitchCountdown;

  /**
   * This countdown is used to wait for the burn wire being successful cut.
   */
  Countdown burnCountdown;

  // Only initial value, new approach is to burn each channel half of the total burn time.
  Countdown channelAlternationCd =
      Countdown(config::LEGACY_SA_DEPL_CHANNEL_ALTERNATION_INTERVAL_SECS * 1000);

  /**
   * The message queue id of the component commanding an action will be stored in this variable.
   * This is necessary to send later the action finish replies.
   */
  MessageQueueId_t rememberCommanderId = 0;

  /** Size of command queue */
  size_t cmdQueueSize = 20;
  GpioIF& gpioInterface;
  gpioId_t deplSA1;
  gpioId_t deplSA2;

  /**
   * After initialization this pointer will hold the reference to the main line switcher object.
   */
  PowerSwitchIF& mainLineSwitcher;

  /** Switch number of the 8V power switch */
  uint8_t mainLineSwitch;

  SdCardMountedIF& sdcMan;

  ActionHelper actionHelper;

  /** Queue to receive messages from other objects. */
  MessageQueueIF* commandQueue = nullptr;

  void readCommandQueue();

  /**
   * @brief   This function performs actions dependent on the current state.
   */
  void handleStateMachine();

  /**
   * @brief   This function polls the 8V switch state and changes the state machine when the
   *          switch has been enabled.
   */
  bool checkMainPowerOn();
  bool checkMainPowerOff();
  bool checkMainPower(bool onOff);

  void allOff();

  ReturnValue_t deploymentTransistorsOff();
  ReturnValue_t saGpioAlternation();
  ReturnValue_t sa1On();
  ReturnValue_t sa1Off();
  ReturnValue_t sa2On();
  ReturnValue_t sa2Off();
};

#endif /* MISSION_DEVICES_SOLARARRAYDEPLOYMENT_H_ */