#ifndef BSP_Q7S_DEVICES_STRHELPER_H_
#define BSP_Q7S_DEVICES_STRHELPER_H_

#include <mission/acs/str/ArcsecDatalinkLayer.h>

#include <string>

#include "OBSWConfig.h"

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

#include "arcsec/client/generated/actionreq.h"
#include "arcsec/common/generated/tmtcstructs.h"
#include "fsfw/devicehandlers/CookieIF.h"
#include "fsfw/objectmanager/SystemObject.h"
#include "fsfw/osal/linux/BinarySemaphore.h"
#include "fsfw/returnvalues/returnvalue.h"
#include "fsfw/tasks/ExecutableObjectIF.h"
#include "fsfw_hal/linux/serial/SerialComIF.h"

/**
 * @brief   Helper class for the star tracker handler to accelerate large data transfers.
 *
 * @author  J. Meier
 */
class StrComHandler : public SystemObject, public DeviceCommunicationIF, public ExecutableObjectIF {
 public:
  static const uint8_t INTERFACE_ID = CLASS_ID::STR_HELPER;

  //! [EXPORT] : [COMMENT] SD card specified in path string not mounted
  static const ReturnValue_t SD_NOT_MOUNTED = MAKE_RETURN_CODE(1);
  //! [EXPORT] : [COMMENT] Specified file does not exist on filesystem
  static const ReturnValue_t FILE_NOT_EXISTS = MAKE_RETURN_CODE(2);
  //! [EXPORT] : [COMMENT] Specified path does not exist
  static const ReturnValue_t PATH_NOT_EXISTS = MAKE_RETURN_CODE(3);
  //! [EXPORT] : [COMMENT] Failed to create download image or read flash file
  static const ReturnValue_t FILE_CREATION_FAILED = MAKE_RETURN_CODE(4);
  //! [EXPORT] : [COMMENT] Region in flash write/read reply does not match expected region
  static const ReturnValue_t REGION_MISMATCH = MAKE_RETURN_CODE(5);
  //! [EXPORT] : [COMMENT] Address in flash write/read reply does not match expected address
  static const ReturnValue_t ADDRESS_MISMATCH = MAKE_RETURN_CODE(6);
  //! [EXPORT] : [COMMENT] Length in flash write/read reply does not match expected length
  static const ReturnValue_t LENGTH_MISMATCH = MAKE_RETURN_CODE(7);
  //! [EXPORT] : [COMMENT] Status field in reply signals error
  static const ReturnValue_t STATUS_ERROR = MAKE_RETURN_CODE(8);
  //! [EXPORT] : [COMMENT] Reply has invalid type ID (should be of action reply type)
  static const ReturnValue_t INVALID_TYPE_ID = MAKE_RETURN_CODE(9);
  static const ReturnValue_t RECEPTION_TIMEOUT = MAKE_RETURN_CODE(10);
  static const ReturnValue_t DECODING_ERROR = MAKE_RETURN_CODE(11);

  static const uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::STR_HELPER;

