#ifndef TILE_BASE_OBJECT_POOL_TYPES_H
#define TILE_BASE_OBJECT_POOL_TYPES_H

#pragma once
#include "tile/base/internal/meta.h"
#include "tile/base/logging.h"
#include "tile/base/make_unique.h"
#include "tile/base/type_index.h"

#include <memory>

/*

template <class F>
constexpr auto is_valid(F&& f) {
  return [f = std::forward<F>(f)](auto&&... args) constexpr {
    // FIXME: Perfect forwarding.
    return std::is_invocable_v<F, decltype(args)...>;
  };
}

// `x` should be used as placeholder's name in` expr`.
#define FLARE_INTERNAL_IS_VALID(expr) \
  ::flare::internal::is_valid([](auto&& x) -> decltype(expr) {})
*/

namespace tile {
template<typename T>
struct PoolTraits;
}

namespace tile {
namespace object_pool {
namespace detail {
struct TypeDescriptor {
    TypeIndex type;

    void *(*create)();
    void (*destroy)(void *);
};

// Has PoolTraits<T>::Create()
template<typename T>
struct PoolTraitsHasCreate {
    template<typename U, decltype(std::declval<PoolTraits<U>>().Create()) * = nullptr>
    static int Test(int);
    template<typename>
    static char Test(...);
    static constexpr bool value = std::is_same<decltype(Test<T>(0)), int>::value;
};

// Has PoolTraits<T>::Destroy(T*)
template<typename T>
struct PoolTraitsHasDestroy {
    template<typename U, decltype(std::declval<PoolTraits<U>>().Destroy(std::declval<U *>())) * = nullptr>
    static int Test(int);
    template<typename>
    static char Test(...);
    static constexpr bool value = std::is_same<decltype(Test<T>(0)), int>::value;
};

// Has PoolTraits<T>::OnGet(T*)
template<typename T>
struct PoolTraitsHasOnGet {
    template<typename U, decltype(std::declval<PoolTraits<U>>().OnGet(std::declval<U *>())) * = nullptr>
    static int Test(int);
    template<typename>
    static char Test(...);
    static constexpr bool value = std::is_same<decltype(Test<T>(0)), int>::value;
};

// Has PoolTraits<T>::OnPut(T*)
template<typename T>
struct PoolTraitsHasOnPut {
    template<typename U, decltype(std::declval<PoolTraits<U>>().OnPut(std::declval<U *>())) * = nullptr>
    static int Test(int);
    template<typename>
    static char Test(...);
    static constexpr bool value = std::is_same<decltype(Test<T>(0)), int>::value;
};

template<typename T>
constexpr bool PoolTraitsHasCreate<T>::value;
template<typename T>
constexpr bool PoolTraitsHasDestroy<T>::value;
template<typename T>
constexpr bool PoolTraitsHasOnGet<T>::value;
template<typename T>
constexpr bool PoolTraitsHasOnPut<T>::value;

template<typename T>
struct PoolTraitsInvokeHook {
    template<typename U, enable_if_t<PoolTraitsHasOnPut<U>::value> * = nullptr>
    static void OnPutProxy(void *p)
    {
        PoolTraits<U>::OnPut(static_cast<T *>(p));
    }

    template<typename U, enable_if_t<!PoolTraitsHasOnPut<U>::value> * = nullptr>
    static void OnPutProxy(void *p)
    {}

    template<typename U, enable_if_t<PoolTraitsHasOnGet<U>::value> * = nullptr>
    static void OnGetProxy(void *p)
    {
        PoolTraits<U>::OnGet(static_cast<T *>(p));
    }

    template<typename U, enable_if_t<!PoolTraitsHasOnGet<U>::value> * = nullptr>
    static void OnGetProxy(void *p)
    {}

    template<typename U, enable_if_t<PoolTraitsHasDestroy<U>::value> * = nullptr>
    static void OnDestroyProxy(void *p)
    {
        PoolTraits<U>::Destroy(static_cast<T *>(p));
    }

