/*
 * 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 <algorithm>
#include <cstring>

#include "Encoder.h"
#include "E57FormatImpl.h"
#include "ImageFileImpl.h"
#include "SourceDestBufferImpl.h"

using namespace e57;
using namespace std;


shared_ptr<Encoder> Encoder::EncoderFactory(unsigned bytestreamNumber,
                                            shared_ptr<CompressedVectorNodeImpl> cVector,
                                            vector<SourceDestBuffer>& sbufs,
                                            ustring& /*codecPath*/)
{
   //??? For now, only handle one input
   if (sbufs.size() != 1)
   {
      throw E57_EXCEPTION2(E57_ERROR_INTERNAL, "sbufsSize=" + toString(sbufs.size()));
   }

   SourceDestBuffer sbuf = sbufs.at(0);

   /// Get node we are going to encode from the CompressedVector's prototype
   NodeImplSharedPtr prototype = cVector->getPrototype();
   ustring path = sbuf.pathName();
   NodeImplSharedPtr encodeNode = prototype->get(path);

#ifdef E57_MAX_VERBOSE
   cout << "Node to encode:" << endl; //???
   encodeNode->dump(2);
#endif
   switch (encodeNode->type())
   {
      case E57_INTEGER:
      {
         shared_ptr<IntegerNodeImpl> ini = dynamic_pointer_cast<IntegerNodeImpl>(encodeNode);  // downcast to correct type
         if (!ini)  // check if failed
         {
            throw E57_EXCEPTION2(E57_ERROR_INTERNAL, "elementName=" + encodeNode->elementName());
         }

         /// Get pointer to parent ImageFileImpl, to call bitsNeeded()
         ImageFileImplSharedPtr imf(encodeNode->destImageFile_);  //??? should be function for this,  imf->parentFile()  --> ImageFile?

         unsigned bitsPerRecord = imf->bitsNeeded(ini->minimum(), ini->maximum());

         //!!! need to pick smarter channel buffer sizes, here and elsewhere
         /// Constuct Integer encoder with appropriate register size, based on number of bits stored.
         if (bitsPerRecord == 0)
         {
            shared_ptr<Encoder> encoder(new ConstantIntegerEncoder(bytestreamNumber, sbuf, ini->minimum()));

            return encoder;
         }

         if (bitsPerRecord <= 8)
         {
            shared_ptr<Encoder> encoder(new BitpackIntegerEncoder<uint8_t>(false, bytestreamNumber, sbuf,
                                                                           DATA_PACKET_MAX/*!!!*/,
                                                                           ini->minimum(), ini->maximum(), 1.0, 0.0));
            return encoder;
         }

         if (bitsPerRecord <= 16)
         {
            shared_ptr<Encoder> encoder(new BitpackIntegerEncoder<uint16_t>(false, bytestreamNumber, sbuf,
                                                                            DATA_PACKET_MAX/*!!!*/,
                                                                            ini->minimum(), ini->maximum(), 1.0, 0.0));
            return encoder;
         }

         if (bitsPerRecord <= 32)
         {
            shared_ptr<Encoder> encoder(new BitpackIntegerEncoder<uint32_t>(false, bytestreamNumber, sbuf,
                                                                            DATA_PACKET_MAX/*!!!*/,
                                                                            ini->minimum(), ini->maximum(), 1.0, 0.0));
            return encoder;
         }

         shared_ptr<Encoder> encoder(new BitpackIntegerEncoder<uint64_t>(false, bytestreamNumber, sbuf,
                                                                         DATA_PACKET_MAX/*!!!*/,
                                                                         ini->minimum(), ini->maximum(), 1.0, 0.0));
         return encoder;
      }

      case E57_SCALED_INTEGER:
      {
         shared_ptr<ScaledIntegerNodeImpl> sini = dynamic_pointer_cast<ScaledIntegerNodeImpl>(encodeNode);  // downcast to correct type
         if (!sini)  // check if failed
            throw E57_EXCEPTION2(E57_ERROR_INTERNAL, "elementName=" + encodeNode->elementName());

         /// Get pointer to parent ImageFileImpl, to call bitsNeeded()
         ImageFileImplSharedPtr imf(encodeNode->destImageFile_);  //??? should be function for this,  imf->parentFile()  --> ImageFile?

         unsigned bitsPerRecord = imf->bitsNeeded(sini->minimum(), sini->maximum());

         //!!! need to pick smarter channel buffer sizes, here and elsewhere
         /// Constuct ScaledInteger encoder with appropriate register size, based on number of bits stored.
         if (bitsPerRecord == 0)
         {
            shared_ptr<Encoder> encoder(new ConstantIntegerEncoder(bytestreamNumber, sbuf, sini->minimum()));

            return encoder;
         }

         if (bitsPerRecord <= 8)
         {
            shared_ptr<Encoder> encoder(new BitpackIntegerEncoder<uint8_t>(true, bytestreamNumber, sbuf,
                                                                           DATA_PACKET_MAX/*!!!*/,
                                                                           sini->minimum(), sini->maximum(),
                                                                           sini->scale(), sini->offset()));
            return encoder;
         }

         if (bitsPerRecord <= 16)
         {
            shared_ptr<Encoder> encoder(new BitpackIntegerEncoder<uint16_t>(true, bytestreamNumber, sbuf,
                                                                            DATA_PACKET_MAX/*!!!*/,
                                                                            sini->minimum(), sini->maximum(),
                                                                            sini->scale(), sini->offset()));
            return encoder;
         }

         if (bitsPerRecord <= 32)
         {
            shared_ptr<Encoder> encoder(new BitpackIntegerEncoder<uint32_t>(true, bytestreamNumber, sbuf,
                                                                            DATA_PACKET_MAX/*!!!*/,
                                                                            sini->minimum(), sini->maximum(),
                                                                            sini->scale(), sini->offset()));
            return encoder;
         }

         shared_ptr<Encoder> encoder(new BitpackIntegerEncoder<uint64_t>(true, bytestreamNumber, sbuf,
                                                                         DATA_PACKET_MAX/*!!!*/,
                                                                         sini->minimum(), sini->maximum(),
                                                                         sini->scale(), sini->offset()));
         return encoder;
      }

      case E57_FLOAT:
      {
         shared_ptr<FloatNodeImpl> fni = dynamic_pointer_cast<FloatNodeImpl>(encodeNode);  // downcast to correct type
         if (!fni)  // check if failed
         {
            throw E57_EXCEPTION2(E57_ERROR_INTERNAL, "elementName=" + encodeNode->elementName());
         }

         //!!! need to pick smarter channel buffer sizes, here and elsewhere
         shared_ptr<Encoder> encoder(new BitpackFloatEncoder(bytestreamNumber, sbuf, DATA_PACKET_MAX/*!!!*/,
                                                             fni->precision()));
         return encoder;
      }

      case E57_STRING:
      {
         shared_ptr<Encoder> encoder(new BitpackStringEncoder(bytestreamNumber, sbuf, DATA_PACKET_MAX/*!!!*/));

         return encoder;
      }

      default:
      {
         throw E57_EXCEPTION2(E57_ERROR_BAD_PROTOTYPE, "nodeType=" + toString(encodeNode->type()));
      }
   }
}

