/*
 * Worldvisions Weaver Software:
 *   Copyright (C) 1997-2002 Net Integration Technologies, Inc.
 * 
 * Gzip encoder/decoder based on zlib.
 */
#include "wvgzip.h"
#include <zlib.h>
#include <assert.h>

#define ZBUFSIZE 10240


WvGzipEncoder::WvGzipEncoder(Mode _mode, size_t _out_limit) :
    out_limit(_out_limit), tmpbuf(ZBUFSIZE), mode(_mode)
{
    ignore_decompression_errors = false;
    full_flush = false;
    init();
}


WvGzipEncoder::~WvGzipEncoder()
{
    close();
}


void WvGzipEncoder::init()
{
    zstr = new z_stream;
    memset(zstr, 0, sizeof(*zstr));
    zstr->zalloc = Z_NULL;
    zstr->zfree = Z_NULL;
    zstr->opaque = NULL;
    zstr->msg = NULL;
    
    int retval;
    if (mode == Deflate)
	retval = deflateInit(zstr, Z_BEST_SPEED);
    else
	retval = inflateInit(zstr);
    
    if (retval != Z_OK)
    {
        seterror("error %s initializing gzip %s: %s", retval,
            mode == Deflate ? "compressor" : "decompressor",
            zstr->msg ? zstr->msg : "unknown");
        return;
    }
    zstr->next_in = zstr->next_out = NULL;
    zstr->avail_in = zstr->avail_out = 0;
}

void WvGzipEncoder::close()
{
    if (mode == Deflate)
        deflateEnd(zstr);
    else
        inflateEnd(zstr);

    delete zstr;

}

bool WvGzipEncoder::_encode(WvBuf &inbuf, WvBuf &outbuf, bool flush)
{
    bool success;
    output = 0;
    for (;;)
    {
        size_t starting_size = inbuf.used();
        prepare(& inbuf);
        bool alldata = inbuf.used() == 0;
        success = process(outbuf, flush && alldata, false);
        if (zstr->avail_in != 0)
        {
            // unget unused data
            inbuf.unget(zstr->avail_in);
            zstr->avail_in = 0;
        }
        if (! success)
            return false;
        if (alldata || (starting_size == inbuf.used()) ||
            (out_limit && (output >= out_limit)))
            return true;
    }
}


bool WvGzipEncoder::_finish(WvBuf &outbuf)
{
    prepare(NULL);
    return process(outbuf, false, true);
}


bool WvGzipEncoder::_reset()
{
    close();
    init();
    return true;
}


void WvGzipEncoder::prepare(WvBuf *inbuf)
{
    assert(zstr->avail_in == 0);
    if (inbuf && inbuf->used() != 0)
    {
        size_t avail = inbuf->optgettable();
        zstr->avail_in = avail;
        zstr->next_in = const_cast<Bytef*>(
            (const Bytef*)inbuf->get(avail));
    }
    else
    {
        zstr->avail_in = 0;
        zstr->next_in = (Bytef*)""; // so it's not NULL
    }
}


bool WvGzipEncoder::process(WvBuf &outbuf, bool flush, bool finish)
{
    int flushmode = finish ? Z_FINISH :
        flush ? (full_flush ? Z_FULL_FLUSH : Z_SYNC_FLUSH) : Z_NO_FLUSH;
    int retval;
    do
    {
        // process the next chunk
        tmpbuf.zap();
        size_t avail_out = tmpbuf.free();
        if (out_limit)
            avail_out = tmpbuf.free() < (out_limit - output) ? tmpbuf.free()
                : (out_limit - output);

        zstr->avail_out = avail_out;
	zstr->next_out = tmpbuf.alloc(avail_out);
	if (mode == Deflate)
	    retval = deflate(zstr, flushmode);
	else
	    retval = inflate(zstr, flushmode);
	tmpbuf.unalloc(zstr->avail_out);

        output += avail_out - zstr->avail_out;

        // consume pending output
        outbuf.merge(tmpbuf);

        if (retval == Z_DATA_ERROR && mode == Inflate
            && ignore_decompression_errors)
            retval = inflateSync(zstr);
    } while (retval == Z_OK && (!out_limit || (out_limit > output)));

    if (retval == Z_STREAM_END)
        setfinished();
    else if (retval != Z_OK && retval != Z_BUF_ERROR &&
             !(retval == Z_DATA_ERROR && mode == Inflate
               && ignore_decompression_errors))
    {
        seterror("error %s during gzip %s: %s", retval,
            mode == Deflate ? "compression" : "decompression",
            zstr->msg ? zstr->msg : "unknown");
        return false;
    }

    return true;
}

