#ifndef MISSION_POWER_GOMSPACEPACKETS_H_
#define MISSION_POWER_GOMSPACEPACKETS_H_

#include <fsfw/serialize/SerialBufferAdapter.h>
#include <fsfw/serialize/SerialFixedArrayListAdapter.h>
#include <fsfw/serialize/SerialLinkedListAdapter.h>
#include <fsfw/serialize/SerializeElement.h>
#include <mission/power/gsDefs.h>

class CspParamRequestBase : public SerialLinkedListAdapter<SerializeIF> {
 public:
  CspParamRequestBase(uint16_t querySize, uint8_t tableId)
      : querySize(querySize), tableId(tableId) {
    setLinks();
  }

 protected:
  void setLinks() {
    setStart(&cspPort);
    cspPort.setNext(&querySize);
    querySize.setNext(&action);
    action.setNext(&tableId);
    tableId.setNext(&payloadlength);
    payloadlength.setNext(&checksum);
    checksum.setNext(&seq);
    seq.setNext(&total);
  }
  SerializeElement<uint8_t> cspPort = GOMSPACE::PARAM_PORT;
  SerializeElement<uint16_t> querySize;
  SerializeElement<uint8_t> action = GOMSPACE::ParamRequestIds::GET;
  SerializeElement<uint8_t> tableId;
  // Default value 0: Fetch whole table.
  SerializeElement<uint16_t> payloadlength = 0;
  SerializeElement<uint16_t> checksum = GOMSPACE::IGNORE_CHECKSUM;
  SerializeElement<uint16_t> seq = 0;
  SerializeElement<uint16_t> total = 0;
};

/**
 * @brief	This class can be used to generated the command for the CspComIF
 * 			to reset the watchdog in a gomspace device.
 */
class WatchdogResetCommand : public SerialLinkedListAdapter<SerializeIF> {
 public:
  WatchdogResetCommand() { setLinks(); }

 private:
  WatchdogResetCommand(const WatchdogResetCommand &command);
  void setLinks() {
    setStart(&cspPort);
    cspPort.setNext(&querySize);
    querySize.setNext(&magic);
  }
  SerializeElement<uint8_t> cspPort = GOMSPACE::P60_PORT_GNDWDT_RESET;
  SerializeElement<uint16_t> querySize = 1;
  /* Sending 0x78 to port 9 of a gomspace device resets the ground watchdog */
  SerializeElement<uint8_t> magic = 0x78;
};

/**
 * @brief	A serial linked list adapter implementation to generate ping
 * 			commands for devices supporting the CSP protocol. This command can
 * 			be sent to the CspComIF which will send out the ping request.
 *
 * @details	A ping request simply sends back the received data provided by the
 * 			data buffer. cspPort and querySize are only informations required
 * 			by the CspComI and other than the data array not physically
 * 			transmitted to the target device.
 */
class CspPingCommand : public SerialLinkedListAdapter<SerializeIF> {
 public:
  /**
   * @brief	Constructor
   *
   * @param querySize_	The size of bytes replied by the ping request.
   * 						Amounts to the number of bytes send.
   * @param data_			Pointer to data which should be sent to the device.
   * 						All data will be sent back by the ping target.
   */
  CspPingCommand(const uint8_t *data_, uint16_t querySize_)
      : querySize(querySize_), data(data_, querySize_) {
    setLinks();
  }

 private:
  CspPingCommand(const CspPingCommand &command);
  void setLinks() {
    setStart(&cspPort);
    cspPort.setNext(&querySize);
    querySize.setNext(&data);
  }
  SerializeElement<uint8_t> cspPort = GOMSPACE::PING_PORT;
  SerializeElement<uint16_t> querySize;
  SerializeElement<SerialBufferAdapter<uint8_t>> data;
};

/**
 * @brief	A serial linked list adapter implementation of the gs_rparam_query_t
 * 			struct defined in rparam.h. Can be used to build the message to set
 * 			a parameter in gomspace devices.
 *
 * @note	cspPort and querySize will not be sent with the CSP packet to the
 * 			gomspace device but are required for the CspComIF to get the port
 * 			and the size to query.
 */
class CspSetParamCommand : public CspParamRequestBase {
 public:
  CspSetParamCommand(uint16_t querySize_, uint16_t payloadlength_, uint16_t addr_,
                     const uint8_t *parameter_, uint8_t parameterCount_, uint8_t tableId = 1)
      : CspParamRequestBase(querySize_, tableId),
        addr(addr_),
        parameter(parameter_, parameterCount_) {
    total.setNext(&addr);
    addr.setNext(&parameter);
    CspParamRequestBase::payloadlength = payloadlength_;
    CspParamRequestBase::action = GOMSPACE::ParamRequestIds::SET;
  }
  CspSetParamCommand(const CspSetParamCommand &command) = delete;

