// ================================================================ //
//                                                                  //
//   File      : arb_progress.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_progress.h>
#include <arb_handlers.h>
#include <algorithm>

using namespace ArbProgress;

// ----------------
//      counter

struct null_counter: public counter {
    null_counter(nestable *owner) : counter(owner) {}

    void inc() OVERRIDE {}
    void implicit_inc() OVERRIDE {}
    void inc_by(SINT) OVERRIDE {}
    void inc_to(SINT) OVERRIDE {}
    void inc_to_avoid_overflow(SINT) OVERRIDE {}
    void done() OVERRIDE {}
    void force_update() OVERRIDE {}
    void auto_subtitles(const char *) OVERRIDE {}
    bool has_auto_subtitles() OVERRIDE { return false; }
    void child_updates_gauge(double ) OVERRIDE {
        arb_assert(0); // wont
    }

#if defined(DUMP_PROGRESS)
    void dump() const OVERRIDE {
        fprintf(stderr, "null_counter\n");
    }
#endif

    counter *clone(nestable *owner, SINT ) const OVERRIDE { return new null_counter(owner); }
};

struct no_counter : public null_counter {
    no_counter(nestable *owner) : null_counter(owner) {}
    void inc() OVERRIDE {
        arb_assert(0); // this is no_counter - so explicit inc() is prohibited!
    }
    void child_updates_gauge(double gauge) OVERRIDE { ownedBy->update_gauge(gauge); }

#if defined(DUMP_PROGRESS)
    void dump() const OVERRIDE {
        fprintf(stderr, "no_counter (=wrapped null_counter)\n");
    }
#endif

    counter *clone(nestable *, SINT ) const OVERRIDE {
        arb_assert(0); // this method previously was not present => previously was cloned as null_counter!
        return NULL;
    }
};

static void warn_and_dump_counter_or_calling_progress(const char *warnmsg, const counter *fallback);

class concrete_counter FINAL_TYPE : public counter { // derived from a Noncopyable
    SINT    explicit_counter; // incremented by calls to inc() etc.
    SINT    implicit_counter; // incremented by child_done()
    SINT    maxcount;         // == 0 -> does not really count (just a wrapper for child progresses)
    double  autoUpdateEvery;
    double  nextAutoUpdate;
    char   *auto_subtitle_prefix;
    SINT    last_auto_counter;

    SINT dispositive_counter() const { return std::max(implicit_counter, explicit_counter); }

    void init(SINT overallCount) {
        arb_assert(overallCount>0);

        implicit_counter  = 0;
        explicit_counter  = 0;
        maxcount          = overallCount;
        autoUpdateEvery   = overallCount/500.0; // update status approx. 500 times
        nextAutoUpdate    = 0;
    }

    bool refresh_if_needed(double my_counter) { // (double needed when called for child_gauge)
        arb_assert(my_counter>=0);
        if (my_counter<nextAutoUpdate) return false;

        {
            double gauge = my_counter/maxcount;
            if (gauge>=1.05) { // 5% overflow detected
                SINT new_maxcount = maxcount*1.2+0.5;

                char *warnmsg = GBS_global_string_copy("Warning: progress indicator overflow (%.1f%%/%li)\n"
                                                       "Estimation of time left is broken (assuming new maximum: +20%% -> %li)\n"
                                                       "Please report this problem to devel@arb-home.de\n"
                                                       "(please mention the full text shown in the progress bar window\n"
                                                       "AND include the newest session log (stored after quitting arb),\n"
                                                       "otherwise we'll not be able to fix this problem)",
                                                       (gauge-1)*100, maxcount, new_maxcount);

                warn_and_dump_counter_or_calling_progress(warnmsg, this);
                free(warnmsg);

                arb_assert(0); // progress overflow

                maxcount = new_maxcount;
                gauge    = my_counter/maxcount;
            }
            ownedBy->update_gauge(gauge);
        }

        if (auto_subtitle_prefix) {
            SINT count = SINT(my_counter+1);
            if (count>last_auto_counter && count <= maxcount) {
                last_auto_counter   = count; // important to set BEFORE calling set_text (which may recurse!)
                const char *autosub = GBS_global_string("%s #%li/%li", auto_subtitle_prefix, count, maxcount);
                ownedBy->set_text(LEVEL_SUBTITLE, autosub);
            }
        }
        nextAutoUpdate += autoUpdateEvery;
        return true;
    }
    void update_display_if_needed() {
        refresh_if_needed(dispositive_counter());
    }

    void force_update() OVERRIDE {
        double oldNext = nextAutoUpdate;
        nextAutoUpdate = 0;
        update_display_if_needed();
        nextAutoUpdate = oldNext;
    }

