//
// 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/rcstring/accumulator.h>
#include <libtardy/rcstring/list.h>
#include <libtardy/tar/input/filter/ar_long_names.h>


tar_input_filter_ar_long_names::~tar_input_filter_ar_long_names()
{
}


tar_input_filter_ar_long_names::tar_input_filter_ar_long_names(
    const pointer &a_deeper
) :
    tar_input_filter(a_deeper),
    first(true)
{
}


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


bool
tar_input_filter_ar_long_names::read_header(tar_header &hdr)
{
    if (!tar_input_filter::read_header(hdr))
        return false;
    if (first)
    {
        // Only do this once.
        first = false;

        // The first file contains the name mapping, if one is present.
        if (hdr.name != "/" && hdr.name != "//")
        {
            // No name mapping in this archive.
            return true;
        }

        // Read all the names into the name mapping.
        read_and_process_name_map(hdr.size);

        // Now read the next file header
        if (!tar_input_filter::read_header(hdr))
            return false;
    }

    // If the name can be mapped, do it.
    names_t::iterator it = names.find(hdr.name);
    if (it != names.end())
        hdr.name = it->second;
    return true;
}


void
tar_input_filter_ar_long_names::read_and_process_name_map(size_t size)
{
    tar_input_filter::read_header_padding();

    //
    // Read all the data into a buffer.
    //
    // This method could probably be more frugal in the amount of memory
    // that it uses (split the lines out of the inner buffer).  We'll do
    // this if it proves to be a problem.
    //
    rcstring_accumulator data;
    while (size > 0)
    {
        char buffer[512];
        size_t want = sizeof(buffer);
        if (want > size)
            want = size;
        size_t nbytes = tar_input_filter::read_data(buffer, want);
        if (nbytes != want)
            fatal("short read (expected %lu, got %lu)", want, nbytes);
        data.push_back(buffer, nbytes);
        size -= nbytes;
    }
    tar_input_filter::read_data_padding();

    //
    // Split the buffer into strings.
    //
    const char *begin = data.get_data();
    const char *cp = begin;
    const char *end = cp + data.size();
    while (cp < end)
    {
        const char *p = (const char *)memmem(cp, end - cp, "/\n", 2);
        if (!p)
            break;
        rcstring key = rcstring::printf("/%lu", (unsigned long)(cp - begin));
        rcstring value(cp, p - cp);

        names.insert(names_t::value_type(key, value));
        key = " " + key;
        names.insert(names_t::value_type(key, value));

        cp = p + 2;
    }
}


size_t
tar_input_filter_ar_long_names::get_maximum_name_length(void)
    const
{
    // Override the filter, it returns whatever the deeper stream returns.
    // This filter allows long names to be effectively unlimited.
    return 0;
}
