#ifndef LINUX_OBC_PDECHANDLER_H_
#define LINUX_OBC_PDECHANDLER_H_

#include <fsfw/timemanager/Countdown.h>

#include "OBSWConfig.h"
#include "PdecConfig.h"
#include "eive/definitions.h"
#include "fsfw/action/ActionHelper.h"
#include "fsfw/action/HasActionsIF.h"
#include "fsfw/objectmanager/SystemObject.h"
#include "fsfw/parameters/ParameterHelper.h"
#include "fsfw/parameters/ReceivesParameterMessagesIF.h"
#include "fsfw/returnvalues/returnvalue.h"
#include "fsfw/storagemanager/StorageManagerIF.h"
#include "fsfw/tasks/ExecutableObjectIF.h"
#include "fsfw/tmtcservices/AcceptsTelecommandsIF.h"
#include "fsfw_hal/common/gpio/gpioDefinitions.h"
#include "fsfw_hal/linux/gpio/LinuxLibgpioIF.h"

struct UioNames {
  const char* configMemory;
  const char* ramMemory;
  const char* registers;
  const char* irq;
};

/**
 * @brief   This class controls the PDEC IP Core implemented in the programmable logic of the
 *          Zynq-7020. All registers and memories of the PDEC IP Core are accessed via UIO
 *          drivers.
 *
 * @details The PDEC IP Core is responsible for processing data received in form of CLTUs from the
 *          S-Band transceiver. This comprises the BCH decoding of the CLTUs and reconstruction of
 *          telecommand transfer frames. Finally the PDEC stores the TC segment transported with
 *          the TC transfer frame in a register. As soon as a new TC has been received a new
 *          frame acceptance report (FAR) will be generated. If the FAR confirms the validity of
 *          a received TC segment, the data can be read out from the associated register.
 *          Currently, the ground software only supports transmissions of CLTUs containing one
 *          space packet.
 *          Link to datasheet of PDEC IP Core: https://eive-cloud.irs.uni-stuttgart.de/index.php/
 *          apps/files/?dir=/EIVE_IRS/Arbeitsdaten/08_Used%20Components/CCSDS_IP_Cores&fileid=1108967
 *
 * @author  J. Meier
 */