    template<typename U, enable_if_t<!PoolTraitsHasDestroy<U>::value> * = nullptr>
    static void OnDestroyProxy(void *p)
    {
        delete static_cast<T *>(p);
    }

    template<typename U, enable_if_t<PoolTraitsHasCreate<U>::value> * = nullptr>
    static void *OnCreateProxy()
    {
        return static_cast<void *>(PoolTraits<U>::Create());
    }

    template<typename U, enable_if_t<!PoolTraitsHasCreate<U>::value> * = nullptr>
    static void *OnCreateProxy()
    {
        return new U();
    }

    static void OnPut(void *p) { OnPutProxy<T>(p); }

    static void OnGet(void *p) { OnGetProxy<T>(p); }

    static void *Create() { return OnCreateProxy<T>(); }

    static void Destroy(void *p) { OnDestroyProxy<T>(p); }
};

template<typename T>
void
OnPutHook(void *p)
{
    PoolTraitsInvokeHook<T>::OnPut(p);
    // if (PoolTraitsHasOnPut<T>::value) {
    //   PoolTraits<T>::OnPut(static_cast<T *>(p));
    // }
}

template<typename T>
void
OnGetHook(void *p)
{
    PoolTraitsInvokeHook<T>::OnGet(p);
    // if (PoolTraitsHasOnGet<T>::value) {
    //   PoolTraits<T>::OnGet(static_cast<T *>(p));
    // }
}

template<typename T>
void *
CreateObject()
{
    return PoolTraitsInvokeHook<T>::Create();
    // if (PoolTraitsHasCreate<T>::value) {
    //   return PoolTraits<T>::Create();
    // } else {
    //   return new T();
    // }
}

template<typename T>
void
DestroyObject(void *ptr)
{
    PoolTraitsInvokeHook<T>::Destroy(ptr);
    // auto p = static_cast<T *>(ptr);
    // if (PoolTraitsHasDestroy<T>::value) {
    //   PoolTraits<T>::Destroy(p);
    // } else {
    //   delete p;
    // }
}

template<typename T>
const TypeDescriptor *
GetTypeDesc()
{
    static const TypeDescriptor desc = {.type    = GetTypeIndex<T>(),
                                        .create  = CreateObject<T>,
                                        .destroy = DestroyObject<T>};
    return &desc;
}

class FixedVector {
public:
    FixedVector() = default;

    explicit FixedVector(void (*deleter)(void *), std::size_t size)
        : deleter_(deleter),
          // objects_(std::unique_ptr<void *[]>(new void *[size])),
          objects_(make_unique<void *[]>(size)),
          current_(objects_.get()),
          end_(objects_.get() + size)
    {
        TILE_CHECK(deleter_);
        TILE_CHECK(size >= 0);
    }

    ~FixedVector()
    {
        while (!empty()) { deleter_(pop_back()); }
    }

    bool empty() const noexcept { return current_ == objects_.get(); }

    std::size_t size() const noexcept { return current_ - objects_.get(); }

    bool full() const noexcept { return current_ == end_; }

    void emplace_back(void *ptr) noexcept
    {
        TILE_DCHECK_LT(current_, end_);
        TILE_DCHECK_GE(current_, objects_.get());
        *current_++ = ptr;
    }

    void *pop_back() noexcept
    {
        TILE_DCHECK_LE(current_, end_);
        TILE_DCHECK_GE(current_, objects_.get());
        return *--current_;
    }

    void refill_from(void **from, std::size_t count)
    {
        TILE_DCHECK(empty());
        TILE_DCHECK_LE(count, end_ - objects_.get());
        memcpy(objects_.get(), from, count * sizeof(void *));
        current_ = objects_.get() + count;
    }

    void **move_out(std::size_t count)
    {
        TILE_DCHECK_LE(count, size());
        current_ -= count;
        return current_;
    }

private:
    void (*deleter_)(void *);
    std::unique_ptr<void *[]> objects_;
    void **current_;
    void **end_;
};

}// namespace detail

}// namespace object_pool

}// namespace tile

#endif// TILE_BASE_OBJECT_POOL_TYPES_H
