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

#ifndef ARB_PROGRESS_H
#define ARB_PROGRESS_H

#ifndef ARB_ASSERT_H
#include <arb_assert.h>
#endif
#ifndef ARB_ERROR_H
#include <arb_error.h>
#endif
#ifndef ARBTOOLS_H
#include <arbtools.h>
#endif
#ifndef ARB_MSG_H
#include "arb_msg.h"
#endif

#if defined(DEBUG)
# define TEST_COUNTERS
#endif

#if !defined(DEVEL_RELEASE)
# define FORCE_WEIGHTED_ESTIMATION // see also ../WINDOW/AW_status.cxx@FORCEWEIGHTEDESTIMATION
#endif

#define DUMP_PROGRESS // undefine DUMP_PROGRESS again? (when progress bug #807 is fixed)

struct arb_status_implementation;

namespace ArbProgress {
    typedef long SINT;

    class nestable;   // nestable progresses

    class weightable { // can weight gauge
        bool weighted;

        double phase1_weight;
        double phase2_weight() const { return 1.0-phase1_weight; }

    public:
        weightable() :
            weighted(false),
            phase1_weight(0.0)
        {}

        explicit weightable(double phase1_weight_) :
            weighted(true),
            phase1_weight(phase1_weight_)
        {}

        bool is_weighted() const { return weighted; }

        double get_adjusted_gauge(double linear_gauge) const {
            arb_assert(linear_gauge>=0);
            arb_assert(linear_gauge<=1);

            double adjusted_gauge = linear_gauge;
            if (is_weighted()) {
                if (linear_gauge<0.5) adjusted_gauge = phase1_weight * linear_gauge * 2;
                else                  adjusted_gauge = phase1_weight + phase2_weight() * (linear_gauge-0.5)*2;
            }

            arb_assert(adjusted_gauge>=0);
            arb_assert(adjusted_gauge<=1);

            return adjusted_gauge;
        }
    };

    class counter : virtual Noncopyable {
    protected:
        nestable *ownedBy;
    public:

        counter(nestable *owner)
            : ownedBy(owner)
        {
            arb_assert(owner); // counter w/o owner is not possible
        }
        virtual ~counter() {
            arb_assert(has_correct_ownership());
        }

        virtual void inc()                              = 0;
        virtual void implicit_inc()                     = 0;
        virtual void inc_by(SINT count)                 = 0;
        virtual void inc_to(SINT x)                     = 0;
        virtual void inc_to_avoid_overflow(SINT x)      = 0;
        virtual void child_updates_gauge(double gauge)  = 0;
        virtual void done()                             = 0;
        virtual void force_update()                     = 0;
        virtual void auto_subtitles(const char *prefix) = 0;
        virtual bool has_auto_subtitles()               = 0;

#if defined(DUMP_PROGRESS)
        virtual void dump() const = 0;
#endif

        virtual counter *clone(nestable *owner, SINT overall_count) const = 0;

        bool owned_by(const nestable *by) const { return ownedBy && ownedBy == by; }
        bool has_correct_ownership() const;
    };

    const int LEVEL_TITLE    = 0;
    const int LEVEL_SUBTITLE = 1;

    class nestable : virtual Noncopyable {
        nestable *prev_recent;   // old value of global 'recent'
        bool      reuse_allowed; // display may be reused by childs

    protected:
#if defined(DUMP_PROGRESS)
        char *name; // dumped name
#define DUMP_AS(NAME) freedup(name,NAME)
#else
#define DUMP_AS(NAME)
#endif

        bool        has_title;
        counter    *cntr; // counter used by this progress
        weightable  speed;

        static nestable                  *recent; // last instance constructed
        static arb_status_implementation *impl;   // defines implementation to display status

        virtual SmartPtr<nestable> create_child_progress(const char *title, SINT overall_count, const weightable& speed_) = 0;

        nestable(counter *counter_, bool has_title_, const weightable& speed_)
            : reuse_allowed(false),
              has_title(has_title_),
              cntr(counter_),
              speed(speed_)
        {
            prev_recent = recent;
            recent      = this;

#if defined(DUMP_PROGRESS)
            name = NULp;
#endif

#if defined(TEST_COUNTERS)
            accept_invalid_counters = false;
#endif
            arb_assert(is_correct_owner());
        }
    public:
#if defined(TEST_COUNTERS)
        bool accept_invalid_counters; // if true, do not complain about unfinished counters
#endif

