//
// tardy - a tar post-processor
// Copyright (C) 1998, 1999, 2001, 2002, 2004, 2008, 2009, 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/errno.h>
#include <libtardy/ac/stdarg.h>
#include <libtardy/ac/stdio.h>
#include <libtardy/ac/string.h>
#include <libexplain/output.h>

#include <libtardy/file/input.h>


file_input::~file_input()
{
    assert(valid());
    delete [] read_ahead_buffer;
}


file_input::file_input() :
    read_ahead_buffer(0),
    read_ahead_size(0),
    read_ahead_pos(0)
{
    assert(valid());
}


void
file_input::fatal(const char *fmt, ...)
    const
{
    va_list ap;
    va_start(ap, fmt);
    char buffer[2000];
    vsnprintf(buffer, sizeof(buffer), fmt, ap);
    va_end(ap);
    explain_output_error_and_die("%s: %s", filename().c_str(), buffer);
}


bool
file_input::valid(void)
    const
{
    if (!read_ahead_buffer)
    {
        return (read_ahead_pos == 0 && read_ahead_size == 0);
    }
    else
    {
        return (read_ahead_size > 0 && read_ahead_pos <= read_ahead_size);
    }
}


int
file_input::read(void *data, size_t data_size)
{
    assert(valid());
    int return_value = 0;
    if (!read_ahead_empty())
    {
        size_t nbytes = read_ahead_size - read_ahead_pos;
        if (nbytes > data_size)
            nbytes = data_size;
        memcpy(data, read_ahead_buffer + read_ahead_pos, nbytes);
        read_ahead_pos += nbytes;
        return_value += nbytes;
        data = (void *)((char *)data + nbytes);
        data_size -= nbytes;
        assert(valid());
    }
    if (data_size > 0)
        return_value += read_inner(data, data_size);
    assert(valid());
    return return_value;
}


void
file_input::unread(const void *data, size_t data_size)
{
    assert(valid());
    // This is tricky: data is unread *backwards* so we have to put
    // the data at the *front* of the buffer.
    if (data_size > read_ahead_pos)
    {
        size_t new_size = (read_ahead_size ? read_ahead_size * 2 : 1024);
        while (new_size < data_size)
            new_size *= 2;
        char *new_buffer = new char [new_size];
        size_t sz = read_ahead_size - read_ahead_pos;
        assert(sz <= new_size);
        size_t new_pos = new_size - sz;
        memcpy(new_buffer + new_pos, read_ahead_buffer + read_ahead_pos, sz);
        delete [] read_ahead_buffer;
        read_ahead_buffer = new_buffer;
        read_ahead_size = new_size;
        read_ahead_pos = new_pos;
        assert(valid());
    }
    read_ahead_pos -= data_size;
    memcpy(read_ahead_buffer + read_ahead_pos, data, data_size);
    assert(valid());
}


int
file_input::peek(void *data, size_t data_size)
{
    int nbytes = read(data, data_size);
    unread(data, nbytes);
    return nbytes;
}


void
file_input::skip(size_t nbytes)
{
    size_t tot = 0;
    while (nbytes > 0)
    {
        char dummy[512];
        size_t n = nbytes;
        if (n > sizeof(dummy))
            n = sizeof(dummy);
        size_t n2 = read(dummy, n);
        if (n != n2)
        {
            fatal
            (
                "short padding read (requested %ld, got %ld)",
                tot + n,
                tot + n2
            );
        }
        nbytes -= n;
        tot += n;
    }
}


unsigned long
file_input::get_position(void)
    const
{
    unsigned read_ahead_available = read_ahead_size - read_ahead_pos;
    unsigned long pos = get_position_inner();
    assert(pos >= read_ahead_available);
    return (pos - read_ahead_available);
}