  //! [EXPORT] : [COMMENT] Image upload failed
  static const Event IMAGE_UPLOAD_FAILED = MAKE_EVENT(0, severity::LOW);
  //! [EXPORT] : [COMMENT] Image download failed
  static const Event IMAGE_DOWNLOAD_FAILED = MAKE_EVENT(1, severity::LOW);
  //! [EXPORT] : [COMMENT] Uploading image to star tracker was successfulop
  static const Event IMAGE_UPLOAD_SUCCESSFUL = MAKE_EVENT(2, severity::LOW);
  //! [EXPORT] : [COMMENT] Image download was successful
  static const Event IMAGE_DOWNLOAD_SUCCESSFUL = MAKE_EVENT(3, severity::LOW);
  //! [EXPORT] : [COMMENT] Finished flash write procedure successfully
  static const Event FLASH_WRITE_SUCCESSFUL = MAKE_EVENT(4, severity::LOW);
  //! [EXPORT] : [COMMENT] Finished flash read procedure successfully
  static const Event FLASH_READ_SUCCESSFUL = MAKE_EVENT(5, severity::LOW);
  //! [EXPORT] : [COMMENT] Flash read procedure failed
  static const Event FLASH_READ_FAILED = MAKE_EVENT(6, severity::LOW);
  //! [EXPORT] : [COMMENT] Firmware update was successful
  static const Event FIRMWARE_UPDATE_SUCCESSFUL = MAKE_EVENT(7, severity::LOW);
  //! [EXPORT] : [COMMENT] Firmware update failed
  static const Event FIRMWARE_UPDATE_FAILED = MAKE_EVENT(8, severity::LOW);
  //! [EXPORT] : [COMMENT] Failed to read communication interface reply data
  //! P1: Return code of failed communication interface read call
  //! P1: Upload/download position for which the read call failed
  static const Event STR_HELPER_READING_REPLY_FAILED = MAKE_EVENT(9, severity::LOW);
  //! [EXPORT] : [COMMENT] Unexpected stop of decoding sequence
  //! P1: Return code of failed communication interface read call
  //! P1: Upload/download position for which the read call failed
  static const Event STR_HELPER_COM_ERROR = MAKE_EVENT(10, severity::LOW);
  //! [EXPORT] : [COMMENT] Star tracker did not send a valid reply for a certain timeout.
  //! P1: Position of upload or download packet for which the packet wa sent. P2: Timeout
  static const Event STR_COM_REPLY_TIMEOUT = MAKE_EVENT(11, severity::LOW);
  //! [EXPORT] : [COMMENT] Error during decoding of received reply occurred
  //! P1: Return value of decoding function
  //! P2: Position of upload/download packet, or address of flash write/read request
  static const Event STR_HELPER_DEC_ERROR = MAKE_EVENT(13, severity::LOW);
  //! [EXPORT] : [COMMENT] Position mismatch
  //! P1: The expected position and thus the position for which the image upload/download failed
  static const Event POSITION_MISMATCH = MAKE_EVENT(14, severity::LOW);
  //! [EXPORT] : [COMMENT] Specified file does not exist
  //! P1: Internal state of str helper
  static const Event STR_HELPER_FILE_NOT_EXISTS = MAKE_EVENT(15, severity::LOW);
  //! [EXPORT] : [COMMENT] Sending packet to star tracker failed
  //! P1: Return code of communication interface sendMessage function
  //! P2: Position of upload/download packet, or address of flash write/read request for which
  //! sending failed
  static const Event STR_HELPER_SENDING_PACKET_FAILED = MAKE_EVENT(16, severity::LOW);
  //! [EXPORT] : [COMMENT] Communication interface requesting reply failed
  //! P1: Return code of failed request
  //! P1: Upload/download position, or address of flash write/read request for which transmission
  //! failed
  static const Event STR_HELPER_REQUESTING_MSG_FAILED = MAKE_EVENT(17, severity::LOW);

  StrComHandler(object_id_t objectId);
  virtual ~StrComHandler();

  ReturnValue_t initialize() override;
  ReturnValue_t performOperation(uint8_t operationCode = 0) override;

  /**
   * @brief   Starts sequence to upload image to star tracker
   *
   * @param uploadImage_  Name including absolute path of the image to upload. Must be previously
   *                      transferred to the OBC with the CFDP protocol.
   */
  ReturnValue_t startImageUpload(std::string uploadImage_);

  /**
   * @brief   Calling this function initiates the download of an image from the star tracker.
   *
   * @param path  Path where downloaded image will be stored
   */
  ReturnValue_t startImageDownload(std::string path);

  /**
   * @brief   Will start the firmware update
   *
   * @param fullname    Full name including absolute path of file containing firmware
   *                    update.
   */
  ReturnValue_t startFirmwareUpdate(std::string fullname);

