// Copyright (C) 2010  Davis E. King (davis@dlib.net)
// License: Boost Software License   See LICENSE.txt for the full license.
#ifndef DLIB_CONSOLE_PROGRESS_INDiCATOR_Hh_
#define DLIB_CONSOLE_PROGRESS_INDiCATOR_Hh_

#include <ctime>
#include <cmath>
#include <limits>
#include <iostream>

namespace dlib
{

// ----------------------------------------------------------------------------------------

    class console_progress_indicator
    {
        /*!
            WHAT THIS OBJECT REPRESENTS
                This object is a tool for reporting how long a task will take
                to complete.  

                For example, consider the following bit of code:

                    console_progress_indicator pbar(100)
                    for (int i = 1; i <= 100; ++i)
                    {
                        pbar.print_status(i);
                        long_running_operation();
                    }

                The above code will print a message to the console each iteration
                which shows the current progress and how much time is remaining until
                the loop terminates.
        !*/

    public:

        inline explicit console_progress_indicator (
            double target_value 
        ); 
        /*!
            ensures
                - #target() == target_value
        !*/

        inline void reset (
            double target_value
        );
        /*!
            ensures
                - #target() == target_value
                - performs the equivalent of:
                    *this = console_progress_indicator(target_value)
                    (i.e. resets this object with a new target value)

        !*/

        inline double target (
        ) const;
        /*!
            ensures
                - This object attempts to measure how much time is
                  left until we reach a certain targeted value.  This
                  function returns that targeted value.
        !*/

        inline bool print_status (
            double cur,
            bool always_print = false,
            std::ostream& out = std::cout
        );
        /*!
            ensures
                - print_status() assumes it is called with values which are linearly
                  approaching target().  It will display the current progress and attempt
                  to predict how much time is remaining until cur becomes equal to target().
                - prints a status message to out which indicates how much more time is
                  left until cur is equal to target()
                - if (always_print) then
                    - This function prints to the screen each time it is called.
                - else
                    - This function throttles the printing so that at most 1 message is
                      printed each second.  Note that it won't print anything to the screen
                      until about one second has elapsed.  This means that the first call
                      to print_status() never prints to the screen.
                - This function returns true if it prints to the screen and false
                  otherwise.
        !*/

    private:

        double target_val;

        time_t start_time;
        double first_val;
        double seen_first_val;
        time_t last_time;

    };

// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
//                               IMPLEMENTATION DETAILS
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------

    console_progress_indicator::
    console_progress_indicator (
        double target_value 
    ) :
        target_val(target_value),
        start_time(0),
        first_val(0),
        seen_first_val(false),
        last_time(0)
    {
    }

// ----------------------------------------------------------------------------------------

    bool console_progress_indicator::
    print_status (
        double cur,
        bool always_print,
        std::ostream& out
    )
    {
        const time_t cur_time = std::time(0);

        // if this is the first time print_status has been called
        // then collect some information and exit.  We will print status
        // on the next call.
        if (!seen_first_val)
        {
            start_time = cur_time;
            last_time = cur_time;
            first_val = cur;
            seen_first_val = true;
            return false;
        }

        if (cur_time != last_time || always_print)
        {
            last_time = cur_time;
            double delta_t = static_cast<double>(cur_time - start_time);
            double delta_val = std::abs(cur - first_val);

            // don't do anything if cur is equal to first_val
            if (delta_val < std::numeric_limits<double>::epsilon())
                return false;

            double seconds = delta_t/delta_val * std::abs(target_val - cur);

            std::ios::fmtflags oldflags = out.flags();

            out.setf(std::ios::fixed,std::ios::floatfield);
            std::streamsize ss;

            if (std::trunc(target_val) == target_val)
                ss = out.precision(0);
            else
                ss = out.precision(2);

            out << "Progress: " << cur << "/" << target_val;
            ss = out.precision(2);
            out << " (" << cur / target_val * 100. << "%). ";

            if (seconds < 60)
            {
                ss = out.precision(0);
                out << "Time remaining: " << seconds << " seconds.                 \r" << std::flush;
            }
            else if (seconds < 60*60)
            {
                ss = out.precision(2);
                out << "Time remaining: " << seconds/60 << " minutes.                 \r" << std::flush;
            }
            else 
            {
                ss = out.precision(2);
                out << "Time remaining: " << seconds/60/60 << " hours.                 \r" << std::flush;
            }

            // restore previous output flags and precision settings
            out.flags(oldflags);
            out.precision(ss);

            return true;
        }

        return false;
    }

// ----------------------------------------------------------------------------------------

    double console_progress_indicator::
    target (
    ) const
    {
        return target_val;
    }

// ----------------------------------------------------------------------------------------

    void console_progress_indicator::
    reset (
        double target_value
    ) 
    {
        *this = console_progress_indicator(target_value);
    }

// ----------------------------------------------------------------------------------------

}

#endif // DLIB_CONSOLE_PROGRESS_INDiCATOR_Hh_