  public:
    concrete_counter(nestable *owner, SINT overall_count) :
        counter(owner),
        auto_subtitle_prefix(NULp),
        last_auto_counter(0)
    {
        arb_assert(overall_count>0);
        init(overall_count);
    }
    ~concrete_counter() OVERRIDE {
        free(auto_subtitle_prefix);
#if defined(TEST_COUNTERS)
        if (!ownedBy->accept_invalid_counters) {
            arb_assert(implicit_counter || explicit_counter); // progress was never incremented

            arb_assert(implicit_counter <= maxcount); // overflow
            arb_assert(explicit_counter <= maxcount); // overflow

            arb_assert(dispositive_counter() == maxcount); // progress did not finish
        }
#endif
    }

#if defined(DUMP_PROGRESS)
    void dump() const OVERRIDE {
        fprintf(stderr,
                "concrete_counter: explicit=%li, implicit=%li, maxcount=%li\n",
                explicit_counter, implicit_counter, maxcount);
    }
#endif

    void auto_subtitles(const char *prefix) OVERRIDE {
        // activates use of a prefixed counter as subtitle: "prefix #some/all"
        arb_assert(!auto_subtitle_prefix);
        freedup(auto_subtitle_prefix, prefix);
        force_update();
    }
    bool has_auto_subtitles() OVERRIDE { return auto_subtitle_prefix; }

    void inc()          OVERRIDE { explicit_counter += 1; update_display_if_needed(); }
    void implicit_inc() OVERRIDE { implicit_counter += 1; update_display_if_needed(); }

    void inc_to(SINT x) OVERRIDE {
        explicit_counter = std::max(explicit_counter, x);
        update_display_if_needed();
    }
    void inc_to_avoid_overflow(SINT x) OVERRIDE {
        inc_to(std::min(maxcount, x));
    }
    void inc_by(SINT count) { inc_to(explicit_counter+count); }

    void done() OVERRIDE {
        implicit_counter = explicit_counter = maxcount;
        force_update();
    }

    counter *clone(nestable *owner, SINT overall_count) const OVERRIDE {
        return new concrete_counter(owner, overall_count);
    }
    void child_updates_gauge(double child_gauge) OVERRIDE {
        refresh_if_needed(dispositive_counter()+child_gauge);
    }
};

// -----------------
//      progress

inline const char *spaced_weightable(const weightable& speed) {
    if (!speed.is_weighted()) return "";
    double phase1_weight = speed.get_adjusted_gauge(0.5);
    return GBS_global_string(" (weighted: %.3f%%:%.3f%%)", phase1_weight*100, (1-phase1_weight)*100);
}

static counter *make_counter(nestable *owner, SINT overall_count) {
    if (overall_count) return new concrete_counter(owner, overall_count);
    return new no_counter(owner);
}

class child_progress FINAL_TYPE : public nestable { // derived from a Noncopyable
    nestable *parent;
    static child_progress *child_triggering_update;

public:
    child_progress(nestable *parent_, const char *title, SINT overall_count, const weightable& speed_) :
        nestable(make_counter(this, overall_count), title, speed_),
        parent(parent_)
    {
        set_text(LEVEL_TITLE, title);
        DUMP_AS(GBS_global_string("child: %s%s", title ? title : "<untitled>", spaced_weightable(speed_)));
    }
    ~child_progress() OVERRIDE {
        parent->child_terminated();
    }

    SmartPtr<nestable> create_child_progress(const char *title, SINT overall_count, const weightable& speed_) OVERRIDE {
        return new child_progress(this, title, overall_count, speed_);
    }

#if defined(DUMP_PROGRESS)
    void dump() const OVERRIDE {
        nestable::dump();
        fprintf(stderr, "is child of\n");
        parent->dump();
    }
#endif

    void set_text(int level, const char *text) OVERRIDE { parent->child_sets_text(level+has_title-1, text); }

    void update_gauge(double gauge) OVERRIDE {
        double adjusted_gauge = speed.get_adjusted_gauge(gauge);
        if (!child_triggering_update) {
            LocallyModify<child_progress*> storeUpdater(child_triggering_update, this);
            parent->update_parent_gauge(adjusted_gauge);
        }
        else {
            parent->update_parent_gauge(adjusted_gauge);
        }
    }

    static const child_progress *calling_child_progress() { return child_triggering_update; }
};

child_progress *child_progress::child_triggering_update = NULp;

