#ifndef BSP_Q7S_MEMORY_SDCARDACCESSMANAGER_H_
#define BSP_Q7S_MEMORY_SDCARDACCESSMANAGER_H_

#include <fsfw/objectmanager/SystemObject.h>
#include <poll.h>

#include <array>
#include <cstdint>
#include <optional>
#include <string>
#include <utility>

#include "events/subsystemIdRanges.h"
#include "fsfw/events/Event.h"
#include "fsfw/returnvalues/returnvalue.h"
#include "fsfw_hal/linux/CommandExecutor.h"
#include "mission/memory/SdCardMountedIF.h"
#include "mission/memory/definitions.h"
#include "returnvalues/classIds.h"

class MutexIF;

/**
 * @brief   Manages handling of SD cards like switching them on or off or getting the current
 *          state
 */
class SdCardManager : public SystemObject, public SdCardMountedIF {
  friend class SdCardAccess;

 public:
  using mountInitCb = ReturnValue_t (*)(void* args);

  enum class Operations { SWITCHING_ON, SWITCHING_OFF, MOUNTING, UNMOUNTING, IDLE };

  enum class OpStatus { IDLE, TIMEOUT, ONGOING, SUCCESS, FAIL };

  using SdStatePair = std::pair<sd::SdState, sd::SdState>;

  struct SdInfo {
    sd::SdCard pref = sd::SdCard::NONE;
    sd::SdCard other = sd::SdCard::NONE;
    sd::SdCard active = sd::SdCard::NONE;
  } sdInfo;

  static constexpr uint8_t INTERFACE_ID = CLASS_ID::SD_CARD_MANAGER;

  static constexpr ReturnValue_t OP_ONGOING = returnvalue::makeCode(INTERFACE_ID, 0);
  static constexpr ReturnValue_t ALREADY_ON = returnvalue::makeCode(INTERFACE_ID, 1);
  static constexpr ReturnValue_t ALREADY_MOUNTED = returnvalue::makeCode(INTERFACE_ID, 2);
  static constexpr ReturnValue_t ALREADY_OFF = returnvalue::makeCode(INTERFACE_ID, 3);
  static constexpr ReturnValue_t STATUS_FILE_NEXISTS = returnvalue::makeCode(INTERFACE_ID, 10);
  static constexpr ReturnValue_t STATUS_FILE_FORMAT_INVALID =
      returnvalue::makeCode(INTERFACE_ID, 11);
  static constexpr ReturnValue_t MOUNT_ERROR = returnvalue::makeCode(INTERFACE_ID, 12);
  static constexpr ReturnValue_t UNMOUNT_ERROR = returnvalue::makeCode(INTERFACE_ID, 13);
  static constexpr ReturnValue_t SYSTEM_CALL_ERROR = returnvalue::makeCode(INTERFACE_ID, 14);
  static constexpr ReturnValue_t POPEN_CALL_ERROR = returnvalue::makeCode(INTERFACE_ID, 15);

  static constexpr uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::FILE_SYSTEM;

  static constexpr Event SANITIZATION_FAILED = event::makeEvent(SUBSYSTEM_ID, 0, severity::LOW);
  static constexpr Event MOUNTED_SD_CARD = event::makeEvent(SUBSYSTEM_ID, 1, severity::INFO);

  // C++17 does not support constexpr std::string yet
  static constexpr char SD_0_DEV_NAME[] = "/dev/mmcblk0p1";
  static constexpr char SD_1_DEV_NAME[] = "/dev/mmcblk1p1";

  static constexpr char SD_STATE_FILE[] = "/tmp/sd_status.txt";

  virtual ~SdCardManager();

  static void create();

  /**
   * Returns the single instance of the SD card manager.
   */
  static SdCardManager* instance();

  /**
   * Set the preferred SD card which will determine which SD card will be used as the primary
   * SD card in hot redundant and cold redundant mode. This function will not switch the
   * SD cards which are currently on and mounted, this needs to be implemented by
   * an upper layer by using #switchOffSdCard , #switchOnSdCard and #updateSdCardStateFile
   * @param sdCard
   * @return
   */
  ReturnValue_t setPreferredSdCard(sd::SdCard sdCard);

  /**
   * Get the currently configured preferred SD card
   * @param sdCard
   * @return
   */
  sd::SdCard getPreferredSdCard() const override;

  /**
   * Switch on the specified SD card.
   * @param sdCard
   * @param doMountSdCard     Mount the SD card after switching it on, which is necessary
   *                          to use it
   * @param statusPair        If the status pair is already available, it can be passed here
   * @return - returnvalue::OK on success, ALREADY_ON if it is already on,
   *           SYSTEM_CALL_ERROR on system error
   */
  ReturnValue_t switchOnSdCard(sd::SdCard sdCard, bool doMountSdCard = true,
                               SdStatePair* statusPair = nullptr);