Encoder::Encoder(unsigned bytestreamNumber)
   : bytestreamNumber_(bytestreamNumber)
{
}

#ifdef E57_DEBUG
void Encoder::dump(int indent, std::ostream& os) const
{
   os << space(indent) << "bytestreamNumber:       " << bytestreamNumber_ << endl;
}
#endif

///================

BitpackEncoder::BitpackEncoder(unsigned bytestreamNumber, SourceDestBuffer& sbuf, unsigned outputMaxSize, unsigned alignmentSize)
   : Encoder(bytestreamNumber),
     sourceBuffer_(sbuf.impl()),
     outBuffer_(outputMaxSize),
     outBufferFirst_(0),
     outBufferEnd_(0),
     outBufferAlignmentSize_(alignmentSize),
     currentRecordIndex_(0)
{
}

unsigned BitpackEncoder::sourceBufferNextIndex()
{
   return(sourceBuffer_->nextIndex());
}

uint64_t BitpackEncoder::currentRecordIndex()
{
   return(currentRecordIndex_);
}

size_t BitpackEncoder::outputAvailable()
{
   return(outBufferEnd_ - outBufferFirst_);
}

void BitpackEncoder::outputRead(char* dest, const size_t byteCount)
{
#ifdef E57_MAX_VERBOSE
   cout << "BitpackEncoder::outputRead() called, dest=" << dest << " byteCount="<< byteCount << endl; //???
#endif

   /// Check we have enough bytes in queue
   if (byteCount > outputAvailable())
      throw E57_EXCEPTION2(E57_ERROR_INTERNAL, "byteCount=" + toString(byteCount) + " outputAvailable=" + toString(outputAvailable()));

   /// Copy output bytes to caller
   memcpy(dest, &outBuffer_[outBufferFirst_], byteCount);

#ifdef E57_MAX_VERBOSE
   {
      unsigned i;
      for (i=0; i < byteCount && i < 20; i++)
         cout << "  outBuffer[" << outBufferFirst_+i << "]=" << static_cast<unsigned>(static_cast<unsigned char>(outBuffer_[outBufferFirst_+i])) << endl; //???
      if (i < byteCount)
         cout << "  " << byteCount-1 << " bytes unprinted..." << endl;
   }
#endif

   /// Advance head pointer.
   outBufferFirst_ += byteCount;

   /// Don't slide remaining data down now, wait until do some more processing (that's when data needs to be aligned).
}

