#ifndef MISSION_DEVICES_DEVICEDEFINITIONS_SPBASE_H_
#define MISSION_DEVICES_DEVICEDEFINITIONS_SPBASE_H_

#include <fsfw/tmtcpacket/ccsds/SpacePacketCreator.h>
#include <fsfw/tmtcpacket/ccsds/SpacePacketReader.h>

#include <cstdint>

namespace ploc {

struct SpTcParams {
  SpTcParams(SpacePacketCreator& creator) : creator(creator) {}

  SpTcParams(SpacePacketCreator& creator, uint8_t* buf, size_t maxSize)
      : creator(creator), buf(buf), maxSize(maxSize) {}

  void setPayloadLen(size_t payloadLen_) { dataFieldLen = payloadLen_ + 2; }

  void setDataFieldLen(size_t dataFieldLen_) { dataFieldLen = dataFieldLen_; }

  SpacePacketCreator& creator;
  uint8_t* buf = nullptr;
  size_t maxSize = 0;
  size_t dataFieldLen = 0;
};

class SpTcBase {
 public:
  SpTcBase(SpTcParams params) : spParams(params) {
    payloadStart = spParams.buf + ccsds::HEADER_LEN;
    updateSpFields();
  }

  SpTcBase(SpTcParams params, uint16_t apid, uint16_t seqCount) : spParams(params) {
    spParams.creator.setApid(apid);
    spParams.creator.setSeqCount(seqCount);
    payloadStart = spParams.buf + ccsds::HEADER_LEN;
    updateSpFields();
  }

  void updateSpFields() {
    spParams.creator.setDataLen(spParams.dataFieldLen - 1);
    spParams.creator.setPacketType(ccsds::PacketType::TC);
  }

  const uint8_t* getFullPacket() const { return spParams.buf; }

  size_t getFullPacketLen() const { return spParams.creator.getFullPacketLen(); }

  uint16_t getApid() const { return spParams.creator.getApid(); }

  ReturnValue_t checkPayloadLen() {
    if (ccsds::HEADER_LEN + spParams.dataFieldLen > spParams.maxSize) {
      return SerializeIF::BUFFER_TOO_SHORT;
    }

    return returnvalue::OK;
  }

  ReturnValue_t serializeHeader() {
    updateSpFields();
    size_t serLen = 0;
    return spParams.creator.serializeBe(spParams.buf, serLen, spParams.maxSize);
  }

  ReturnValue_t checkSizeAndSerializeHeader() {
    ReturnValue_t result = checkPayloadLen();
    if (result != returnvalue::OK) {
      return result;
    }
    return serializeHeader();
  }

  ReturnValue_t calcCrc() {
    /* Calculate crc */
    uint16_t crc = CRC::crc16ccitt(spParams.buf, getFullPacketLen() - 2);

    /* Add crc to packet data field of space packet */
    size_t serializedSize = 0;
    return SerializeAdapter::serialize(&crc, spParams.buf + getFullPacketLen() - 2, &serializedSize,
                                       spParams.maxSize, SerializeIF::Endianness::BIG);
  }

 protected:
  ploc::SpTcParams spParams;
  uint8_t* payloadStart;
};

/**
 * @brief   Class for handling tm replies of the supervisor.
 */
class SpTmReader : public SpacePacketReader {
 public:
  SpTmReader() = default;
  /**
   * @brief   Constructor creates idle packet and sets length field to maximum allowed size.
   */
  SpTmReader(const uint8_t* buf, size_t maxSize) : SpacePacketReader(buf, maxSize) {}

  ReturnValue_t setData(const uint8_t* buf, size_t maxSize) {
    return setReadOnlyData(buf, maxSize);
  }

  /**
   * @brief Returns the payload data length (data field length without CRC)
   */
  uint16_t getPayloadDataLength() { return getPacketDataLen() - 2; }

  ReturnValue_t checkCrc() {
    if (CRC::crc16ccitt(getFullData(), getFullPacketLen()) != 0) {
      return returnvalue::FAILED;
    }
    return returnvalue::OK;
  }
};

}  // namespace ploc

#endif /* MISSION_DEVICES_DEVICEDEFINITIONS_SPBASE_H_ */