 private:
  SerializeElement<uint16_t> addr;
  SerializeElement<SerialBufferAdapter<uint8_t>> parameter;
};

/**
 * @brief	This class can be used to generate a get param command for the
 * 			gomspace devices which will be sent to the device communication
 * 			interface object.
 *
 * @note 	cspPort and querySize only serve as information for the CspComIF
 * 			and will not be transmitted physically to the target device.
 */
class CspGetParamCommand : public CspParamRequestBase {
 public:
  /* The size of the header of a gomspace CSP packet. */
  static const uint8_t GS_HDR_LENGTH = 12;

  CspGetParamCommand(uint16_t querySize_, uint8_t tableId_, uint16_t addresslength_, uint16_t addr_)
      : CspParamRequestBase(querySize_, tableId_), addr(addr_) {
    total.setNext(&addr);
    CspParamRequestBase::tableId = tableId_;
    CspParamRequestBase::payloadlength = addresslength_;
  }
  CspGetParamCommand(const CspGetParamCommand &command) = delete;

 private:
  SerializeElement<uint16_t> addr;
};

/**
 * @brief   This class can be used to generate a get param command for the
 *          gomspace devices which will be sent to the device communication
 *          interface object.
 *
 * @note    cspPort and querySize only serve as information for the CspComIF
 *          and will not be transmitted physically to the target device.
 */
class RequestFullTableCommand : public CspParamRequestBase {
 public:
  RequestFullTableCommand(uint16_t querySize_, uint8_t tableId_)
      : CspParamRequestBase(querySize_, tableId_) {}

  RequestFullTableCommand(const RequestFullTableCommand &command) = delete;

 private:
};

/**
 * @brief	This class can be used to deserialize replies from gomspace devices
 * 			and extract the relevant data.
 */
class CspGetParamReply : public SerialLinkedListAdapter<SerializeIF> {
 public:
  /**
   * @brief	Constructor
   *
   * @param payloadBuffer	Pointer to a buffer to store the payload data of
   * 						the CSP packet.
   * @param payloadBufferSz	The size of the payload buffer where the payload
   * 							data will be stored.
   */
  CspGetParamReply(uint8_t *payloadBuffer_, uint8_t payloadBufferSz_)
      : payload(payloadBuffer_, payloadBufferSz_) {
    setLinks();
  }

  uint8_t getAction() { return action; }

  uint8_t getTableId() { return tableId; }

  uint16_t getLength() { return length; }

  uint16_t getAddress() { return addr; }

 private:
  CspGetParamReply(const CspGetParamReply &reply);
  void setLinks() {
    setStart(&action);
    action.setNext(&tableId);
    tableId.setNext(&length);
    length.setNext(&checksum);
    checksum.setNext(&seq);
    seq.setNext(&total);
    total.setNext(&addr);
    addr.setNext(&payload);
  }

  SerializeElement<uint8_t> action;
  SerializeElement<uint8_t> tableId;
  SerializeElement<uint16_t> length;  // length of address field + payload data
  SerializeElement<uint16_t> checksum;
  SerializeElement<uint16_t> seq;
  SerializeElement<uint16_t> total;
  SerializeElement<uint16_t> addr;
  SerializeElement<SerialBufferAdapter<uint8_t>> payload;
};

/**
 * @brief	This class generates telemetry packets containing data from
 * 			CSP get-parameter-replies.
 */
class ParamReply : public SerialLinkedListAdapter<SerializeIF> {
 public:
  /**
   * @brief	Constructor
   *
   * @param payloadBuffer	Pointer to a buffer to store the payload data of
   * 						the CSP packet.
   * @param payloadBufferSz	The size of the payload buffer where the payload
   * 							data will be stored.
   */
  ParamReply(uint8_t action_, uint8_t tableId_, uint16_t addr_, uint16_t payloadLength_,
             uint8_t *payloadBuffer_)
      : action(action_),
        tableId(tableId_),
        addr(addr_),
        payloadLength(payloadLength_),
        payload(payloadBuffer_, payloadLength) {
    setLinks();
  }

 private:
  ParamReply(const CspGetParamReply &reply);
  void setLinks() {
    setStart(&action);
    action.setNext(&tableId);
    tableId.setNext(&addr);
    addr.setNext(&payloadLength);
    payloadLength.setNext(&payload);
  }
  SerializeElement<uint8_t> action;
  SerializeElement<uint8_t> tableId;
  SerializeElement<uint16_t> addr;
  SerializeElement<uint16_t> payloadLength;
  SerializeElement<SerialBufferAdapter<uint16_t>> payload;
};

