///
/// Helper classes and functions used among the code.
///
/// @author: Yunhai Li (yhli)
///

#ifndef TBPLAS_BASE_UTILS_H
#define TBPLAS_BASE_UTILS_H

#include <chrono>
#include <map>
#include <string>

namespace tbplas::base {

using time_point_t = std::chrono::time_point<std::chrono::high_resolution_clock>;

/// @brief Class for measuring the time usage of function calls within programs.
class Timer {
private:
    /// @brief time of last tic call
    std::map<std::string, time_point_t> start_time_;

    /// @brief where the slots are being tracked for measuring time usage
    std::map<std::string, bool> track_status_;

    /// @brief time usage between tic/toc calls
    std::map<std::string, double> time_usage_;

    /// @brief overall time usage
    std::map<std::string, double> total_time_usage_;

    /// @brief width of time in output
    int width_ = 12;

    /// @brief precision of time in output (number of digits after the period.)
    int prec_ = 3;

public:
    /// @param[in] width width of time in output
    /// @param[in] prec precision of time in output
    explicit Timer(int width = 12, int prec = 3);

    /// @brief Begin tracking time usage for given slot.
    /// @param[in] slot name of the slot
    void tic(const std::string& slot);

    /// @brief Stop tracking time usage for given slot.
    /// @param[in] slot name of the slot
    void toc(const std::string& slot);

    /// @brief Report time usage between tic/toc calls.
    void report_time();

    /// @brief Report overall time usage.
    void report_total_time();

    /// @brief Reset start time, track status and time usages.
    void reset();
};

/// @brief Class for reporting the progress for a time-consuming task.
class ProgressBar {
private:
    /*  Schematic plot of the working mechanism:
        num_tasks:  50
        scale_unit: 5
        scales:     0----5----10----15----20----25----30----35----40----45----50
        next_scale: ......................* ->  * ->  * ->  * ->  * ->  * ->  *
        counter:    ................^
    */
    /// @brief total number of tasks
    /// @note For example, if you are going to do some calculation for a vector with
    /// length of 1000, then set it to 1000.
    int num_tasks_;

    /// @brief number of tasks between two neighboring scales
    int scale_unit_;

    /// @brief the next scale waiting for counter
    /// @note If counter exceeds next_scale, then we can know one part of the task finish.
    int next_scale_;

    /// @brief number of finished jobs
    int counter_;

    /// @brief starting time of the first job
    time_point_t time_start_;

    /// @brief message to print at each progress report
    std::string job_msg_;

public:
    /// @param[in] num_tasks total number of tasks
    /// @param[in] scale_unit number of tasks between two progress reports
    /// @param[in] job_msg message to print at each progress report
    explicit ProgressBar(int num_tasks, int scale_unit = 10, std::string job_msg = "job");

    /// @brief Reset everything of the progress bar.
    /// @param[in] num_tasks total number of tasks
    /// @param[in] scale_unit number of tasks between two progress reports
    /// @param[in] job_msg message to print at each progress report
    void reset(int num_tasks, int scale_unit = 10, std::string job_msg = "job");

    /// @brief Reset next_scale and counter.
    void reset();

    /// @brief Increase the counter by 1.
    void count();
};

} // namespace tbplas::base
#endif
