/**
 * \file
 * \brief   Utilities header
 *
 * \author
 *    Christos Choutouridis AEM:8997
 *    <cchoutou@ece.auth.gr>
 */
#ifndef UTILS_HPP_
#define UTILS_HPP_

#include <vector>
#include <iostream>
#include <chrono>
#include <unistd.h>
#include <algorithm>

#include "config.h"


/*!
 * A Logger for entire program.
 */
struct Log {
    struct Endl {} endl;    //!< a tag object to to use it as a new line request.

    //! We provide logging via << operator
    template<typename T>
    Log &operator<<(T &&t) {
        if (config.verbose) {
            if (line_) {
                std::cout << "[Log]: " << t;
                line_ = false;
            } else
                std::cout << t;
        }
        return *this;
    }

    // overload for special end line handling
    Log &operator<<(Endl e) {
        (void) e;
        if (config.verbose) {
            std::cout << '\n';
            line_ = true;
        }
        return *this;
    }

private:
    bool line_{true};
};

extern Log logger;

/*!
 * A small timing utility based on chrono that supports timing rounds
 * and returning the median of them. Time can accumulate to the measurement
 * for each round.
 */
struct Timing {
    using Tpoint = std::chrono::steady_clock::time_point;
    using Tduration = std::chrono::microseconds;
    using microseconds = std::chrono::microseconds;
    using milliseconds = std::chrono::milliseconds;
    using seconds = std::chrono::seconds;

    //! Setup measurement rounds
    void init(size_t rounds) {
        duration_.resize(rounds);
        for (auto& d : duration_)
            d = Tduration::zero();
    }

    //! tool to mark the starting point
    Tpoint start() noexcept { return mark_ = std::chrono::steady_clock::now(); }

    //! tool to mark the ending point
    Tpoint stop() noexcept {
        Tpoint now = std::chrono::steady_clock::now();
        duration_[current_] += dt(now, mark_);
        return now;
    }

    //! Switch timing slot
    void next() noexcept {
        ++current_;
        current_ %= duration_.size();
    }

    Tduration& median() noexcept {
        std::sort(duration_.begin(), duration_.end());
        return duration_[duration_.size()/2];
    }

    //! A duration calculation utility
    static Tduration dt(Tpoint t2, Tpoint t1) noexcept {
        return std::chrono::duration_cast<Tduration>(t2 - t1);
    }

    //! Tool to print the time interval
    static void print_duration(const Tduration& duration, const char *what) noexcept {
        if (std::chrono::duration_cast<microseconds>(duration).count() < 10000)
            std::cout << "[Timing] " << what << ": "
                      << std::to_string(std::chrono::duration_cast<microseconds>(duration).count()) << " [usec]\n";
        else if (std::chrono::duration_cast<milliseconds>(duration).count() < 10000)
            std::cout << "[Timing] " << what << ": "
                      << std::to_string(std::chrono::duration_cast<milliseconds>(duration).count()) << " [msec]\n";
        else {
            char stime[26]; // fit ulong
            auto sec  = std::chrono::duration_cast<seconds>(duration).count();
            auto msec = (std::chrono::duration_cast<milliseconds>(duration).count() % 1000) / 10;  // keep 2 digit
            std::sprintf(stime, "%ld.%1ld", sec, msec);
            std::cout << "[Timing] " << what << ": " << stime << " [sec]\n";
        }

    }

private:
    size_t current_{0};
    Tpoint mark_{};
    std::vector<Tduration> duration_{1};
};

/*!
 * A "high level function"-like utility macro to forward a function call
 * and accumulate the execution time to the corresponding timing object.
 *
 * @param   Tim     The Timing object [Needs to have methods start() and stop()]
 * @param   Func    The function name
 * @param   ...     The arguments to pass to function (the preprocessor way)
 */
#define timeCall(Tim, Func, ...)    \
    Tim.start();                    \
    Func(__VA_ARGS__);              \
    Tim.stop();                     \


/*!
 * A utility to check if a number is power of two
 *
 * @tparam Integral     The integral type of the number to check
 * @param x             The number to check
 * @return              True if it is power of 2, false otherwise
 */
template <typename Integral>
constexpr inline bool isPowerOfTwo(Integral x) noexcept {
    return (!(x & (x - 1)) && x);
}


#endif /* UTILS_HPP_ */
