#include "tile/base/object_pool/types.h"

#include "gtest/gtest.h"

namespace tile {
struct HasObject {};

struct NoObject {};

template<>
struct PoolTraits<HasObject> {
    static void *Create()
    {
        last_create_ptr = new HasObject();
        return last_create_ptr;
    }

    static void Destroy(void *p)
    {
        last_destroy_ptr = p;
        delete static_cast<HasObject *>(p);
    }

    static void OnGet(void *p) { last_get_ptr = p; }

    static void OnPut(void *p) { last_put_ptr = p; }

    static void *last_create_ptr;
    static void *last_destroy_ptr;
    static void *last_get_ptr;
    static void *last_put_ptr;
};

void *PoolTraits<HasObject>::last_create_ptr  = nullptr;
void *PoolTraits<HasObject>::last_get_ptr     = nullptr;
void *PoolTraits<HasObject>::last_put_ptr     = nullptr;
void *PoolTraits<HasObject>::last_destroy_ptr = nullptr;

namespace object_pool {
namespace detail {

TEST(PoolTraitsHas, All)
{
    ASSERT_TRUE(PoolTraitsHasCreate<HasObject>::value);
    ASSERT_TRUE(PoolTraitsHasDestroy<HasObject>::value);
    ASSERT_TRUE(PoolTraitsHasOnGet<HasObject>::value);
    ASSERT_TRUE(PoolTraitsHasOnPut<HasObject>::value);

    ASSERT_FALSE(PoolTraitsHasCreate<NoObject>::value);
    ASSERT_FALSE(PoolTraitsHasDestroy<NoObject>::value);
    ASSERT_FALSE(PoolTraitsHasOnGet<NoObject>::value);
    ASSERT_FALSE(PoolTraitsHasOnPut<NoObject>::value);
}

TEST(PoolTraitsInvikeHook, HasPoolTraits)
{
    void *obj = CreateObject<HasObject>();
    ASSERT_EQ(obj, PoolTraits<HasObject>::last_create_ptr);
    OnGetHook<HasObject>(obj);
    ASSERT_EQ(obj, PoolTraits<HasObject>::last_get_ptr);
    OnPutHook<HasObject>(obj);
    ASSERT_EQ(obj, PoolTraits<HasObject>::last_put_ptr);
    DestroyObject<HasObject>(obj);
    ASSERT_EQ(obj, PoolTraits<HasObject>::last_destroy_ptr);
}

TEST(PoolTraitsInvikeHook, NoPoolTraits)
{
    void *obj = CreateObject<NoObject>();
    ASSERT_NE(obj, nullptr);
    OnGetHook<NoObject>(obj);
    OnPutHook<NoObject>(obj);
    DestroyObject<NoObject>(obj);
}

TEST(PoolTraits, CreateAndDestroy)
{
    int active = 0;

    struct NoObjectTest {
        NoObjectTest() {}

        ~NoObjectTest() { --*val; }

        void SetValAndAdd(int *val)
        {
            this->val = val;
            ++*val;
        }

        int *val;
    };

    std::vector<void *> ptrs;
    for (int i = 0; i < 100; ++i) {
        ASSERT_EQ(active, i);
        ptrs.push_back(CreateObject<NoObjectTest>());
        static_cast<NoObjectTest *>(ptrs.back())->SetValAndAdd(&active);
        ASSERT_EQ(active, i + 1);
    }

    while (!ptrs.empty()) {
        ASSERT_EQ(active, ptrs.size());
        DestroyObject<NoObjectTest>(ptrs.back());
        ptrs.pop_back();
        ASSERT_EQ(active, ptrs.size());
    }
}

TEST(TypeDescriptor, GetTypeDesc)
{
    const TypeDescriptor *desc = GetTypeDesc<NoObject>();
    void *obj                  = desc->create();
    ASSERT_NE(obj, nullptr);
    desc->destroy(obj);
}

}// namespace detail
}// namespace object_pool
}// namespace tile
