//
// tardy - a tar post-processor
// Copyright (C) 2002, 2008, 2009, 2011, 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/stddef.h>
#include <libtardy/ac/stdio.h>
#include <libtardy/ac/string.h>
#include <libtardy/ac/sys/types.h>
#include <libtardy/ac/sys/stat.h>
#include <libtardy/ac/unistd.h>

#include <libtardy/filenamelist/filter/progress.h>


filenamelist_filter_progress::~filenamelist_filter_progress()
{
}


filenamelist_filter_progress::filenamelist_filter_progress(
    const filenamelist::pointer &a_deeper
) :
    filenamelist_filter(a_deeper),
    state(state_begin),
    position(0),
    start_time(0),
    next_time(0),
    size_total(0),
    size_position(0),
    spinner_state(0),
    show(isatty(2))
{
}


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


static void
format_size_inner(char *buffer, size_t sizeof_buffer, double d, char scale)
{
    if (d < 10)
    {
        snprintf(buffer, sizeof_buffer, "%4.2f%c", d, scale);
        return;
    }
    if (d < 100)
    {
        snprintf(buffer, sizeof_buffer, "%4.1f%c", d, scale);
        return;
    }
    snprintf(buffer, sizeof(buffer), "%4.0f%c", d, scale);
}


static char *
format_size(long long n)
{
    static char buffer[10];
    if (n < 10000)
    {
        snprintf(buffer, sizeof(buffer), "%4d ", (int)n);
        return buffer;
    }
    double d = n / 1024.;
    if (d < 10000)
    {
        format_size_inner(buffer, sizeof(buffer), d, 'K');
        return buffer;
    }
    d /= 1024;
    if (d < 10000)
    {
        format_size_inner(buffer, sizeof(buffer), d, 'M');
        return buffer;
    }
    d /= 1024;
    if (d < 10000)
    {
        format_size_inner(buffer, sizeof(buffer), d, 'G');
        return buffer;
    }
    d /= 1024;
    if (d < 10000)
    {
        format_size_inner(buffer, sizeof(buffer), d, 'T');
        return buffer;
    }
    d /= 1024;
    if (d < 10000)
    {
        format_size_inner(buffer, sizeof(buffer), d, 'P');
        return buffer;
    }
    d /= 1024;
    format_size_inner(buffer, sizeof(buffer), d, 'E');
    return buffer;
}


char
filenamelist_filter_progress::spinner(void)
{
    return ("|/-\\"[spinner_state++ & 3]);
}


void
filenamelist_filter_progress::show_start_status(void)
{
    if (!show)
        return;
    time_t now;
    time(&now);
    if (now < next_time)
        return;
    next_time = now + 1;

    char buffer[80];
    strcpy(buffer, "\rReading file list: ");
    char *bp = buffer + strlen(buffer);
    snprintf(bp, buffer + sizeof(buffer) - bp, "%s files, ",
        format_size(items.size()));
    bp += strlen(bp);
    snprintf(bp, buffer + sizeof(buffer) - bp, "%sB ",
        format_size(size_total));
    bp += strlen(bp);
    snprintf(bp, buffer + sizeof(buffer) - bp, "%c ", spinner());
    if (::write(2, buffer, strlen(buffer)) < 0)
        show = 0;
}


static const char *
format_time(long x)
{
    static char buffer[20];
    if (x < 60 * 60)
    {
        snprintf(buffer, sizeof(buffer), "%2dm%02ds",
            (int)(x / 60), (int)(x % 60));
        return buffer;
    }
    x = (x + 30) / 60;
    if (x < 24 * 60)
    {
        snprintf(buffer, sizeof(buffer), "%2dh%02dm",
            (int)(x / 60), (int)(x % 60));
        return buffer;
    }
    x = (x + 30) / 60;
    if (x < 100 * 24)
    {
        snprintf(buffer, sizeof(buffer), "%2dd%02dh",
            (int)(x / 24), (int)(x % 24));
        return buffer;
    }
    return "forever";
}


