// Description:
//   A compressed (file) input/output stream.
//
// Copyright (C) 2001 Frank Becker
//
// This program is free software; you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free Software
// Foundation;  either version 2 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
//
#include <Trace.hpp>
#include <Endian.hpp>
#include <zStream.hpp>
#include <stdlib.h>

ofstream &operator<<( ofstream &outfile, Uint32 i)
{       
    char *pi = (char*)&i;
        
    if( ! ::isLittleEndian()) swap( pi, 4);
    
    outfile.write( pi, 4);
        
    return outfile;
}       

ifstream &operator>>( ifstream &infile, Uint32 &i)
{           
    char *pi = (char*)&i;
        
    infile.read( pi, 4);
    
    if( ! ::isLittleEndian()) swap( pi, 4);
        
    return infile;
}       

//-----------------------------------------------------------------------------

ziStreamBuffer::ziStreamBuffer( ifstream &fd):
    _fstream( fd),
    _eof(false),
    _compressed(true),
    _isOK(false)
{
    XTRACE();
    _init();
}

ziStreamBuffer::~ziStreamBuffer()
{
    XTRACE();
}

streamsize ziStreamBuffer::xsgetn(char* s, streamsize n)
{
    XTRACE();

    if( !_compressed)
    {
	if( _fstream.eof()) return EOF;
	_fstream.read( s, n);
	return  _fstream.gcount();
    }

    if( _eof) return EOF;

    _stream.next_out = (Byte*)s;
    _stream.avail_out = (uInt)n;

    do
    {
	if( _stream.avail_in == 0)
	{
	    _fstream.read( (char*)_inBuf, ZSTREAM_BUFFSIZE);
	    _stream.next_in = _inBuf;
	    _stream.avail_in = (uInt)_fstream.gcount();
	}
	int err = inflate(&_stream, Z_NO_FLUSH);
	if( err == Z_STREAM_END) _eof = true;
        else if( err != Z_OK)
        {
	    LOG_ERROR << "ziStreamBuffer::xsgetn error during inflate: " 
                      << err << endl;
            exit(-1);
        }
    }
    while( (_stream.avail_out > 0) && !_eof);

    return n-_stream.avail_out;
}

void ziStreamBuffer::_init( void)
{
    XTRACE();
    if( !_fstream.is_open())
    {
	LOG_ERROR << "Input stream not open!" << endl;
    }

//    xsetflags( ios::binary);

    Uint32 magic;
    _fstream >> magic;

    if( magic != ZSTREAM_MAGIC)
    {
//	LOG_WARNING << "Treating as uncompressed file..." << endl;
	_compressed = false;
	_fstream.seekg( -4, ios::cur);
    }

    _stream.zalloc = (alloc_func)0;
    _stream.zfree = (free_func)0;
    _stream.opaque = (voidpf)0;

    _stream.next_in = _inBuf;
    _stream.avail_in = 0;

    if( inflateInit( &_stream) != Z_OK)
    {
	LOG_ERROR << "Unable to initialize ziStreamBuffer" << endl;
    }
    _isOK = true;
}

//-----------------------------------------------------------------------------

zoStreamBuffer::zoStreamBuffer( ofstream &fd):
    _fstream( fd),
    _isOK(false)
{
    XTRACE();
    _init();
}

zoStreamBuffer::~zoStreamBuffer()
{
    XTRACE();

    int zerr;
    do
    {
	zerr = deflate(&_stream, Z_FINISH);
	_flush();
    }
    while( zerr == Z_OK);

//      LOG_DEBUG << _stream.total_in << " IN bytes." << endl;
//      LOG_DEBUG << _stream.total_out << " OUT bytes." << endl;

    if( deflateEnd( &_stream) != Z_OK)
    {
	LOG_ERROR << "Problem destroying zoStreamBuffer." << endl;
    }
}

streamsize zoStreamBuffer::xsputn(const char* s, streamsize n)
{
//    XTRACE();

    _stream.next_in = (Byte*)s;
    _stream.avail_in = (uInt)n;

    while( _stream.avail_in != 0)
    {
	if( deflate(&_stream, Z_NO_FLUSH) != Z_OK)
	{
	    LOG_ERROR << "Compression error!" << endl;
	    break;
	}
	if( _stream.avail_out == 0)
	{
	    _flush();
	}
    }

    return n-_stream.avail_in;
}

void zoStreamBuffer::_flush( void)
{
    XTRACE();
    int len = ZSTREAM_BUFFSIZE - _stream.avail_out;
    if( len)
    {
	_fstream.write( (char*)_outBuf, len);
	_stream.next_out = _outBuf;
	_stream.avail_out = ZSTREAM_BUFFSIZE;
    }
}

void zoStreamBuffer::_init( void)
{
    XTRACE();
    if( !_fstream.is_open())
    {
	LOG_ERROR << "Output stream not open!" << endl;
    }
    
//    xsetflags( ios::binary);

    _fstream << (Uint32) ZSTREAM_MAGIC;

    _stream.zalloc = (alloc_func)0;
    _stream.zfree = (free_func)0;
    _stream.opaque = (voidpf)0;

    _stream.next_out = _outBuf;
    _stream.avail_out = ZSTREAM_BUFFSIZE;

    if( deflateInit( &_stream, Z_BEST_COMPRESSION) != Z_OK)
    {
	LOG_ERROR << "Unable to initialize zoStreamBuffer" << endl;
    }
    _isOK = true;
}

//-----------------------------------------------------------------------------

//Note: This rather stupid looking *new + ref for _streambuf is to avoid 
//gcc asking for -fhuge-objects when cross compiling to windows.

ziStream::ziStream( ifstream &inf):
    super(0),
    _streambuf( *new ziStreamBuffer( inf))
{
    XTRACE();
    init( &_streambuf);
}

ziStream::~ziStream()
{
    XTRACE();
    delete &_streambuf;
}

//-----------------------------------------------------------------------------

zoStream::zoStream( ofstream &outf):
    super(0),
    _streambuf( *new zoStreamBuffer( outf))
{
    XTRACE();
    init( &_streambuf);
}

zoStream::~zoStream()
{
    XTRACE();
    delete &_streambuf;
}

//-----------------------------------------------------------------------------
