// =========================================================== //
//                                                             //
//   File      : arb_sleep.h                                   //
//   Purpose   :                                               //
//                                                             //
//   Coded by Ralf Westram (coder@reallysoft.de) in May 2013   //
//   Institute of Microbiology (Technical University Munich)   //
//   http://www.arb-home.de/                                   //
//                                                             //
// =========================================================== //

#ifndef ARB_SLEEP_H
#define ARB_SLEEP_H

#ifndef _UNISTD_H
#include <unistd.h>
#endif
#ifndef _GLIBCXX_ALGORITHM
#include <algorithm>
#endif
#ifndef _TIME_H
#include <time.h>
#endif
#ifndef _SYS_TIME_H
#include <sys/time.h>
#endif

// #define TRACE_SLEEP

enum TimeUnit { USEC = 1, MS = 1000, SEC = 1000*MS };

inline void ARB_sleep(int amount, TimeUnit tu) {
    arb_assert(amount>=0);
    arb_assert(amount<1000*1000); // use different TimeUnit

    struct timespec t;
    switch (tu) {
        case USEC:
            t.tv_sec  = 0;
            t.tv_nsec = amount*1000;
            break;
        case MS:
            t.tv_sec  = amount/MS;
            t.tv_nsec = (amount-t.tv_sec*MS)*MS*1000;
            break;
        case SEC:
            t.tv_sec  = amount;
            t.tv_nsec = 0;
            break;
    }

    arb_assert(t.tv_sec>=0);
    arb_assert(t.tv_nsec>=0 && t.tv_nsec<=999999999);

    while (1) {
        struct timespec remain;

        int res = nanosleep(&t, &remain);
        if (res == 0) break;
        // nanosleep has been interrupted by signal -> call again
        t = remain;
    }
}

#if defined(TRACE_SLEEP)
inline const char *timeUnitAbbr(TimeUnit tu) {
    switch (tu) {
        case USEC: return "usec";
        case MS: return "ms";
        case SEC: return "s";
    }
    arb_assert(0);
    return "";
}
#endif

class ARB_inc_sleep {
    int      curr_wait, max_wait, inc;
    TimeUnit unit;

    void slowdown() { curr_wait = std::min(max_wait, curr_wait+inc); }
public:
    ARB_inc_sleep(int min_amount, int max_amount, TimeUnit tu, int increment)
        : curr_wait(min_amount),
          max_wait(max_amount),
          inc(increment),
          unit(tu)
    {
        arb_assert(curr_wait>0);
        arb_assert(max_amount>=curr_wait);
        arb_assert(increment>0);
    }

    void sleep() {
#if defined(TRACE_SLEEP)
        fprintf(stderr, "pid %i waits %i %s\n", getpid(), curr_wait, timeUnitAbbr(unit));
#endif
        ARB_sleep(curr_wait, unit);
        slowdown();
    }
};

class ARB_timestamp {
    // timer which can be asked how much time passed since it was initialized
    timeval t1;

public:
    ARB_timestamp() { update(); }
    void update() { gettimeofday(&t1, NULp); }

    long usec_since() const {
        timeval t2;
        gettimeofday(&t2, NULp);
        return (t2.tv_sec - t1.tv_sec) * SEC + (t2.tv_usec - t1.tv_usec);
    }
    long ms_since() const { return usec_since()/MS; }
    long sec_since() const { return usec_since()/SEC; }
};

class ARB_timeout {
    // short timeout
    ARB_timestamp start;
    long          amount_usec;
public:
    ARB_timeout(int amount, TimeUnit tu)
        : amount_usec(amount*tu)
    {}
    bool passed() const {
        return start.usec_since()>=amount_usec;
    }

    void restart(double factor) {
        // restart and multiply timeout by factor
        // (use 1.0 to restart with previous timeout)

        start.update();
        amount_usec = amount_usec * factor + 0.5;
    }
};


#else
#error arb_sleep.h included twice
#endif // ARB_SLEEP_H
