/*
 * Copyright 2009 - 2010 Kevin Ackley (kackley@gwi.net)
 *
 * Permission is hereby granted, free of charge, to any person or organization
 * obtaining a copy of the software and accompanying documentation covered by
 * this license (the "Software") to use, reproduce, display, distribute,
 * execute, and transmit the Software, and to prepare derivative works of the
 * Software, and to permit third-parties to whom the Software is furnished to
 * do so, all subject to the following:
 *
 * The copyright notices in the Software and this entire statement, including
 * the above license grant, this restriction and the following disclaimer,
 * must be included in all copies of the Software, in whole or in part, and
 * all derivative works of the Software, unless such copies or derivative
 * works are solely in the form of machine-executable object code generated by
 * a source language processor.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
 * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
 * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#include <cstring>

#include "CheckedFile.h"
#include "Packet.h"


using namespace e57;

struct IndexPacket
{
      static constexpr unsigned MAX_ENTRIES = 2048;

      const uint8_t     packetType = INDEX_PACKET;

      uint8_t     packetFlags = 0;    // flag bitfields
      uint16_t    packetLogicalLengthMinus1 = 0;
      uint16_t    entryCount = 0;
      uint8_t     indexLevel = 0;
      uint8_t     reserved1[9] = {};   // must be zero

      struct IndexPacketEntry
      {
            uint64_t    chunkRecordNumber = 0;
            uint64_t    chunkPhysicalOffset = 0;
      } entries[MAX_ENTRIES];

      void        verify(unsigned bufferLength = 0, uint64_t totalRecordCount = 0, uint64_t fileSize = 0) const;

#ifdef E57_DEBUG
      void        dump(int indent = 0, std::ostream& os = std::cout) const;
#endif
};

struct EmptyPacketHeader
{
      const uint8_t     packetType = EMPTY_PACKET;

      uint8_t     reserved1 = 0;     // must be zero
      uint16_t    packetLogicalLengthMinus1 = 0;

      void        verify(unsigned bufferLength = 0) const; //???use

#ifdef E57_DEBUG
      void        dump(int indent = 0, std::ostream& os = std::cout) const;
#endif
};

//=============================================================================
// PacketReadCache

PacketReadCache::PacketReadCache(CheckedFile* cFile, unsigned packetCount)
   : cFile_(cFile),
     entries_(packetCount)
{
   if (packetCount == 0)
   {
      throw E57_EXCEPTION2(E57_ERROR_INTERNAL, "packetCount=" + toString(packetCount));
   }
}

std::unique_ptr<PacketLock> PacketReadCache::lock( uint64_t packetLogicalOffset, char* &pkt )
{
#ifdef E57_MAX_VERBOSE
   std::cout << "PacketReadCache::lock() called, packetLogicalOffset=" << packetLogicalOffset << std::endl;
#endif

   /// Only allow one locked packet at a time.
   if ( lockCount_ > 0 )
   {
      throw E57_EXCEPTION2(E57_ERROR_INTERNAL, "lockCount=" + toString(lockCount_));
   }

   /// Offset can't be 0
   if ( packetLogicalOffset == 0 )
   {
      throw E57_EXCEPTION2(E57_ERROR_INTERNAL, "packetLogicalOffset=" + toString(packetLogicalOffset));
   }

   /// Linear scan for matching packet offset in cache
   for ( unsigned i = 0; i < entries_.size(); ++i )
   {
      auto  &entry = entries_[i];

      if (packetLogicalOffset == entry.logicalOffset_)
      {
         /// Found a match, so don't have to read anything
#ifdef E57_MAX_VERBOSE
         std::cout << "  Found matching cache entry, index=" << i << std::endl;
#endif
         /// Mark entry with current useCount (keeps track of age of entry).
         entry.lastUsed_ = ++useCount_;

         /// Publish buffer address to caller
         pkt = entry.buffer_;

         /// Create lock so we are sure that we will be unlocked when use is finished.
         std::unique_ptr<PacketLock> plock( new PacketLock( this, i ) );

         /// Increment cache lock just before return
         ++lockCount_;

         return plock;
      }
   }
   /// Get here if didn't find a match already in cache.

   /// Find least recently used (LRU) packet buffer
   unsigned oldestEntry = 0;
   unsigned oldestUsed = entries_.at(0).lastUsed_;

   for ( unsigned i = 0; i < entries_.size(); ++i )
   {
      const auto  &entry = entries_[i];

      if ( entry.lastUsed_ < oldestUsed )
      {
         oldestEntry = i;
         oldestUsed = entry.lastUsed_;
      }
   }
#ifdef E57_MAX_VERBOSE
   std::cout << "  Oldest entry=" << oldestEntry << " lastUsed=" << oldestUsed << std::endl;
#endif

   readPacket(oldestEntry, packetLogicalOffset);

   /// Publish buffer address to caller
   pkt = entries_[oldestEntry].buffer_;

   /// Create lock so we are sure we will be unlocked when use is finished.
   std::unique_ptr<PacketLock> plock( new PacketLock( this, oldestEntry ) );

   /// Increment cache lock just before return
   ++lockCount_;

   return plock;
}

void PacketReadCache::unlock(unsigned lockedEntry)
{
   //??? why lockedEntry not used?
#ifdef E57_MAX_VERBOSE
   std::cout << "PacketReadCache::unlock() called, lockedEntry=" << lockedEntry << std::endl;
#endif

   if (lockCount_ != 1)
   {
      throw E57_EXCEPTION2(E57_ERROR_INTERNAL, "lockCount=" + toString(lockCount_));
   }

   --lockCount_;
}

void PacketReadCache::readPacket(unsigned oldestEntry, uint64_t packetLogicalOffset)
{
#ifdef E57_MAX_VERBOSE
   std::cout << "PacketReadCache::readPacket() called, oldestEntry=" << oldestEntry << " packetLogicalOffset=" << packetLogicalOffset << std::endl;
#endif

   /// Read header of packet first to get length.  Use EmptyPacketHeader since it has the commom fields to all packets.
   EmptyPacketHeader header;

   cFile_->seek(packetLogicalOffset, CheckedFile::Logical);
   cFile_->read(reinterpret_cast<char*>(&header), sizeof(header));

   /// Can't verify packet header here, because it is not really an EmptyPacketHeader.
   unsigned packetLength = header.packetLogicalLengthMinus1+1;

   /// Be paranoid about packetLength before read
   if (packetLength > DATA_PACKET_MAX)
   {
      throw E57_EXCEPTION2(E57_ERROR_BAD_CV_PACKET, "packetLength=" + toString(packetLength));
   }

   auto  &entry = entries_.at(oldestEntry);

   /// Now read in whole packet into preallocated buffer_.  Note buffer is
   cFile_->seek(packetLogicalOffset, CheckedFile::Logical);
   cFile_->read(entry.buffer_, packetLength);

   /// Verify that packet is good.
   switch (header.packetType)
   {
      case DATA_PACKET: {
         auto dpkt = reinterpret_cast<DataPacket*>(entry.buffer_);

         dpkt->verify(packetLength);
#ifdef E57_MAX_VERBOSE
         std::cout << "  data packet:" << std::endl;
         dpkt->dump(4); //???
#endif
      }
         break;
      case INDEX_PACKET: {
         auto ipkt = reinterpret_cast<IndexPacket*>(entry.buffer_);

         ipkt->verify(packetLength);
#ifdef E57_MAX_VERBOSE
         std::cout << "  index packet:" << std::endl;
         ipkt->dump(4); //???
#endif
      }
         break;
      case EMPTY_PACKET: {
         auto hp = reinterpret_cast<EmptyPacketHeader*>(entry.buffer_);

         hp->verify(packetLength);
#ifdef E57_MAX_VERBOSE
         std::cout << "  empty packet:" << std::endl;
         hp->dump(4); //???
#endif
      }
         break;
      default:
         throw E57_EXCEPTION2(E57_ERROR_INTERNAL, "packetType=" + toString(header.packetType));
   }

   entry.logicalOffset_ = packetLogicalOffset;

   /// Mark entry with current useCount (keeps track of age of entry).
   /// This is a cache, so a small hiccup when useCount_ overflows won't hurt.
   entry.lastUsed_ = ++useCount_;
}

#ifdef E57_DEBUG
void PacketReadCache::dump(int indent, std::ostream& os)
{
   os << space(indent) << "lockCount: " << lockCount_ << std::endl;
   os << space(indent) << "useCount:  " << useCount_ << std::endl;
   os << space(indent) << "entries:" << std::endl;
   for (unsigned i=0; i < entries_.size(); i++) {
      os << space(indent) << "entry[" << i << "]:" << std::endl;
      os << space(indent+4) << "logicalOffset:  " << entries_[i].logicalOffset_ << std::endl;
      os << space(indent+4) << "lastUsed:        " << entries_[i].lastUsed_ << std::endl;
      if (entries_[i].logicalOffset_ != 0) {
         os << space(indent+4) << "packet:" << std::endl;
         switch (reinterpret_cast<EmptyPacketHeader*>(entries_.at(i).buffer_)->packetType) {
            case DATA_PACKET: {
               auto dpkt = reinterpret_cast<DataPacket*>(entries_.at(i).buffer_);
               dpkt->dump(indent+6, os);
            }
               break;
            case INDEX_PACKET: {
               auto ipkt = reinterpret_cast<IndexPacket*>(entries_.at(i).buffer_);
               ipkt->dump(indent+6, os);
            }
               break;
            case EMPTY_PACKET: {
               auto hp = reinterpret_cast<EmptyPacketHeader*>(entries_.at(i).buffer_);
               hp->dump(indent+6, os);
            }
               break;
            default:
               throw E57_EXCEPTION2(E57_ERROR_INTERNAL,
                                    "packetType=" + toString(reinterpret_cast<EmptyPacketHeader*>(entries_.at(i).buffer_)->packetType));
         }
      }
   }
}
#endif

//=============================================================================
// PacketLock

PacketLock::PacketLock(PacketReadCache* cache, unsigned cacheIndex)
   : cache_(cache),
     cacheIndex_(cacheIndex)
{
#ifdef E57_MAX_VERBOSE
   std::cout << "PacketLock() called" << std::endl;
#endif
}

PacketLock::~PacketLock()
{
#ifdef E57_MAX_VERBOSE
   std::cout << "~PacketLock() called" << std::endl;
#endif
   try {
      /// Note cache must live longer than lock, this is reasonable assumption.
      cache_->unlock(cacheIndex_);
   } catch (...) {
      //??? report?
   }
}

//=============================================================================
// DataPacketHeader

DataPacketHeader::DataPacketHeader()
{
   /// Double check that packet struct is correct length.  Watch out for RTTI increasing the size.
   static_assert( sizeof( DataPacketHeader ) == 6, "Unexpected size of DataPacketHeader" );
}

void DataPacketHeader::reset()
{
   packetFlags = 0;
   packetLogicalLengthMinus1 = 0;
   bytestreamCount = 0;
}

void DataPacketHeader::verify(unsigned bufferLength) const
{
   /// Verify that packet is correct type
   if (packetType != DATA_PACKET)
   {
      throw E57_EXCEPTION2(E57_ERROR_BAD_CV_PACKET, "packetType=" + toString(packetType));
   }

   /// ??? check reserved flags zero?

   /// Check packetLength is at least large enough to hold header
   unsigned packetLength = packetLogicalLengthMinus1+1;
   if (packetLength < sizeof(*this))
      throw E57_EXCEPTION2(E57_ERROR_BAD_CV_PACKET, "packetLength=" + toString(packetLength));

   /// Check packet length is multiple of 4
   if (packetLength % 4)
      throw E57_EXCEPTION2(E57_ERROR_BAD_CV_PACKET, "packetLength=" + toString(packetLength));

   /// Check actual packet length is large enough.
   if (bufferLength > 0 && packetLength > bufferLength) {
      throw E57_EXCEPTION2(E57_ERROR_BAD_CV_PACKET,
                           "packetLength=" + toString(packetLength)
                           + " bufferLength=" + toString(bufferLength));
   }

   /// Make sure there is at least one entry in packet  ??? 0 record cvect allowed?
   if (bytestreamCount == 0)
      throw E57_EXCEPTION2(E57_ERROR_BAD_CV_PACKET, "bytestreamCount=" + toString(bytestreamCount));

   /// Check packet is at least long enough to hold bytestreamBufferLength array
   if (sizeof(DataPacketHeader) + 2*bytestreamCount > packetLength) {
      throw E57_EXCEPTION2(E57_ERROR_BAD_CV_PACKET,
                           "packetLength=" + toString(packetLength)
                           + " bytestreamCount=" + toString(bytestreamCount));
   }
}

#ifdef E57_DEBUG
void DataPacketHeader::dump(int indent, std::ostream& os) const
{
   os << space(indent) << "packetType:                " << static_cast<unsigned>(packetType) << std::endl;
   os << space(indent) << "packetFlags:               " << static_cast<unsigned>(packetFlags) << std::endl;
   os << space(indent) << "packetLogicalLengthMinus1: " << packetLogicalLengthMinus1 << std::endl;
   os << space(indent) << "bytestreamCount:           " << bytestreamCount << std::endl;
}
#endif

//=============================================================================
// DataPacket

DataPacket::DataPacket()
{
   /// Double check that packet struct is correct length.  Watch out for RTTI increasing the size.
   static_assert( sizeof( DataPacket ) == 64*1024, "Unexpected size of DataPacket" );
}

void DataPacket::verify(unsigned bufferLength) const
{
   //??? do all packets need versions?  how extend without breaking older checking?  need to check file version#?

   /// Verify header is good
   auto hp = reinterpret_cast<const DataPacketHeader*>(this);

   hp->verify(bufferLength);

   /// Calc sum of lengths of each bytestream buffer in this packet
   auto bsbLength = reinterpret_cast<const uint16_t*>(&payload[0]);
   unsigned totalStreamByteCount = 0;

   for (unsigned i=0; i < header.bytestreamCount; i++)
   {
      totalStreamByteCount += bsbLength[i];
   }

   /// Calc size of packet needed
   const unsigned packetLength = header.packetLogicalLengthMinus1+1;
   const unsigned needed = sizeof(DataPacketHeader) + 2*header.bytestreamCount + totalStreamByteCount;
#ifdef E57_MAX_VERBOSE
   std::cout << "needed=" << needed << " actual=" << packetLength << std::endl; //???
#endif

   /// If needed is not with 3 bytes of actual packet size, have an error
   if (needed > packetLength || needed+3 < packetLength)
   {
      throw E57_EXCEPTION2(E57_ERROR_BAD_CV_PACKET,
                           "needed=" + toString(needed)
                           + "packetLength=" + toString(packetLength));
   }

   /// Verify that padding at end of packet is zero
   for (unsigned i=needed; i < packetLength; i++)
   {
      if (reinterpret_cast<const char*>(this)[i] != 0)
      {
         throw E57_EXCEPTION2(E57_ERROR_BAD_CV_PACKET, "i=" + toString(i));
      }
   }
}

char* DataPacket::getBytestream(unsigned bytestreamNumber, unsigned& byteCount)
{
#ifdef E57_MAX_VERBOSE
   std::cout << "getBytestream called, bytestreamNumber=" << bytestreamNumber << std::endl;
#endif

   /// Verify that packet is correct type
   if (header.packetType != DATA_PACKET)
   {
      throw E57_EXCEPTION2(E57_ERROR_BAD_CV_PACKET, "packetType=" + toString(header.packetType));
   }

   /// Check bytestreamNumber in bounds
   if (bytestreamNumber >= header.bytestreamCount)
   {
      throw E57_EXCEPTION2(E57_ERROR_INTERNAL,
                           "bytestreamNumber=" + toString(bytestreamNumber)
                           + "bytestreamCount=" + toString(header.bytestreamCount));
   }

   /// Calc positions in packet
   auto bsbLength = reinterpret_cast<uint16_t*>(&payload[0]);
   auto streamBase = reinterpret_cast<char*>(&bsbLength[header.bytestreamCount]);

   /// Sum size of preceeding stream buffers to get position
   unsigned totalPreceeding = 0;
   for (unsigned i=0; i < bytestreamNumber; i++)
      totalPreceeding += bsbLength[i];

   byteCount = bsbLength[bytestreamNumber];

   /// Double check buffer is completely within packet
   if (sizeof(DataPacketHeader) + 2*header.bytestreamCount + totalPreceeding + byteCount > header.packetLogicalLengthMinus1 + 1U)
   {
      throw E57_EXCEPTION2(E57_ERROR_INTERNAL,
                           "bytestreamCount=" + toString(header.bytestreamCount)
                           + " totalPreceeding=" + toString(totalPreceeding)
                           + " byteCount=" + toString(byteCount)
                           + " packetLogicalLengthMinus1=" + toString(header.packetLogicalLengthMinus1));
   }

   /// Return start of buffer
   return(&streamBase[totalPreceeding]);
}

unsigned DataPacket::getBytestreamBufferLength(unsigned bytestreamNumber)
{
   //??? for now:
   unsigned byteCount;
   (void) getBytestream(bytestreamNumber, byteCount);
   return(byteCount);
}

#ifdef E57_DEBUG
void DataPacket::dump(int indent, std::ostream& os) const
{
   if (header.packetType != DATA_PACKET)
   {
      throw E57_EXCEPTION2(E57_ERROR_INTERNAL, "packetType=" + toString(header.packetType));
   }

   reinterpret_cast<const DataPacketHeader*>(this)->dump(indent, os);

   auto bsbLength = reinterpret_cast<const uint16_t*>(&payload[0]);
   auto p = reinterpret_cast<const uint8_t*>(&bsbLength[header.bytestreamCount]);

   for (unsigned i=0; i < header.bytestreamCount; i++)
   {
      os << space(indent) << "bytestream[" << i << "]:" << std::endl;
      os << space(indent+4) << "length: " << bsbLength[i] << std::endl;
      /*====
           unsigned j;
           for (j=0; j < bsbLength[i] && j < 10; j++)
               os << space(indent+4) << "byte[" << j << "]=" << (unsigned)p[j] << endl;
           if (j < bsbLength[i])
               os << space(indent+4) << bsbLength[i]-j << " more unprinted..." << endl;
   ====*/
      p += bsbLength[i];
      if (p - reinterpret_cast<const uint8_t*>(this) > DATA_PACKET_MAX)
      {
         throw E57_EXCEPTION2(E57_ERROR_INTERNAL, "size=" + toString(p - reinterpret_cast<const uint8_t*>(this)));
      }
   }
}
#endif

