#ifndef MISSION_DEVICES_PLOCSUPERVISORHANDLER_H_
#define MISSION_DEVICES_PLOCSUPERVISORHANDLER_H_

#include <linux/payload/PlocSupvUartMan.h>
#include <linux/payload/plocSupvDefs.h>
#include <mission/controller/controllerdefinitions/PowerCtrlDefinitions.h>

#include "OBSWConfig.h"
#include "devices/powerSwitcherList.h"
#include "fsfw/devicehandlers/DeviceHandlerBase.h"
#include "fsfw/timemanager/Countdown.h"
#include "fsfw_hal/linux/gpio/Gpio.h"
#include "fsfw_hal/linux/gpio/LinuxLibgpioIF.h"
#include "fsfw_hal/linux/serial/SerialComIF.h"

#ifdef XIPHOS_Q7S
#include "bsp_q7s/fs/SdCardManager.h"
#endif

using supv::ExecutionReport;
using supv::TcBase;

static constexpr bool DEBUG_PLOC_SUPV = true;
static constexpr bool REDUCE_NORMAL_MODE_PRINTOUT = true;

/**
 * @brief	This is the device handler for the supervisor of the PLOC which is programmed by
 *          Thales.
 *
 * @details The PLOC uses the space packet protocol for communication. On each command the PLOC
 *          answers with at least one acknowledgment and one execution report.
 *          Flight manual:
 *          https://egit.irs.uni-stuttgart.de/redmine/projects/eive-flight-manual/wiki/PLOC_Commands
 *          ILH ICD: https://eive-cloud.irs.uni-stuttgart.de/index.php/apps/files/?dir=/EIVE_IRS/
 *          Arbeitsdaten/08_Used%20Components/PLOC&fileid=940960
 * @author	J. Meier
 */
class PlocSupervisorHandler : public DeviceHandlerBase {
 public:
  PlocSupervisorHandler(object_id_t objectId, CookieIF* comCookie, Gpio uartIsolatorSwitch,
                        power::Switch_t powerSwitch, PlocSupvUartManager& supvHelper);
  virtual ~PlocSupervisorHandler();

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

