//! To compile code including this header file, you'll need to links it
//! against librt. On GCC, use: g++ -lrt main.cpp -o main

#ifndef CHRONO_HPP_
#define CHRONO_HPP_

#include <stdexcept>
#include <time.h>

namespace benchmark {

namespace details {
    //! \brief Substarct two timespec structures.
    //! \param t1 The first time point.
    //! \param t2 The second time point.
    //! \return The difference between the two timespec structures.
    timespec tdiff(const timespec& t1, const timespec& t2) {
        timespec result;
        // The tv_sec member corresponds to a number of seconds while tv_nsec
        // member corresponds to the number of nanoseconds expired in the
        // current second.
        if ((t2.tv_nsec - t1.tv_nsec) < 0 /* ns */) {
            result.tv_sec = t2.tv_sec - t1.tv_sec - 1;
            result.tv_nsec = 1000000000 /* ns */ + t2.tv_nsec - t1.tv_nsec;
        } else {
            result.tv_sec = t2.tv_sec - t1.tv_sec;
            result.tv_nsec = t2.tv_nsec - t1.tv_nsec;
        }
        return result;
    }

    //! \brief Add two timespec structures.
    //! \param t1 The first time point.
    //! \param t2 The second time point.
    //! \return The difference between the two timespec structures.
    timespec tadd(const timespec& t1, const timespec& t2) {
        timespec result;
        // The tv_sec member corresponds to a number of seconds while tv_nsec
        // member corresponds to the number of nanoseconds expired in the
        // current second.
        if ((t1.tv_nsec + t2.tv_nsec) > 1000000000 /* ns */) {
            result.tv_sec = t1.tv_sec + t2.tv_sec + 1;
            result.tv_nsec = t1.tv_nsec + t2.tv_nsec - 1000000000 /* ns */;
        } else {
            result.tv_sec = t1.tv_sec + t2.tv_sec;
            result.tv_nsec = t1.tv_nsec + t2.tv_nsec;
        }
        return result;
    }
}   // namespace details

//! \brief Chronometer's clock specifier.
struct Clock {
    enum Type {
        Thread,
        Process,
    };
};

//! \brief Wrapper around the clock_gettime function.
class Chronometer {
private:
    timespec _start;    /**< Chronometer start time, relative to process start. */
    timespec _total;    /**< Total chronometer running time. */
    int _clock_id;         /**< Chronometer underlying clock (thread or process). */
    bool _stopped;      /**< Set to false on chronometer start. */

public:
    //! \brief Default chronometer's constructor.
    //!
    //! Build a process based chronometer.
    Chronometer(): _stopped(true),
        _clock_id(CLOCK_PROCESS_CPUTIME_ID) {

        _total.tv_sec = 0;
        _total.tv_nsec = 0;
    }

    //! \brief Chronometer's constructor.
    //! \param clock The clock type used by the chronometer.
    //!
    //! Build a chronometer, specifying the underlying clock.
    Chronometer(Clock::Type clock): _stopped(true) {
        if (clock == Clock::Thread) {
            _clock_id = CLOCK_THREAD_CPUTIME_ID;
        } else {
            _clock_id = CLOCK_PROCESS_CPUTIME_ID;
        }

        _total.tv_sec = 0;
        _total.tv_nsec = 0;
    }

    //! \brief Start chronometer.
    void start() throw(std::runtime_error) {
        if (_stopped) {
            if (clock_gettime(_clock_id, &_start) != 0) {
                throw std::runtime_error("Error while retrieving clock value.");
            }
            _stopped = false;
        }
    }

    //! \brief Stop chronometer.
    //! \post The elapsed process or thread time since last call to
    //! the start method is added to the current chronometer's time.
    void stop() throw(std::runtime_error) {
        if (!_stopped) {
            timespec _stop;
            if (clock_gettime(_clock_id, &_stop) != 0) {
                throw std::runtime_error("Error while retrieving clock value.");
            }
            timespec diff = details::tdiff(_start, _stop);
            _total = details::tadd(_total, diff);
            _stopped = true;
        }
    }

    //! \brief Reset chronometer's time.
    void reset() {
        _stopped = true;
        _total.tv_nsec = 0;
        _total.tv_sec = 0;
    }

    //! \brief Retrieve chronometer's second count.
    //! \return The chronometer's second count.
    long int nsec() const throw (std::logic_error) {
        if (!_stopped) {
            throw std::logic_error("Can't retrieve "
                    "time while chronometer is running.");
        }
        return _total.tv_nsec;
    }

    //! \brief Retrieve chronometer's nanosecond count.
    //! \return The chronometer's nanosecond count.
    long int sec() const throw (std::logic_error) {
        if (!_stopped) {
            throw std::logic_error("Can't retrieve "
                    "time while chronometer is running.");
        }
        return _total.tv_sec;
    }
};

}   // namespace benchmark

#endif /* CHRONO_HPP_ */