//=============================================================================
// IndexPacket

void IndexPacket::verify(unsigned bufferLength, uint64_t totalRecordCount, uint64_t fileSize) const
{
   //??? do all packets need versions?  how extend without breaking older checking?  need to check file version#?

   /// Verify that packet is correct type
   if (packetType != INDEX_PACKET)
      throw E57_EXCEPTION2(E57_ERROR_BAD_CV_PACKET, "packetType=" + toString(packetType));

   /// Check packetLength is at least large enough to hold header
   unsigned packetLength = packetLogicalLengthMinus1+1;
   if (packetLength < sizeof(*this))
      throw E57_EXCEPTION2(E57_ERROR_BAD_CV_PACKET, "packetLength=" + toString(packetLength));

   /// Check packet length is multiple of 4
   if (packetLength % 4)
      throw E57_EXCEPTION2(E57_ERROR_BAD_CV_PACKET, "packetLength=" + toString(packetLength));

   /// Make sure there is at least one entry in packet  ??? 0 record cvect allowed?
   if (entryCount == 0)
      throw E57_EXCEPTION2(E57_ERROR_BAD_CV_PACKET, "entryCount=" + toString(entryCount));

   /// Have to have <= 2048 entries
   if (entryCount > MAX_ENTRIES)
      throw E57_EXCEPTION2(E57_ERROR_BAD_CV_PACKET, "entryCount=" + toString(entryCount));

   /// Index level should be <= 5.  Because (5+1)* 11 bits = 66 bits, which will cover largest number of chunks possible.
   if (indexLevel > 5)
      throw E57_EXCEPTION2(E57_ERROR_BAD_CV_PACKET, "indexLevel=" + toString(indexLevel));

   /// Index packets above level 0 must have at least two entries (otherwise no point to existing).
   ///??? check that this is in spec
   if (indexLevel > 0 && entryCount < 2)
      throw E57_EXCEPTION2(E57_ERROR_BAD_CV_PACKET, "indexLevel=" + toString(indexLevel) + " entryCount=" + toString(entryCount));

   /// If not later version, verify reserved fields are zero. ??? test file version
   /// if (version <= E57_FORMAT_MAJOR) { //???
   for (unsigned i=0; i < sizeof(reserved1); i++) {
      if (reserved1[i] != 0)
         throw E57_EXCEPTION2(E57_ERROR_BAD_CV_PACKET, "i=" + toString(i));
   }

   /// Check actual packet length is large enough.
   if (bufferLength > 0 && packetLength > bufferLength) {
      throw E57_EXCEPTION2(E57_ERROR_BAD_CV_PACKET,
                           "packetLength=" + toString(packetLength)
                           + " bufferLength=" + toString(bufferLength));
   }

   /// Check if entries will fit in space provided
   unsigned neededLength = 16 + 8*entryCount;
   if (packetLength < neededLength) {
      throw E57_EXCEPTION2(E57_ERROR_BAD_CV_PACKET,
                           "packetLength=" + toString(packetLength)
                           + " neededLength=" + toString(neededLength));
   }

#ifdef E57_MAX_DEBUG
   /// Verify padding at end is zero.
   const char* p = reinterpret_cast<const char*>(this);
   for (unsigned i=neededLength; i < packetLength; i++) {
      if (p[i] != 0)
         throw E57_EXCEPTION2(E57_ERROR_BAD_CV_PACKET, "i=" + toString(i));
   }

   /// Verify records and offsets are in sorted order
   for (unsigned i=0; i < entryCount; i++) {
      /// Check chunkRecordNumber is in bounds
      if (totalRecordCount > 0 && entries[i].chunkRecordNumber >= totalRecordCount) {
         throw E57_EXCEPTION2(E57_ERROR_BAD_CV_PACKET,
                              "i=" + toString(i)
                              + " chunkRecordNumber=" + toString(entries[i].chunkRecordNumber)
                              + " totalRecordCount=" + toString(totalRecordCount));
      }

      /// Check record numbers are strictly increasing
      if (i > 0 && entries[i-1].chunkRecordNumber >= entries[i].chunkRecordNumber) {
         throw E57_EXCEPTION2(E57_ERROR_BAD_CV_PACKET,
                              "i=" + toString(i)
                              + " prevChunkRecordNumber=" + toString(entries[i-1].chunkRecordNumber)
               + " currentChunkRecordNumber=" + toString(entries[i].chunkRecordNumber));
      }

      /// Check chunkPhysicalOffset is in bounds
      if (fileSize > 0 && entries[i].chunkPhysicalOffset >= fileSize) {
         throw E57_EXCEPTION2(E57_ERROR_BAD_CV_PACKET,
                              "i=" + toString(i)
                              + " chunkPhysicalOffset=" + toString(entries[i].chunkPhysicalOffset)
                              + " fileSize=" + toString(fileSize));
      }

      /// Check chunk offsets are strictly increasing
      if (i > 0 && entries[i-1].chunkPhysicalOffset >= entries[i].chunkPhysicalOffset) {
         throw E57_EXCEPTION2(E57_ERROR_BAD_CV_PACKET,
                              "i=" + toString(i)
                              + " prevChunkPhysicalOffset=" + toString(entries[i-1].chunkPhysicalOffset)
               + " currentChunkPhysicalOffset=" + toString(entries[i].chunkPhysicalOffset));
      }
   }
#endif
}

