#ifndef MISSION_PAYLOAD_PLOCSPBASE_H_
#define MISSION_PAYLOAD_PLOCSPBASE_H_

#include <fsfw/globalfunctions/CRC.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) {}

  // Set full payload length. Must also consider the two CRC bytes
  void setFullPayloadLen(size_t fullPayloadLen_) { fullPayloadLen = fullPayloadLen_; }

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

class SpTcBase {
 public:
  SpTcBase(SpTcParams params) : SpTcBase(params, 0x00, 1, 0) {}

  SpTcBase(SpTcParams params, uint16_t apid, size_t payloadLen)
      : SpTcBase(params, apid, payloadLen, 0) {}

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

  void updateSpFields() {
    updateLenFromParams();
    spParams.creator.setPacketType(ccsds::PacketType::TC);
  }

  void updateLenFromParams() { spParams.creator.setDataLenField(spParams.fullPayloadLen - 1); }
  const uint8_t* getFullPacket() const { return spParams.buf; }

  const uint8_t* getPacketData() const { return spParams.buf + ccsds::HEADER_LEN; }

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

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

  uint16_t getSeqCount() const { return spParams.creator.getSequenceCount(); }

  ReturnValue_t checkPayloadLen() {
    if (ccsds::HEADER_LEN + spParams.fullPayloadLen > 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 calcAndSetCrc() {
    /* 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);
  }

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

}  // namespace ploc

#endif /* MISSION_PAYLOAD_PLOCSPBASE_H_ */