void BitpackEncoder::outputClear()
{
   outBufferFirst_     = 0;
   outBufferEnd_       = 0;
}

void BitpackEncoder::sourceBufferSetNew(std::vector<SourceDestBuffer>& sbufs)
{
   /// Verify that this encoder only has single input buffer
   if (sbufs.size() != 1)
      throw E57_EXCEPTION2(E57_ERROR_INTERNAL, "sbufsSize=" + toString(sbufs.size()));

   sourceBuffer_ = sbufs.at(0).impl();
}

size_t BitpackEncoder::outputGetMaxSize()
{
   /// Its size that matters here, not capacity
   return(outBuffer_.size());
}

void BitpackEncoder::outputSetMaxSize(unsigned byteCount)
{
   /// Ignore if trying to shrink buffer (queue might get messed up).
   if (byteCount > outBuffer_.size())
      outBuffer_.resize(byteCount);
}

void BitpackEncoder::outBufferShiftDown()
{
   /// Move data down closer to beginning of outBuffer_.
   /// But keep outBufferEnd_ as a multiple of outBufferAlignmentSize_.
   /// This ensures that writes into buffer can occur on natural boundaries.
   /// Otherwise some CPUs will fault.

   if (outBufferFirst_ == outBufferEnd_) {
      /// Buffer is empty, reset indices to 0
      outBufferFirst_ = 0;
      outBufferEnd_   = 0;
      return;
   }

   /// Round newEnd up to nearest multiple of outBufferAlignmentSize_.
   size_t newEnd = outputAvailable();
   size_t remainder = newEnd % outBufferAlignmentSize_;
   if (remainder > 0)
      newEnd += outBufferAlignmentSize_ - remainder;
   size_t newFirst = outBufferFirst_ - (outBufferEnd_ - newEnd);
   size_t byteCount = outBufferEnd_ - outBufferFirst_;

   /// Double check round up worked
   if (newEnd % outBufferAlignmentSize_)
      throw E57_EXCEPTION2(E57_ERROR_INTERNAL, "newEnd=" + toString(newEnd) + " outBufferAlignmentSize=" + toString(outBufferAlignmentSize_));

   /// Be paranoid before memory copy
   if (newFirst+byteCount > outBuffer_.size()) {
      throw E57_EXCEPTION2(E57_ERROR_INTERNAL,
                           "newFirst=" + toString(newFirst)
                           + " byteCount=" + toString(byteCount)
                           + " outBufferSize=" + toString(outBuffer_.size()));
   }

   /// Move available data down closer to beginning of outBuffer_.  Overlapping regions ok with memmove().
   memmove(&outBuffer_[newFirst], &outBuffer_[outBufferFirst_], byteCount);

   /// Update indexes
   outBufferFirst_ = newFirst;
   outBufferEnd_  = newEnd;
}

#ifdef E57_DEBUG
void BitpackEncoder::dump(int indent, std::ostream& os) const
{
   Encoder::dump(indent, os);
   os << space(indent) << "sourceBuffer:" << endl;
   sourceBuffer_->dump(indent+4, os);
   os << space(indent) << "outBuffer.size:           " << outBuffer_.size() << endl;
   os << space(indent) << "outBufferFirst:           " << outBufferFirst_ << endl;
   os << space(indent) << "outBufferEnd:             " << outBufferEnd_ << endl;
   os << space(indent) << "outBufferAlignmentSize:   " << outBufferAlignmentSize_ << endl;
   os << space(indent) << "currentRecordIndex:       " << currentRecordIndex_ << endl;
   os << space(indent) << "outBuffer:" << endl;
   unsigned i;
   for (i = 0; i < outBuffer_.size() && i < 20; i++)
      os << space(indent+4) << "outBuffer[" << i << "]: " << static_cast<unsigned>(static_cast<unsigned char>(outBuffer_.at(i))) << endl;
   if (i < outBuffer_.size())
      os << space(indent+4) << outBuffer_.size()-i << " more unprinted..." << endl;
}
#endif

