/*
 * counter.c - transcode progress counter routines
 * Written by Andrew Church <achurch@achurch.org>
 *
 * This file is part of transcode, a video stream processing tool.
 * transcode is free software, distributable under the terms of the GNU
 * General Public License (version 2 or later).  See the file COPYING
 * for details.
 */

#include "transcode.h"
#include "counter.h"
#include "frame_threads.h"
#include <math.h>

/*************************************************************************/

static int counter_active = 0;    /* Is the counter active? */
static int frames_to_encode = 0;  /* Total number of frames to encode */
static int encoded_frames = 0;    /* Number of frames encoded so far */
static double encoded_time = 0;   /* Time spent encoding so far */
static int frames_to_skip = 0;    /* Total number of frames to skip */
static int skipped_frames = 0;    /* Number of frames skipped so far */
static double skipped_time = 0;   /* Time spent skipping so far */
static int highest_frame = 0;     /* Highest frame number to be seen */

static int printed = 0;           /* Have we printed a line? */

static void print_counter_line(int encoding, int frame, int first, int last,
                               double fps, double done, double timestamp,
                               int secleft, int decodebuf[2], int filterbuf[2],
                               int encodebuf[2]);

/*************************************************************************/
/*************************************************************************/

/**
 * counter_on:  Activate the counter display.
 *
 * Parameters:
 *     None.
 * Return value:
 *     None.
 */

void counter_on(void)
{
    counter_active = 1;
}

/*************************************************************************/

/**
 * counter_off:  Deactivate the counter display.
 *
 * Parameters:
 *     None.
 * Return value:
 *     None.
 * Side effects:
 *     When in human-readable mode (tc_progress_meter == 1), if the counter
 *     has been displayed at least once, a newline is written to standard
 *     output.
 */

void counter_off(void)
{
    if (printed) {
        if (tc_progress_meter == 1)
            fprintf(stderr, "\n");
        printed = 0;
    }
    counter_active = 0;
}

/*************************************************************************/

/**
 * counter_add_range:  Add the given range of frames to the total number of
 * frames to be encoded or skipped.
 *
 * Parameters:
 *      first: First frame of range.
 *       last: Last frame of range.
 *     encode: True (nonzero) if frames are to be encoded.
 *             False (zero) if frames are being skipped.
 * Return value:
 *     None.
 */

void counter_add_range(int first, int last, int encode)
{
    if (encode) {
        frames_to_encode += last+1 - first;
    } else {
        frames_to_skip += last+1 - first;
    }
    if (last > highest_frame)
        highest_frame = last;
}

/*************************************************************************/

/**
 * counter_reset_ranges:  Reset the counter's stored range data.
 *
 * Parameters:
 *     None.
 * Return value:
 *     None.
 */

void counter_reset_ranges(void)
{
    frames_to_encode = 0;
    encoded_frames = 0;
    encoded_time = 0;
    frames_to_skip = 0;
    skipped_frames = 0;
    skipped_time = 0;
    highest_frame = 0;
}

/*************************************************************************/

/**
 * counter_print:  Display the progress counter, if active.
 *
 * Parameters:
 *     encoding: True (nonzero) if frames are being encoded.
 *               False (zero) if frames are being skipped.
 *        frame: Current frame being encoded or skipped.
 *        first: First frame of current range.
 *         last: Last frame of current range, -1 if unknown.
 * Return value:
 *     None.
 */

