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

#include "fsfw/cfdp/pdu/FinishedPduCreator.h"
#include "fsfw/cfdp/pdu/FinishedPduReader.h"
#include "fsfw/globalfunctions/arrayprinter.h"

TEST_CASE("Finished PDU", "[cfdp][pdu]") {
  using namespace cfdp;
  ReturnValue_t result = returnvalue::OK;
  std::array<uint8_t, 256> fnBuffer = {};
  uint8_t* buffer = fnBuffer.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);

  cfdp::Lv emptyFsMsg;
  FinishedInfo info(cfdp::ConditionCode::INACTIVITY_DETECTED,
                    cfdp::FileDeliveryCode::DATA_INCOMPLETE,
                    cfdp::FileDeliveryStatus::DISCARDED_DELIBERATELY);

  SECTION("Serialize") {
    FinishPduCreator serializer(pduConf, info);
    result = serializer.serialize(&buffer, &sz, fnBuffer.size(), SerializeIF::Endianness::NETWORK);
    REQUIRE(result == returnvalue::OK);
    REQUIRE(serializer.getSerializedSize() == 12);
    REQUIRE(((fnBuffer[1] << 8) | fnBuffer[2]) == 2);
    REQUIRE(fnBuffer[10] == cfdp::FileDirective::FINISH);
    REQUIRE(((fnBuffer[sz - 1] >> 4) & 0x0f) == cfdp::ConditionCode::INACTIVITY_DETECTED);
    REQUIRE(((fnBuffer[sz - 1] >> 2) & 0x01) == cfdp::FileDeliveryCode::DATA_INCOMPLETE);
    REQUIRE((fnBuffer[sz - 1] & 0b11) == cfdp::FileDeliveryStatus::DISCARDED_DELIBERATELY);
    REQUIRE(sz == 12);

    // Add a filestore response
    std::string firstName = "hello.txt";
    cfdp::StringLv firstNameLv(firstName);
    FilestoreResponseTlv response(cfdp::FilestoreActionCode::DELETE_FILE,
                                  cfdp::FSR_APPEND_FILE_1_NOT_EXISTS, firstNameLv, nullptr);
    FilestoreResponseTlv* responsePtr = &response;
    REQUIRE(response.getSerializedSize() == 14);
    size_t len = 1;
    info.setFilestoreResponsesArray(&responsePtr, &len, &len);
    serializer.updateDirectiveFieldLen();
    REQUIRE(serializer.getSerializedSize() == 12 + 14);
    sz = 0;
    buffer = fnBuffer.data();
    result = serializer.serialize(&buffer, &sz, fnBuffer.size(), SerializeIF::Endianness::NETWORK);
    REQUIRE(result == returnvalue::OK);
    REQUIRE(serializer.getSerializedSize() == 12 + 14);
    REQUIRE(serializer.getPduDataFieldLen() == 16);

    // Add two filestore responses and a fault location parameter
    std::string secondName = "hello2.txt";
    cfdp::StringLv secondNameLv(secondName);
    FilestoreResponseTlv response2(cfdp::FilestoreActionCode::DENY_FILE, cfdp::FSR_SUCCESS,
                                   secondNameLv, nullptr);
    REQUIRE(response2.getSerializedSize() == 15);
    len = 2;
    std::array<FilestoreResponseTlv*, 2> responses{&response, &response2};
    info.setFilestoreResponsesArray(responses.data(), &len, &len);
    serializer.updateDirectiveFieldLen();

    EntityIdTlv faultLoc(destId);
    REQUIRE(faultLoc.getSerializedSize() == 4);
    info.setFaultLocation(&faultLoc);
    serializer.updateDirectiveFieldLen();
    sz = 0;
    buffer = fnBuffer.data();
    result = serializer.serialize(&buffer, &sz, fnBuffer.size(), SerializeIF::Endianness::NETWORK);
    REQUIRE(result == returnvalue::OK);
    info.setConditionCode(cfdp::ConditionCode::FILESTORE_REJECTION);
    REQUIRE(serializer.getSerializedSize() == 12 + 14 + 15 + 4);
    REQUIRE(sz == 12 + 14 + 15 + 4);
    info.setFileStatus(cfdp::FileDeliveryStatus::DISCARDED_FILESTORE_REJECTION);
    REQUIRE(info.getFileStatus() == cfdp::FileDeliveryStatus::DISCARDED_FILESTORE_REJECTION);
    info.setDeliveryCode(cfdp::FileDeliveryCode::DATA_INCOMPLETE);
    REQUIRE(info.getDeliveryCode() == cfdp::FileDeliveryCode::DATA_INCOMPLETE);
    for (size_t maxSz = 0; maxSz < 45; maxSz++) {
      sz = 0;
      buffer = fnBuffer.data();
      result = serializer.serialize(&buffer, &sz, maxSz, SerializeIF::Endianness::NETWORK);
      REQUIRE(result != returnvalue::OK);
    }
  }

  SECTION("Deserialize") {
    FinishedInfo emptyInfo;
    FinishPduCreator serializer(pduConf, info);
    result = serializer.serialize(&buffer, &sz, fnBuffer.size(), SerializeIF::Endianness::NETWORK);
    REQUIRE(result == returnvalue::OK);
    FinishPduReader deserializer(fnBuffer.data(), fnBuffer.size(), emptyInfo);
    result = deserializer.parseData();
    REQUIRE(result == returnvalue::OK);
    REQUIRE(emptyInfo.getFileStatus() == cfdp::FileDeliveryStatus::DISCARDED_DELIBERATELY);
    REQUIRE(emptyInfo.getConditionCode() == cfdp::ConditionCode::INACTIVITY_DETECTED);
    REQUIRE(emptyInfo.getDeliveryCode() == cfdp::FileDeliveryCode::DATA_INCOMPLETE);

    // Add a filestore response
    sz = 0;
    buffer = fnBuffer.data();
    std::string firstName = "hello.txt";
    cfdp::StringLv firstNameLv(firstName);
    FilestoreResponseTlv response(cfdp::FilestoreActionCode::DELETE_FILE, cfdp::FSR_NOT_PERFORMED,
                                  firstNameLv, nullptr);
    FilestoreResponseTlv* responsePtr = &response;
    size_t len = 1;
    info.setFilestoreResponsesArray(&responsePtr, &len, &len);
    serializer.updateDirectiveFieldLen();
    REQUIRE(serializer.getPduDataFieldLen() == 16);
    result = serializer.serialize(&buffer, &sz, fnBuffer.size(), SerializeIF::Endianness::NETWORK);
    REQUIRE(result == returnvalue::OK);
    FilestoreResponseTlv emptyResponse(firstNameLv, nullptr);
    responsePtr = &emptyResponse;
    emptyInfo.setFilestoreResponsesArray(&responsePtr, nullptr, &len);
    FinishPduReader deserializer2(fnBuffer.data(), fnBuffer.size(), emptyInfo);
    result = deserializer2.parseData();
    REQUIRE(result == returnvalue::OK);
    REQUIRE(emptyInfo.getFsResponsesLen() == 1);
    FilestoreResponseTlv** responseArray = nullptr;
    emptyInfo.getFilestoreResonses(&responseArray, nullptr, nullptr);
    REQUIRE(responseArray[0]->getActionCode() == cfdp::FilestoreActionCode::DELETE_FILE);
    REQUIRE(responseArray[0]->getStatusCode() == cfdp::FSR_NOT_PERFORMED);
    auto& fileNameRef = responseArray[0]->getFirstFileName();
    size_t stringSize = 0;
    const char* string = reinterpret_cast<const char*>(fileNameRef.getValue(&stringSize));
    std::string firstFileName(string, stringSize);
    REQUIRE(firstFileName == "hello.txt");

    // Add two filestore responses and a fault location parameter
    std::string secondName = "hello2.txt";
    cfdp::StringLv secondNameLv(secondName);
    FilestoreResponseTlv response2(cfdp::FilestoreActionCode::DENY_FILE, cfdp::FSR_SUCCESS,
                                   secondNameLv, nullptr);
    REQUIRE(response2.getSerializedSize() == 15);
    len = 2;
    std::array<FilestoreResponseTlv*, 2> responses{&response, &response2};
    info.setFilestoreResponsesArray(responses.data(), &len, &len);
    serializer.updateDirectiveFieldLen();

    EntityIdTlv faultLoc(destId);
    info.setFaultLocation(&faultLoc);
    serializer.updateDirectiveFieldLen();
    sz = 0;
    buffer = fnBuffer.data();
    result = serializer.serialize(&buffer, &sz, fnBuffer.size(), SerializeIF::Endianness::NETWORK);
    REQUIRE(result == returnvalue::OK);
    EntityId emptyId;
    EntityIdTlv emptyFaultLoc(emptyId);
    emptyInfo.setFaultLocation(&emptyFaultLoc);
    response.setFilestoreMessage(&emptyFsMsg);
    emptyInfo.setFilestoreResponsesArray(responses.data(), &len, &len);
    response2.setFilestoreMessage(&emptyFsMsg);
    FinishPduReader deserializer3(fnBuffer.data(), fnBuffer.size(), emptyInfo);
    result = deserializer3.parseData();
    REQUIRE(result == returnvalue::OK);
    auto& infoRef = deserializer3.getInfo();
    REQUIRE(deserializer3.getWholePduSize() == 45);

    size_t invalidMaxLen = 1;
    emptyInfo.setFilestoreResponsesArray(responses.data(), &len, &invalidMaxLen);
    result = deserializer3.parseData();
    REQUIRE(result == cfdp::FINISHED_CANT_PARSE_FS_RESPONSES);
    emptyInfo.setFilestoreResponsesArray(nullptr, nullptr, nullptr);
    result = deserializer3.parseData();
    REQUIRE(result == cfdp::FINISHED_CANT_PARSE_FS_RESPONSES);

    // Clear condition code
    auto tmp = fnBuffer[11];
    fnBuffer[11] = fnBuffer[11] & ~0xf0;
    fnBuffer[11] = fnBuffer[11] | (cfdp::ConditionCode::NO_ERROR << 4);
    emptyInfo.setFilestoreResponsesArray(responses.data(), &len, &len);
    result = deserializer3.parseData();
    REQUIRE(result == cfdp::INVALID_TLV_TYPE);

    fnBuffer[11] = tmp;
    // Invalid TLV type, should be entity ID
    fnBuffer[sz - 4] = cfdp::TlvType::FILESTORE_REQUEST;
    result = deserializer3.parseData();
    REQUIRE(result == cfdp::INVALID_TLV_TYPE);

    for (size_t maxSz = 0; maxSz < 45; maxSz++) {
      FinishPduReader faultyDeser(fnBuffer.data(), maxSz, emptyInfo);
      result = faultyDeser.parseData();
      REQUIRE(result != returnvalue::OK);
    }
  }
}