        virtual ~nestable() {
            arb_assert(this == recent); // nestable|s should be destroyed in-order!

            delete cntr;
            recent = prev_recent;
#if defined(DUMP_PROGRESS)
            arb_assert(name); // progress was not named (dump() wont show anything useful; use DUMP_AS in ctor of derived class)
            free(name);
#endif
        }

        static SmartPtr<nestable> create(const char *title, SINT overall_count, const double *phase1_fraction);
        static SmartPtr<nestable> create_suppressor();

#if defined(FORCE_WEIGHTED_ESTIMATION)
        static bool have_weighted_progress();
        bool this_or_recent_is_weighted() const {
            return speed.is_weighted() || (prev_recent && prev_recent->this_or_recent_is_weighted());
        }
#endif

        bool aborted() const;
        virtual void set_text(int level, const char *text) = 0;
        virtual void update_gauge(double gauge)            = 0;

#if defined(DUMP_PROGRESS)
        virtual void dump() const;
#endif

        void child_sets_text(int level, const char *text) {
            set_text(level+1-reuse_allowed+cntr->has_auto_subtitles(), text);
        }
        void allow_title_reuse() { reuse_allowed = true; }

        void update_parent_gauge(double gauge) { cntr->child_updates_gauge(gauge); }
        void child_terminated() { cntr->implicit_inc(); }

        void initial_update() { cntr->force_update(); }
        void force_update() { cntr->force_update(); }

        void inc() { cntr->inc(); }
        void inc_by(SINT count) { arb_assert(count>=0); cntr->inc_by(count); }
        void inc_to(SINT x) { cntr->inc_to(x); }
        void inc_to_avoid_overflow(SINT x) { cntr->inc_to_avoid_overflow(x); }
        void done() { cntr->done(); }
        void auto_subtitles(const char *prefix) { cntr->auto_subtitles(prefix); }

        static void show_comment(const char *comment) {
            if (recent) recent->set_text(ArbProgress::LEVEL_SUBTITLE, comment);
        }

        bool owns(const counter *pwnd) const { return cntr && cntr == pwnd; }
        bool is_correct_owner() const { return owns(cntr) && cntr->owned_by(this); }
    };

    inline bool counter::has_correct_ownership() const { return owned_by(ownedBy) && ownedBy->owns(this); }

};

// ----------------------
//      arb_progress

enum WeightedProgressMarker { WEIGHTED }; // just a marker to emphasize weighted progresses

class arb_progress : virtual Noncopyable {
    SmartPtr<ArbProgress::nestable> used;

    typedef ArbProgress::SINT PINT;

    void setup(const char *title, PINT overall_count, const double *phase1_fraction = NULp) {
        bool acceptInvalid = false;
        if (overall_count<0) { // assume overflow
            GB_warningf("Warning: progress indicator underflow (%li, %s)\n"
                        "Please report to devel@arb-home.de\n"
                        "Estimation of time left is broken :/",
                        overall_count, title ? title : "<no title>");

#if defined(DEVEL_RALF)
            arb_assert(0); // overflow?
#endif

            overall_count = 10L*INT_MAX; // fake sth big
            acceptInvalid = true;
        }

        used = ArbProgress::nestable::create(title, overall_count, phase1_fraction);
        if (acceptInvalid) accept_invalid_counters();
        used->initial_update();
    }
    // cppcheck-suppress functionConst
    void accept_invalid_counters() {
#if defined(TEST_COUNTERS)
        used->accept_invalid_counters = true;
#endif
    }

public:
    // ------------------------------
    // recommended interface:

    arb_progress(const char *title, PINT overall_count) {
        // open a progress indicator
        //
        // expects to be incremented 'overall_count' times
        //      incrementation is done either
        //      - explicitly by calling one of the inc...()-functions below or
        //      - implicitely by creating another arb_progress while this one remains
        //
        // if you can't ahead-determine the exact number of incrementations,
        // specify an upper-limit and call .done() before dtor.
        setup(title, overall_count);
    }
    explicit arb_progress(const char *title) {
        // open a wrapping progress indicator
        //
        // expects NOT to be incremented explicitly!
        //      if arb_progresses are created while this exists, they reuse the progress window.
        //      Useful to avoid spamming the user with numerous popping-up progress windows.
        setup(title, 0);
    }
    explicit arb_progress(PINT overall_count) {
        // open counting progress (reuses text of parent progress).
        //
        // Useful to separate text- from gauge-display or
        // to summarize several consecutive child-progresses into one.
        setup(NULp, overall_count);
    }