void counter_print(int encoding, int frame, int first, int last)
{
    vob_t *vob = tc_get_vob();
    struct timeval tv;
    struct timezone dummy_tz = {0,0};
    double now, timediff, fps, time;
    int buf_im[2], buf_fl[2], buf_ex[2];
    /* Values of 'first' and `last' during last call (-1 = not called yet) */
    static int old_first = -1, old_last = -1;
    /* Time of first call for this range */
    static double start_time = 0;
    /* Time of last call */
    static double old_time = 0;

    if (!tc_progress_meter
     || !tc_progress_rate
     || !counter_active
     || frame % tc_progress_rate != 0
    ) {
        return;
    }
    if (frame < 0 || first < 0) {
        static int warned = 0;
        if (!warned) {
            tc_log_warn(__FILE__, "invalid arguments to counter_print"
                        " (%d,%d,%d,%d)", encoding, frame, first, last);
            warned = 1;
        }
        return;
    }

#ifdef HAVE_GETTIMEOFDAY
    if (gettimeofday(&tv, &dummy_tz) != 0) {
        static int warned = 0;
        if (!warned) {
            tc_log_warn(__FILE__, "gettimeofday() failed!");
            warned = 1;
        }
        return;
    }
    now = tv.tv_sec + (double)tv.tv_usec/1000000.0;
#else
    now = time(NULL);
#endif

    timediff = now - old_time;
    old_time = now;
    if (old_first != first || old_last != last) {
        /* In human-readable mode, start a new counter line for each range
         * if we don't know the total number of frames to be encoded. */
        if (tc_progress_meter == 1 && old_first != -1 && frames_to_encode == 0)
            fprintf(stderr, "\n");
        start_time = now;
        old_first = first;
        old_last = last;
        /* We decrement the frame counts here to compensate for this frame
         * which took an unknown amount of time to complete. */
        if (encoding && frames_to_encode > 0)
            frames_to_encode--;
        else if (!encoding && frames_to_skip > 0)
            frames_to_skip--;
        return;
    }

    /* Note that we don't add 1 to the numerator here, since start_time is
     * the time we were called for the first frame, so frame first+1 is one
     * one frame later than start_time, not two. */
    if (now > start_time) {
        fps = (frame - first) / (now - start_time);
    } else {
        /* No time has passed (maybe we don't have gettimeofday()) */
        fps = 0;
    }

    vframe_get_counters(&buf_im[0], &buf_fl[0], &buf_ex[0]);
    aframe_get_counters(&buf_im[1], &buf_fl[1], &buf_ex[1]);

    time = (double)frame / ((vob->ex_fps<1.0) ? 1.0 : vob->ex_fps);

    if (last == -1) {
        /* Can't calculate ETA, just display current timestamp */
        print_counter_line(encoding, frame, first, -1, fps, -1, time, -1,
                           buf_im, buf_fl, buf_ex);

    } else if (frames_to_encode == 0) {
        /* Total number of frames unknown, just display for current range */
        double done = (double)(frame - first + 1) / (double)(last+1 - first);
        int secleft = fps>0 ? ((last+1)-frame) / fps : -1;
        print_counter_line(encoding, frame, first, last, fps, done, time,
                           secleft, buf_im, buf_fl, buf_ex);

    } else {
        /* Estimate time remaining for entire run */
        double done;
        int secleft;
        if (encoding) {
            encoded_frames++;
            encoded_time += timediff;
        } else {
            skipped_frames++;
            skipped_time += timediff;
        }
        if (encoded_frames > frames_to_encode)
            frames_to_encode = encoded_frames;
        if (skipped_frames > frames_to_skip)
            frames_to_skip = skipped_frames;
        if (encoded_frames == 0) {
            /* We don't know how long it will take to encode frames; avoid
             * understating the ETA, and just say we don't know */
            secleft = -1;
        } else {
            double encode_fps, skip_fps, total_time;
            /* Find the processing speed for encoding and skipping */
            encode_fps = encoded_time ? encoded_frames / encoded_time : 0;
            if (skipped_frames > 0 && skipped_time > 0) {
                skip_fps = skipped_frames / skipped_time;
            } else {
                /* Just assume the same FPS for skipping as for encoding.
                 * Overstating the ETA isn't as bad as understating it, and
                 * certainly better than giving the user "unknown" for an
                 * entire encoding range. */
                skip_fps = encode_fps;
            }
            if (encode_fps > 0) {
                /* Estimate the total processing time required */
                total_time = (frames_to_encode / encode_fps)
                           + (frames_to_skip / skip_fps);
                /* Determine time left (round up) */
                secleft = ceil(total_time - (encoded_time + skipped_time));
            } else {
                total_time = -1;
                secleft = -1;
            }
            /* Use the proper overall FPS in the status line */
            fps = encoding ? encode_fps : skip_fps;
        }
        /* Just use the frame ratio for completion percentage */
        done = (double)(encoded_frames + skipped_frames)
             / (double)(frames_to_encode + frames_to_skip);
        print_counter_line(encoding, frame, 0, highest_frame, fps, done,
                           time, secleft, buf_im, buf_fl, buf_ex);
    }

    fflush(stdout);
}