void
filenamelist_filter_progress::show_middle_status(void)
{
    if (!show)
        return;
    time_t now;
    time(&now);
    if (now < next_time)
        return;
    next_time = now + 1;

    char buffer[80];
    char *bp = buffer;
    *bp++ = '\r';

    snprintf(bp, buffer + sizeof(buffer) - bp, "%s of ",
        format_size(position));
    bp += strlen(bp);
    snprintf(bp, buffer + sizeof(buffer) - bp, "%s files, ",
        format_size(items.size()));
    bp += strlen(bp);

    snprintf(bp, buffer + sizeof(buffer) - bp, "%sB of ",
        format_size(size_position));
    bp += strlen(bp);
    snprintf(bp, buffer + sizeof(buffer) - bp, "%sB, ",
        format_size(size_total));
    bp += strlen(bp);

    time_t elapsed = now - start_time;
    if (elapsed > 5 && size_total)
    {
        long long rate = size_position / elapsed;
        snprintf(bp, buffer + sizeof(buffer) - bp, "%sB/sec, ",
        format_size(rate));
        bp += strlen(bp);

        double frac = (double)size_position / (double)size_total;
        time_t total_time = time_t(elapsed / frac);
        time_t remaining_time = total_time - elapsed;
        snprintf(bp, buffer + sizeof(buffer) - bp, "ETA %s ",
        format_time(remaining_time));
        bp += strlen(bp);
    }

    snprintf(bp, buffer + sizeof(buffer) - bp, "%c ", spinner());
    if (::write(2, buffer, strlen(buffer)) < 0)
        show = 0;
}


void
filenamelist_filter_progress::show_end_status(void)
{
    if (!show)
        return;
    time_t now;
    time(&now);

    char buffer[80];
    char *bp = buffer;
    *bp++ = '\r';

    snprintf(bp, buffer + sizeof(buffer) - bp, "%s files, ",
        format_size(items.size()));
    bp += strlen(bp);

    snprintf(bp, buffer + sizeof(buffer) - bp, "%sB, ",
        format_size(size_total));
    bp += strlen(bp);

    time_t elapsed = now - start_time;
    if (elapsed < 1)
        elapsed = 1;
    long long rate = size_position / elapsed;
    snprintf(bp, buffer + sizeof(buffer) - bp, "%sB/sec, ",
        format_size(rate));
    bp += strlen(bp);

    snprintf(bp, buffer + sizeof(buffer) - bp, "%s     ",
        format_time(elapsed));
    bp += strlen(bp);
    while (bp < buffer + sizeof(buffer) - 1)
        *bp++ = ' ';
    *bp++ = '\n';

    if (::write(2, buffer, sizeof(buffer)) < 0)
        show = 0;
}


bool
filenamelist_filter_progress::read_one_line(rcstring &result)
{
    switch (state)
    {
    case state_begin:
        {
            rcstring temp;
            while (read_one_deeper(temp))
            {
                show_start_status();
                struct stat st;
                if (lstat(temp.c_str(), &st))
                    memset(&st, 0, sizeof(st));
                off_t size = S_ISREG(st.st_mode) ? st.st_size : 0;
                // round up
                size = ((size + 511) & ~511) + 512;
                items.push_back(item_t(temp, size));
                size_total += size;
            }
            state = state_middle;
            time(&start_time);
        }
        // fall through...

    case state_middle:
        if (position < items.size())
        {
            show_middle_status();
            item_t item = items[position++];
            result = item.name;
            size_position += item.size;
            return true;
        }
        show_end_status();
        state = state_end;
        // fall through...

    case state_end:
        break;
    }
    return false;
}


filenamelist_filter_progress::item_t::~item_t()
{
}


filenamelist_filter_progress::item_t::item_t(
    const rcstring &a_name,
    off_t a_size
) :
    name(a_name),
    size(a_size)
{
}


filenamelist_filter_progress::item_t::item_t(const item_t &rhs) :
    name(rhs.name),
    size(rhs.size)
{
}


filenamelist_filter_progress::item_t &
filenamelist_filter_progress::item_t::operator=(const item_t &rhs)
{
    if (this != &rhs)
    {
        name = rhs.name;
        size = rhs.size;
    }
    return *this;
}


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