// ================================================================ //
//                                                                  //
//   File      : arb_handlers.cxx                                   //
//   Purpose   :                                                    //
//                                                                  //
//   Coded by Ralf Westram (coder@reallysoft.de) in November 2010   //
//   Institute of Microbiology (Technical University Munich)        //
//   http://www.arb-home.de/                                        //
//                                                                  //
// ================================================================ //

#include <arb_handlers.h>
#include <arb_msg.h>
#include "arb_misc.h"
#include <smartptr.h>
#include <unistd.h>
#include <time.h>
#include <arb_algo.h>

// AISC_MKPT_PROMOTE:#ifndef ARB_CORE_H
// AISC_MKPT_PROMOTE:#include <arb_core.h>
// AISC_MKPT_PROMOTE:#endif

static FILE *arberr = stderr;
static FILE *arbout = stdout;

static void to_arberr(const char *msg) {
    fflush(arbout);
    fprintf(arberr, "%s\n", msg);
    fflush(arberr);
}
static void to_arbout(const char *msg) {
    fprintf(arbout, "%s\n", msg);
}

// -------------------------
//      ARB_arbout_status

const int  WIDTH = 70;
const char CHAR  = '.';

const int MIN_SECONDS_PER_ROW = 10;   // otherwise decrease number of rows
const int MAX_SECONDS_PER_ROW = 3*60; // otherwise increase number of rows

const int DEFAULT_HEIGHT = 12; // 12 can be devided by 2, 3, 4, 6 (so subtitles tend to be at start of line)
const int MAXHEIGHT      = 3*DEFAULT_HEIGHT;

#if defined(DEBUG)
// # define TRACE_BASIC_COUNTER
#endif

class BasicStatus : virtual Noncopyable {
    int         openCount;
    char       *subtitle;
    const char *cursor;

    int printed;
    int height; // number of rows to print (gets decided while printing 1st row)

    bool mayReconsiderHeight;   // true -> 'height' may change
    int  printedAtHeightChange; // value of 'printed' at time when 'height' was reconsidered and changed

    time_t start;

    void reset() {
        printed  = 0;
        cursor   = NULp;
        subtitle = NULp;

        time(&start);

        mayReconsiderHeight       = true;
        printedAtHeightChange = 0;

        height = DEFAULT_HEIGHT;
    }

public:
    BasicStatus() : openCount(0) { reset(); }
    ~BasicStatus() { if (openCount) close(); }

    void open(const char *title) {
        arb_assert(++openCount <= 1);
        fprintf(arbout, "Progress: %s\n", title);
        reset();
    }
    void close() {
        freenull(subtitle);
        fprintf(arbout, "[done]\n");
        arb_assert(--openCount >= 0);
        fflush(arbout);
    }

    int next_LF() const { return printed-printed%WIDTH+WIDTH; }

    void set_subtitle(const char *stitle) {
        arb_assert(openCount == 1);
        if (!cursor) {
#if defined(FORCE_WEIGHTED_ESTIMATION)
            if (strcmp(stitle, "REQUEST_ESTIMATION") == 0) { // hack
                return;
            }
#endif
            freeset(subtitle, GBS_global_string_copy("{%s}", stitle));
            cursor = subtitle;
        }
    }

    static double estimate_overall_seconds(double seconds_passed, double gauge) {
        return seconds_passed / gauge;
    }

