#ifndef _TILE_BASE_OBJECT_POOL_H
#define _TILE_BASE_OBJECT_POOL_H

#pragma once

#include "tile/base/internal/macro.h"
#include "tile/base/likely.h"
#include "tile/base/logging.h"
#include "tile/base/object_pool/disabled.h"
#include "tile/base/object_pool/global.h"
#include "tile/base/object_pool/thread_local.h"

#include <chrono>
#include <mutex>
#include <utility>

namespace tile {
enum class PoolType { Disabled, ThreadLocal, MemoryNodeShared, Global };

template<typename T>
struct PoolTraits {
    // general attribute
    // ======
    // static constexpr tile::PoolType kType = tile::PoolType::Disabled;

    // ======
    // thread_local attribute
    // static constexpr std::size_t kLowWaterMark = 0;
    // static constexpr std::size_t kHighWaterMark = 0;
    // static constexpr std::chrono::duration<A,B> kMaxIdle =
    // std::chrono::milliseconds(3000);
    //
    // 1. if the number of objects in the pool is greate than `kHighWaterMark`,
    //   the object will be destroyed.
    // 2. if the last_used time of the object is greater than `kMaxIdle` and the
    // number of objects in the pool is greater than `kLowWaterMark`, the object
    // will be destroyed.

    // hooks
    // T * Create();
    // void  Destroy(T* ptr);
    // void OnGet(T* ptr);
    // void OnPut(T* ptr);

    static_assert(sizeof(T) == 0,
                  "You need to specialize `tile::object_pool::PoolTraits` to "
                  "specify parameters before using `object_pool::Xxx`.");
};

namespace object_pool {
namespace detail {

// disabled
template<typename T>
inline tile::enable_if_t<PoolTraits<T>::kType == PoolType::Disabled> *
GetWithoutHook()
{
    return disabled::Get(*GetTypeDesc<T>());
}

template<typename T>
inline tile::enable_if_t<PoolTraits<T>::kType == PoolType::Disabled>
PutWithoutHook(void *ptr)
{
    disabled::Put(*GetTypeDesc<T>(), ptr);
}

// thread_local
template<typename T>
inline tile::enable_if_t<PoolTraits<T>::kType == PoolType::ThreadLocal> *
GetWithoutHook()
{
    return tls::Get(*GetTypeDesc<T>(), tls::GetThradLocalPool<T>());
}

template<typename T>
inline tile::enable_if_t<PoolTraits<T>::kType == PoolType::ThreadLocal>
PutWithoutHook(void *ptr)
{
    return tls::Put(*GetTypeDesc<T>(), tls::GetThradLocalPool<T>(), ptr);
}

// global
template<typename T>
inline tile::enable_if_t<PoolTraits<T>::kType == PoolType::Global> *
GetWithoutHook()
{
    return global::Get(*GetTypeDesc<T>());
}

template<typename T>
inline tile::enable_if_t<PoolTraits<T>::kType == PoolType::Global>
PutWithoutHook(void *ptr)
{
    return global::Put(*GetTypeDesc<T>(), ptr);
}

// memory node shared
template<typename T>
inline tile::enable_if_t<PoolTraits<T>::kType == PoolType::MemoryNodeShared> *
GetWithoutHook()
{
    return global::Get(*GetTypeDesc<T>());
    // return tls::Get(*GetTypeDesc<T>(), tls::GetThradLocalPool<T>());
}

template<typename T>
inline tile::enable_if_t<PoolTraits<T>::kType == PoolType::MemoryNodeShared>
PutWithoutHook(void *ptr)
{
    return global::Put(*GetTypeDesc<T>(), ptr);
    // return tls::Put(*GetTypeDesc<T>(), tls::GetThradLocalPool<T>(), ptr);
}

template<typename T>
inline void *
Get()
{
    auto rc = GetWithoutHook<T>();
    OnGetHook<T>(rc);
    return rc;
}

template<typename T>
inline void
Put(void *ptr)
{
    TILE_DCHECK(ptr,
                "I'm pretty sure null pointer is not what you got when you "
                "called `Get`.");
    OnPutHook<T>(ptr);
    PutWithoutHook<T>(ptr);
}

}// namespace detail
}// namespace object_pool

template<typename T>
class PooledPtr final {
public:
    constexpr PooledPtr();
    ~PooledPtr();

    constexpr PooledPtr(std::nullptr_t);

    constexpr explicit PooledPtr(T *ptr) : ptr_(ptr) {}

    PooledPtr(PooledPtr &&ptr) noexcept;
    PooledPtr &operator=(PooledPtr &&ptr) noexcept;
    constexpr explicit operator bool() const noexcept;

    constexpr T *Get() const noexcept;
    constexpr T *operator->() const noexcept;
    constexpr T &operator*() const noexcept;
    PooledPtr &operator=(std::nullptr_t);

    // `ptr` must be obtained from calling `Leak()` on another `PooledPtr`.
    void Reset(T *ptr = nullptr) noexcept;

    // Ownership is transferred to caller.
    TILE_NODISCARD T *Leak() noexcept;

private:
    T *ptr_;
};

namespace object_pool {
template<typename T>
PooledPtr<T>
Get()
{
    return PooledPtr<T>(static_cast<T *>(detail::Get<T>()));
}

template<typename T>
void
Put(typename std::common_type<T>::type *ptr)
{
    detail::Put<T>(ptr);
}

}// namespace object_pool

template<typename T>
constexpr PooledPtr<T>::PooledPtr() : ptr_(nullptr)
{}

template<typename T>
PooledPtr<T>::~PooledPtr()
{
    if (ptr_) { object_pool::Put<T>(ptr_); }
}

template<typename T>
PooledPtr<T>::PooledPtr(PooledPtr &&ptr) noexcept : ptr_(ptr.ptr_)
{
    ptr.ptr_ = nullptr;
}

template<typename T>
PooledPtr<T> &
PooledPtr<T>::operator=(PooledPtr &&ptr) noexcept
{
    if (TILE_LIKELY(this != &ptr)) { Reset(ptr.Leak()); }
    return *this;
}

template<typename T>
constexpr PooledPtr<T>::operator bool() const noexcept
{
    return !!ptr_;
}

template<typename T>
constexpr T *
PooledPtr<T>::Get() const noexcept
{
    return ptr_;
}

template<typename T>
constexpr T *
PooledPtr<T>::operator->() const noexcept
{
    return ptr_;
}

template<typename T>
constexpr T &
PooledPtr<T>::operator*() const noexcept
{
    return *ptr_;
}

template<typename T>
void
PooledPtr<T>::Reset(T *ptr) noexcept
{
    if (ptr_) { object_pool::Put<T>(ptr_); }
    ptr_ = ptr;
}

template<typename T>
T *
PooledPtr<T>::Leak() noexcept
{
    T *rc = nullptr;
    std::swap(rc, ptr_);
    return rc;
}

template<typename T>
constexpr bool
operator==(const PooledPtr<T> &ptr, std::nullptr_t)
{
    return ptr.Get() == nullptr;
}

template<typename T>
constexpr bool
operator==(std::nullptr_t, const PooledPtr<T> &ptr)
{
    return ptr.Get() == nullptr;
}
}// namespace tile
#endif// _TILE_BASE_OBJECT_POOL_H