//================

BitpackFloatEncoder::BitpackFloatEncoder(unsigned bytestreamNumber, SourceDestBuffer& sbuf, unsigned outputMaxSize, FloatPrecision precision)
   : BitpackEncoder(bytestreamNumber, sbuf, outputMaxSize, (precision==E57_SINGLE) ? sizeof(float) : sizeof(double)),
     precision_(precision)
{
}

uint64_t BitpackFloatEncoder::processRecords(size_t recordCount)
{
#ifdef E57_MAX_VERBOSE
   cout << "  BitpackFloatEncoder::processRecords() called, recordCount=" << recordCount << endl; //???
#endif

   /// Before we add any more, try to shift current contents of outBuffer_ down to beginning of buffer.
   /// This leaves outBufferEnd_ at a natural boundary.
   outBufferShiftDown();

   size_t typeSize = (precision_ == E57_SINGLE) ? sizeof(float) : sizeof(double);

#ifdef E57_DEBUG
   /// Verify that outBufferEnd_ is multiple of typeSize (so transfers of floats are aligned naturally in memory).
   if (outBufferEnd_ % typeSize)
      throw E57_EXCEPTION2(E57_ERROR_INTERNAL, "outBufferEnd=" + toString(outBufferEnd_) + " typeSize=" + toString(typeSize));
#endif

   /// Figure out how many records will fit in output.
   size_t maxOutputRecords = (outBuffer_.size() - outBufferEnd_) / typeSize;

   /// Can't process more records than will safely fit in output stream
   if (recordCount > maxOutputRecords)
      recordCount = maxOutputRecords;

   if (precision_ == E57_SINGLE) {
      /// Form the starting address for next available location in outBuffer
      auto outp = reinterpret_cast<float*>(&outBuffer_[outBufferEnd_]);

      /// Copy floats from sourceBuffer_ to outBuffer_
      for (unsigned i=0; i < recordCount; i++) {
         outp[i] = sourceBuffer_->getNextFloat();
#ifdef E57_MAX_VERBOSE
         cout << "encoding float: " << outp[i] << endl;
#endif
      }
   } else {  /// E57_DOUBLE precision
      /// Form the starting address for next available location in outBuffer
      auto outp = reinterpret_cast<double*>(&outBuffer_[outBufferEnd_]);

      /// Copy doubles from sourceBuffer_ to outBuffer_
      for (unsigned i=0; i < recordCount; i++) {
         outp[i] = sourceBuffer_->getNextDouble();
#ifdef E57_MAX_VERBOSE
         cout << "encoding double: " << outp[i] << endl;
#endif
      }
   }

   /// Update end of outBuffer
   outBufferEnd_ += recordCount*typeSize;

   /// Update counts of records processed
   currentRecordIndex_ += recordCount;

   return(currentRecordIndex_);
}

bool BitpackFloatEncoder::registerFlushToOutput()
{
   /// Since have no registers in encoder, return success
   return(true);
}

float BitpackFloatEncoder::bitsPerRecord()
{
   return((precision_ == E57_SINGLE) ? 32.0F: 64.0F);
}

#ifdef E57_DEBUG
void BitpackFloatEncoder::dump(int indent, std::ostream& os) const
{
   BitpackEncoder::dump(indent, os);
   if (precision_ == E57_SINGLE)
      os << space(indent) << "precision:                E57_SINGLE" << endl;
   else
      os << space(indent) << "precision:                E57_DOUBLE" << endl;
}
#endif

//================

BitpackStringEncoder::BitpackStringEncoder(unsigned bytestreamNumber, SourceDestBuffer& sbuf, unsigned outputMaxSize)
   : BitpackEncoder(bytestreamNumber, sbuf, outputMaxSize, 1),
     totalBytesProcessed_(0),
     isStringActive_(false),
     prefixComplete_(false),
     currentCharPosition_(0)
{
}

