#pragma once

#ifndef WMC_CLOCK_H_INCLUDED
#define WMC_CLOCK_H_INCLUDED

#include <iosfwd>
#include <iostream>
#include <inttypes.h>
#include <math.h>
#include <sys/times.h>
#include <unistd.h>

namespace NWebmaster {

struct time_data {
    typedef uint64_t    time_t;
    time_t    user_ms;
    time_t    sys_ms;
    time_t    real_ms;

    time_data(time_t u, time_t s, time_t r) // in milliseconds
        : user_ms(u)
        , sys_ms(s)
        , real_ms(r) {}

    time_data()
        : user_ms(0)
        , sys_ms(0)
        , real_ms(0) {}

    time_data &operator-=(const time_data &other) {
        user_ms -= other.user_ms;
        sys_ms -= other.sys_ms;
        real_ms -= other.real_ms;
        return *this;
    }

    time_data &operator+=(const time_data &other) {
        user_ms += other.user_ms;
        sys_ms += other.sys_ms;
        real_ms += other.real_ms;
        return *this;
    }

    time_data &operator*=(double d) {
        user_ms = lround(user_ms * d);
        sys_ms = lround(sys_ms * d);
        real_ms = lround(real_ms * d);
        return *this;
    }

    time_data &operator/=(double d) {
        user_ms = lround(user_ms / d);
        sys_ms = lround(sys_ms / d);
        real_ms = lround(real_ms / d);
        return *this;
    }

    void reset() {
        user_ms = 0;
        sys_ms = 0;
        real_ms = 0;
    }
};

inline
time_data operator-(const time_data &one, const time_data &other) {
    time_data t(one);
    t -= other;
    return t;
}

inline
time_data operator*(const time_data &one, double d) {
    time_data t(one);
    t *= d;
    return t;
}

inline
time_data operator/(const time_data &one, double d) {
    time_data t(one);
    t /= d;
    return t;
}

class Clock { // : private time_data
public:
    Clock() {
        reset();
    }

    static unsigned int get_clk() {
        static unsigned int clk = sysconf(_SC_CLK_TCK);
        return clk;
    }

    time_data elapsed() const {
        tms     st_cpu;
        clock_t t = ::times(&st_cpu);
        time_data delta(st_cpu.tms_utime - user_tx, st_cpu.tms_stime - sys_tx, t - real_tx);
        return delta * (1000.0 / get_clk());
    }

    void  reset() {
        tms     st_cpu;
        real_tx = ::times(&st_cpu);
        user_tx = st_cpu.tms_utime;
        sys_tx = st_cpu.tms_stime;
    }

//    operator time_data() const { time_data t(user_tx, sys_tx, real_tx); t *= (1000.0 / clocksPerSecond); return t; }
private:
    uint64_t    user_tx;
    uint64_t    sys_tx;
    uint64_t    real_tx;
};

class ClockAccumulator {
public:
    ClockAccumulator(time_data &td) : td(td) {}
    ~ClockAccumulator() {
        td += elapsed();
    }

    time_data elapsed() const {
        return cl.elapsed();
    }

private:
    Clock cl;
    time_data &td;
};

inline std::ostream &operator<<(std::ostream &os, const NWebmaster::time_data &t) {
    return (os << t.user_ms << ":" << t.sys_ms << ":" << t.real_ms);
}

} // namespace NWebmaster

#endif // WMC_TRACE_H_INCLUDED
