#include "lib/test.h"
#include <chrono>
#include <panda/unievent/test/AsyncTest.h>
#include <panda/unievent/Timer.h>
#include <thread>

using namespace panda;
using namespace unievent;
using namespace test;

TEST_PREFIX("timer: ", "[timer]");

static int64_t get_time() {
    using namespace std::chrono;
    return duration_cast< milliseconds >(steady_clock::now().time_since_epoch()).count();
}

#define REQUIRE_ELAPSED(T0, EXPECTED) do { \
    auto diff = get_time() - T0;           \
    REQUIRE(diff >= EXPECTED);             \
} while(0)

#define CHECK_APPROX(val, expected, dev) CHECK(abs((long)val - (long)expected) <= dev)

TEST("static once") {
    AsyncTest test(1000, 1);
    auto t0 = get_time();
    int timeout = 30;
    auto timer = Timer::create_once(timeout, [&](auto) {
        test.happens();
        REQUIRE_ELAPSED(t0, timeout);
    }, test.loop);
    test.await(timer->event);
}

TEST("static repeat") {
    AsyncTest test(1000, 3);
    int timeout = 30;
    auto t0 = get_time();
    size_t counter = 3;
    auto timer = Timer::create(timeout, [&](auto& t) {
        test.happens();
        REQUIRE_ELAPSED(t0, timeout);
        if (--counter == 0) t->stop();
        t0 = get_time();
    }, test.loop);
    test.run();
}

TEST("event listener") {
    auto s = [](auto lst) {
        TimerSP h = new Timer;
        h->event_listener(&lst);
        h->event.add([&](auto){ lst.cnt += 10; });
        h->call_now();
        CHECK(lst.cnt == 11);
    };
    SECTION("std") {
        struct Lst : ITimerListener {
            int cnt = 0;
            void on_timer (const TimerSP&) override { ++cnt; }
        };
        s(Lst());
    }
    SECTION("self") {
        struct Lst : ITimerSelfListener {
            int cnt = 0;
            void on_timer () override { ++cnt; }
        };
        s(Lst());
    }
}

TEST("due_in") {
    AsyncTest test(1000, 0);
    TimerSP t = new Timer(test.loop);
    test.loop->update_time();

    SECTION("normal") {
        t->start(10);
        CHECK_APPROX(t->due_in(), 10, 1);
        auto now = test.loop->now();
        std::this_thread::sleep_for(std::chrono::milliseconds(2));
        test.loop->update_time();
        if (test.loop->now() - now <= 3) { // protect from insane sleep
            CHECK_APPROX(t->due_in(), 8, 1);
        }
    }
    SECTION("expired") {
        t->start(1);
        std::this_thread::sleep_for(std::chrono::milliseconds(2));
        test.loop->update_time();
        CHECK(t->due_in() == 0);
    }
    SECTION("non armed") {
        CHECK(t->due_in() == 0);
    }
}

TEST("pause/resume") {
    AsyncTest test(1000, 0);
    TimerSP t = new Timer(test.loop);

    SECTION("pause") {
        t->event.add([](auto){ FAIL(); });
        t->start(2);
        test.run_nowait();
        t->pause();
        test.run();
        SUCCEED("loop inactive");
    }
    SECTION("pause inactive timer") {
        t->pause();
        test.run();
        SUCCEED("ok");
    }
    SECTION("resume") {
        test.set_expected(1);
        t->event.add([](auto){ FAIL(); });
        t->start(40);
        std::this_thread::sleep_for(std::chrono::milliseconds(20));
        test.run_nowait();
        SECTION("normal") {
            t->pause();
        }
        SECTION("pause paused timer is no-op") {
            t->pause();
            t->pause();
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(30));
        test.run_nowait();
        t->resume();
        CHECK(t->due_in() <= 20);
        t->event.remove_all();
        t->event.add([&](auto){ test.happens(); });
        std::this_thread::sleep_for(std::chrono::milliseconds(30));
        test.run_nowait();
    }
    SECTION("resume non-paused timer") {
        SECTION("active") {
            t->start(1);
            auto res = t->resume();
            REQUIRE(!res);
            CHECK(res.error() & std::errc::invalid_argument);
        }

        SECTION("stopped") {
            t->start(1);
            t->stop();
            auto res = t->resume();
            REQUIRE(!res);
            CHECK(res.error() & std::errc::invalid_argument);
        }

        SECTION("paused&stopped") {
            t->start(1);
            t->pause();
            t->stop();
            auto res = t->resume();
            REQUIRE(!res);
            CHECK(res.error() & std::errc::invalid_argument);
        }
    }
}