#include <array>
#include <catch2/catch_test_macros.hpp>

#include "fsfw/cfdp/pdu/NakPduCreator.h"
#include "fsfw/cfdp/pdu/NakPduReader.h"
#include "fsfw/cfdp/pdu/PduConfig.h"
#include "fsfw/globalfunctions/arrayprinter.h"

TEST_CASE("NAK PDU", "[cfdp][pdu]") {
  using namespace cfdp;
  ReturnValue_t result = returnvalue::OK;
  std::array<uint8_t, 256> nakBuffer = {};
  uint8_t* buffer = nakBuffer.data();
  size_t sz = 0;
  EntityId destId(WidthInBytes::TWO_BYTES, 2);
  TransactionSeqNum seqNum(WidthInBytes::TWO_BYTES, 15);
  EntityId sourceId(WidthInBytes::TWO_BYTES, 1);
  PduConfig pduConf(sourceId, destId, TransmissionMode::ACKNOWLEDGED, seqNum);

  Fss startOfScope(50);
  Fss endOfScope(1050);
  NakInfo info(startOfScope, endOfScope);
  SECTION("Serializer") {
    NakPduCreator serializer(pduConf, info);
    result = serializer.serialize(&buffer, &sz, nakBuffer.size(), SerializeIF::Endianness::NETWORK);
    REQUIRE(result == returnvalue::OK);
    REQUIRE(serializer.getSerializedSize() == 19);
    REQUIRE(serializer.FileDirectiveCreator::getSerializedSize() == 11);
    REQUIRE(sz == 19);
    REQUIRE(serializer.getPduDataFieldLen() == 9);
    REQUIRE(((nakBuffer[1] << 8) | nakBuffer[2]) == 0x09);
    REQUIRE(nakBuffer[10] == cfdp::FileDirective::NAK);
    uint32_t scope = 0;
    result = SerializeAdapter::deSerialize(&scope, nakBuffer.data() + 11, nullptr,
                                           SerializeIF::Endianness::NETWORK);
    REQUIRE(result == returnvalue::OK);
    REQUIRE(scope == 50);
    result = SerializeAdapter::deSerialize(&scope, nakBuffer.data() + 15, nullptr,
                                           SerializeIF::Endianness::NETWORK);
    REQUIRE(result == returnvalue::OK);
    REQUIRE(scope == 1050);

    NakInfo::SegmentRequest segReq0(cfdp::Fss(2020), cfdp::Fss(2520));
    NakInfo::SegmentRequest segReq1(cfdp::Fss(2932), cfdp::Fss(3021));
    // Now add 2 segment requests to NAK info and serialize them as well
    std::array<NakInfo::SegmentRequest, 2> segReqs = {segReq0, segReq1};
    size_t segReqsLen = segReqs.size();
    info.setSegmentRequests(segReqs.data(), &segReqsLen, &segReqsLen);
    uint8_t* buffer = nakBuffer.data();
    size_t sz = 0;
    serializer.updateDirectiveFieldLen();
    result = serializer.serialize(&buffer, &sz, nakBuffer.size(), SerializeIF::Endianness::NETWORK);
    REQUIRE(result == returnvalue::OK);
    REQUIRE(serializer.getSerializedSize() == 35);
    REQUIRE(serializer.getPduDataFieldLen() == 25);
    REQUIRE(((nakBuffer[1] << 8) | nakBuffer[2]) == 25);
    uint32_t segReqScopes = 0;
    result = SerializeAdapter::deSerialize(&segReqScopes, nakBuffer.data() + 19, nullptr,
                                           SerializeIF::Endianness::NETWORK);
    REQUIRE(result == returnvalue::OK);
    REQUIRE(segReqScopes == 2020);
    result = SerializeAdapter::deSerialize(&segReqScopes, nakBuffer.data() + 23, nullptr,
                                           SerializeIF::Endianness::NETWORK);
    REQUIRE(result == returnvalue::OK);
    REQUIRE(segReqScopes == 2520);
    result = SerializeAdapter::deSerialize(&segReqScopes, nakBuffer.data() + 27, nullptr,
                                           SerializeIF::Endianness::NETWORK);
    REQUIRE(result == returnvalue::OK);
    REQUIRE(segReqScopes == 2932);
    result = SerializeAdapter::deSerialize(&segReqScopes, nakBuffer.data() + 31, nullptr,
                                           SerializeIF::Endianness::NETWORK);
    REQUIRE(result == returnvalue::OK);
    REQUIRE(segReqScopes == 3021);

    for (size_t maxSz = 0; maxSz < 35; maxSz++) {
      uint8_t* buffer = nakBuffer.data();
      size_t sz = 0;
      result = serializer.serialize(&buffer, &sz, maxSz, SerializeIF::Endianness::NETWORK);
      REQUIRE(result == SerializeIF::BUFFER_TOO_SHORT);
    }
    for (size_t sz = 35; sz > 0; sz--) {
      uint8_t* buffer = nakBuffer.data();
      size_t locSize = sz;
      result = serializer.serialize(&buffer, &locSize, 35, SerializeIF::Endianness::NETWORK);
      REQUIRE(result == SerializeIF::BUFFER_TOO_SHORT);
    }
  }

  SECTION("Deserializer") {
    NakPduCreator serializer(pduConf, info);
    result = serializer.serialize(&buffer, &sz, nakBuffer.size(), SerializeIF::Endianness::NETWORK);
    REQUIRE(result == returnvalue::OK);

    info.getStartOfScope().setFileSize(0, false);
    info.getEndOfScope().setFileSize(0, false);
    NakPduReader deserializer(nakBuffer.data(), nakBuffer.size(), info);
    result = deserializer.parseData();
    REQUIRE(result == returnvalue::OK);
    REQUIRE(deserializer.getWholePduSize() == 19);
    REQUIRE(info.getStartOfScope().getSize() == 50);
    REQUIRE(info.getEndOfScope().getSize() == 1050);

    NakInfo::SegmentRequest segReq0(cfdp::Fss(2020), cfdp::Fss(2520));
    NakInfo::SegmentRequest segReq1(cfdp::Fss(2932), cfdp::Fss(3021));
    // Now add 2 segment requests to NAK info and serialize them as well
    std::array<NakInfo::SegmentRequest, 2> segReqs = {segReq0, segReq1};
    size_t segReqsLen = segReqs.size();
    info.setSegmentRequests(segReqs.data(), &segReqsLen, &segReqsLen);
    uint8_t* buffer = nakBuffer.data();
    size_t sz = 0;
    serializer.updateDirectiveFieldLen();
    result = serializer.serialize(&buffer, &sz, nakBuffer.size(), SerializeIF::Endianness::NETWORK);
    REQUIRE(result == returnvalue::OK);

    NakPduReader deserializeWithSegReqs(nakBuffer.data(), nakBuffer.size(), info);
    result = deserializeWithSegReqs.parseData();
    REQUIRE(result == returnvalue::OK);
    NakInfo::SegmentRequest* segReqsPtr = nullptr;
    size_t readSegReqs = 0;
    info.getSegmentRequests(&segReqsPtr, &readSegReqs, nullptr);
    REQUIRE(readSegReqs == 2);
    REQUIRE(segReqsPtr[0].first.getSize() == 2020);
    REQUIRE(segReqsPtr[0].second.getSize() == 2520);
    REQUIRE(segReqsPtr[1].first.getSize() == 2932);
    REQUIRE(segReqsPtr[1].second.getSize() == 3021);
    REQUIRE(deserializeWithSegReqs.getPduDataFieldLen() == 25);
    REQUIRE(info.getSegmentRequestsLen() == 2);
    for (size_t idx = 0; idx < 34; idx++) {
      NakPduReader faultyDeserializer(nakBuffer.data(), idx, info);
      result = faultyDeserializer.parseData();
      REQUIRE(result != returnvalue::OK);
    }
    for (size_t pduFieldLen = 0; pduFieldLen < 25; pduFieldLen++) {
      nakBuffer[1] = (pduFieldLen >> 8) & 0xff;
      nakBuffer[2] = pduFieldLen & 0xff;
      NakPduReader faultyDeserializer(nakBuffer.data(), nakBuffer.size(), info);
      result = faultyDeserializer.parseData();
      if (pduFieldLen == 9) {
        REQUIRE(info.getSegmentRequestsLen() == 0);
      } else if (pduFieldLen == 17) {
        REQUIRE(info.getSegmentRequestsLen() == 1);
      } else if (pduFieldLen == 25) {
        REQUIRE(info.getSegmentRequestsLen() == 2);
      }
      if (pduFieldLen != 9 and pduFieldLen != 17 and pduFieldLen != 25) {
        REQUIRE(result != returnvalue::OK);
      }
    }
    info.setMaxSegmentRequestLen(5);
    REQUIRE(info.getSegmentRequestsMaxLen() == 5);
  }
}