static void warn_and_dump_counter_or_calling_progress(const char *warnmsg, const counter *fallback) {
    GB_warning(warnmsg);
    fprintf(stderr, "-------------------- warn_and_dump_counter_or_calling_progress [start]\n");
    fputs(warnmsg, stderr);
    fputc('\n', stderr);
#if defined(DUMP_PROGRESS)
    const child_progress *updater = child_progress::calling_child_progress();
    if (updater) {
        updater->dump();
    }
    else { // assume this counter has been updated directly
        fallback->dump();
    }
#endif
    fprintf(stderr, "-------------------- warn_and_dump_counter_or_calling_progress [end]\n");
}

class initial_progress: public nestable {

public:
    initial_progress(const char *title, counter *counter_, const weightable& speed_)
        : nestable(counter_, title, speed_)
    {
        DUMP_AS(GBS_global_string("initial: %s%s", title ? title : "<untitled>", spaced_weightable(speed_)));
        if (!title) title = "..."; // use fake title (nestable got no title, so it will be replaced by child title)
        impl->openstatus(title);
    }
    ~initial_progress() OVERRIDE {
        update_gauge(1.0); // due to numeric issues it often only counts up to 99.999%
        impl->closestatus();
    }

    SmartPtr<nestable> create_child_progress(const char *title, SINT overall_count, const weightable& speed_) OVERRIDE {
        return new child_progress(this, title, overall_count, speed_);
    }

    void set_text(int level, const char *text) OVERRIDE {
        if (!text) return;
        switch (level+has_title-1) {
            case LEVEL_TITLE: impl->set_title(text); break;
            case LEVEL_SUBTITLE:
#if defined(FORCE_WEIGHTED_ESTIMATION)
                if (speed.is_weighted() || have_weighted_progress()) {
                    cntr->force_update(); // send most recent gauge
                    impl->set_subtitle("REQUEST_ESTIMATION"); // hack: force estimation
                }
#endif
                impl->set_subtitle(text);
                break;
        }
    }

    void update_gauge(double gauge) OVERRIDE {
        arb_assert(gauge>=0);
        arb_assert(gauge<=1);

        double adjusted_gauge = speed.get_adjusted_gauge(gauge);
        impl->set_gauge(adjusted_gauge);
    }
};

struct initial_selfRef_progress : public initial_progress {
    initial_selfRef_progress(const char *title, SINT overall_count, const weightable& speed_) :
        initial_progress(title, make_counter(this, overall_count), speed_)
    {}
};

class null_progress: public nestable {
    null_progress(counter *counter_to_clone, SINT overall_count, const weightable& speed_) : // only used by create_child_progress
        nestable(counter_to_clone->clone(this, overall_count), false, speed_)
    {
        DUMP_AS("null_progress (cloned)");
    }
public:
    explicit null_progress() : // creates a suppressor
        nestable(new null_counter(this), false, weightable())
    {
        DUMP_AS("null_progress (suppressor)");
    }

    SmartPtr<nestable> create_child_progress(const char*, SINT overall_count, const weightable& speed_) OVERRIDE {
        return new null_progress(cntr, overall_count, speed_);
    }
    void set_text(int,const char*) OVERRIDE {}
    void update_gauge(double) OVERRIDE {
        arb_assert(!speed.is_weighted()); // you can't weight nothing!
    }
};

// -------------------------
//      progress factory

nestable                  *nestable::recent = NULp;
arb_status_implementation *nestable::impl   = NULp; // defines implementation to display status

SmartPtr<nestable> nestable::create(const char *title, SINT overall_count, const double *phase1_fraction) {
    weightable weighted = phase1_fraction ? weightable(*phase1_fraction) : weightable();
    arb_assert(implicated(weighted.is_weighted(), overall_count == 2UL));

    if (recent) {
        return recent->create_child_progress(title, overall_count, weighted);
    }
    impl = &active_arb_handlers->status;
    return new initial_selfRef_progress(title, overall_count, weighted);
}

SmartPtr<nestable> nestable::create_suppressor() {
    return new null_progress;
}

#if defined(FORCE_WEIGHTED_ESTIMATION)
bool nestable::have_weighted_progress() {
    return recent->this_or_recent_is_weighted();
}
#endif

bool nestable::aborted() const {
    if (impl) return impl->user_abort();
    return false;
}


// --------------------------
//      progress dumpers

#if defined(DUMP_PROGRESS)

// not inlined in header (otherwise they are missing while debugging)

void nestable::dump() const {
    fprintf(stderr, "progress %s\n", name);
    fprintf(stderr, "counter: ");
    cntr->dump();
}

void arb_progress::dump() const {
    fprintf(stderr, "--------------------\n");
    used->dump();
}

#endif

