//
// 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/file/input/xz.h>


file_input_xz::~file_input_xz()
{
    delete [] buf;
    lzma_end(&strm);
}


static lzma_stream strm_zero = LZMA_STREAM_INIT;

file_input_xz::file_input_xz(const file_input::pointer &a_deeper) :
    deeper(a_deeper),
    strm(strm_zero),
    end_of_file(false),
    pos(0),
    buf(new char [BUFFER_SIZE]),
    deeper_at_end(false)
{
    uint64_t memlimit = UINT64_MAX;
    uint32_t flags = 0;
    lzma_ret err = lzma_auto_decoder(&strm, memlimit, flags);
    if (err != LZMA_OK)
        drop_dead("lzma_auto_decoder", err);
}


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


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

    strm.avail_out = data_size;
    strm.next_out = (uint8_t *)data;

    for (;;)
    {
        if (strm.avail_in == 0)
        {
            size_t n = deeper->read(buf, BUFFER_SIZE);
            strm.avail_in = n;
            strm.next_in = (uint8_t *)buf;
            if (n == 0)
                deeper_at_end = true;
        }

        lzma_action action = deeper_at_end ? LZMA_FINISH : LZMA_RUN;
        lzma_ret err = lzma_code(&strm, action);
        switch (err)
        {
        case LZMA_OK:
            if
            (
                deeper_at_end
            &&
                strm.avail_in == 0
            &&
                strm.avail_out > 0
            )
            {
                drop_dead("lzma_code", LZMA_BUF_ERROR);
                //NOTREACHED
            }
            break;

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

        default:
            drop_dead("lzma-code", err);
            //NOTREACHED
        }

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


void
file_input_xz::drop_dead(const char *caption, lzma_ret err)
{
    fatal("%s: %s", caption, lzma_strerror(err));
}


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