
#include "zipios++/zipios-config.h"

#include "zipios++/meta-iostreams.h"

#include <zlib.h>

#include "zipios++/fcollexceptions.h"
#include "zipios++/deflateoutputstreambuf.h"

#include "outputstringstream.h"

namespace zipios {

using std::cerr ;
using std::endl ;

DeflateOutputStreambuf::DeflateOutputStreambuf( streambuf *outbuf, bool user_init, 
						bool del_outbuf ) 
  : FilterOutputStreambuf( outbuf, del_outbuf ),
    _zs_initialized ( false            ),
    _invecsize      ( 1000             ),
    _invec          ( _invecsize       ),
    _outvecsize     ( 1000             ),
    _outvec         ( _outvecsize      )
{
  // NOTICE: It is important that this constructor and the methods it
  // calls doesn't do anything with the output streambuf _outbuf The
  // reason is that this class can be subclassed, and the subclass
  // should get a chance to write to the buffer first

  // zlib init:
  _zs.zalloc = Z_NULL ;
  _zs.zfree  = Z_NULL ;
  _zs.opaque = Z_NULL ;

  if ( user_init && ! init() )
    cerr << "DeflateOutputStreambuf::reset() failed!\n" ; // FIXME: throw something

}


DeflateOutputStreambuf::~DeflateOutputStreambuf() {
  closeStream() ;
}


// This method is called in the constructor, so it must not write
// anything to the output streambuf _outbuf (see notice in
// constructor)
bool DeflateOutputStreambuf::init( int comp_level ) {
  static const int default_mem_level = 8 ;

  // _zs.next_in and avail_in must be set according to
  // zlib.h (inline doc).
  _zs.next_in  = reinterpret_cast< unsigned char * >( &( _invec[ 0 ] ) ) ;
  _zs.avail_in = 0 ;

  _zs.next_out  = reinterpret_cast< unsigned char * >( &( _outvec[ 0 ] ) ) ;
  _zs.avail_out = _outvecsize ;

  int err ;
  if( _zs_initialized ) {                    // just reset it
    endDeflation() ;
    err = deflateReset( &_zs ) ;
    // FIXME: bug, for deflateReset we do not update the compression level
  } else {                                   // init it
    err = deflateInit2( &_zs, comp_level, Z_DEFLATED, -MAX_WBITS, 
			default_mem_level, Z_DEFAULT_STRATEGY ) ;
    /* windowBits is passed < 0 to tell that no zlib header should be
       written. */
    _zs_initialized = true ;
  }

  // streambuf init:
  setp( &( _invec[ 0 ] ), &( _invec[ 0 ] ) + _invecsize ) ;

  _crc32 = crc32( 0, Z_NULL, 0 ) ;
  _overflown_bytes = 0 ;

  if ( err == Z_OK )
    return true ;
  else
    return false ;
}


bool DeflateOutputStreambuf::closeStream() {
  int err = Z_OK ;
  if( _zs_initialized ) {
    endDeflation() ;
    err = deflateEnd( &_zs ) ;
    _zs_initialized = false ;
  }
  
  if ( err == Z_OK )
    return true ;
  else {
    cerr << "DeflateOutputStreambuf::closeStream(): deflateEnd failed" ;
#ifdef HAVE_ZERROR
    cerr << ": " << zError( err ) ;
#endif
    cerr << endl ;
    return false ;
  }
}


int DeflateOutputStreambuf::overflow( int c ) {
  _zs.avail_in = static_cast< uInt >( pptr() - pbase() ) ;
  _zs.next_in = reinterpret_cast< unsigned char * >( &( _invec[ 0 ] ) ) ;

  _crc32 = crc32( _crc32, _zs.next_in, _zs.avail_in ) ; // update crc32
  _overflown_bytes += _zs.avail_in ;

  _zs.next_out  = reinterpret_cast< unsigned char * >( &( _outvec[ 0 ] ) ) ;
  _zs.avail_out = _outvecsize ;

  // Deflate until _invec is empty.
  int err = Z_OK ;
  while ( ( _zs.avail_in > 0 || _zs.avail_out == 0 ) && err == Z_OK ) {
    if ( _zs.avail_out == 0 )
      flushOutvec() ;

    err = deflate( &_zs, Z_NO_FLUSH ) ;
  }

  flushOutvec() ;
  
  // Update 'put' pointers
  setp( &( _invec[ 0 ] ), &( _invec[ 0 ] ) + _invecsize ) ;
  
  if( err != Z_OK && err != Z_STREAM_END ) {
#if defined (HAVE_STD_IOSTREAM) && defined (USE_STD_IOSTREAM)
    // Throw an exception to make istream set badbit
    OutputStringStream msgs ;
    msgs << "Deflation failed" ;
#ifdef HAVE_ZERROR
    msgs << ": " << zError( err ) ;
#endif
    throw IOException( msgs.str() ) ;
#endif
    cerr << "Deflation failed\n" ;
    return EOF ;
  }

  if ( c != EOF ) {
    *pptr() = c ;
    pbump( 1 ) ;
  }

  return 0 ;
}

int DeflateOutputStreambuf::sync() {
  // FIXME: Do something
//    return overflow() ;
  return 0 ;
}


bool DeflateOutputStreambuf::flushOutvec() {
  int deflated_bytes = _outvecsize - _zs.avail_out ;
  int bc = static_cast< int >( _outbuf->sputn( &( _outvec[ 0 ] ), deflated_bytes ) ) ;

  _zs.next_out = reinterpret_cast< unsigned char * >( &( _outvec[ 0 ] ) ) ;
  _zs.avail_out = _outvecsize ;

  return deflated_bytes == bc ;
}


void DeflateOutputStreambuf::endDeflation() {
  overflow() ;

  _zs.next_out  = reinterpret_cast< unsigned char * >( &( _outvec[ 0 ] ) ) ;
  _zs.avail_out = _outvecsize ;

  // Deflate until _invec is empty.
  int err = Z_OK ;

  while ( err == Z_OK ) {
    if ( _zs.avail_out == 0 )
      flushOutvec() ;

    err = deflate( &_zs, Z_FINISH ) ;
  }

  flushOutvec() ;

  if ( err != Z_STREAM_END ) {
    cerr << "DeflateOutputStreambuf::endDeflation(): deflation failed:\n" ;
#ifdef HAVE_ZERROR
    cerr << ": " << zError( err ) ;
#endif
    cerr << endl ;
  }
}


} // namespace

/** \file
    Implementation of DeflateOutputStreambuf.
*/

/*
  Zipios++ - a small C++ library that provides easy access to .zip files.
  Copyright (C) 2000  Thomas Søndergaard
  
  This library 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 of the License, or (at your option) any later version.
  
  This library 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
  Lesser General Public License for more details.
  
  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
*/
