/***************************************************************************
 *   Copyright (C) 2009 by Valerio Pilo                                    *
 *   valerio@kmess.org                                                     *
 *                                                                         *
 *   Copyright (C) 2009 by Adam Goossens                                   *
 *   adam@kmess.org                                                        *
 ***************************************************************************/

/***************************************************************************
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU Lesser General Public License as        *
 *   published by the Free Software Foundation; either version 2.1 of the  *
 *   License, or (at your option) any later version.                       *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU Lesser General Public      *
 *   License along with this program; if not, write to the                 *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include "datasource.h"

#include "isfqt-internal.h"


using namespace Isf;
using namespace Compress;



/**
 * Empty constructor.
 */
DataSource::DataSource()
: currentBitIndex_( 8 ) // Invalid position in the current byte
{
  buffer_.open( QBuffer::ReadWrite );

  currentByte_.resize( 8 );
}



/**
 * Constructor.
 *
 * @param data Byte array to copy and use as data source
 */
DataSource::DataSource( const QByteArray &data )
: currentBitIndex_( 8 )
{
  buffer_.setData( data );
  buffer_.open( QBuffer::ReadWrite );
  buffer_.seek( 0 );

  currentByte_.resize( 8 );

  // Move the first byte of data in the bit array
  moveByteToBitArray();
}



/**
 * Destructor.
 */
DataSource::~DataSource()
{
}



/**
 * Insert a byte at the end of the data.
 *
 * @param byte The byte to append
 */
void DataSource::append( char byte )
{
  bool wasEmpty = ( buffer_.size() == 0 );

  if( ! wasEmpty )
  {
    buffer_.seek( buffer_.size() );
  }

  if( buffer_.write( &byte, 1 ) != 1 )
  {
    qWarning() << "DataSource::append() - Write failed at buffer position" << buffer_.pos();
    return;
  }

  // Prepare the first byte to be read
  if( wasEmpty )
  {
    currentBitIndex_ = 8;
  }
}



/**
 * Insert bits at the end of the data.
 *
 * @param constBits The list of bits to append
 */
void DataSource::append( const QBitArray &constBits )
{
  if( constBits.size() == 0 )
  {
#ifdef ISFQT_DEBUG_VERBOSE
    qDebug() << "Not writing an empty bit array, skipping.";
#endif
    return;
  }

  if( buffer_.size() != 0 )
  {
    buffer_.seek( buffer_.size() );
  }

  quint8 initialPos = 0;
  quint8 size = constBits.size();

  // If there are unwritten bits, add them to the input bit array
  if( currentBitIndex_ != 8 )
  {
    size += currentBitIndex_;
  }

  QBitArray bits( size );

  // Copy on the input bit array the current byte then the input bits
  if( currentBitIndex_ != 8 )
  {
    initialPos = currentBitIndex_;
    for( quint8 idx = 0; idx < currentBitIndex_; ++idx )
    {
      bits[ idx ] = currentByte_[ idx ];
    }
  }
  for( quint8 idx = 0; idx < constBits.size(); ++idx )
  {
    bits[ idx + initialPos ] = constBits[ idx ];
  }

  quint8 bit = 7;
  quint8 byte = 0;
  QByteArray bytes;
  quint8 byteWiseSize = bits.size() - ( bits.size() % 8 );

  // Skip writing if there are no full bytes
  if( byteWiseSize != 0 )
  {
    for( quint8 idx = 0; idx < byteWiseSize; ++idx )
    {
      byte |= bits[ idx ] << bit;

      // A byte was filled, push it to the bytearray
      if( bit-- == 0 )
      {
        bytes.append( byte );
        bit = 7;
        byte = 0;
      }
    }

    // Write the bytes in the buffer
    if( bytes.size() > 0 && buffer_.write( bytes ) != bytes.size() )
    {
      qWarning() << "DataSource::append() - Write failed at buffer position" << buffer_.pos();
      return;
    }
  }

  // Move any remaining bits to the current byte
  quint8 remainingBitSize = ( bits.size() - byteWiseSize );
  if( remainingBitSize > 0 )
  {
    for( currentBitIndex_ = 0; currentBitIndex_ < remainingBitSize; ++currentBitIndex_ )
    {
      currentByte_[ currentBitIndex_ ] = bits[ currentBitIndex_ + byteWiseSize ];
    }
  }
  else
  {
    currentByte_.clear();
    currentByte_.resize( 8 );
    currentBitIndex_ = 8;
  }
}



/**
 * Insert bytes at the end of the data.
 *
 * @param bytes The bytes to append
 */
void DataSource::append( const QByteArray &bytes )
{
  bool wasEmpty = ( buffer_.size() == 0 );

  if( ! wasEmpty )
  {
    buffer_.seek( buffer_.size() );
  }

  if( buffer_.write( bytes ) != bytes.size() )
  {
    qWarning() << "DataSource::append() - Write of" << bytes.size() << "bytes failed at buffer position" << buffer_.pos();
    return;
  }

  // Prepare the first byte to be read
  if( wasEmpty )
  {
    currentBitIndex_ = 8;
  }
}



