#include "tile/base/internal/time_keeper.h"

#include "tile/base/chrono.h"
#include "tile/base/ref_ptr.h"
#include "tile/testing/main.h"

#include "gtest/gtest.h"

#include <vector>

namespace tile {
namespace internal {

constexpr auto kTestDuration = std::chrono::seconds(5);
constexpr auto kInterval     = std::chrono::milliseconds(100);
constexpr auto kTimerNum     = 100;
constexpr auto kExpFactor    = 0.5;

TEST(TimeKeeper, OneFastTimer)
{
    int x         = 0;
    auto timer_id = TimeKeeper::Instance()->AddTimer(ReadCoarseSteadyClock(), kInterval, [&](TimerID) { ++x; }, false);
    std::this_thread::sleep_for(kTestDuration);
    TimeKeeper::Instance()->KillTimer(timer_id);
    constexpr auto expires_count = kTestDuration / kInterval;
    constexpr auto eps           = expires_count * kExpFactor;
    ASSERT_NEAR(x, expires_count, eps);
}

TEST(TimeKeeper, SlowTimer)
{
    std::vector<TimerID> timers;
    std::atomic<int> x{0};

    for (int i = 0; i != kTimerNum; ++i) {
        timers.push_back(TimeKeeper::Instance()->AddTimer(ReadSteadyClock(), kInterval, [&](TimerID) { ++x; }, true));
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
    }
    std::this_thread::sleep_for(2 * kTestDuration);
    for (auto &&e : timers) { TimeKeeper::Instance()->KillTimer(e); }

    const auto expires_count = kTimerNum * (2 * kTestDuration / kInterval);
    const auto eps           = expires_count * kExpFactor;
    ASSERT_NEAR(x, expires_count, eps);
}

TEST(TimeKeeper, FastTimer)
{
    std::vector<TimerID> timers;
    std::atomic<int> x{0};

    for (int i = 0; i != kTimerNum; ++i) {
        timers.push_back(TimeKeeper::Instance()->AddTimer(ReadSteadyClock(), kInterval, [&](TimerID) { ++x; }, false));
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
    }
    std::this_thread::sleep_for(2 * kTestDuration);
    for (auto &&e : timers) { TimeKeeper::Instance()->KillTimer(e); }

    const auto expires_count = kTimerNum * (2 * kTestDuration / kInterval);
    const auto eps           = expires_count * kExpFactor;
    ASSERT_NEAR(x, expires_count, eps);
}

TEST(TimeKeeper, TimerIDConv)
{
    TimeKeeper::EntryPtr ptr = MakeRefCounted<TimeKeeper::Entry>();
    auto timer_id            = reinterpret_cast<TimerID>(ptr.Get());
    auto ptr2                = reinterpret_cast<TimeKeeper::Entry *>(timer_id);
    ASSERT_EQ(ptr.Get(), ptr2);
    ASSERT_NE(ptr2, nullptr);
}

}// namespace internal
}// namespace tile