uint64_t BitpackStringEncoder::processRecords(size_t recordCount)
{
#ifdef E57_MAX_VERBOSE
   cout << "  BitpackStringEncoder::processRecords() called, recordCount=" << recordCount << endl; //???
#endif

   /// Before we add any more, try to shift current contents of outBuffer_ down to beginning of buffer.
   outBufferShiftDown();

   /// Figure out how many bytes outBuffer can accept.
   size_t bytesFree = outBuffer_.size() - outBufferEnd_;

   /// Form the starting address for next available location in outBuffer
   char* outp = &outBuffer_[outBufferEnd_];
   unsigned recordsProcessed = 0;

   /// Don't start loop unless have at least 8 bytes for worst case string length prefix
   while (recordsProcessed < recordCount && bytesFree >= 8) {  //??? should be able to proceed if only 1 byte free
      if (isStringActive_ && !prefixComplete_) {
         /// Calc the length prefix, either 1 byte or 8 bytes
         size_t len = currentString_.length();
         if (len <= 127) {
#ifdef E57_MAX_VERBOSE
            cout << "encoding short string: (len=" << len << ") """ << currentString_ << """" << endl;
#endif
            /// We can use the short length prefix: b0=0, b7-b1=len
            auto lengthPrefix = static_cast<uint8_t>(len << 1);
            *outp++ = lengthPrefix;
            bytesFree--;
         } else {
#ifdef E57_DEBUG
            /// Double check have space
            if (bytesFree < 8)
               throw E57_EXCEPTION2(E57_ERROR_INTERNAL, "bytesFree=" + toString(bytesFree));
#endif
#ifdef E57_MAX_VERBOSE
            cout << "encoding long string: (len=" << len << ") """ << currentString_ << """" << endl;
#endif
            /// We use the long length prefix: b0=1, b63-b1=len, and store in little endian order
            /// Shift the length and set the least significant bit, b0=1.
            uint64_t lengthPrefix = (static_cast<uint64_t>(len) << 1) | 1LL;
            *outp++ = static_cast<uint8_t>(lengthPrefix);
            *outp++ = static_cast<uint8_t>(lengthPrefix>>(1*8));
            *outp++ = static_cast<uint8_t>(lengthPrefix>>(2*8));
            *outp++ = static_cast<uint8_t>(lengthPrefix>>(3*8));
            *outp++ = static_cast<uint8_t>(lengthPrefix>>(4*8));
            *outp++ = static_cast<uint8_t>(lengthPrefix>>(5*8));
            *outp++ = static_cast<uint8_t>(lengthPrefix>>(6*8));
            *outp++ = static_cast<uint8_t>(lengthPrefix>>(7*8));
            bytesFree -= 8;
         }
         prefixComplete_ = true;
         currentCharPosition_ = 0;
      }
      if (isStringActive_) {
         /// Copy as much string as will fit in outBuffer
         size_t bytesToProcess = min(currentString_.length() - currentCharPosition_, bytesFree);

         for (size_t i = 0; i < bytesToProcess; i++)
            *outp++ = currentString_[currentCharPosition_+i];

         currentCharPosition_ += bytesToProcess;
         totalBytesProcessed_ += bytesToProcess;
         bytesFree -= bytesToProcess;

         /// Check if finished string
         if (currentCharPosition_ == currentString_.length()) {
            isStringActive_ = false;
            recordsProcessed++;
         }
      }
      if (!isStringActive_ && recordsProcessed < recordCount) {
         /// Get next string from sourceBuffer
         currentString_ = sourceBuffer_->getNextString();
         isStringActive_ = true;
         prefixComplete_ = false;
         currentCharPosition_ = 0;
#ifdef E57_MAX_VERBOSE
         cout << "getting next string, length=" << currentString_.length() << endl;
#endif
      }
   }

   /// Update end of outBuffer
   outBufferEnd_ = outBuffer_.size() - bytesFree;

   /// Update counts of records processed
   currentRecordIndex_ += recordsProcessed;

   return(currentRecordIndex_);
}

bool BitpackStringEncoder::registerFlushToOutput()
{
   /// Since have no registers in encoder, return success
   return(true);
}

float BitpackStringEncoder::bitsPerRecord()
{
   /// Return average number of bits in strings + 8 bits for prefix
   if (currentRecordIndex_ > 0)
   {
      return (8.0f*totalBytesProcessed_) / currentRecordIndex_ + 8;
   }

   /// We haven't completed a record yet, so guess 100 bytes per record
   return 100*8.0f;
}

#ifdef E57_DEBUG
void BitpackStringEncoder::dump(int indent, std::ostream& os) const
{
   BitpackEncoder::dump(indent, os);
   os << space(indent) << "totalBytesProcessed:    " << totalBytesProcessed_ << endl;
   os << space(indent) << "isStringActive:         " << isStringActive_ << endl;
   os << space(indent) << "prefixComplete:         " << prefixComplete_ << endl;
   os << space(indent) << "currentString:          " << currentString_ << endl;
   os << space(indent) << "currentCharPosition:    " << currentCharPosition_ << endl;
}
#endif

//================================================================

template <typename RegisterT>
BitpackIntegerEncoder<RegisterT>::BitpackIntegerEncoder(bool isScaledInteger, unsigned bytestreamNumber, SourceDestBuffer& sbuf,
                                                        unsigned outputMaxSize, int64_t minimum, int64_t maximum, double scale, double offset)
   : BitpackEncoder(bytestreamNumber, sbuf, outputMaxSize, sizeof(RegisterT))
{
   /// Get pointer to parent ImageFileImpl
   ImageFileImplSharedPtr imf(sbuf.impl()->destImageFile());  //??? should be function for this,  imf->parentFile()  --> ImageFile?

   isScaledInteger_    = isScaledInteger;
   minimum_            = minimum;
   maximum_            = maximum;
   scale_              = scale;
   offset_             = offset;
   bitsPerRecord_      = imf->bitsNeeded(minimum_, maximum_);
   sourceBitMask_      = (bitsPerRecord_==64) ? ~0 : (1ULL<<bitsPerRecord_)-1;
   registerBitsUsed_   = 0;
   register_           = 0;
}

template <typename RegisterT>
uint64_t BitpackIntegerEncoder<RegisterT>::processRecords(size_t recordCount)
{
   //??? what are state guarantees if get an exception during transfer?
#ifdef E57_MAX_VERBOSE
   cout << "BitpackIntegerEncoder::processRecords() called, sizeof(RegisterT)=" << sizeof(RegisterT) << " recordCount=" << recordCount << endl;
   dump(4);
#endif
#ifdef E57_MAX_DEBUG
   /// Double check that register will hold at least one input records worth of bits
   if (8*sizeof(RegisterT) < bitsPerRecord_)
      throw E57_EXCEPTION2(E57_ERROR_INTERNAL, "bitsPerRecord=" + toString(bitsPerRecord_));
#endif

   /// Before we add any more, try to shift current contents of outBuffer_ down to beginning of buffer.
   /// This leaves outBufferEnd_ at a natural boundary.
   outBufferShiftDown();

#ifdef E57_DEBUG
   /// Verify that outBufferEnd_ is multiple of sizeof(RegisterT) (so transfers of RegisterT are aligned naturally in memory).
   if (outBufferEnd_ % sizeof(RegisterT))
      throw E57_EXCEPTION2(E57_ERROR_INTERNAL, "outBufferEnd=" + toString(outBufferEnd_));
   size_t transferMax = (outBuffer_.size() - outBufferEnd_) / sizeof(RegisterT);
#endif

   /// Precalculate exact maximum number of records that will fit in output before overflow.
   size_t outputWordCapacity = (outBuffer_.size() - outBufferEnd_) / sizeof(RegisterT);
   size_t maxOutputRecords = (outputWordCapacity*8*sizeof(RegisterT) + 8*sizeof(RegisterT) - registerBitsUsed_ - 1) / bitsPerRecord_;

   /// Number of transfers is the smaller of what was requested and what will fit.
   recordCount = min(recordCount, maxOutputRecords);
#ifdef E57_MAX_VERBOSE
   cout << "  outputWordCapacity=" << outputWordCapacity << " maxOutputRecords=" << maxOutputRecords << " recordCount=" << recordCount << endl;
#endif

   /// Form the starting address for next available location in outBuffer
   auto outp = reinterpret_cast<RegisterT*>(&outBuffer_[outBufferEnd_]);
   unsigned outTransferred = 0;

   /// Copy bits from sourceBuffer_ to outBuffer_
   for (unsigned i=0; i < recordCount; i++) {
      int64_t rawValue;

      /// The parameter isScaledInteger_ determines which version of getNextInt64 gets called
      if (isScaledInteger_)
         rawValue = sourceBuffer_->getNextInt64(scale_, offset_);
      else
         rawValue = sourceBuffer_->getNextInt64();

      /// Enforce min/max specification on value
      if (rawValue < minimum_ || maximum_ < rawValue) {
         throw E57_EXCEPTION2(E57_ERROR_VALUE_OUT_OF_BOUNDS,
                              "rawValue=" + toString(rawValue)
                              + " minimum=" + toString(minimum_)
                              + " maximum=" + toString(maximum_));
      }

      auto uValue = static_cast<uint64_t>(rawValue - minimum_);

#ifdef E57_MAX_VERBOSE
      cout << "encoding integer rawValue=" << binaryString(rawValue)  << " = " << hexString(rawValue)  << endl;
      cout << "                 uValue  =" << binaryString(uValue) << " = " << hexString(uValue) << endl;
#endif
#ifdef E57_DEBUG
      /// Double check that no bits outside of the mask are set
      if (uValue & ~static_cast<uint64_t>(sourceBitMask_))
         throw E57_EXCEPTION2(E57_ERROR_INTERNAL, "uValue=" + toString(uValue));
#endif
      /// Mask off upper bits (just in case)
      uValue &= static_cast<uint64_t>(sourceBitMask_);

      /// See if uValue bits will fit in register
      unsigned newRegisterBitsUsed = registerBitsUsed_ + bitsPerRecord_;
#ifdef E57_MAX_VERBOSE
      cout << "  registerBitsUsed=" << registerBitsUsed_ << "  newRegisterBitsUsed=" << newRegisterBitsUsed << endl;
#endif
      if (newRegisterBitsUsed > 8*sizeof(RegisterT)) {
         /// Have more than one registers worth, fill register, transfer, then fill some more
         register_ |= static_cast<RegisterT>(uValue) << registerBitsUsed_;
#ifdef E57_DEBUG
         /// Before transfer, double check address within bounds
         if (outTransferred >= transferMax) {
            throw E57_EXCEPTION2(E57_ERROR_INTERNAL,
                                 "outTransferred=" + toString(outTransferred)
                                 + " transferMax" + toString(transferMax));
         }
#endif
         outp[outTransferred] = register_;

         outTransferred++;

         register_ = static_cast<RegisterT>(uValue) >> (8*sizeof(RegisterT) - registerBitsUsed_);
         registerBitsUsed_ = newRegisterBitsUsed - 8*sizeof(RegisterT);
      } else if (newRegisterBitsUsed == 8*sizeof(RegisterT)) {
         /// Input will exactly fill register, insert value, then transfer
         register_ |= static_cast<RegisterT>(uValue) << registerBitsUsed_;
#ifdef E57_DEBUG
         /// Before transfer, double check address within bounds
         if (outTransferred >= transferMax) {
            throw E57_EXCEPTION2(E57_ERROR_INTERNAL,
                                 "outTransferred=" + toString(outTransferred)
                                 + " transferMax" + toString(transferMax));
         }
#endif
         outp[outTransferred] = register_;

         outTransferred++;

         register_ = 0;
         registerBitsUsed_ = 0;
      } else {
         /// There is extra room in register, insert value, but don't do transfer yet
         register_ |= static_cast<RegisterT>(uValue) << registerBitsUsed_;
         registerBitsUsed_ = newRegisterBitsUsed;
      }
#ifdef E57_MAX_VERBOSE
      cout << "  After " << outTransferred << " transfers and " << i+1 << " records, encoder:" << endl;
      dump(4);
#endif
   }

   /// Update tail of output buffer
   outBufferEnd_ += outTransferred * sizeof(RegisterT);
#ifdef E57_DEBUG
   /// Double check end is ok
   if (outBufferEnd_ > outBuffer_.size()) {
      throw E57_EXCEPTION2(E57_ERROR_INTERNAL,
                           "outBufferEnd=" + toString(outBufferEnd_)
                           + " outBuffersize=" + toString(outBuffer_.size()));
   }
#endif

   /// Update counts of records processed
   currentRecordIndex_ += recordCount;

   return(currentRecordIndex_);
}

template <typename RegisterT>
bool BitpackIntegerEncoder<RegisterT>::registerFlushToOutput()
{
#ifdef E57_MAX_VERBOSE
   cout << "BitpackIntegerEncoder::registerFlushToOutput() called, sizeof(RegisterT)=" << sizeof(RegisterT) << endl;
   dump(4);
#endif
   /// If have any used bits in register, transfer to output, padded in MSBits with zeros to RegisterT boundary
   if (registerBitsUsed_ > 0)
   {
      if (outBufferEnd_ < outBuffer_.size() - sizeof(RegisterT))
      {
         auto outp = reinterpret_cast<RegisterT*>(&outBuffer_[outBufferEnd_]);
         *outp = register_;
         register_ = 0;
         registerBitsUsed_ = 0;
         outBufferEnd_ += sizeof(RegisterT);
         return true;  // flush succeeded  ??? is this used? correctly?
      }

      return false;  // flush didn't complete (not enough room).
   }

   return true;
}

template <typename RegisterT>
float BitpackIntegerEncoder<RegisterT>::bitsPerRecord()
{
   return(static_cast<float>(bitsPerRecord_));
}

#ifdef E57_DEBUG
template <typename RegisterT>
void BitpackIntegerEncoder<RegisterT>::dump(int indent, std::ostream& os) const
{
   BitpackEncoder::dump(indent, os);
   os << space(indent) << "isScaledInteger:  " << isScaledInteger_ << endl;
   os << space(indent) << "minimum:          " << minimum_ << endl;
   os << space(indent) << "maximum:          " << maximum_ << endl;
   os << space(indent) << "scale:            " << scale_ << endl;
   os << space(indent) << "offset:           " << offset_ << endl;
   os << space(indent) << "bitsPerRecord:    " << bitsPerRecord_ << endl;
   os << space(indent) << "sourceBitMask:    " << binaryString(sourceBitMask_) << " " << hexString(sourceBitMask_) << endl;
   os << space(indent) << "register:         " << binaryString(register_) << " " << hexString(register_) << endl;
   os << space(indent) << "registerBitsUsed: " << registerBitsUsed_ << endl;
}
#endif

//================================================================

ConstantIntegerEncoder::ConstantIntegerEncoder(unsigned bytestreamNumber, SourceDestBuffer& sbuf, int64_t minimum)
   : Encoder(bytestreamNumber),
     sourceBuffer_(sbuf.impl()),
     currentRecordIndex_(0),
     minimum_(minimum)
{}

uint64_t ConstantIntegerEncoder::processRecords(size_t recordCount)
{
#ifdef E57_MAX_VERBOSE
   cout << "ConstantIntegerEncoder::processRecords() called, recordCount=" << recordCount << endl;
   dump(4);
#endif

   /// Check that all source values are == minimum_
   for (unsigned i = 0; i < recordCount; i++) {
      int64_t nextInt64 = sourceBuffer_->getNextInt64();
      if (nextInt64 != minimum_)
         throw E57_EXCEPTION2(E57_ERROR_VALUE_OUT_OF_BOUNDS, "nextInt64=" + toString(nextInt64) + " minimum=" + toString(minimum_));
   }

   /// Update counts of records processed
   currentRecordIndex_ += recordCount;

   return(currentRecordIndex_);
}

unsigned ConstantIntegerEncoder::sourceBufferNextIndex()
{
   return(sourceBuffer_->nextIndex());
}

uint64_t ConstantIntegerEncoder::currentRecordIndex()
{
   return(currentRecordIndex_);
}

float ConstantIntegerEncoder::bitsPerRecord()
{
   /// We don't produce any output
   return(0.0);
}

bool ConstantIntegerEncoder::registerFlushToOutput()
{
   return(true);
}

size_t ConstantIntegerEncoder::outputAvailable()
{
   /// We don't produce any output
   return(0);
}

void ConstantIntegerEncoder::outputRead(char* /*dest*/, const size_t byteCount)
{
   /// Should never request any output data
   if (byteCount > 0)
      throw E57_EXCEPTION2(E57_ERROR_INTERNAL, "byteCount=" + toString(byteCount));
}

void ConstantIntegerEncoder::outputClear()
{}

void ConstantIntegerEncoder::sourceBufferSetNew(std::vector<SourceDestBuffer>& sbufs)
{
   /// Verify that this encoder only has single input buffer
   if (sbufs.size() != 1)
      throw E57_EXCEPTION2(E57_ERROR_INTERNAL, "sbufsSize=" + toString(sbufs.size()));

   sourceBuffer_ = sbufs.at(0).impl();
}

size_t ConstantIntegerEncoder::outputGetMaxSize()
{
   /// We don't produce any output
   return(0);
}

void ConstantIntegerEncoder::outputSetMaxSize(unsigned /*byteCount*/)
{
   /// Ignore, since don't produce any output
}

#ifdef E57_DEBUG
void ConstantIntegerEncoder::dump(int indent, std::ostream& os) const
{
   Encoder::dump(indent, os);
   os << space(indent) << "currentRecordIndex:  " << currentRecordIndex_ << endl;
   os << space(indent) << "minimum:             " << minimum_ << endl;
   os << space(indent) << "sourceBuffer:" << endl;
   sourceBuffer_->dump(indent+4, os);
}
#endif