/**
 * Get whether the buffer is finished.
 *
 * The buffer is finished (at the end) when there are no more
 * bytes to read. If the considerBits parameter is set, the
 * buffer is considered finished when all the bits have been
 * read.
 *
 * @param considerBits Whether to consider the data source as a bits source
 * @return bool
 */
bool DataSource::atEnd( bool considerBits ) const
{
  if( considerBits )
  {
    // There's no more data only when both the buffer AND all the bits
    // have been read
    return buffer_.atEnd() && ( currentBitIndex_ >= 8 );
  }

  return buffer_.atEnd();
}



/**
 * Clear the data buffer.
 */
void DataSource::clear()
{
  buffer_.close();
  buffer_.setData( QByteArray() );
  buffer_.open( QIODevice::ReadWrite );
  buffer_.seek( 0 );

  currentBitIndex_ = 8;

  currentByte_.clear();
  currentByte_.resize( 8 );
}



/**
 * Return a reference to the internal data array.
 *
 * @return byte array
 */
const QByteArray &DataSource::data() const
{
  return buffer_.data();
}



/**
 * Flush the current byte to the buffer.
 *
 * When in bit mode, this method can be used to save less than 8 bits
 * in the stream. The unsed bits are set to 0.
 */
void DataSource::flush()
{
  if( currentBitIndex_ >= 8 )
  {
    return;
  }

  quint8 bit = 7;
  quint8 byte = 0;
  for( quint8 index = 0; index < currentBitIndex_; ++index )
  {
    byte |= currentByte_[ index ] << bit--;
  }

  // Write the bytes in the buffer
  if( buffer_.write( (char*)&byte, 1 ) != 1 )
  {
    qWarning() << "DataSource::flush() - Write failed at buffer position" << buffer_.pos();
  }
}



/**
 * Retrieve the index of the current bit.
 *
 * @return Current position within the byte we're reading at the moment
 */
quint8 DataSource::getBitIndex()
{
  return currentBitIndex_;
}



/**
 * Retrieve the next bit from the data.
 *
 * @param ok If set, it will contain whether the call was successful or not.
 * @return bool
 */
bool DataSource::getBit( bool *ok )
{
  if( currentBitIndex_ >= 8 )
  {
    // Also check the index again, in case somehow moveByteToBitArray()
    // fails to move the bit index
    if( ( ! moveByteToBitArray() ) || currentBitIndex_ >= 8 )
    {
      if( ok != 0 )
      {
        *ok = false;
      }
      return false;
    }

    Q_ASSERT( currentBitIndex_ == 0 );
  }

  Q_ASSERT( currentByte_.size() == 8 );

  if( ok != 0 )
  {
    *ok = true;
  }

  ++currentBitIndex_;

  return currentByte_.at( currentBitIndex_ - 1 );
}



/**
 * Retrieve the next [amount] bits from the data.
 *
 * @param amount Number of bits to read
 * @param ok If set, it will contain whether the call was successful or not.
 * @return quint64 with the set of read bits
 */
quint64 DataSource::getBits( quint8 amount, bool *ok )
{
  if( amount > 64 )
  {
    qWarning() << "DataSource:getBits() - Cannot retrieve" << amount << "bits, the maximum is 64 bits!";

    if( ok != 0 )
    {
      *ok = false;
    }

    return 0LL;
  }

  bool gotBitOk = false;
  quint8 bitIndex = 0;
  quint64 value = 0;

  while( ! atEnd( true ) && bitIndex < amount )
  {
    bool bit = getBit( &gotBitOk );

    if( ! gotBitOk )
    {
      if( ok != 0 )
      {
        *ok = false;
      }

      return 0LL;
    }

    if( bit )
    {
#if Q_BYTE_ORDER == Q_BIG_ENDIAN
      value |= 1 << bitIndex;
#else
      value |= 1 << ( amount - bitIndex - 1 );
#endif
    }

    ++bitIndex;
  }

  // If more bits must be read, but are not available, abort
#ifdef ISFQT_DEBUG_VERBOSE
  if( ( bitIndex < amount ) && atEnd( true ) )
  {
    qDebug() << "Unable to read all bits: only" << bitIndex << "of" << amount << "were available.";
  }
#endif

  // Done!
  if( ok != 0 )
  {
    *ok = true;
  }

  return value;
}



/**
 * Retrieve the next byte from the data
 *
 * @param ok If set, it will contain whether the call was successful or not.
 * @return char
 */
char DataSource::getByte( bool *ok )
{
  bool   gotBitOk;
  quint8 pos      = 0;
  qint8  byte     = 0;

  while( pos < 8 )
  {
    bool bit = getBit( &gotBitOk );

    if( ! gotBitOk )
    {
      if( ok != 0 )
      {
        *ok = false;
      }

      return 0;
    }

#if Q_BYTE_ORDER == Q_BIG_ENDIAN
    byte |= ( bit << pos++ );
#else
    byte |= ( bit << ( 7 - pos++ ) );
#endif
  }

  // Done!
  if( ok != 0 )
  {
    *ok = true;
  }
  return byte;
}