    void set_gauge(double gauge) {
        arb_assert(openCount == 1);

        int wanted = int(gauge*WIDTH*height);
        if (printed<wanted) {
#if defined(TRACE_BASIC_COUNTER)
            fprintf(arbout, "\nBasicStatus::set_gauge(gauge=%f) -> wanted=%i printed=%i; ", gauge, wanted, printed);
#endif
            while (printed<wanted) {
                if (cursor && cursor[0]) {
                    fputc(*cursor++, arbout);
                }
                else {
                    cursor = NULp;
                    fputc(CHAR, arbout);
                }
                int nextLF = next_LF();
                printed++;
                if (printed == nextLF) {
                    mayReconsiderHeight = false;

                    time_t now;
                    time(&now);

                    fprintf(arbout, " [%5.1f%%]", printed*100.0/(WIDTH*height));
                    bool   done    = (printed == (WIDTH*height));
                    double seconds = difftime(now, start);

                    const char *whatshown = done ? "used" : "left";
                    fprintf(arbout, " %s: ", whatshown);

                    long show_sec = done ? seconds : long((estimate_overall_seconds(seconds, gauge)-seconds)+0.5);
                    fputs(GBS_readable_timediff(show_sec), arbout);
                    fputc('\n', arbout);

                    nextLF = next_LF();
                }
                else if (mayReconsiderHeight) {
                    if ((2*printed) > nextLF) {
                        mayReconsiderHeight = false; // stop redeciding
                    }
                    else {
                        time_t now;
                        time(&now);

                        double seconds         = difftime(now, start)+0.5;
                        double overall_seconds = estimate_overall_seconds(seconds, gauge);

                        int max_wanted_height = int(overall_seconds/MIN_SECONDS_PER_ROW+0.5);
                        int min_wanted_height = int(overall_seconds/MAX_SECONDS_PER_ROW+0.5);

                        arb_assert(min_wanted_height <= max_wanted_height);

                        int old_height = height;

                        height = force_in_range(min_wanted_height, DEFAULT_HEIGHT, max_wanted_height);
                        height = force_in_range(1, height, MAXHEIGHT);

                        if (old_height != height) {
#if defined(TRACE_BASIC_COUNTER)
                            fprintf(arbout, "redeciding height=%i printedAtHeightChange=%i printed=%i wanted=%i\n", height, printedAtHeightChange, printed, wanted);
#endif
                            printedAtHeightChange = std::max(printedAtHeightChange, printed);
                            // now 'wanted' got wrong -> rerun this loop by recursion
                            set_gauge(gauge);
                            return;
                        }
                    }
                }
            }
            fflush(arbout);
#if defined(DEBUG)
            // ARB_sleep(25, MS); // uncomment to see slow status
#endif
        }
        else if (!mayReconsiderHeight && printed>printedAtHeightChange && wanted<printed) {
            arb_assert(wanted>0);
            int print_limit = (printed * 9) / 10;
            if (wanted<print_limit) {
                int print_again_from = (wanted * 8) / 10;
                fprintf(arbout, "\n *** progress counter ran backwards *** (wanted=%i printed=%i print_limit=%i print_again_from=%i)\n", wanted, printed, print_limit, print_again_from);
                printed              = print_again_from;

                // rerun this loop by recursion:
                set_gauge(gauge);
                arb_assert(0); // progress went backwards. to find reason, uncomment TRACE_BASIC_COUNTER and run in debugger!
                return;
            }
        }
    }
};

static BasicStatus status;

static void basic_openstatus(const char *title) { status.open(title); }
static void basic_closestatus() { status.close(); }
static void basic_set_title(const char */*title*/) { }
static void basic_set_subtitle(const char *stitle) { status.set_subtitle(stitle); }
static void basic_set_gauge(double gauge) {
    arb_assert(gauge>=0);
    status.set_gauge(gauge);
}
static bool basic_user_abort() { return false; }

static arb_status_implementation ARB_arbout_status = {
    AST_FORWARD,
    basic_openstatus,   // openstatus
    basic_closestatus,  // closestatus
    basic_set_title,    // set_title
    basic_set_subtitle, // set_subtitle
    basic_set_gauge,    // set_gauge
    basic_user_abort    // user_abort
};

static arb_handlers arbout_handlers = {
    to_arberr,
    to_arbout,
    to_arbout,
    ARB_arbout_status,
};

arb_handlers *active_arb_handlers = &arbout_handlers; // default-handlers

void ARB_install_handlers(arb_handlers& handlers) { active_arb_handlers = &handlers; }

void ARB_redirect_handlers_to(FILE *errStream, FILE *outStream) {
    arberr = errStream;
    arbout = outStream;
}
