//
// tardy - a tar post-processor
// Copyright (C) 2012 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/sys/resource.h>

#include <libtardy/file/input/bunzip2.h>


void
file_input_bunzip2::bzlib_fatal_error(int err)
{
    if (err >= 0)
        return;
    fatal("gunzip %s: %s", deeper->filename().c_str(), BZ2_strerror(err));
}


file_input_bunzip2::~file_input_bunzip2()
{
    //
    // Ask bzlib to free any stream resources it may be using.
    //
    int err = BZ2_bzDecompressEnd(&stream);
    if (err < 0)
        bzlib_fatal_error(err);

    //
    // Return unused input to the deeper stream.
    //
    if (stream.avail_in)
    {
        deeper->unread(stream.next_in, stream.avail_in);
        stream.next_in = 0;
        stream.avail_in = 0;
    }

    delete [] buf;
    buf = 0;
}


file_input_bunzip2::file_input_bunzip2(const file_input::pointer &a_deeper) :
    deeper(a_deeper),
    end_of_file(false),
    pos(0),
    buf(new char [BUFFER_SIZE]),
    deeper_at_end(false)
{
    stream.bzalloc = 0;
    stream.bzfree = 0;
    stream.opaque = 0;

    int verbosity = 0;

    //
    // If we are running with limited resources we use a slower
    // algorithm that uses less memory.
    //
    int small = 0;
#if defined(RLIMIT_AS) || defined(RLIM_INFINITY)
    struct rlimit memory_limit;
    getrlimit(RLIMIT_AS, &memory_limit);
    if (memory_limit.rlim_cur != RLIM_INFINITY)
        small = 1;
#endif
    int err = BZ2_bzDecompressInit(&stream, verbosity, small);
    if (err != BZ_OK)
        bzlib_fatal_error(err);
    stream.avail_in = 0;
    stream.next_in = 0;
}


file_input_bunzip2::pointer
file_input_bunzip2::create(const file_input::pointer &a_deeper)
{
    return pointer(new file_input_bunzip2(a_deeper));
}


size_t
file_input_bunzip2::read_inner(void *data, size_t data_size)
{
    if (end_of_file)
        return 0;

    if (!data)
    {
        bzlib_fatal_error(BZ_PARAM_ERROR);
        //NOTREACHED
    }
    if (data_size == 0)
        return 0;

    stream.avail_out = data_size;
    stream.next_out = (char *)data;

    for (;;)
    {
        if (stream.avail_in == 0)
        {
            long n = deeper->read(buf, BUFFER_SIZE);
            stream.avail_in = n;
            stream.next_in = buf;
            if (n == 0)
                deeper_at_end = true;
        }

        int err = BZ2_bzDecompress(&stream);
        switch (err)
        {
        case BZ_OK:
            if
            (
                deeper_at_end
            &&
                stream.avail_in == 0
            &&
                stream.avail_out > 0
            )
            {
                bzlib_fatal_error(BZ_UNEXPECTED_EOF);
                //NOTREACHED
            }
            break;

        case BZ_STREAM_END:
            {
                end_of_file = true;
                int nbytes = (data_size - stream.avail_out);
                pos += nbytes;
                return nbytes;
            }

        default:
            bzlib_fatal_error(err);
            //NOTREACHED
        }

        if (stream.avail_out == 0)
        {
            pos += data_size;
            return data_size;
        }
    }
}


// vim: set ts=8 sw=4 et :