  /**
   * @brief   Starts the flash read procedure
   *
   * @param path                 Path where file with read flash data will be created
   * @param startRegion          Region form where to start reading
   * @param length               Number of bytes to read from flash
   */
  ReturnValue_t startFlashRead(std::string path, uint8_t startRegion, uint32_t length);

  /**
   * @brief   Can be used to interrupt a running data transfer.
   */
  void stopProcess();

  /**
   * @brief   Changes the dafault name of downloaded images
   */
  void setDownloadImageName(std::string filename);

  /**
   * @brief   Sets the name of the file which will be created to store the data read from flash
   */
  void setFlashReadFilename(std::string filename);

  /**
   * @brief	Disables timestamp generation when new file is created
   */
  void disableTimestamping();

  /**
   * @brief	Enables timestamp generation when new file is created
   */
  void enableTimestamping();

 private:
  //! [EXPORT] : [SKIP]
  static constexpr ReturnValue_t NO_SERIAL_DATA_READ = MAKE_RETURN_CODE(128);

  // Size of one image part which can be sent per action request
  static const size_t SIZE_IMAGE_PART = 1024;
  static const uint32_t FLASH_REGION_SIZE = 0x20000;

  struct ImageDownload {
    static const uint32_t LAST_POSITION = 4095;
  };

  static const uint32_t MAX_POLLS = 10000;

  static const uint8_t ACTION_DATA_OFFSET = 3;
  static const uint8_t POS_OFFSET = 3;
  static const uint8_t IMAGE_DATA_OFFSET = 6;
  static const uint8_t FLASH_READ_DATA_OFFSET = 9;
  static const uint8_t REGION_OFFSET = 3;
  static const uint8_t ADDRESS_OFFSET = 4;
  static const size_t CHUNK_SIZE = 1024;
  static const size_t CONFIG_MAX_DOWNLOAD_RETRIES = 3;
  static const uint32_t FLASH_ERASE_DELAY = 500;

  enum class InternalState {
    SLEEPING,
    POLL_ONE_REPLY,
    UPLOAD_IMAGE,
    DOWNLOAD_IMAGE,
    FLASH_READ,
    FIRMWARE_UPDATE
  };

  InternalState state = InternalState::SLEEPING;

  ArcsecDatalinkLayer datalinkLayer;

  MutexIF *lock;
  BinarySemaphore semaphore;

  Countdown replyTimeout = Countdown(20);

  struct UploadImage {
    // Name including absolute path of image to upload
    std::string uploadFile;
  };
  UploadImage uploadImage;

  struct DownloadImage {
    // Path where the downloaded image will be stored
    std::string path;
    // Default name of downloaded image, can be changed via command
    std::string filename = "image.bin";
  };
  DownloadImage downloadImage;

  struct FlashWrite {
    // File which contains data to write when executing the flash write command
    std::string fullname;
    // The first region to write to
    uint8_t firstRegion = 0;
    // Maximum region the flash write command is allowed to write to
    uint8_t lastRegion = 0;
    // Will be set with the flash write command and specifies the start address where to write the
    // flash data to
    uint32_t address = 0;
  };
  FlashWrite flashWrite;

  struct FlashRead {
    // Path where the file containing the read data will be stored
    std::string path = "";
    // Default name of file containing the data read from flash, can be changed via command
    std::string filename = "flashread.bin";
    // Will be set with the flash read command
    uint8_t startRegion = 0;
    // Number of bytes to read from flash
    uint32_t size = 0;
  };
  FlashRead flashRead;

#ifdef XIPHOS_Q7S
  SdCardManager *sdcMan = nullptr;
#endif

  std::array<uint8_t, startracker::MAX_FRAME_SIZE> cmdBuf{};
  std::array<uint8_t, 4096> recBuf{};

  bool replyWasReceived = false;
  const uint8_t *replyPtr = nullptr;
  size_t replyLen = 0;
  ReturnValue_t replyResult = returnvalue::OK;

  bool terminate = false;

#ifdef EGSE
  bool timestamping = false;
#else
  bool timestamping = true;
#endif