/*************************************************************************/

/**
 * print_counter_line:  Helper function to format display arguments into a
 * progress counter line depending on settings.
 *
 * Parameters:
 *      encoding: True (nonzero) if frames are being encoded.
 *                False (zero) if frames are being skipped.
 *         frame: Current frame being encoded or skipped.
 *         first: First frame of current range.
 *          last: Last frame of current range, -1 if unknown.
 *           fps: Estimated frames processed per second.
 *          done: Completion ratio (0..1), -1 if unknown.
 *     timestamp: Timestamp of current frame, in seconds.
 *       secleft: Estimated time remaining to completion, in seconds (-1 if
 *                unknown).
 *     decodebuf: Number of buffered frames awaiting decoding [V, A].
 *     filterbuf: Number of buffered frames awaiting filtering [V, A].
 *     encodebuf: Number of buffered frames awaiting encoding [V, A].
 * Return value:
 *     None.
 */

static void print_counter_line(int encoding, int frame, int first, int last,
                               double fps, double done, double timestamp,
                               int secleft,
                               int decodebuf[2], int filterbuf[2], int encodebuf[2])
{
    if (tc_progress_meter == 2) {
        /* Raw data format */
        printf("encoding=%d frame=%d first=%d last=%d fps=%.3f done=%.6f"
               " timestamp=%.3f timeleft=%d decodebuf=%d filterbuf=%d"
               " encodebuf=%d\n",
               encoding, frame, first, last, fps, done,
               timestamp, secleft, decodebuf[0] + decodebuf[1],
               filterbuf[0] + filterbuf[1], encodebuf[0] + encodebuf[1]);
    } else if (last < 0 || done < 0 || secleft < 0) {
        int timeint = floor(timestamp);
        fprintf(stderr, "%s frames [%d-%d], %6.2f fps, CFT: %d:%02d:%02d,"
                        "  (%2d,%2d|%2d,%2d|%2d,%2d) \r",
                encoding ? "encoding" : "skipping",
                first, frame,
                fps,
                timeint/3600, (timeint/60) % 60, timeint % 60,
                decodebuf[0], decodebuf[1], filterbuf[0], filterbuf[1],
                encodebuf[0], encodebuf[1]
        );
    } else {
        char eta_buf[100];
        if (secleft < 0) {
            snprintf(eta_buf, sizeof(eta_buf), "--:--:--");
        } else {
            snprintf(eta_buf, sizeof(eta_buf), "%d:%02d:%02d",
                     secleft/3600, (secleft/60) % 60, secleft % 60);
        }
        fprintf(stderr, "%s frame [%d/%d], %6.2f fps, %5.1f%%, ETA: %s,"
                        "  (%2d,%2d|%2d,%2d|%2d,%2d) \r",
                encoding ? "encoding" : "skipping",
                frame, last+1,
                fps,
                100*done,
                eta_buf,
                decodebuf[0], decodebuf[1], filterbuf[0], filterbuf[1],
                encodebuf[0], encodebuf[1]
        );
    }
    printed = 1;
}

/*************************************************************************/

/*
 * Local variables:
 *   c-file-style: "stroustrup"
 *   c-file-offsets: ((case-label . *) (statement-case-intro . *))
 *   indent-tabs-mode: nil
 * End:
 *
 * vim: expandtab shiftwidth=4:
 */