class PdecHandler : public SystemObject,
                    public ExecutableObjectIF,
                    public HasActionsIF,
                    public ReceivesParameterMessagesIF {
 public:
  static constexpr dur_millis_t IRQ_TIMEOUT_MS = 500;

  enum class Modes { POLLED, IRQ };

  /**
   * @brief   Constructor
   * @param objectId  Object ID of PDEC handler system object
   * @param tcDestinationId   Object ID of object responsible for processing TCs.
   * @param gpioComIF Pointer to GPIO interace responsible for driving GPIOs.
   * @param pdecReset GPIO ID of GPIO connected to the reset signal of the PDEC.
   * @param uioConfigMemory String of uio device file same mapped to the PDEC memory space
   * @param uioregsiters  String of uio device file same mapped to the PDEC register space
   */
  PdecHandler(object_id_t objectId, object_id_t tcDestinationId, LinuxLibgpioIF* gpioComIF,
              gpioId_t pdecReset, UioNames names);

  virtual ~PdecHandler();

  ReturnValue_t performOperation(uint8_t operationCode = 0);

  ReturnValue_t initialize() override;

  MessageQueueId_t getCommandQueue() const;

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

  ReturnValue_t getParameter(uint8_t domainId, uint8_t uniqueIdentifier,
                             ParameterWrapper* parameterWrapper, const ParameterWrapper* newValues,
                             uint16_t startAtIndex) override;

  static const uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::PDEC_HANDLER;

  //! [EXPORT] : [COMMENT] Frame acceptance report signals an invalid frame
  //! P1: The frame analysis information (FrameAna field of PDEC_FAR register)
  //! P2: When frame declared illegal this parameter this parameter gives information about the
  //! reason (IReason field of the PDEC_FAR register)
  static const Event INVALID_TC_FRAME = MAKE_EVENT(1, severity::HIGH);
  //! [EXPORT] : [COMMENT] Read invalid FAR from PDEC after startup
  static const Event INVALID_FAR = MAKE_EVENT(2, severity::HIGH);
  //! [EXPORT] : [COMMENT] Carrier lock detected
  static const Event CARRIER_LOCK = MAKE_EVENT(3, severity::INFO);
  //! [EXPORT] : [COMMENT] Bit lock detected (data valid)
  static const Event BIT_LOCK_PDEC = MAKE_EVENT(4, severity::INFO);
  //! [EXPORT] : [COMMENT] Lost carrier lock
  static const Event LOST_CARRIER_LOCK_PDEC = MAKE_EVENT(5, severity::INFO);
  //! [EXPORT] : [COMMENT] Lost bit lock
  static const Event LOST_BIT_LOCK_PDEC = MAKE_EVENT(6, severity::INFO);
  //! [EXPORT] : [COMMENT] Too many IRQs over the time window of one second. P1: Allowed TCs
  static constexpr Event TOO_MANY_IRQS = MAKE_EVENT(7, severity::MEDIUM);
  static constexpr Event POLL_SYSCALL_ERROR_PDEC =
      event::makeEvent(SUBSYSTEM_ID, 8, severity::MEDIUM);
  static constexpr Event WRITE_SYSCALL_ERROR_PDEC =
      event::makeEvent(SUBSYSTEM_ID, 9, severity::HIGH);
  //! [EXPORT] : [COMMENT] Failed to pull PDEC reset to low
  static constexpr Event PDEC_RESET_FAILED = event::makeEvent(SUBSYSTEM_ID, 10, severity::HIGH);
  //! [EXPORT] : [COMMENT] Failed to open the IRQ uio file
  static constexpr Event OPEN_IRQ_FILE_FAILED = event::makeEvent(SUBSYSTEM_ID, 11, severity::HIGH);

 private:
  static const uint8_t INTERFACE_ID = CLASS_ID::PDEC_HANDLER;

  static constexpr Modes OP_MODE = Modes::IRQ;

  static const ReturnValue_t ABANDONED_CLTU_RETVAL = MAKE_RETURN_CODE(0xA0);
  static const ReturnValue_t FRAME_DIRTY_RETVAL = MAKE_RETURN_CODE(0xA1);
  static const ReturnValue_t FRAME_ILLEGAL_ONE_REASON = MAKE_RETURN_CODE(0xA2);
  static const ReturnValue_t FRAME_ILLEGAL_MULTIPLE_REASONS = MAKE_RETURN_CODE(0xA2);
  static const ReturnValue_t AD_DISCARDED_LOCKOUT_RETVAL = MAKE_RETURN_CODE(0xA3);
  static const ReturnValue_t AD_DISCARDED_WAIT_RETVAL = MAKE_RETURN_CODE(0xA4);
  static const ReturnValue_t AD_DISCARDED_NS_VS = MAKE_RETURN_CODE(0xA5);

  //! [EXPORT] : [COMMENT] Received action message with unknown action id
  static const ReturnValue_t COMMAND_NOT_IMPLEMENTED = MAKE_RETURN_CODE(0xB0);

  static const ReturnValue_t NO_REPORT_RETVAL = MAKE_RETURN_CODE(0xA6);
  //! Error in version number and reserved A and B fields
  static const ReturnValue_t ERROR_VERSION_NUMBER_RETVAL = MAKE_RETURN_CODE(0xA7);
  //! Illegal combination of bypass and control command flag
  static const ReturnValue_t ILLEGAL_COMBINATION_RETVAL = MAKE_RETURN_CODE(0xA8);
  //! Spacecraft identifier did not match
  static const ReturnValue_t INVALID_SC_ID_RETVAL = MAKE_RETURN_CODE(0xA9);
  //! VC identifier bits 0 to 4 did not match
  static const ReturnValue_t INVALID_VC_ID_MSB_RETVAL = MAKE_RETURN_CODE(0xAA);
  //! VC identifier bit 5 did not match
  static const ReturnValue_t INVALID_VC_ID_LSB_RETVAL = MAKE_RETURN_CODE(0xAB);
  //! N(S) of BC or BD frame not set to all zeros
  static const ReturnValue_t NS_NOT_ZERO_RETVAL = MAKE_RETURN_CODE(0xAC);
  //! Invalid BC control command
  static const ReturnValue_t INVALID_BC_CC = MAKE_RETURN_CODE(0xAE);

  static const uint32_t QUEUE_SIZE = config::CCSDS_HANDLER_QUEUE_SIZE;

  // Action IDs
  static const ActionId_t PRINT_CLCW = 0;
  // Print PDEC monitor register
  static const ActionId_t PRINT_PDEC_MON = 1;

#ifdef TE0720_1CFA
  static const int CONFIG_MEMORY_MAP_SIZE = 0x400;
  static const int RAM_MAP_SIZE = 0x4000;
  static const int REGISTER_MAP_SIZE = 0x10000;
#else
  static const int CONFIG_MEMORY_MAP_SIZE = 0x400;
  static const int RAM_MAP_SIZE = 0x4000;
  static const int REGISTER_MAP_SIZE = 0x4000;
#endif /* BOARD_TE0720 == 1 */

  static const size_t MAX_TC_SEGMENT_SIZE = 1017;
  static const uint8_t MAP_ID_MASK = 0x3F;

#ifdef TE0720_1CFA
  static const uint32_t PHYSICAL_RAM_BASE_ADDRESS = 0x32000000;
#else
  static const uint32_t PHYSICAL_RAM_BASE_ADDRESS = 0x26000000;
#endif

  // Expected value stored in FAR register after reset
  static const uint32_t FAR_RESET = 0x7FE0;

  static const uint32_t TC_SEGMENT_LEN = 1017;

  static const uint32_t NO_RF_MASK = 0x8000;
  static const uint32_t NO_BITLOCK_MASK = 0x4000;

  static const uint32_t MAX_INIT_TRIES = 20;

  class ParameterId {
   public:
    // ID of the parameter to update the positive window of AD frames
    static const uint8_t POSITIVE_WINDOW = 0;
    // ID of the parameter to update the negative window of AD frames
    static const uint8_t NEGATIVE_WINDOW = 1;
  };

  static constexpr uint32_t MAX_ALLOWED_IRQS_PER_WINDOW = 800;

  enum class FrameAna_t : uint8_t {
    ABANDONED_CLTU,
    FRAME_DIRTY,
    FRAME_ILLEGAL,
    FRAME_ILLEGAL_MULTI_REASON,
    AD_DISCARDED_LOCKOUT,
    AD_DISCARDED_WAIT,
    AD_DISCARDED_NS_VR,
    FRAME_ACCEPTED
  };

  enum class IReason_t : uint8_t {
    NO_REPORT,
    ERROR_VERSION_NUMBER,
    ILLEGAL_COMBINATION,
    INVALID_SC_ID,
    INVALID_VC_ID_LSB,
    INVALID_VC_ID_MSB,
    NS_NOT_ZERO,
    INCORRECT_BC_CC
  };

  enum class State : uint8_t { INIT, PDEC_RESET, RUNNING, WAIT_FOR_RECOVERY };

  static uint32_t CURRENT_FAR;

  Countdown genericCheckCd = Countdown(IRQ_TIMEOUT_MS);
  object_id_t tcDestinationId;

  AcceptsTelecommandsIF* tcDestination = nullptr;

  LinuxLibgpioIF* gpioComIF = nullptr;

  uint32_t interruptCounter = 0;
  Countdown interruptWindowCd = Countdown(1000);

  /**
   * Reset signal is required to hold PDEC in reset state until the configuration has been
   * written to the appropriate memory space.
   * Can also be used to reboot PDEC in case of erros.
   */
  gpioId_t pdecReset = gpio::NO_GPIO;

  uint32_t tcAbortCounter = 0;

  ActionHelper actionHelper;

  StorageManagerIF* tcStore = nullptr;

  MessageQueueIF* commandQueue = nullptr;

  State state = State::INIT;

  /**
   * Pointer pointing to base address of the PDEC memory space.
   * This address is equivalent with the base address of the section named configuration area in
   * the PDEC datasheet.
   */
  uint32_t* memoryBaseAddress = nullptr;

  uint32_t* ramBaseAddress = nullptr;

  // Pointer pointing to base address of register space
  uint32_t* registerBaseAddress = nullptr;

  uint8_t tcSegment[TC_SEGMENT_LEN];

  // Used to check carrier and bit lock changes (default set to no rf and no bitlock)
  uint32_t lastClcw = 0xC000;

  bool carrierLock = false;
  bool bitLock = false;

  UioNames uioNames;

  ParameterHelper paramHelper;

  PdecConfig pdecConfig;

  uint32_t initTries = 0;

  /**
   * @brief	Performs initialization stuff which must be performed in first
   * 				loop of running task
   *
   * @return OK if successful, otherwise FAILED
   */
  ReturnValue_t firstLoop();

  /**
   * @brief   Reads and handles messages stored in the commandQueue
   */
  void readCommandQueue(void);

  ReturnValue_t polledOperation();
  ReturnValue_t irqOperation();
  ReturnValue_t handleInitState();
  void openIrqFile(int* fd);
  ReturnValue_t checkAndHandleIrqs(int fd, uint32_t& info);

  uint32_t readFar();

  /**
   * @brief   This functions writes the configuration parameters to the configuration
   *          section of the PDEC.
   */
  void writePdecConfigDuringReset(PdecConfig& config);

  /**
   * @brief   Reading the FAR resets the set stat flag which signals a new TC. Without clearing
   *          this flag no new TC will be excepted. After start up the flag is set and needs
   *          to be reset.
   *          Stat flag 0 - new TC received
   *          Stat flag 1 - old TC (ready to receive next TC)
   */
  ReturnValue_t resetFarStatFlag();

  /**
   * @brief   Releases the PDEC from reset state. PDEC will start with loading the written
   *          configuration parameters.
   */
  ReturnValue_t releasePdec();

  /**
   * @brief Will set PDEC in reset state. Use releasePdec() to release PDEC
   * 				from reset state
   *
   * @return OK if successful, otherwise error return value
   */
  ReturnValue_t pdecToReset();

  /**
   * @brief   Reads the FAR register and checks if a new TC has been received.
   */
  bool newTcReceived();

  /**
   * @brief   Checks if carrier lock or bit lock has been detected and triggers appropriate
   *          event.
   */
  void checkLocks();

  void resetIrqLimiters();

  /**
   * @brief   Analyzes the FramAna field (frame analysis data) of a FAR report.
   *
   * @return  True if frame valid, otherwise false.
   */
  bool checkFrameAna(uint32_t pdecFar);

  /**
   * @brief   This function handles the IReason field of the frame analysis report.
   *
   * @details In case frame as been declared illegal for multiple reasons, the reason with the
   *          lowest value will be shown.
   */
  void handleIReason(uint32_t pdecFar, ReturnValue_t parameter1);

  /**
   * @brief   Handles the reception of new TCs. Reads the pointer to the storage location of the
   *          new TC segment, extracts the PUS packet and forwards the data to the object
   *          responsible for processing the TC.
   */
  void handleNewTc();

  /**
   * @brief   Function reads the last received TC segment from the PDEC memory and copies
   *          the data to the tcSegement array.
   *
   * @param tcLength The length of the received TC.
   *
   */
  ReturnValue_t readTc(uint32_t& tcLength);

  /**
   * @brief   Prints the tc segment data
   */
  void printTC(uint32_t tcLength);

  /**
   * @brief   This function calculates the entry for the configuration of the MAP ID routing.
   *
   * @param mapAddr   The MAP ID to configure
   * @param moduleId  The destination module where all TCs with the map id mapAddr will be routed
   *                  to.
   *
   * @details The PDEC has different modules where the TCs can be routed to. A lookup table is
   *          used which links the MAP ID field to the destination module. The entry for this
   *          lookup table is created by this function and must be stored in the configuration
   *          memory region of the PDEC. The entry has a specific format
   */
  uint8_t calcMapAddrEntry(uint8_t moduleId);

  /**
   * brief    Returns the 32-bit wide communication link control word (CLCW)
   */
  uint32_t getClcw();

  /**
   * @brief	Returns the PDEC monitor register content
   *
   */
  uint32_t getPdecMon();

  /**
   * @brief    Reads and prints the CLCW. Can be useful for debugging.
   */
  void printClcw();

  /**
   * @brief	Prints monitor register information to debug console.
   */
  void printPdecMon();

  std::string getMonStatusString(uint32_t status);
};

#endif /* LINUX_OBC_PDECHANDLER_H_ */