text: //
  //  PacketList.cpp
  //
  //
  //  Created by Clement on 7/13/15.
  //  Copyright 2015 High Fidelity, Inc.
  //  Copyright 2021 Vircadia contributors.
  //
  //  Distributed under the Apache License, Version 2.0.
  //  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
  //
  
  #include "PacketList.h"
  
  #include "../NetworkLogging.h"
  
  #include <chrono>
  #include <QDebug>
  
  using namespace udt;
  
  int packetListMetaTypeId = qRegisterMetaType<PacketList*>("PacketList*");
  
  std::unique_ptr<PacketList> PacketList::create(PacketType packetType, QByteArray extendedHeader,
                                                 bool isReliable, bool isOrdered) {
      auto packetList = std::unique_ptr<PacketList>(new PacketList(packetType, extendedHeader,
                                                                   isReliable, isOrdered));
      packetList->open(WriteOnly);
      return packetList;
  }
  
  std::unique_ptr<PacketList> PacketList::fromReceivedPackets(std::list<std::unique_ptr<Packet>>&& packets) {
      auto packetList = std::unique_ptr<PacketList>(new PacketList(PacketType::Unknown, QByteArray(), true, true));
      packetList->_packets = std::move(packets);
      packetList->open(ReadOnly);
      return packetList;
  }
  
  PacketList::PacketList(PacketType packetType, QByteArray extendedHeader, bool isReliable, bool isOrdered) :
      _packetType(packetType),
      _isOrdered(isOrdered),
      _isReliable(isReliable),
      _extendedHeader(extendedHeader)
  {
      Q_ASSERT_X(!(!_isReliable && _isOrdered), "PacketList", "Unreliable ordered PacketLists are not currently supported");
  }
  
  PacketList::PacketList(PacketList&& other) :
      _packetType(other._packetType),
      _packets(std::move(other._packets)),
      _isOrdered(other._isOrdered),
      _isReliable(other._isReliable),
      _extendedHeader(std::move(other._extendedHeader))
  {
  }
  
  SockAddr PacketList::getSenderSockAddr() const {
      return _packets.size() > 0 ? _packets.front()->getSenderSockAddr() : SockAddr();
  }
  
  void PacketList::startSegment() {
      _segmentStartIndex = _currentPacket ? _currentPacket->pos() : _extendedHeader.size();
  }
  
  void PacketList::endSegment() {
      _segmentStartIndex = -1;
  }
  
  size_t PacketList::getDataSize() const {
      size_t totalBytes = 0;
      for (const auto& packet : _packets) {
          totalBytes += packet->getDataSize();
      }
  
      if (_currentPacket) {
          totalBytes += _currentPacket->getDataSize();
      }
  
      return totalBytes;
  }
  
  size_t PacketList::getMessageSize() const {
      size_t totalBytes = 0;
      for (const auto& packet: _packets) {
          totalBytes += packet->getPayloadSize();
      }
      
      if (_currentPacket) {
          totalBytes += _currentPacket->getPayloadSize();
      }
      
      return totalBytes;
  }
  
  std::unique_ptr<Packet> PacketList::createPacket() {
      // use the static create method to create a new packet
      // If this packet list is supposed to be ordered then we consider this to be part of a message
      bool isPartOfMessage = _isOrdered;
      return Packet::create(-1, _isReliable, isPartOfMessage);
  }
  
  std::unique_ptr<Packet> PacketList::createPacketWithExtendedHeader() {
      // use the static create method to create a new packet
      auto packet = createPacket();
  
      if (!_extendedHeader.isEmpty()) {
          // add the extended header to the front of the packet
          if (packet->write(_extendedHeader) == -1) {
              qCDebug(networking) << "Could not write extendedHeader in PacketList::createPacketWithExtendedHeader"
                  << "- make sure that _extendedHeader is not larger than the payload capacity.";
          }
      }
  
      return packet;
  }
  
  void PacketList::closeCurrentPacket(bool shouldSendEmpty) {
      if (shouldSendEmpty && !_currentPacket && _packets.empty()) {
          _currentPacket = createPacketWithExtendedHeader();
      }
      
      if (_currentPacket) {
          // move the current packet to our list of packets
          _packets.push_back(std::move(_currentPacket));
      }
  }
  
  QByteArray PacketList::getMessage() const {
      size_t sizeBytes = 0;
  
      for (const auto& packet : _packets) {
          sizeBytes += packet->size();
      }
  
      QByteArray data;
      data.reserve((int)sizeBytes);
  
      for (auto& packet : _packets) {
          data.append(packet->getPayload(), packet->getPayloadSize());
      }
  
      return data;
  }
  
  void PacketList::preparePackets(MessageNumber messageNumber) {
      Q_ASSERT(_packets.size() > 0);
      
      if (_packets.size() == 1) {
          _packets.front()->writeMessageNumber(messageNumber, Packet::PacketPosition::ONLY, 0);
      } else {
          const auto second = ++_packets.begin();
          const auto last = --_packets.end();
          Packet::MessagePartNumber messagePartNumber = 0;
          std::for_each(second, last, [&](const PacketPointer& packet) {
              packet->writeMessageNumber(messageNumber, Packet::PacketPosition::MIDDLE, ++messagePartNumber);
          });
          
          _packets.front()->writeMessageNumber(messageNumber, Packet::PacketPosition::FIRST, 0);
          _packets.back()->writeMessageNumber(messageNumber, Packet::PacketPosition::LAST, ++messagePartNumber);
      }
  }
  
  const qint64 PACKET_LIST_WRITE_ERROR = -1;
  
  qint64 PacketList::writeString(const QString& string) {
      QByteArray data = string.toUtf8();
      writePrimitive(static_cast<uint32_t>(data.length()));
      return writeData(data.constData(), data.length());
  }
  
  qint64 PacketList::writeData(const char* data, qint64 maxSize) {
      auto sizeRemaining = maxSize;
  
      while (sizeRemaining > 0) {
          if (!_currentPacket) {
              // we don't have a current packet, time to set one up
              _currentPacket = createPacketWithExtendedHeader();
          }
  
          // check if this block of data can fit into the currentPacket
          if (sizeRemaining <= _currentPacket->bytesAvailableForWrite()) {
              // it fits, just write it to the current packet
              _currentPacket->write(data, sizeRemaining);
  
              sizeRemaining = 0;
          } else {
              // it does not fit - this may need to be in the next packet
  
              if (!_isOrdered) {
                  auto newPacket = createPacketWithExtendedHeader();
  
                  if (_segmentStartIndex >= 0) {
                      // We in the process of writing a segment for an unordered PacketList.
                      // We need to try and pull the first part of the segment out to our new packet
  
                      // check now to see if this is an unsupported write
                      int segmentSize = _currentPacket->pos() - _segmentStartIndex;
                      
                      if (segmentSize + sizeRemaining > newPacket->getPayloadCapacity()) {
                          // this is an unsupported case - the segment is bigger than the size of an individual packet
                          // but the PacketList is not going to be sent ordered
                          qCDebug(networking) << "Error in PacketList::writeData - attempted to write a segment to an unordered packet that is"
                              << "larger than the payload size.";
                          Q_ASSERT(false);
                          
                          // we won't be writing this new data to the packet
                          // go back before the current segment and return -1 to indicate error
                          _currentPacket->seek(_segmentStartIndex);
                          _currentPacket->setPayloadSize(_segmentStartIndex);
                          
                          return PACKET_LIST_WRITE_ERROR;
                      } else {
                          // copy from currentPacket where the segment started to the beginning of the newPacket
                          newPacket->write(_currentPacket->getPayload() + _segmentStartIndex, segmentSize);
                          
                          // shrink the current payload to the actual size of the packet
                          _currentPacket->setPayloadSize(_segmentStartIndex);
                          
                          // the current segment now starts at the beginning of the new packet
                          _segmentStartIndex = _extendedHeader.size();
                      }
                  }
                  
                  if (sizeRemaining > newPacket->getPayloadCapacity()) {
                      // this is an unsupported case - attempting to write a block of data larger
                      // than the capacity of a new packet in an unordered PacketList
                      qCDebug(networking) << "Error in PacketList::writeData - attempted to write data to an unordered packet that is"
                          << "larger than the payload size.";
                      Q_ASSERT(false);
                      
                      return PACKET_LIST_WRITE_ERROR;
                  }
  
                  // move the current packet to our list of packets
                  _packets.push_back(std::move(_currentPacket));
  
                  // write the data to the newPacket
                  newPacket->write(data, sizeRemaining);
  
                  // swap our current packet with the new packet
                  _currentPacket.swap(newPacket);
  
                  // We've written all of the data, so set sizeRemaining to 0
                  sizeRemaining = 0;
              } else {
                  // we're an ordered PacketList - let's fit what we can into the current packet and then put the leftover
                  // into a new packet
  
                  int numBytesToEnd = _currentPacket->bytesAvailableForWrite();
                  _currentPacket->write(data, numBytesToEnd);
  
                  // Remove number of bytes written from sizeRemaining
                  sizeRemaining -= numBytesToEnd;
  
                  // Move the data pointer forward
                  data += numBytesToEnd;
  
                  // move the current packet to our list of packets
                  _packets.push_back(std::move(_currentPacket));
              }
          }
      }
  
      return maxSize;
  }
  
  p_high_resolution_clock::time_point PacketList::getFirstPacketReceiveTime() const {
      using namespace std::chrono;;
      if (!_packets.empty()) {
          return _packets.front()->getReceiveTime();
      }
      return p_high_resolution_clock::time_point();
  }