//
// tardy - a tar post-processor
// Copyright (C) 2011 Peter Miller
//
// 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 3 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 General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//

#include <libtardy/ac/string.h>

#include <libtardy/endian.h>
#include <libtardy/file/output/gzip.h>


#ifndef Z_BUFSIZE
#ifdef MAXSEG_64K
#define Z_BUFSIZE 4096 // minimize memory usage for 16-bit DOS
#else
#define Z_BUFSIZE 16384
#endif
#endif

static unsigned char gzip_magic[2] = { 0x1F, 0x8B }; // gzip magic header


file_output_gzip::~file_output_gzip()
{
    //
    // finish sending the compressed stream
    //
    stream.avail_in = 0; // should be zero already anyway
    if (stream.avail_out == 0)
    {
            deeper->write(outbuf, Z_BUFSIZE);
            stream.next_out = outbuf;
            stream.avail_out = Z_BUFSIZE;
    }
    for (;;)
    {
        int err = deflate(&stream, Z_FINISH);
        if (err < 0)
            drop_dead(err);
        uInt len = Z_BUFSIZE - stream.avail_out;
        if (!len)
            break;
        deeper->write(outbuf, len);
        stream.next_out = outbuf;
        stream.avail_out = Z_BUFSIZE;
    }

    //
    // and the trailer
    //
    put4(crc);
    put4(stream.total_in);

    //
    // Clean up any resources we were using.
    //
    if (stream.state != NULL)
        deflateEnd(&stream);
    delete [] outbuf;
    outbuf = 0;
}


void
file_output_gzip::put4(unsigned long x)
{
    unsigned char buffer[4];
    endian_set4le(buffer, x);
    deeper->write(buffer, sizeof(buffer));
}



file_output_gzip::file_output_gzip(const file_output::pointer &a_deeper) :
    deeper(a_deeper),
    outbuf(new Byte [Z_BUFSIZE]),
    crc(0),
    pos(0)
{
    crc = crc32(0L, Z_NULL, 0);
    stream.avail_in = 0;
    stream.avail_out = 0;
    stream.next_in = NULL;
    stream.next_out = NULL;
    stream.opaque = (voidpf)0;
    stream.zalloc = (alloc_func)0;
    stream.zfree = (free_func)0;

    //
    // Set the parameters for the compression.
    // Note: windowBits is passed < 0 to suppress zlib header.
    //
    int err =
        deflateInit2
        (
            &stream,
            Z_BEST_COMPRESSION, // level
            Z_DEFLATED,         // method
            -MAX_WBITS,         // windowBits
            DEF_MEM_LEVEL,      // memLevel
            Z_DEFAULT_STRATEGY  // strategy
        );
    if (err != Z_OK)
        drop_dead(err);

    stream.next_out = outbuf;
    stream.avail_out = Z_BUFSIZE;

    //
    // Write a very simple .gz header:
    //
    unsigned char header[10];
    memcpy(header, gzip_magic, sizeof(gzip_magic));
    header[2] = Z_DEFLATED;
    header[3] = 0; // flags
    endian_set4le(header + 4, 0); // time
    header[8] = 0; // xflags
    header[9] = 3; // always use unix OS_CODE
    deeper->write(header, sizeof(header));
}


file_output::pointer
file_output_gzip::create(const file_output::pointer &a_deeper)
{
    return pointer(new file_output_gzip(a_deeper));
}


void
file_output_gzip::drop_dead(int err)
{
    deeper->fatal("gzip: %s", z_error(err));
}


rcstring
file_output_gzip::filename(void)
    const
{
    rcstring fn = deeper->filename();
    rcstring fn_lc = fn.downcase();
    if (fn_lc.ends_with(".z"))
        return fn.substring(0, fn.size() - 2);
    if (fn_lc.ends_with(".gz"))
        return fn.substring(0, fn.size() - 3);
    if (fn_lc.ends_with(".tgz"))
        return fn.substring(0, fn.size() - 4) + ".tar";
    return fn;
}


void
file_output_gzip::write(const void *data, int data_size)
{
    stream.next_in = (Bytef *)data;
    stream.avail_in = data_size;
    while (stream.avail_in != 0)
    {
        if (stream.avail_out == 0)
        {
            deeper->write(outbuf, Z_BUFSIZE);
            stream.next_out = outbuf;
            stream.avail_out = Z_BUFSIZE;
        }
        int err = deflate(&stream, Z_NO_FLUSH);
        if (err != Z_OK)
            drop_dead(err);
    }
    crc = crc32(crc, (Bytef *)data, data_size);
    pos += data_size;
}