  int serialPort = 0;
  struct termios tty = {};

  // Queue id of raw data receiver
  MessageQueueId_t rawDataReceiver = MessageQueueIF::NO_QUEUE;

  ReturnValue_t initializeInterface(CookieIF *cookie) override;
  ReturnValue_t sendMessage(CookieIF *cookie, const uint8_t *sendData, size_t sendLen) override;
  ReturnValue_t getSendSuccess(CookieIF *cookie) override;
  ReturnValue_t requestReceiveMessage(CookieIF *cookie, size_t requestLen) override;
  ReturnValue_t readReceivedMessage(CookieIF *cookie, uint8_t **buffer, size_t *size) override;

  ReturnValue_t handleSerialReception();

  /**
   * @brief   Performs image uploading
   */
  ReturnValue_t performImageUpload();

  /**
   * @brief Performs firmware update
   *
   * @return    returnvalue::OK if successful, otherwise error return value
   */
  ReturnValue_t performFirmwareUpdate();

  /**
   * @brief   Performs download of last taken image from the star tracker.
   *
   * @details Download is split over multiple packets transporting each a maximum of 1024 bytes.
   *          In case the download of one position fails, the same packet will be again
   *          requested. If the download of the packet fails CONFIG_MAX_DOWNLOAD_RETRIES times,
   *          the download will be stopped.
   */
  ReturnValue_t performImageDownload();

  /**
   * @brief   Handles flash write procedure
   *
   * @param  ID of first region to write to
   *
   * @return returnvalue::OK if successful, otherwise returnvalue::FAILED
   */
  ReturnValue_t performFlashWrite();

  /**
   * @brief   Sends a sequence of commands to the star tracker to read larger parts from the
   *          flash memory.
   */
  ReturnValue_t performFlashRead();

  /**
   * @brief   Sends packet to the star tracker and reads reply by using the communication
   *          interface
   * @details
   * The reply frame is stored in the data link layer helper. A pointer to the start of the frame
   * is assigned to the @replyPtr member of this class. The frame length will be assigned to
   * the @replyLen member.
   * @param size        Size of data beforehand written to the commandBuffer
   * @param parameter   Parameter 2 of trigger event function
   *
   * @return  returnvalue::OK if successful, otherwise returnvalue::FAILED
   */
  ReturnValue_t sendAndRead(size_t size, uint32_t parameter);

  /**
   * @brief   Checks the header (type id and status fields) of the action reply
   *
   * @return  returnvalue::OK if reply confirms success of packet transfer, otherwise REUTRN_FAILED
   */
  ReturnValue_t checkActionReply(size_t replySize);

  /**
   * @brief   Checks the position field in a star tracker upload/download reply.
   *
   * @param expectedPosition  Value of expected position
   *
   * @return returnvalue::OK if received position matches expected position, otherwise
   * returnvalue::FAILED
   */
  ReturnValue_t checkReplyPosition(uint32_t expectedPosition);

#ifdef XIPHOS_Q7S
  /**
   * @brief   Checks if a path points to an sd card and whether the SD card is monuted.
   *
   * @return  SD_NOT_MOUNTED id SD card is not mounted, otherwise returnvalue::OK
   */
  ReturnValue_t checkPath(std::string name);
#endif

  /**
   * @brief Unlocks a range of flash regions
   *
   * @param from    First region in range to unlock
   * @param to      Last region in range to unlock
   *
   */
  ReturnValue_t unlockAndEraseRegions(uint32_t from, uint32_t to);

  /**
   * The reply frame is stored in the data link layer helper. A pointer to the start of the frame
   * is assigned to the @replyPtr member of this class. The frame length will be assigned to
   * the @replyLen member.
   * @param failParameter
   * @return
   */
  ReturnValue_t readOneReply(uint32_t failParameter);

  void resetReplyHandlingState();
};

#endif /* BSP_Q7S_DEVICES_STRHELPER_H_ */