#include "tile/base/object_pool.h"

#include "tile/base/thread/spinlock.h"

#include "gtest/gtest.h"

#include <thread>
#include <vector>

namespace tile {

int alive = 0;

struct C {
    C() { ++alive; }

    ~C() { --alive; }
};

template<>
struct PoolTraits<C> {
    static constexpr auto kType                         = PoolType::ThreadLocal;
    static constexpr std::size_t kLowWaterMark          = 16;
    static constexpr std::size_t kHighWaterMark         = 128;
    static constexpr std::chrono::milliseconds kMaxIdle = std::chrono::milliseconds(3000);
};

constexpr PoolType PoolTraits<C>::kType;
constexpr std::size_t PoolTraits<C>::kLowWaterMark;
constexpr std::size_t PoolTraits<C>::kHighWaterMark;
constexpr std::chrono::milliseconds PoolTraits<C>::kMaxIdle;

struct D {
    int x;
    static int put_called;
};

int D::put_called = 0;

template<>
struct PoolTraits<D> {
    static constexpr auto kType                         = PoolType::ThreadLocal;
    static constexpr std::size_t kLowWaterMark          = 16;
    static constexpr std::size_t kHighWaterMark         = 128;
    static constexpr std::chrono::milliseconds kMaxIdle = std::chrono::milliseconds(3000);

    static void OnGet(D *p) { p->x = 0; }

    static void OnPut(D *) { ++D::put_called; }
};

constexpr PoolType PoolTraits<D>::kType;
constexpr std::size_t PoolTraits<D>::kLowWaterMark;
constexpr std::size_t PoolTraits<D>::kHighWaterMark;
constexpr std::chrono::milliseconds PoolTraits<D>::kMaxIdle;

template<typename Rep, typename Period>
static void
BusySleep(std::chrono::duration<Rep, Period> dur)
{
    auto end = ReadCoarseSteadyClock() + dur;
    while (ReadCoarseSteadyClock() < end) { RelaxCpu(); }
}

namespace object_pool {

TEST(ThreadLocalPool, All)
{
    auto start = ReadCoarseSteadyClock();
    // create 1000 unref objects;
    ASSERT_EQ(alive, 0);
    {
        std::vector<PooledPtr<C>> ptrs;
        for (int i = 0; i != 1000; ++i) { ptrs.push_back(Get<C>()); }
        ASSERT_EQ(alive, 1000);
        ptrs.clear();
    }
    for (int i = 0; i != 200; ++i) {
        // std::this_thread::sleep_for(std::chrono::milliseconds(10));
        BusySleep(std::chrono::milliseconds(10));
        Get<C>().Reset();// Trigger wash out if possible.
    }
    if ((ReadCoarseSteadyClock() - start) < PoolTraits<C>::kMaxIdle) {
        ASSERT_EQ(PoolTraits<C>::kHighWaterMark + PoolTraits<C>::kLowWaterMark,
                  alive);// High-water mark.
    }

    // Max idle not reached. No effect.
    Get<C>().Reset();
    if ((ReadCoarseSteadyClock() - start) < PoolTraits<C>::kMaxIdle) {
        ASSERT_EQ(PoolTraits<C>::kHighWaterMark + PoolTraits<C>::kLowWaterMark, alive);
    }

    BusySleep(std::chrono::milliseconds(5000));
    // std::this_thread::sleep_for(std::chrono::milliseconds(5000));
    for (int i = 0; i != 100; ++i) {
        Get<C>().Reset();// We have a limit on objects to loop per call. So we may
                         // need several calls to lower alive objects to low-water
                         // mark.
        BusySleep(std::chrono::milliseconds(10));
        // std::this_thread::sleep_for(
        //     std::chrono::milliseconds(10)); // The limit on wash interval..
    }
    // Low-water mark. + 1 for the object just freed (as it's fresh and won't be
    // affected by low-water mark.).
    ASSERT_EQ(PoolTraits<C>::kLowWaterMark + 1, alive);

    Put<C>(Get<C>().Leak());// Don't leak.
    ASSERT_EQ(PoolTraits<C>::kLowWaterMark + 1, alive);
}

TEST(ThreadLocalPool, OnGetHook)
{
    {
        auto ptr = Get<D>();
    }
    {
        auto ptr = Get<D>();
        ASSERT_EQ(0, ptr->x);
    }
    ASSERT_EQ(2, D::put_called);
}

}// namespace object_pool
}// namespace tile
