/**
* Copyright (c) NVIDIA CORPORATION & AFFILIATES, 2001-2014. ALL RIGHTS RESERVED.
* Copyright (C) UT-Battelle, LLC. 2014. ALL RIGHTS RESERVED.
* See file LICENSE for terms.
*/

#include <common/test.h>
extern "C" {
#include <ucs/time/timer_wheel.h>
}

#include <time.h>

/**
 * note: the fast timer precision is dependent on context switch latency!!!
 * expected timer precision 2x wheel resolution plus some time for processing and
 * context switching
 */
class twheel : public ucs::test {
protected:

    struct hr_timer {
        ucs_wtimer_t timer;
        int          tid;
        ucs_time_t   start_time;
        ucs_time_t   end_time;
        ucs_time_t   d;
        ucs_time_t   total_time;
        twheel       *self;
    };

    ucs_twheel_t m_wheel;

    // @override
    virtual void init();

    // @override
    virtual void cleanup();

    static void timer_func(ucs_wtimer_t *self);
    static bool check_all_timers_triggered(std::vector<struct hr_timer> &t);

    void timer_expired(struct hr_timer *t);
    void add_timer(struct hr_timer *t);
    void init_timer(struct hr_timer *t, int id);
    void init_timerv(struct hr_timer *v, int n);
    void set_timer_delta(struct hr_timer *t, int how);
};

void twheel::init()
{
    ucs_twheel_init(&m_wheel, ucs_time_from_usec(32) * ucs::test_time_multiplier(),
                    ucs_get_time());
}

void twheel::cleanup()
{
    ucs_twheel_cleanup(&m_wheel);
}

void twheel::timer_func(ucs_wtimer_t *self)
{
    struct hr_timer *t = ucs_container_of(self, struct hr_timer, timer);
    t->self->timer_expired(t);
}

bool twheel::check_all_timers_triggered(std::vector<struct hr_timer> &t)
{
    for (int i = 0; i < t.size(); i++) {
        if (t[i].end_time == (ucs_time_t)0) {
            return false;
        }
    }

    return true;
}

void twheel::timer_expired(struct hr_timer *t)
{
    t->total_time += (m_wheel.now - t->start_time);
    t->end_time   = m_wheel.now;
}

void twheel::add_timer(struct hr_timer *t)
{
    t->end_time = 0;
    ASSERT_EQ(ucs_wtimer_add(&m_wheel, &t->timer, t->d), UCS_OK);
    t->start_time = ucs_get_time();
}

void twheel::init_timer(struct hr_timer *t, int id)
{
    t->tid        = id;
    t->total_time = 0;
    t->self       = this;
    ucs_wtimer_init(&t->timer, timer_func);
}

void twheel::init_timerv(struct hr_timer *v, int n)
{
    for (int i = 0; i < n; i++) {
        init_timer(&v[i], i);
    }
}

void twheel::set_timer_delta(struct hr_timer *t, int how)
{
    int slot;

    switch (how) {
    case 0:
        /* first */
        slot = 1;
        break;
    case 1:
        /* last */
        slot = m_wheel.num_slots - 1;
        break;
    case 2:
        /* middle */
        slot = m_wheel.num_slots / 2;
        break;
    case -2:
        /* overflow */
        slot = m_wheel.num_slots + (ucs::rand() % 1000000);
        break;
    default:
        slot = 1 + ucs::rand() % (m_wheel.num_slots - 2);
        break;
    }

    if (how == -2) {
        t->d = m_wheel.res + m_wheel.res * (m_wheel.num_slots - 1) / 2;
    } else {
        t->d = m_wheel.res + m_wheel.res * slot / 2;
    }
}

#define N_LOOPS 20

UCS_TEST_SKIP_COND_F(twheel, precision_single, true) {
    // Test is broken
#if 0
    struct hr_timer t;
    ucs_time_t now;
    int i, k;
    int fail_count;

    init_timer(&t, 0);
    for (k = 0; k < 10; k++ ) {
        set_timer_delta(&t, k);
        fail_count = 0;
        for (i = 0; i < N_LOOPS; i++) {
            t.total_time = 0;
            add_timer(&t);
            do {
                now = ucs_get_time();
                ucs_twheel_sweep(&m_wheel, now);
            } while (t.end_time == 0);

            if ((ucs_time_t)::abs(t.total_time - t.d) > 2 * m_wheel.res) {
                ++fail_count;
            }
        }
        EXPECT_LE(fail_count, N_LOOPS / 3);
    }
#endif
}

#define N_TIMERS 10000

UCS_TEST_SKIP_COND_F(twheel, precision_multi, true) {
    // Test is broken
#if 0
    std::vector<struct hr_timer> t(N_TIMERS);
    ucs_time_t start, now, eps;
    init_timerv(&t[0], N_TIMERS);
    for (int i = 0; i < N_TIMERS; i++) {
        set_timer_delta(&t[i], i);
        add_timer(&t[i]);
    }

    start = ucs_get_time();
    /* all timers were delayed by at most eps */
    eps = start - m_wheel.now;
    do {
        now = ucs_get_time();
        ucs_twheel_sweep(&m_wheel, now);
    } while (now < start + m_wheel.res * m_wheel.num_slots);

    /* all timers should ve been triggered
     * correct delta
     */
    for (int i = 0; i < N_TIMERS; i++) {
        EXPECT_NE(t[i].end_time, (ucs_time_t)0);
        EXPECT_NEAR(t[i].total_time,  t[i].d, 2 * m_wheel.res + eps);
    }
#endif
}

UCS_TEST_F(twheel, add_twice) {
    struct hr_timer t;

    init_timer(&t, 0);

    set_timer_delta(&t, -1);
    add_timer(&t);

    set_timer_delta(&t, -1);
    EXPECT_EQ(ucs_wtimer_add(&m_wheel, &t.timer, t.d), UCS_ERR_BUSY);
    do {
        ucs_twheel_sweep(&m_wheel, ucs_get_time());
        /* coverity[loop_condition] */
    } while(t.end_time == 0);
}


UCS_TEST_SKIP_COND_F(twheel, add_overflow, true) {
    // Test is broken
#if 0
    struct hr_timer t;
    init_timer(&t, 0);

    t.total_time = 0;
    set_timer_delta(&t, -2);
    for (int i = 0; i < N_LOOPS; i++) {
        add_timer(&t);
        do {
            ucs_twheel_sweep(&m_wheel, ucs_get_time());
        } while (t.end_time == 0);
    }
    EXPECT_NEAR(t.total_time , t.d * N_LOOPS, 4 * N_LOOPS * m_wheel.res);
#endif
}

UCS_TEST_F(twheel, delayed_sweep) {
    std::vector<struct hr_timer> t(N_TIMERS);

    init_timerv(&t[0], N_TIMERS);
    for (int i = 0; i < N_TIMERS; i++) {
        set_timer_delta(&t[i], i);
        add_timer(&t[i]);
    }

    /* Sleep up to to seconds. Finish the test if the timers are triggered
       earlier, fail otherwise. */
    for (int i = 0; i < 10; i++) {
        sleep(1);
        ucs_twheel_sweep(&m_wheel, ucs_get_time());

        if (check_all_timers_triggered(t)) {
            return;
        }
    }

    /* all timers should have been triggered */
    GTEST_FAIL() << "Timers were not triggered after timeout";
}

