#pragma once

#include <boost/noncopyable.hpp>
#include <chrono>
#include <string>
#include <sstream>
#include <vector>

namespace maps::mrc::common {

/**
 * @brief Specialized type of clock for measuring time intervals
 * @note NOT thread-safe
 * Example:
 * @code{.cpp}
 * Stopwatch timer{Stopwatch::Running};
 * // do stuff
 * std::cout << "My Activity took: " << timer.elapsed<std::chrono::seconds>();
 * @endcode
 */
class Stopwatch : boost::noncopyable {
public:
    enum State { Stopped, Running };

    explicit Stopwatch(State state = Stopped);

    void start();
    void stop();
    void reset();

    template <class ToDuration>
    size_t elapsed() const
    {
        auto result = elapsed_;
        if (state_ == Running) {
            result += Clock::now() - start_;
        }
        return std::chrono::duration_cast<ToDuration>(result).count();
    }

private:
    using Clock = std::chrono::high_resolution_clock;
    using TimePoint = Clock::time_point;
    using Duration = Clock::duration;

    State state_;
    TimePoint start_;
    Duration elapsed_;
};


/**
* Print total time between construction and toString() call. This interval can be split into smaller ones.
* @code
* IntervalsTimer timer{"Call"};
* foo();
* timer.end("First");
* bar();
* timer.end("Second");
* print(timer.toString()); // Call 100ms (First 50ms + Second 50ms )
**/
class IntervalsTimer {
public:
    explicit IntervalsTimer(std::string name = {});

    IntervalsTimer& endInterval(std::string interval);

    template <class ToDuration>
    size_t totalElapsed() const
    {
        Duration result{};
        for (const auto& item: splits_) {
            result += item.duration;
        }
        return std::chrono::duration_cast<ToDuration>(result).count();
    }

    std::string toString() const
    {
        using OutputDuration = std::chrono::milliseconds;
        std::ostringstream os;
        if (!name_.empty()) {
            os << name_ << " ";
        }
        os << totalElapsed<OutputDuration>() << "ms (";
        bool firstInterval = true;
        for (const auto& item: splits_) {
            if (firstInterval) {
                firstInterval = false;
            } else {
                os << " + ";
            }

            os << item.name << " "
                << std::chrono::duration_cast<OutputDuration>(item.duration).count()
                << "ms";
        }
        os << ")";
        return os.str();
    }

private:
    using Clock = std::chrono::high_resolution_clock;
    using TimePoint = Clock::time_point;
    using Duration = Clock::duration;

    struct Split {
        std::string name;
        Duration duration;
    };

    std::string name_;
    TimePoint start_;
    TimePoint currentIntervalStart_;
    std::vector<Split> splits_;
};

} // namespace maps::mrc::common