    arb_progress() {
        // plain wrapper (avoids multiple implicit increments of its parent).
        //
        // usage-conditions:
        // * caller increments progress in a loop and
        // * loop calls one or more callees and callees open multiple progress bars.
        //
        // in this case the parent progress would be implicitely incremented several times
        // per loop, resulting in wrong gauge.
        //
        // if you know the number of opened progresses, use arb_progress(PINT),
        // otherwise add one wrapper-progress into the loop.
        setup(NULp, 0);
    }

private:
    // forbid constructing arb_progress with integer types that could overflow in caller code:
    template <typename NUM>          arb_progress(const char *t, NUM n);
    template <typename NUM> explicit arb_progress(NUM n);
public:
    // explicitely overload wanted ctors:
    explicit arb_progress(char *title) { setup(title, 0); }
    explicit arb_progress(unsigned long overall_count) { arb_assert(overall_count<=LONG_MAX); setup(NULp, overall_count); }
    arb_progress(const char *title, unsigned long overall_count) { arb_assert(overall_count<=LONG_MAX); setup(title, overall_count); }
    arb_progress(WeightedProgressMarker, const char *title, double phase1_fraction) { setup(title, 2UL, &phase1_fraction); } // a name weighted progress (always has count of 2)
    arb_progress(WeightedProgressMarker, double phase1_fraction)                    { setup(NULp,  2UL, &phase1_fraction); } // an unnamed  weighted progress (always has count of 2)

    void allow_title_reuse() { used->allow_title_reuse(); }

    void subtitle(const char *stitle) { used->set_text(ArbProgress::LEVEL_SUBTITLE, stitle); }

    GB_ERROR error_if_aborted() {
        return aborted() ? "Operation aborted on user request" : NULp;
    }

    GB_ERROR inc_and_error_if_aborted() {
        inc();
        return error_if_aborted();
    }

    void inc_and_check_user_abort(GB_ERROR& error)  { if (!error) error = inc_and_error_if_aborted(); else accept_invalid_counters(); }
    void inc_and_check_user_abort(ARB_ERROR& error) { if (!error) error = inc_and_error_if_aborted(); else accept_invalid_counters(); }

    bool aborted() {
        // true if user pressed "Abort"
        bool aborted_ = used->aborted();
#if defined(TEST_COUNTERS)
        if (aborted_) accept_invalid_counters();
#endif
        return aborted_;
    }

    void auto_subtitles(const char *prefix) {
        // automatically update subtitles ("prefix #/#")
        // prefix = NULp -> switch off
        used->auto_subtitles(prefix);
    }
    static void show_comment(const char *comment) {
        // Like subtitle(), but w/o needing to know anything about a eventually open progress
        // e.g. used to show ARB is connecting to ptserver
        ArbProgress::nestable::show_comment(comment);
    }

    // ------------------------------
    // less recommended interface:

    void inc() { used->inc(); } // increment progress
    const arb_progress& operator++() { inc(); return *this; } // ++progress

    void inc_by(PINT count) { arb_assert(count>=0); used->inc_by(count); }
    void inc_to(PINT x) { used->inc_to(x); }
    void inc_to_avoid_overflow(PINT x) { used->inc_to_avoid_overflow(x); }

    void sub_progress_skipped() { used->child_terminated(); }

    void done() { used->done(); } // set "done" (aka 100%). Useful when exiting some loop early
#if defined(DUMP_PROGRESS)
    void dump() const;
#endif
    void force_update() { used->force_update(); }
};

// -------------------------------
//      arb_suppress_progress

class arb_suppress_progress : virtual Noncopyable {
    SmartPtr<ArbProgress::nestable> suppressor;
public:
    arb_suppress_progress()
        : suppressor(ArbProgress::nestable::create_suppressor())
    {}
};


#else
#error arb_progress.h included twice
#endif // ARB_PROGRESS_H