#ifdef E57_DEBUG
void IndexPacket::dump(int indent, std::ostream& os) const
{
   os << space(indent) << "packetType:                " << static_cast<unsigned>(packetType) << std::endl;
   os << space(indent) << "packetFlags:               " << static_cast<unsigned>(packetFlags) << std::endl;
   os << space(indent) << "packetLogicalLengthMinus1: " << packetLogicalLengthMinus1 << std::endl;
   os << space(indent) << "entryCount:                " << entryCount << std::endl;
   os << space(indent) << "indexLevel:                " << indexLevel << std::endl;
   unsigned i;
   for (i=0; i < entryCount && i < 10; i++) {
      os << space(indent) << "entry[" << i << "]:" << std::endl;
      os << space(indent+4) << "chunkRecordNumber:    " << entries[i].chunkRecordNumber << std::endl;
      os << space(indent+4) << "chunkPhysicalOffset:  " << entries[i].chunkPhysicalOffset << std::endl;
   }
   if (i < entryCount)
   {
      os << space(indent) << entryCount-i << "more entries unprinted..." << std::endl;
   }
}
#endif

//=============================================================================
// EmptyPacketHeader

void EmptyPacketHeader::verify(unsigned bufferLength) const
{
   /// Verify that packet is correct type
   if (packetType != EMPTY_PACKET)
      throw E57_EXCEPTION2(E57_ERROR_BAD_CV_PACKET, "packetType=" + toString(packetType));

   /// Check packetLength is at least large enough to hold header
   unsigned packetLength = packetLogicalLengthMinus1+1;
   if (packetLength < sizeof(*this))
      throw E57_EXCEPTION2(E57_ERROR_BAD_CV_PACKET, "packetLength=" + toString(packetLength));

   /// Check packet length is multiple of 4
   if (packetLength % 4)
      throw E57_EXCEPTION2(E57_ERROR_BAD_CV_PACKET, "packetLength=" + toString(packetLength));

   /// Check actual packet length is large enough.
   if (bufferLength > 0 && packetLength > bufferLength) {
      throw E57_EXCEPTION2(E57_ERROR_BAD_CV_PACKET,
                           "packetLength=" + toString(packetLength)
                           + " bufferLength=" + toString(bufferLength));
   }
}

#ifdef E57_DEBUG
void EmptyPacketHeader::dump(int indent, std::ostream& os) const
{
   os << space(indent) << "packetType:                " << static_cast<unsigned>(packetType) << std::endl;
   os << space(indent) << "packetLogicalLengthMinus1: " << packetLogicalLengthMinus1 << std::endl;
}
#endif