/**
 * Retrieve the next [amount] bytes from the data.
 *
 * @param amount Number of bytes to read
 * @param ok If set, it will contain whether the call was successful or not.
 * @return byte array of read bytes
 */
QByteArray DataSource::getBytes( quint8 amount, bool *ok )
{
  QByteArray bytes;
  bool       gotByteOk;
  quint8     index     = 0;

  while( ! atEnd() && index < amount )
  {
    quint8 byte = getByte( &gotByteOk );

    if( ! gotByteOk )
    {
      if( ok != 0 )
      {
        *ok = false;
      }

      return QByteArray();
    }

    bytes.append( byte );
    ++index;
  }

  // Done!
  if( ok != 0 )
  {
    *ok = true;
  }

  return bytes;
}



/**
 * Move a byte from the buffer into the 'current byte' bit array.
 *
 * @return bool
 */
bool DataSource::moveByteToBitArray()
{
  quint8 byte = 0;

  if( buffer_.size() == 0 )
  {
    qWarning() << "DataSource:moveByteToBitArray() - The buffer is empty!";
    currentByte_.clear();
    currentBitIndex_ = 8;
    return false;
  }

  if( buffer_.atEnd() )
  {
    qWarning() << "DataSource:moveByteToBitArray() - The buffer was completely parsed!";
    currentByte_.clear();
    currentBitIndex_ = 8;
    return false;
  }

  if( buffer_.getChar( (char*)&byte ) != 1 )
  {
    qWarning() << "DataSource::moveByteToBitArray() - Read failed at buffer position" << buffer_.pos();
    return false;
  }

  for( qint8 i = 0; i < 8; i++ )
  {
#if Q_BYTE_ORDER == Q_BIG_ENDIAN
    currentByte_.setBit( i, byte & ( 1 << i ) );
#else
    currentByte_.setBit( 7 - i, byte & ( 1 << i ) );
#endif
  }

  currentBitIndex_ = 0;

  return true;
}



/**
 * Get the current position within the internal data buffer.
 *
 * @return qint64
 */
qint64 DataSource::pos() const
{
  return buffer_.pos();
}



/**
 * Insert a byte at the beginning of the data.
 *
 * @param byte Byte to add
 */
void DataSource::prepend( char byte )
{
  buffer_.buffer().prepend( byte );

  bool wasAtBeginning = ( buffer_.pos() == 0 );

  seekRelative( 1 );

  if( wasAtBeginning )
  {
    moveByteToBitArray();
  }
}



/**
 * Insert bytes at the beginning of the data.
 *
 * @param bytes Bytes to add
 */
void DataSource::prepend( const QByteArray &bytes )
{
  buffer_.buffer().prepend( bytes );

  bool wasAtBeginning = ( buffer_.pos() == 0 );

  seekRelative( bytes.size() );

  if( wasAtBeginning )
  {
    moveByteToBitArray();
  }
}



//
/**
 * Return to the start of the buffer.
 */
void DataSource::reset()
{
  buffer_.reset();
  currentBitIndex_ = 8;
}



//
/**
 * Seek back and forth in the stream.
 *
 * A negative pos will move backwards within the stream.
 *
 * @param pos Relative position where to move
 */
void DataSource::seekRelative( int pos )
{
  if( pos < 0 && buffer_.pos() <= 0 )
  {
#ifdef ISFQT_DEBUG_VERBOSE
    qWarning() << "Cannot seek back!";
#endif
    return;
  }
  if( pos > 0 && ( buffer_.size() - buffer_.pos() ) < pos )
  {
#ifdef ISFQT_DEBUG_VERBOSE
    qWarning() << "Cannot seek forward!";
#endif
    return;
  }

  buffer_.seek( buffer_.pos() + pos );
}



/**
 * Replace the data byte array with another.
 *
 * @param data New array to copy and use as the data source
 */
void DataSource::setData( const QByteArray &data )
{
  buffer_.close();
  buffer_.setData( data );
  buffer_.open( QIODevice::ReadWrite );
  buffer_.seek( 0 );

  currentBitIndex_ = 8;
}



/**
 * Skip the rest of the current byte and read the next one.
 *
 * When using the data source in bit mode, this skips the
 * bits remaining in the current byte and prepares to read
 * the next byte.
 */
void DataSource::skipToNextByte()
{
  if( currentBitIndex_ == 0 )
  {
    return;
  }

  moveByteToBitArray();
}



/**
 * Skip the rest of the current byte and read the previous one.
 *
 * When using the data source in bit mode, this skips the
 * bits remaining in the current byte and prepares to read
 * the next byte.
 */
void DataSource::skipToPrevByte()
{
  currentBitIndex_ = 8;
}



//
/**
 * Get the size of the data.
 *
 * @return qint64
 */
qint64 DataSource::size() const
{
  return buffer_.size();
}