/**
 * @brief	This class generates the reply containing data from a full housekeeping table
 * request of the PDU2.
 */
class Pdu2FullTableReply : public SerialLinkedListAdapter<SerializeIF> {
 public:
  /**
   * @brief	Constructor
   *
   * @param action_	The command which triggered the full table request.
   * @param tableId_	The id of the requested table.
   * @param tableDataset_	The dataset holding the table data.
   */
  Pdu2FullTableReply(uint8_t action_, uint8_t tableId_, SerializeIF *tableDataset_)
      : action(action_), tableId(tableId_), dataset(tableDataset_) {
    setLinks();
  }

 private:
  Pdu2FullTableReply(const Pdu2FullTableReply &reply);
  void setLinks() {
    setStart(&action);
    action.setNext(&tableId);
    tableId.setNext(&dataset);
  }
  SerializeElement<uint8_t> action;
  SerializeElement<uint8_t> tableId;
  LinkedElement<SerializeIF> dataset;
};

/**
 * @brief	This class helps to unpack information from an action message
 * 			  to set a parameter in gomspace devices. The action message can be
 * 			  for example received from the PUS Service 8.
 */
class SetParamMessageUnpacker : public SerialLinkedListAdapter<SerializeIF> {
 public:
  /* Largest parameter is a uint32_t */
  static const uint32_t MAX_SIZE = 4;

  SetParamMessageUnpacker() { setLinks(); }

  uint16_t getAddress() { return address; }

  uint8_t *getParameter() { return parameter->front(); }

  uint8_t getParameterSize() { return parameter->size; }

 private:
  void setLinks() {
    setStart(&address);
    address.setNext(&parameter);
  }
  SetParamMessageUnpacker(const SetParamMessageUnpacker &message);
  SerializeElement<uint16_t> address;
  SerializeElement<SerialFixedArrayListAdapter<uint8_t, MAX_SIZE, uint8_t>> parameter;
};

/**
 * @brief   This class generates a message which can be sent to the GomspaceDeviceHandler to
 *          command a parameter change.
 *
 * @details Structure of set parameter command:
 *          |   memory address  |   size of parameter value |   parameter value |
 */
class GomspaceSetParamMessage : public SerialLinkedListAdapter<SerializeIF> {
 public:
  /* The size of the largest parameter */
  static const uint8_t MAX_SIZE = 4;

  /**
   * @brief   Constructor
   *
   * @param memoryAddress   The address of the parameter to change in the configuration table.
   * @param parameterValue  Pointer to the parameter value to set.
   * @param parameterSize The size of the parameter.
   *
   */
  GomspaceSetParamMessage(uint16_t memoryAddress, const uint8_t *parameterValue,
                          uint8_t parameterSize)
      : memoryAddress(memoryAddress), parameterValueBuffer(parameterValue, parameterSize, true) {
    setLinks();
  }

 private:
  GomspaceSetParamMessage(const GomspaceSetParamMessage &reply);
  void setLinks() {
    setStart(&memoryAddress);
    memoryAddress.setNext(&parameterValueBuffer);
  }
  SerializeElement<uint16_t> memoryAddress;
  /**
   * Parameter can be uint8_t, uint16_t or uint32_t. Thus max size of parameterValueBuffer is
   * four bytes.
   */
  SerializeElement<SerialBufferAdapter<uint8_t>> parameterValueBuffer;
};

/**
 * @brief	This class helps to unpack information from an action message
 * 			to get a parameter from gomspace devices. The action message can be
 * 			for example received from the PUS Service 8.
 */
class GetParamMessageUnpacker : public SerialLinkedListAdapter<SerializeIF> {
 public:
  GetParamMessageUnpacker() { setLinks(); }

  uint8_t getTableId() { return tableId; }

  uint16_t getAddress() { return address; }

  uint8_t getParameterSize() { return parameterSize; }

 private:
  GetParamMessageUnpacker(const GetParamMessageUnpacker &message);
  void setLinks() {
    setStart(&tableId);
    tableId.setNext(&address);
    address.setNext(&parameterSize);
  }
  SerializeElement<uint8_t> tableId;
  SerializeElement<uint16_t> address;  // The memory address offset within the table
  /* The size of the requested value (e.g. temperature is a uint16_t value) */
  SerializeElement<uint8_t> parameterSize;
};

#endif /* MISSION_POWER_GOMSPACEPACKETS_H_ */