#pragma once

#include <new>
#include <utility>

#include "libipc/def.h"

namespace ipc {
namespace mem {

class pool_alloc {
public:
    static void* alloc(std::size_t size);
    static void  free (void* p, std::size_t size);
};

////////////////////////////////////////////////////////////////
/// construct/destruct an object
////////////////////////////////////////////////////////////////

namespace detail {

template <typename T>
struct impl {
    template <typename... P>
    static T* construct(T* p, P&&... params) {
        ::new (p) T(std::forward<P>(params)...);
        return p;
    }

    static void destruct(T* p) {
        reinterpret_cast<T*>(p)->~T();
    }
};

template <typename T, size_t N>
struct impl<T[N]> {
    using type = T[N];

    template <typename... P>
    static type* construct(type* p, P&&... params) {
        for (size_t i = 0; i < N; ++i) {
            impl<T>::construct(&((*p)[i]), std::forward<P>(params)...);
        }
        return p;
    }

    static void destruct(type* p) {
        for (size_t i = 0; i < N; ++i) {
            impl<T>::destruct(&((*p)[i]));
        }
    }
};

} // namespace detail

template <typename T, typename... P>
T* construct(T* p, P&&... params) {
    return detail::impl<T>::construct(p, std::forward<P>(params)...);
}

template <typename T, typename... P>
T* construct(void* p, P&&... params) {
    return construct(static_cast<T*>(p), std::forward<P>(params)...);
}

template <typename T>
void destruct(T* p) {
    return detail::impl<T>::destruct(p);
}

template <typename T>
void destruct(void* p) {
    destruct(static_cast<T*>(p));
}

////////////////////////////////////////////////////////////////
/// general alloc/free
////////////////////////////////////////////////////////////////

inline void* alloc(std::size_t size) {
    return pool_alloc::alloc(size);
}

template <typename T, typename... P>
T* alloc(P&&... params) {
    return construct<T>(pool_alloc::alloc(sizeof(T)), std::forward<P>(params)...);
}

inline void free(void* p, std::size_t size) {
    pool_alloc::free(p, size);
}

template <typename T>
void free(T* p) {
    if (p == nullptr) return;
    destruct(p);
    pool_alloc::free(p, sizeof(T));
}

} // namespace mem
} // namespace ipc