 protected:
  void doStartUp() override;
  void doShutDown() override;
  ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t* id) override;
  ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t* id) override;
  void fillCommandAndReplyMap() override;
  ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t* commandData,
                                        size_t commandDataLen) override;
  ReturnValue_t scanForReply(const uint8_t* start, size_t remainingSize, DeviceCommandId_t* foundId,
                             size_t* foundLen) override;
  ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t* packet) override;
  uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override;
  ReturnValue_t initializeLocalDataPool(localpool::DataPool& localDataPoolMap,
                                        LocalDataPoolManager& poolManager) override;
  ReturnValue_t enableReplyInReplyMap(DeviceCommandMap::iterator command,
                                      uint8_t expectedReplies = 1, bool useAlternateId = false,
                                      DeviceCommandId_t alternateReplyID = 0) override;
  size_t getNextReplyLength(DeviceCommandId_t deviceCommand) override;
  // ReturnValue_t doSendReadHook() override;
  void doOffActivity() override;

 private:
  static const uint16_t APID_MASK = 0x7FF;
  static const uint16_t PACKET_SEQUENCE_COUNT_MASK = 0x3FFF;
  static const uint8_t EXE_STATUS_OFFSET = 10;
  static const uint8_t SIZE_NULL_TERMINATOR = 1;
  // 5 s
  static const uint32_t EXECUTION_DEFAULT_TIMEOUT = 5000;
  // 70 S
  static const uint32_t ACKNOWLEDGE_DEFAULT_TIMEOUT = 5000;
  // 60 s
  static const uint32_t MRAM_DUMP_EXECUTION_TIMEOUT = 60000;
  // 70 s
  static const uint32_t COPY_ADC_TO_MRAM_TIMEOUT = 70000;
  // 60 s
  static const uint32_t MRAM_DUMP_TIMEOUT = 60000;

  enum class StartupState : uint8_t {
    OFF,
    BOOTING,
    SET_TIME,
    WAIT_FOR_TIME_REPLY,
    TIME_WAS_SET,
    ON
  };

  static constexpr bool SET_TIME_DURING_BOOT = true;

  StartupState startupState = StartupState::OFF;

  uint8_t commandBuffer[supv::MAX_COMMAND_SIZE];
  SpacePacketCreator creator;
  supv::TcParams spParams = supv::TcParams(creator);

  /**
   * This variable is used to store the id of the next reply to receive. This is necessary
   * because the PLOC sends as reply to each command at least one acknowledgment and execution
   * report.
   */
  DeviceCommandId_t nextReplyId = supv::NONE;

  SerialComIF* uartComIf = nullptr;
  LinuxLibgpioIF* gpioComIF = nullptr;
  Gpio uartIsolatorSwitch;
  bool shutdownCmdSent = false;
  // Yeah, I am using an extra variable because I once again don't know
  // what the hell the base class is doing and I don't care anymore.
  bool normalCommandIsPending = false;
  // True men implement their reply timeout handling themselves!
  Countdown normalCmdCd = Countdown(2000);
  bool commandIsPending = false;
  Countdown cmdCd = Countdown(2000);

  supv::HkSet hkset;
  supv::BootStatusReport bootStatusReport;
  supv::LatchupStatusReport latchupStatusReport;
  supv::CountersReport countersReport;
  supv::AdcReport adcReport;

  const power::Switch_t powerSwitch = power::NO_SWITCH;
  supv::TmBase tmReader;

  PlocSupvUartManager& uartManager;
  MessageQueueIF* eventQueue = nullptr;

  /** Number of expected replies following the MRAM dump command */
  uint32_t expectedMramDumpPackets = 0;
  uint32_t receivedMramDumpPackets = 0;
  /** Set to true as soon as a complete space packet is present in the spacePacketBuffer */
  bool packetInBuffer = false;

  /** This buffer is used to concatenate space packets received in two different read steps */
  uint8_t spacePacketBuffer[supv::MAX_PACKET_SIZE];

#ifdef XIPHOS_Q7S
  SdCardManager* sdcMan = nullptr;
