#include "utils.h"

#include <cassert>
#include <iomanip>
#include <ios>
#include <iostream>

namespace tbplas::base {

Timer::Timer(int width, int prec)
    : width_(width)
    , prec_(prec)
{
}

void Timer::tic(const std::string& slot)
{
    start_time_[slot] = std::chrono::high_resolution_clock::now();
    track_status_[slot] = true;
}

void Timer::toc(const std::string& slot)
{
    assert(track_status_[slot]);
    time_point_t end = std::chrono::high_resolution_clock::now();
    int64_t duration_ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start_time_[slot]).count();
    double duration_s = static_cast<double>(duration_ms) * 1e-3;
    time_usage_[slot] = duration_s;
    total_time_usage_[slot] += duration_s;
    track_status_[slot] = false;
}

void Timer::report_time()
{
    std::streamsize prev_precision = std::cout.precision();
    std::cout.setf(std::ios::fixed);
    std::cout.precision(prec_);
    auto iter = start_time_.begin();
    for (; iter != start_time_.end(); ++iter) {
        const std::string& slot = (*iter).first;
        assert(!track_status_[slot]);
        std::cout << std::setw(width_) << slot << ":"
                  << std::setw(width_) << time_usage_[slot] << " sec.\n";
    }
    std::cout.unsetf(std::ios::fixed);
    std::cout.precision(prev_precision);
}

void Timer::report_total_time()
{
    std::streamsize prev_precision = std::cout.precision();
    std::cout.setf(std::ios::fixed);
    std::cout.precision(prec_);
    auto iter = start_time_.begin();
    for (; iter != start_time_.end(); ++iter) {
        const std::string& slot = (*iter).first;
        assert(!track_status_[slot]);
        std::cout << std::setw(width_) << slot << ":"
                  << std::setw(width_) << total_time_usage_[slot] << " sec.\n";
    }
    std::cout.unsetf(std::ios::fixed);
    std::cout.precision(prev_precision);
}

void Timer::reset()
{
    start_time_.empty();
    track_status_.empty();
    time_usage_.empty();
    total_time_usage_.empty();
}

ProgressBar::ProgressBar(int num_tasks, int scale_unit, std::string job_msg)
    : num_tasks_(num_tasks)
    , scale_unit_(scale_unit)
    , next_scale_(scale_unit)
    , counter_(0)
    , time_start_(std::chrono::high_resolution_clock::now())
    , job_msg_(std::move(job_msg))
{
}

void ProgressBar::reset(int num_tasks, int scale_unit, std::string job_msg)
{
    num_tasks_ = num_tasks;
    scale_unit_ = scale_unit;
    next_scale_ = scale_unit_;
    counter_ = 0;
    time_start_ = std::chrono::high_resolution_clock::now();
    job_msg_ = std::move(job_msg);
}

void ProgressBar::reset()
{
    next_scale_ = scale_unit_;
    counter_ = 0;
    time_start_ = std::chrono::high_resolution_clock::now();
}

void ProgressBar::count()
{
    ++counter_;
    if (counter_ >= next_scale_) {
        next_scale_ += scale_unit_;
        if (next_scale_ > num_tasks_) { // In case where num_tasks cannot be divided by scale_unit,
            next_scale_ = num_tasks_; // next_scale may exceed num_tasks in the last update.
        }

        // Evaluate time usage
        time_point_t time_end = std::chrono::high_resolution_clock::now();
        int64_t duration_ms = std::chrono::duration_cast<std::chrono::milliseconds>(time_end - time_start_).count();
        double duration_s = static_cast<double>(duration_ms) * 1e-3;

        // Evaluate progress
        std::streamsize prev_precision = std::cout.precision();
        std::cout.setf(std::ios::fixed);
        std::cout.precision(3);
        // double percent = 100.0 * static_cast<double>(counter_) / static_cast<double>(num_tasks_);
        // std::cout << "Finished" << std::setw(8) << percent << "%"
        //           << std::setw(16) << duration_s << " sec.\n";
        std::cout << job_msg_ << std::setw(8) << counter_
                  << " of" << std::setw(8) << num_tasks_
                  << std::setw(16) << duration_s << " sec." << std::endl;
        std::cout.unsetf(std::ios::fixed);
        std::cout.precision(prev_precision);
    }
}
} // namespace tbplas::base
