/*
 * Original work Copyright 2009 - 2010 Kevin Ackley (kackley@gwi.net)
 * Modified work Copyright 2018 - 2020 Andy Maloney <asmaloney@gmail.com>
 *
 * 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 <cmath>
#include <numeric>

#include "CheckedFile.h"
#include "CompressedVectorNodeImpl.h"
#include "CompressedVectorWriterImpl.h"
#include "ImageFileImpl.h"
#include "SectionHeaders.h"
#include "SourceDestBufferImpl.h"

namespace e57
{
   struct SortByBytestreamNumber
   {
      bool operator()( const std::shared_ptr<Encoder> &lhs, const std::shared_ptr<Encoder> &rhs ) const
      {
         return ( lhs->bytestreamNumber() < rhs->bytestreamNumber() );
      }
   };

   CompressedVectorWriterImpl::CompressedVectorWriterImpl( std::shared_ptr<CompressedVectorNodeImpl> ni,
                                                           std::vector<SourceDestBuffer> &sbufs ) :
      cVector_( ni ),
      isOpen_( false ) // set to true when succeed below
   {
      //???  check if cvector already been written (can't write twice)

      /// Empty sbufs is an error
      if ( sbufs.empty() )
      {
         throw E57_EXCEPTION2( E57_ERROR_BAD_API_ARGUMENT,
                               "imageFileName=" + cVector_->imageFileName() + " cvPathName=" + cVector_->pathName() );
      }

      /// Get CompressedArray's prototype node (all array elements must match this
      /// type)
      proto_ = cVector_->getPrototype();

      /// Check sbufs well formed (matches proto exactly)
      setBuffers( sbufs ); //??? copy code here?

      /// For each individual sbuf, create an appropriate Encoder based on the
      /// cVector_ attributes
      for ( unsigned i = 0; i < sbufs_.size(); i++ )
      {
         /// Create vector of single sbuf  ??? for now, may have groups later
         std::vector<SourceDestBuffer> vTemp;
         vTemp.push_back( sbufs_.at( i ) );

         ustring codecPath = sbufs_.at( i ).pathName();

         /// Calc which stream the given path belongs to.  This depends on position
         /// of the node in the proto tree.
         NodeImplSharedPtr readNode = proto_->get( sbufs.at( i ).pathName() );
         uint64_t bytestreamNumber = 0;
         if ( !proto_->findTerminalPosition( readNode, bytestreamNumber ) )
         {
            throw E57_EXCEPTION2( E57_ERROR_INTERNAL, "sbufIndex=" + toString( i ) );
         }

         /// EncoderFactory picks the appropriate encoder to match type declared in
         /// prototype
         bytestreams_.push_back(
            Encoder::EncoderFactory( static_cast<unsigned>( bytestreamNumber ), cVector_, vTemp, codecPath ) );
      }

      /// The bytestreams_ vector must be ordered by bytestreamNumber, not by order
      /// called specified sbufs, so sort it.
      sort( bytestreams_.begin(), bytestreams_.end(), SortByBytestreamNumber() );
#ifdef E57_MAX_DEBUG
      /// Double check that all bytestreams are specified
      for ( unsigned i = 0; i < bytestreams_.size(); i++ )
      {
         if ( bytestreams_.at( i )->bytestreamNumber() != i )
         {
            throw E57_EXCEPTION2( E57_ERROR_INTERNAL, "bytestreamIndex=" + toString( i ) + " bytestreamNumber=" +
                                                         toString( bytestreams_.at( i )->bytestreamNumber() ) );
         }
      }
#endif

      ImageFileImplSharedPtr imf( ni->destImageFile_ );

      /// Reserve space for CompressedVector binary section header, record location
      /// so can save to when writer closes. Request that file be extended with
      /// zeros since we will write to it at a later time (when writer closes).
      sectionHeaderLogicalStart_ = imf->allocateSpace( sizeof( CompressedVectorSectionHeader ), true );

      sectionLogicalLength_ = 0;
      dataPhysicalOffset_ = 0;
      topIndexPhysicalOffset_ = 0;
      recordCount_ = 0;
      dataPacketsCount_ = 0;
      indexPacketsCount_ = 0;

      /// Just before return (and can't throw) increment writer count  ??? safer
      /// way to assure don't miss close?
      imf->incrWriterCount();

      /// If get here, the writer is open
      isOpen_ = true;
   }

   CompressedVectorWriterImpl::~CompressedVectorWriterImpl()
   {
#ifdef E57_MAX_VERBOSE
      std::cout << "~CompressedVectorWriterImpl() called" << std::endl; //???
#endif

      try
      {
         if ( isOpen_ )
         {
            close();
         }
      }
      catch ( ... )
      {
         //??? report?
      }
   }

   void CompressedVectorWriterImpl::close()
   {
#ifdef E57_MAX_VERBOSE
      std::cout << "CompressedVectorWriterImpl::close() called" << std::endl; //???
#endif
      ImageFileImplSharedPtr imf( cVector_->destImageFile_ );

      /// Before anything that can throw, decrement writer count
      imf->decrWriterCount();

      checkImageFileOpen( __FILE__, __LINE__, static_cast<const char *>( __FUNCTION__ ) );
      /// don't call checkWriterOpen();

      if ( !isOpen_ )
      {
         return;
      }

      /// Set closed before do anything, so if get fault and start unwinding, don't
      /// try to close again.
      isOpen_ = false;

      /// If have any data, write packet
      /// Write all remaining ioBuffers and internal encoder register cache into
      /// file. Know we are done when totalOutputAvailable() returns 0 after a
      /// flush().
      flush();
      while ( totalOutputAvailable() > 0 )
      {
         packetWrite();
         flush();
      }

      /// Compute length of whole section we just wrote (from section start to
      /// current start of free space).
      sectionLogicalLength_ = imf->unusedLogicalStart_ - sectionHeaderLogicalStart_;
#ifdef E57_MAX_VERBOSE
      std::cout << "  sectionLogicalLength_=" << sectionLogicalLength_ << std::endl; //???
#endif

      /// Prepare CompressedVectorSectionHeader
      CompressedVectorSectionHeader header;
      header.sectionLogicalLength = sectionLogicalLength_;
      header.dataPhysicalOffset = dataPhysicalOffset_;      ///??? can be zero, if no data written ???not set yet
      header.indexPhysicalOffset = topIndexPhysicalOffset_; ///??? can be zero, if no data written ???not set
                                                            /// yet
#ifdef E57_MAX_VERBOSE
      std::cout << "  CompressedVectorSectionHeader:" << std::endl;
      header.dump( 4 ); //???
#endif
#ifdef E57_DEBUG
      /// Verify OK before write it.
      header.verify( imf->file_->length( CheckedFile::Physical ) );
#endif

      /// Write header at beginning of section, previously allocated
      imf->file_->seek( sectionHeaderLogicalStart_ );
      imf->file_->write( reinterpret_cast<char *>( &header ), sizeof( header ) );

      /// Set address and size of associated CompressedVector
      cVector_->setRecordCount( recordCount_ );
      cVector_->setBinarySectionLogicalStart( sectionHeaderLogicalStart_ );

      /// Free channels
      bytestreams_.clear();

#ifdef E57_MAX_VERBOSE
      std::cout << "  CompressedVectorWriter:" << std::endl;
      dump( 4 );
#endif
   }

   bool CompressedVectorWriterImpl::isOpen() const
   {
      /// don't checkImageFileOpen(__FILE__, __LINE__, __FUNCTION__), or
      /// checkWriterOpen()
      return isOpen_;
   }

   std::shared_ptr<CompressedVectorNodeImpl> CompressedVectorWriterImpl::compressedVectorNode() const
   {
      return cVector_;
   }

   void CompressedVectorWriterImpl::setBuffers( std::vector<SourceDestBuffer> &sbufs )
   {
      /// don't checkImageFileOpen

      /// If had previous sbufs_, check to see if new ones have changed in
      /// incompatible way
      if ( !sbufs_.empty() )
      {
         if ( sbufs_.size() != sbufs.size() )
         {
            throw E57_EXCEPTION2( E57_ERROR_BUFFERS_NOT_COMPATIBLE,
                                  "oldSize=" + toString( sbufs_.size() ) + " newSize=" + toString( sbufs.size() ) );
         }

         for ( size_t i = 0; i < sbufs_.size(); ++i )
         {
            std::shared_ptr<SourceDestBufferImpl> oldbuf = sbufs_[i].impl();
            std::shared_ptr<SourceDestBufferImpl> newBuf = sbufs[i].impl();

            /// Throw exception if old and new not compatible
            oldbuf->checkCompatible( newBuf );
         }
      }

      /// Check sbufs well formed: no dups, no missing, no extra
      /// For writing, all data fields in prototype must be presented for writing
      /// at same time.
      proto_->checkBuffers( sbufs, false );

      sbufs_ = sbufs;
   }

   void CompressedVectorWriterImpl::write( std::vector<SourceDestBuffer> &sbufs, const size_t requestedRecordCount )
   {
      /// don't checkImageFileOpen, write(unsigned) will do it
      /// don't checkWriterOpen(), write(unsigned) will do it

      setBuffers( sbufs );
      write( requestedRecordCount );
   }

   void CompressedVectorWriterImpl::write( const size_t requestedRecordCount )
   {
#ifdef E57_MAX_VERBOSE
      std::cout << "CompressedVectorWriterImpl::write() called" << std::endl; //???
#endif
      checkImageFileOpen( __FILE__, __LINE__, static_cast<const char *>( __FUNCTION__ ) );
      checkWriterOpen( __FILE__, __LINE__, static_cast<const char *>( __FUNCTION__ ) );

      /// Check that requestedRecordCount is not larger than the sbufs
      if ( requestedRecordCount > sbufs_.at( 0 ).impl()->capacity() )
      {
         throw E57_EXCEPTION2( E57_ERROR_BAD_API_ARGUMENT,
                               "requested=" + toString( requestedRecordCount ) +
                                  " capacity=" + toString( sbufs_.at( 0 ).impl()->capacity() ) + " imageFileName=" +
                                  cVector_->imageFileName() + " cvPathName=" + cVector_->pathName() );
      }

      /// Rewind all sbufs so start reading from beginning
      for ( auto &sbuf : sbufs_ )
      {
         sbuf.impl()->rewind();
      }

      /// Loop until all channels have completed requestedRecordCount transfers
      uint64_t endRecordIndex = recordCount_ + requestedRecordCount;
      while ( true )
      {
         /// Calc remaining record counts for all channels
         uint64_t totalRecordCount = 0;
         for ( auto &bytestream : bytestreams_ )
         {
            totalRecordCount += endRecordIndex - bytestream->currentRecordIndex();
         }
#ifdef E57_MAX_VERBOSE
         std::cout << "  totalRecordCount=" << totalRecordCount << std::endl; //???
#endif

         /// We are done if have no more work, break out of loop
         if ( totalRecordCount == 0 )
         {
            break;
         }

         /// Estimate how many records can write before have enough data to fill
         /// data packet to efficient length Efficient packet length is >= 75%
         /// of maximum packet length. It is OK if get too much data (more than
         /// one packet) in an iteration. Reader will be able to handle packets
         /// whose streams are not exactly synchronized to the record
         /// boundaries. But try to do a good job of keeping the stream
         /// synchronization "close enough" (so a reader that can cache only two
         /// packets is efficient).

#ifdef E57_MAX_VERBOSE
         std::cout << "  currentPacketSize()=" << currentPacketSize() << std::endl; //???
#endif

#ifdef E57_WRITE_CRAZY_PACKET_MODE
         ///??? depends on number of streams
         constexpr size_t E57_TARGET_PACKET_SIZE = 500;
#else
         constexpr size_t E57_TARGET_PACKET_SIZE = ( DATA_PACKET_MAX * 3 / 4 );
#endif
         /// If have more than target fraction of packet, send it now
         if ( currentPacketSize() >= E57_TARGET_PACKET_SIZE )
         { //???
            packetWrite();
            continue; /// restart loop so recalc statistics (packet size may not be
                      /// zero after write, if have too much data)
         }

         ///??? useful?
         /// Get approximation of number of bytes per record of CompressedVector
         /// and total of bytes used
         float totalBitsPerRecord = 0; // an estimate of future performance
         for ( auto &bytestream : bytestreams_ )
         {
            totalBitsPerRecord += bytestream->bitsPerRecord();
         }

#ifdef E57_MAX_VERBOSE
         const float totalBytesPerRecord = std::max( totalBitsPerRecord / 8, 0.1F ); //??? trust

         std::cout << "  totalBytesPerRecord=" << totalBytesPerRecord << std::endl; //???
#endif

         /// Don't allow straggler to get too far behind. ???
         /// Don't allow a single channel to get too far ahead ???
         /// Process channels that are furthest behind first. ???

         ///!!!! For now just process one record per loop until packet is full
         /// enough, or completed request
         for ( auto &bytestream : bytestreams_ )
         {
            if ( bytestream->currentRecordIndex() < endRecordIndex )
            {
               //!!! For now, process up to 50 records at a time
               uint64_t recordCount = endRecordIndex - bytestream->currentRecordIndex();
               recordCount = ( recordCount < 50ULL ) ? recordCount : 50ULL; // min(recordCount, 50ULL);
               bytestream->processRecords( static_cast<unsigned>( recordCount ) );
            }
         }
      }

      recordCount_ += requestedRecordCount;

      /// When we leave this function, will likely still have data in channel
      /// ioBuffers as well as partial words in Encoder registers.
   }

   size_t CompressedVectorWriterImpl::totalOutputAvailable() const
   {
      size_t total = 0;

      for ( const auto &bytestream : bytestreams_ )
      {
         total += bytestream->outputAvailable();
      }

      return total;
   }

   size_t CompressedVectorWriterImpl::currentPacketSize() const
   {
      /// Calc current packet size
      return ( sizeof( DataPacketHeader ) + bytestreams_.size() * sizeof( uint16_t ) + totalOutputAvailable() );
   }

   uint64_t CompressedVectorWriterImpl::packetWrite()
   {
#ifdef E57_MAX_VERBOSE
      std::cout << "CompressedVectorWriterImpl::packetWrite() called" << std::endl; //???
#endif

      /// Double check that we have work to do
      size_t totalOutput = totalOutputAvailable();
      if ( totalOutput == 0 )
      {
         return ( 0 );
      }
#ifdef E57_MAX_VERBOSE
      std::cout << "  totalOutput=" << totalOutput << std::endl; //???
#endif

      /// Calc maximum number of bytestream values can put in data packet.
      size_t packetMaxPayloadBytes =
         DATA_PACKET_MAX - sizeof( DataPacketHeader ) - bytestreams_.size() * sizeof( uint16_t );
#ifdef E57_MAX_VERBOSE
      std::cout << "  packetMaxPayloadBytes=" << packetMaxPayloadBytes << std::endl; //???
#endif

      /// Allocate vector for number of bytes that each bytestream will write to
      /// file.
      std::vector<size_t> count( bytestreams_.size() );

      /// See if we can fit into a single data packet
      if ( totalOutput < packetMaxPayloadBytes )
      {
         /// We can fit everything in one packet
         for ( unsigned i = 0; i < bytestreams_.size(); i++ )
         {
            count.at( i ) = bytestreams_.at( i )->outputAvailable();
         }
      }
      else
      {
         /// We have too much data for one packet.  Send proportional amounts from
         /// each bytestream. Adjust packetMaxPayloadBytes down by one so have a
         /// little slack for floating point weirdness.
         float fractionToSend = ( packetMaxPayloadBytes - 1 ) / static_cast<float>( totalOutput );
         for ( unsigned i = 0; i < bytestreams_.size(); i++ )
         {
            /// Round down here so sum <= packetMaxPayloadBytes
            count.at( i ) =
               static_cast<unsigned>( std::floor( fractionToSend * bytestreams_.at( i )->outputAvailable() ) );
         }
      }
#ifdef E57_MAX_VERBOSE
      for ( unsigned i = 0; i < bytestreams_.size(); i++ )
      {
         std::cout << "  count[" << i << "]=" << count.at( i ) << std::endl; //???
      }
#endif

#ifdef E57_DEBUG
      /// Double check sum of count is <= packetMaxPayloadBytes
      const size_t totalByteCount = std::accumulate( count.begin(), count.end(), 0 );

      if ( totalByteCount > packetMaxPayloadBytes )
      {
         throw E57_EXCEPTION2( E57_ERROR_INTERNAL, "totalByteCount=" + toString( totalByteCount ) +
                                                      " packetMaxPayloadBytes=" + toString( packetMaxPayloadBytes ) );
      }
#endif

      /// Get smart pointer to ImageFileImpl from associated CompressedVector
      ImageFileImplSharedPtr imf( cVector_->destImageFile_ );

      /// Use temp buf in object (is 64KBytes long) instead of allocating each time
      /// here
      char *packet = reinterpret_cast<char *>( &dataPacket_ );
#ifdef E57_MAX_VERBOSE
      std::cout << "  packet=" << packet << std::endl; //???
#endif

      /// To be safe, clear header part of packet
      dataPacket_.header.reset();

      /// Write bytestreamBufferLength[bytestreamCount] after header, in
      /// dataPacket_
      auto bsbLength = reinterpret_cast<uint16_t *>( &packet[sizeof( DataPacketHeader )] );
#ifdef E57_MAX_VERBOSE
      std::cout << "  bsbLength=" << bsbLength << std::endl; //???
#endif
      for ( unsigned i = 0; i < bytestreams_.size(); i++ )
      {
         bsbLength[i] = static_cast<uint16_t>( count.at( i ) ); // %%% Truncation
#ifdef E57_MAX_VERBOSE
         std::cout << "  Writing " << bsbLength[i] << " bytes into bytestream " << i << std::endl; //???
#endif
      }

      /// Get pointer to end of data so far
      char *p = reinterpret_cast<char *>( &bsbLength[bytestreams_.size()] );
#ifdef E57_MAX_VERBOSE
      std::cout << "  after bsbLength, p=" << p << std::endl; //???
#endif

      /// Write contents of each bytestream in dataPacket_
      for ( size_t i = 0; i < bytestreams_.size(); i++ )
      {
         size_t n = count.at( i );

#ifdef E57_DEBUG
         /// Double check we aren't accidentally going to write off end of
         /// vector<char>
         if ( &p[n] > &packet[DATA_PACKET_MAX] )
         {
            throw E57_EXCEPTION2( E57_ERROR_INTERNAL, "n=" + toString( n ) );
         }
#endif

         /// Read from encoder output into packet
         bytestreams_.at( i )->outputRead( p, n );

         /// Move pointer to end of current data
         p += n;
      }

      /// Length of packet is difference in beginning pointer and ending pointer
      auto packetLength = static_cast<unsigned>( p - packet ); ///??? pointer diff portable?
#ifdef E57_MAX_VERBOSE
      std::cout << "  packetLength=" << packetLength << std::endl; //???
#endif

#ifdef E57_DEBUG
      /// Double check that packetLength is what we expect
      if ( packetLength != sizeof( DataPacketHeader ) + bytestreams_.size() * sizeof( uint16_t ) + totalByteCount )
      {
         throw E57_EXCEPTION2( E57_ERROR_INTERNAL, "packetLength=" + toString( packetLength ) + " bytestreamSize=" +
                                                      toString( bytestreams_.size() * sizeof( uint16_t ) ) +
                                                      " totalByteCount=" + toString( totalByteCount ) );
      }
#endif

      /// packetLength must be multiple of 4, if not, add some zero padding
      while ( packetLength % 4 )
      {
         /// Double check we aren't accidentally going to write off end of
         /// vector<char>
         if ( p >= &packet[DATA_PACKET_MAX - 1] )
         {
            throw E57_EXCEPTION1( E57_ERROR_INTERNAL );
         }
         *p++ = 0;
         packetLength++;
#ifdef E57_MAX_VERBOSE
         std::cout << "  padding with zero byte, new packetLength=" << packetLength << std::endl; //???
#endif
      }

      /// Prepare header in dataPacket_, now that we are sure of packetLength
      dataPacket_.header.packetLogicalLengthMinus1 = static_cast<uint16_t>( packetLength - 1 ); // %%% Truncation
      dataPacket_.header.bytestreamCount = static_cast<uint16_t>( bytestreams_.size() );        // %%% Truncation

      /// Double check that data packet is well formed
      dataPacket_.verify( packetLength );

      /// Write whole data packet at beginning of free space in file
      uint64_t packetLogicalOffset = imf->allocateSpace( packetLength, false );
      uint64_t packetPhysicalOffset = imf->file_->logicalToPhysical( packetLogicalOffset );
      imf->file_->seek( packetLogicalOffset ); //??? have seekLogical and seekPhysical instead?
                                               // more explicit
      imf->file_->write( packet, packetLength );

#ifdef E57_MAX_VERBOSE
//  std::cout << "data packet:" << std::endl;
//  dataPacket_.dump(4);
#endif

      /// If first data packet written for this CompressedVector binary section,
      /// save address to put in section header
      ///??? what if no data packets?
      ///??? what if have exceptions while write, what is state of file?  will
      /// close report file
      /// good/bad?
      if ( dataPacketsCount_ == 0 )
      {
         dataPhysicalOffset_ = packetPhysicalOffset;
      }
      dataPacketsCount_++;

      ///!!! update seekIndex here? if started new chunk?

      /// Return physical offset of data packet for potential use in seekIndex
      return ( packetPhysicalOffset ); //??? needed
   }

   void CompressedVectorWriterImpl::flush()
   {
      for ( auto &bytestream : bytestreams_ )
      {
         bytestream->registerFlushToOutput();
      }
   }

   void CompressedVectorWriterImpl::checkImageFileOpen( const char *srcFileName, int srcLineNumber,
                                                        const char *srcFunctionName ) const
   {
      // unimplemented...
   }

   void CompressedVectorWriterImpl::checkWriterOpen( const char *srcFileName, int srcLineNumber,
                                                     const char *srcFunctionName ) const
   {
      if ( !isOpen_ )
      {
         throw E57Exception( E57_ERROR_WRITER_NOT_OPEN,
                             "imageFileName=" + cVector_->imageFileName() + " cvPathName=" + cVector_->pathName(),
                             srcFileName, srcLineNumber, srcFunctionName );
      }
   }

   void CompressedVectorWriterImpl::dump( int indent, std::ostream &os )
   {
      os << space( indent ) << "isOpen:" << isOpen_ << std::endl;

      for ( unsigned i = 0; i < sbufs_.size(); i++ )
      {
         os << space( indent ) << "sbufs[" << i << "]:" << std::endl;
         sbufs_.at( i ).dump( indent + 4, os );
      }

      os << space( indent ) << "cVector:" << std::endl;
      cVector_->dump( indent + 4, os );

      os << space( indent ) << "proto:" << std::endl;
      proto_->dump( indent + 4, os );

      for ( unsigned i = 0; i < bytestreams_.size(); i++ )
      {
         os << space( indent ) << "bytestreams[" << i << "]:" << std::endl;
         bytestreams_.at( i )->dump( indent + 4, os );
      }

      /// Don't call dump() for DataPacket, since it may contain junk when
      /// debugging.  Just print a few byte values.
      os << space( indent ) << "dataPacket:" << std::endl;
      auto p = reinterpret_cast<uint8_t *>( &dataPacket_ );

      for ( unsigned i = 0; i < 40; ++i )
      {
         os << space( indent + 4 ) << "dataPacket[" << i << "]: " << static_cast<unsigned>( p[i] ) << std::endl;
      }
      os << space( indent + 4 ) << "more unprinted..." << std::endl;

      os << space( indent ) << "sectionHeaderLogicalStart: " << sectionHeaderLogicalStart_ << std::endl;
      os << space( indent ) << "sectionLogicalLength:      " << sectionLogicalLength_ << std::endl;
      os << space( indent ) << "dataPhysicalOffset:        " << dataPhysicalOffset_ << std::endl;
      os << space( indent ) << "topIndexPhysicalOffset:    " << topIndexPhysicalOffset_ << std::endl;
      os << space( indent ) << "recordCount:               " << recordCount_ << std::endl;
      os << space( indent ) << "dataPacketsCount:          " << dataPacketsCount_ << std::endl;
      os << space( indent ) << "indexPacketsCount:         " << indexPacketsCount_ << std::endl;
   }
}