#endif

  // Path to supervisor specific files on SD card
  std::string supervisorFilePath = "ploc/supervisor";
  std::string activeMramFile;

  Countdown executionReportTimeout = Countdown(EXECUTION_DEFAULT_TIMEOUT, false);
  Countdown acknowledgementReportTimeout = Countdown(ACKNOWLEDGE_DEFAULT_TIMEOUT, false);
  // Vorago nees some time to boot properly
  Countdown bootTimeout = Countdown(supv::BOOT_TIMEOUT_MS);
  Countdown mramDumpTimeout = Countdown(MRAM_DUMP_TIMEOUT);

  PoolEntry<uint16_t> adcRawEntry = PoolEntry<uint16_t>(16);
  PoolEntry<uint16_t> adcEngEntry = PoolEntry<uint16_t>(16);
  PoolEntry<uint32_t> latchupCounters = PoolEntry<uint32_t>(7);
  PoolEntry<uint8_t> fmcStateEntry = PoolEntry<uint8_t>(1);
  PoolEntry<uint8_t> bootStateEntry = PoolEntry<uint8_t>(1);
  PoolEntry<uint8_t> bootCyclesEntry = PoolEntry<uint8_t>(1);
  PoolEntry<uint32_t> tempSupEntry = PoolEntry<uint32_t>(1);

  /**
   * @brief Adjusts the timeout of the execution report dependent on command
   */
  void setExecutionTimeout(DeviceCommandId_t command);

  void handlePacketPrint();

  /**
   * @brief   Handles event messages received from the supervisor helper
   */
  void handleEvent(EventMessage* eventMessage);

  ReturnValue_t getSwitches(const uint8_t** switches, uint8_t* numberOfSwitches);

  /**
   * @brief   This function checks the crc of the received PLOC reply.
   *
   * @param start Pointer to the first byte of the reply.
   * @param foundLen  Pointer to the length of the whole packet.
   *
   * @return  returnvalue::OK if CRC is ok, otherwise CRC_FAILURE.
   */
  ReturnValue_t verifyPacket(const uint8_t* start, size_t foundLen);

  /**
   * @brief   This function handles the acknowledgment report.
   *
   * @param data  Pointer to the data holding the acknowledgment report.
   *
   * @return  returnvalue::OK if successful, otherwise an error code.
   */
  ReturnValue_t handleAckReport(const uint8_t* data);

  /**
   * @brief   This function handles the data of a execution report.
   *
   * @param data  Pointer to the received data packet.
   *
   * @return  returnvalue::OK if successful, otherwise an error code.
   */
  ReturnValue_t handleExecutionReport(const uint8_t* data);

  /**
   * @brief   This function handles the housekeeping report. This means verifying the CRC of the
   *          reply and filling the appropriate dataset.
   *
   * @param data  Pointer to the data buffer holding the housekeeping read report.
   *
   * @return  returnvalue::OK if successful, otherwise an error code.
   */
  ReturnValue_t handleHkReport(const uint8_t* data);

  /**
   * @brief   This function calls the function to check the CRC of the received boot status report
   *          and fills the associated dataset with the boot status information.
   */
  ReturnValue_t handleBootStatusReport(const uint8_t* data);

  ReturnValue_t handleLatchupStatusReport(const uint8_t* data);
  ReturnValue_t handleCounterReport(const uint8_t* data);
  void handleBadApidServiceCombination(Event result, unsigned int apid, unsigned int serviceId);
  ReturnValue_t handleAdcReport(const uint8_t* data);
  ReturnValue_t genericHandleTm(const char* contextString, const uint8_t* data,
                                LocalPoolDataSetBase& set);

  void disableCommand(DeviceCommandId_t cmd);

  /**
   * @brief   Depending on the current active command, this function sets the reply id of the
   *          next reply after a successful acknowledgment report has been received. This is
   *          required by the function getNextReplyLength() to identify the length of the next
   *          reply to read.
   */
  void setNextReplyId();

  /**
   * @brief   This function handles action message replies in case the telemetry has been
   *          requested by another object.
   *
   * @param data  Pointer to the telemetry data.
   * @param dataSize  Size of telemetry in bytes.
   * @param replyId   Id of the reply. This will be added to the ActionMessage.
   */
  void handleDeviceTm(const uint8_t* data, size_t dataSize, DeviceCommandId_t replyId);

  /**
   * @brief   This function prepares a space packet which does not transport any data in the
   *          packet data field apart from the crc.
   */
  ReturnValue_t prepareEmptyCmd(uint16_t apid, uint8_t serviceId);

  /**
   * @brief   This function initializes the space packet to select the boot image of the MPSoC.
   */
  ReturnValue_t prepareSelBootImageCmd(const uint8_t* commandData);

  ReturnValue_t prepareDisableHk();

  /**
   * @brief   This function fills the commandBuffer with the data to update the time of the
   *          PLOC supervisor.
   */
  ReturnValue_t prepareSetTimeRefCmd();

  /**
   * @brief   This function fills the commandBuffer with the data to change the boot timeout
   *          value in the PLOC supervisor.
   */
  ReturnValue_t prepareSetBootTimeoutCmd(const uint8_t* commandData);

  ReturnValue_t prepareRestartTriesCmd(const uint8_t* commandData);

  ReturnValue_t prepareFactoryResetCmd(const uint8_t* commandData, size_t len);

  /**
   * @brief   This function fills the command buffer with the packet to enable or disable the
   *          watchdogs on the PLOC.
   */
  void prepareWatchdogsEnableCmd(const uint8_t* commandData);

  /**
   * @brief   This function fills the command buffer with the packet to set the watchdog timer
   *          of one of the three watchdogs (PS, PL, INT).
   */
  ReturnValue_t prepareWatchdogsConfigTimeoutCmd(const uint8_t* commandData);

  ReturnValue_t prepareLatchupConfigCmd(const uint8_t* commandData,
                                        DeviceCommandId_t deviceCommand);
  ReturnValue_t prepareSetAlertLimitCmd(const uint8_t* commandData);
  ReturnValue_t prepareSetAdcEnabledChannelsCmd(const uint8_t* commandData);
  ReturnValue_t prepareSetAdcWindowAndStrideCmd(const uint8_t* commandData);
  ReturnValue_t prepareSetAdcThresholdCmd(const uint8_t* commandData);
  ReturnValue_t prepareRunAutoEmTest(const uint8_t* commandData);
  ReturnValue_t prepareWipeMramCmd(const uint8_t* commandData);
  ReturnValue_t prepareSetGpioCmd(const uint8_t* commandData, size_t commandDataLen);
  ReturnValue_t prepareReadGpioCmd(const uint8_t* commandData, size_t commandDataLen);

  /**
   * @brief   Copies the content of a space packet to the command buffer.
   */
  void finishTcPrep(TcBase& tc);

  /**
   * @brief   In case an acknowledgment failure reply has been received this function disables
   *          all previously enabled commands and resets the exepected replies variable of an
   *          active command.
   */
  void disableAllReplies();

  void disableReply(DeviceCommandId_t replyId);

  /**
   * @brief   This function sends a failure report if the active action was commanded by an other
   *          object.
   *
   * @param replyId   The id of the reply which signals a failure.
   * @param status    A status byte which gives information about the failure type.
   */
  void sendFailureReport(DeviceCommandId_t replyId, ReturnValue_t status);

  /**
   * @brief   This function disables the execution report reply. Within this function also the
   *          the variable expectedReplies of an active command will be set to 0.
   */
  void disableExeReportReply();

  /**
   * @brief   This function generates the Service 8 packets for the MRAM dump data.
   */
  ReturnValue_t handleMramDumpPacket(DeviceCommandId_t id);

  /**
   * @brief   With this function the number of expected replies following an MRAM dump command
   *          will be increased. This is necessary to release the command in case not all replies
   *          have been received.
   */
  void increaseExpectedMramReplies(DeviceCommandId_t id);

  /**
   * @brief   Writes the data of the MRAM dump to a file. The file will be created when receiving
   *          the first packet.
   */
  ReturnValue_t handleMramDumpFile(DeviceCommandId_t id);

  /**
   * @brief   Extracts the length field of a spacePacket referenced by the spacePacket pointer.
   *
   * @param spacePacket   Pointer to the buffer holding the space packet.
   *
   * @return The value stored in the length field of the data field.
   */
  uint16_t readSpacePacketLength(uint8_t* spacePacket);

  /**
   * @brief   Extracts the sequence flags from a space packet referenced by the spacePacket
   *          pointer.
   *
   * @param spacePacket   Pointer to the buffer holding the space packet.
   *
   * @return uint8_t where the two least significant bits hold the sequence flags.
   */
  uint8_t readSequenceFlags(uint8_t* spacePacket);

  ReturnValue_t createMramDumpFile();
  ReturnValue_t getTimeStampString(std::string& timeStamp);

  ReturnValue_t prepareSetShutdownTimeoutCmd(const uint8_t* commandData);

  ReturnValue_t extractUpdateCommand(const uint8_t* commandData, size_t size,
                                     supv::UpdateParams& params);
  ReturnValue_t extractBaseParams(const uint8_t** commandData, size_t& remSize,
                                  supv::UpdateParams& params);
  ReturnValue_t eventSubscription();

  ReturnValue_t handleExecutionSuccessReport(ExecutionReport& report);
  void handleExecutionFailureReport(ExecutionReport& report);

  void printAckFailureInfo(uint16_t statusCode, DeviceCommandId_t commandId);

  pwrctrl::EnablePl enablePl = pwrctrl::EnablePl(objects::POWER_CONTROLLER);
  ReturnValue_t checkModeCommand(Mode_t commandedMode, Submode_t commandedSubmode,
                                 uint32_t* msToReachTheMode) override;
};

#endif /* MISSION_DEVICES_PLOCSUPERVISORHANDLER_H_ */