  /**
   * Switch off the specified SD card.
   * @param sdCard
   * @param doUnmountSdCard   Unmount the SD card before switching the card off, which makes
   *                          the operation safer
   * @param statusPair        If the status pair is already available, it can be passed here
   * @return - returnvalue::OK on success, ALREADY_ON if it is already on,
   *           SYSTEM_CALL_ERROR on system error
   */
  ReturnValue_t switchOffSdCard(sd::SdCard sdCard, bool doUnmountSdCard = true,
                                SdStatePair* statusPair = nullptr);

  /**
   * Update the state file or creates one if it does not exist. You need to call this
   * function before calling #sdCardActive
   * @return
   *  - returnvalue::OK if the state file was updated successfully
   *  - CommandExecutor::COMMAND_PENDING: Non-blocking command is pending
   *  - returnvalue::FAILED: blocking command failed
   */
  ReturnValue_t updateSdCardStateFile();

  /**
   * Get the state of the SD cards. If the state file does not exist, this function will
   * take care of updating it. If it does not, the function will use the state file to get
   * the status of the SD cards and set the field of the provided boolean pair.
   * @param active Pair of booleans, where the first entry is the state of the first SD card
   * and the second one the state of the second SD card
   * @return - returnvalue::OK if the state was read successfully
   *         - STATUS_FILE_FORMAT_INVALID if there was an issue with the state file. The user
   *           should call #updateSdCardStateFile again in that case
   *         - STATUS_FILE_NEXISTS if the status file does not exist
   */
  ReturnValue_t getSdCardsStatus(SdStatePair& active);

  /**
   * Mount the specified SD card. This is necessary to use it.
   * @param sdCard
   * @return
   */
  ReturnValue_t mountSdCard(sd::SdCard sdCard);

  /**
   * Set the currently active SD card. This does not necessarily mean that the SD card is on or
   * mounted
   * @param sdCard
   */
  void setActiveSdCard(sd::SdCard sdCard) override;
  /**
   * Get the currently active SD card. This does not necessarily mean that the SD card is on or
   * mounted
   * @return
   */
  sd::SdCard getActiveSdCard() const override;

  /**
   * Unmount the specified SD card. This is recommended before switching it off. The SD card
   * can't be used after it has been unmounted.
   * @param sdCard
   * @return
   */
  ReturnValue_t unmountSdCard(sd::SdCard sdCard);

  /**
   * In case that there is a discrepancy between the preferred SD card and the currently
   * mounted one, this function will sanitize the state by attempting to mount the
   * currently preferred SD card. If the caller already has state information, it can be
   * passed into the function. For now, this operation will be enforced in blocking mode.
   * @param statusPair    Current SD card status capture with #getSdCardActiveStatus
   * @param prefSdCard    Preferred SD card captured with #getPreferredSdCard
   * @throws std::bad_alloc if one of the two arguments was a nullptr and an allocation failed
   * @return
   */
  ReturnValue_t sanitizeState(SdStatePair* statusPair = nullptr,
                              sd::SdCard prefSdCard = sd::SdCard::NONE);

  /**
   * If sd::SdCard::NONE is passed as an argument, this function will get the currently
   * preferred SD card from the scratch buffer.
   * @param prefSdCardPtr
   * @return
   */
  std::string getCurrentMountPrefix() const override;

  OpStatus checkCurrentOp(Operations& currentOp);

  /**
   * If there are issues with the state machine, it can be reset with this function
   */
  void resetState();

  void setBlocking(bool blocking);
  void setPrintCommandOutput(bool print);

  /**
   * @brief   Checks if an SD card is mounted.
   *
   * @param sdCard    The SD card to check
   *
   * @return  true if mounted, otherwise false
   */
  bool isSdCardUsable(sd::SdCard sdCard) override;

  ReturnValue_t isSdCardMountedReadOnly(sd::SdCard sdcard, bool& readOnly);

  ReturnValue_t remountReadWrite(sd::SdCard sdcard);

  ReturnValue_t performFsck(sd::SdCard sdcard, bool printOutput, int& linuxError);

 private:
  CommandExecutor cmdExecutor;
  Operations currentOp = Operations::IDLE;
  bool blocking = false;
  bool sdCardActive = true;
  bool printCmdOutput = true;
  MutexIF* mutex = nullptr;

  SdCardManager();

  ReturnValue_t setSdCardState(sd::SdCard sdCard, bool on);

  void processSdStatusLine(SdStatePair& active, std::string& line, uint8_t& idx,
                           sd::SdCard& currentSd);

  std::string currentPrefix;

  static SdCardManager* INSTANCE;
};

#endif /* BSP_Q7S_MEMORY_SDCARDACCESSMANAGER_H_ */