#pragma once

#include "nalf_alloc.h"

struct TNoHeapAlloc {
    void* operator new(size_t);
    void* operator new[](size_t);

    // implemented and available for gcc virtual destructors
protected:
    void operator delete(void*) {
        Y_FAIL();
    }
    void operator delete[](void*) {
        Y_FAIL();
    }

    void operator delete(void*, const std::nothrow_t&) {
        Y_FAIL();
    }
    void operator delete[](void*, const std::nothrow_t&) {
        Y_FAIL();
    }
};

template <typename TFinal>
struct TSystemAllocHelper {
    // override new/delete to happen with system-alloc, system-free, useful for structures which could be allocated before allocator setup is complete
    // (allocator themself)

    void* operator new(size_t sz) {
        Y_VERIFY(sz == sizeof(TFinal));
        return NNumaAwareLockFreeAllocator::SystemAllocation(sz);
    }

    void* operator new[](size_t sz) {
        Y_VERIFY(sz == sizeof(TFinal));
        return NNumaAwareLockFreeAllocator::SystemAllocation(sz);
    }

    void operator delete(void* mem) {
        NNumaAwareLockFreeAllocator::SystemFree(mem, sizeof(TFinal));
    }

    void operator delete[](void* mem) {
        NNumaAwareLockFreeAllocator::SystemFree(mem, sizeof(TFinal));
    }

    void operator delete(void* mem, const std::nothrow_t&) {
        NNumaAwareLockFreeAllocator::SystemFree(mem, sizeof(TFinal));
    }

    void operator delete[](void* mem, const std::nothrow_t&) {
        NNumaAwareLockFreeAllocator::SystemFree(mem, sizeof(TFinal));
    }
};

template <NNumaAwareLockFreeAllocator::TAllocHint::EHint H>
struct TWithNalfAlloc {
#if !defined(NALF_FORCE_MALLOC_FREE)
    // override new/delete to happen with nalf
    void* operator new(size_t sz) {
        return NNumaAwareLockFreeAllocator::Allocate(sz, H);
    }

    void* operator new[](size_t sz) {
        return NNumaAwareLockFreeAllocator::Allocate(sz, H);
    }

    void operator delete(void* mem) {
        NNumaAwareLockFreeAllocator::Free(mem);
    }

    void operator delete[](void* mem) {
        NNumaAwareLockFreeAllocator::Free(mem);
    }

    void operator delete(void* mem, const std::nothrow_t&) {
        NNumaAwareLockFreeAllocator::Free(mem);
    }

    void operator delete[](void* mem, const std::nothrow_t&) {
        NNumaAwareLockFreeAllocator::Free(mem);
    }
#endif // NALF_FORCE_MALLOC_FREE
};

struct TWithNalfIncrementalAlloc : TWithNalfAlloc<NNumaAwareLockFreeAllocator::TAllocHint::Incremental> {};
struct TWithNalfForceIncrementalAlloc : TWithNalfAlloc<NNumaAwareLockFreeAllocator::TAllocHint::ForceIncremental> {};
struct TWithNalfChunkedAlloc : TWithNalfAlloc<NNumaAwareLockFreeAllocator::TAllocHint::Chunked> {};
struct TWithNalfForceChunkedAlloc : TWithNalfAlloc<NNumaAwareLockFreeAllocator::TAllocHint::ForceChunked> {};
struct TWithNalfSystemAlloc : TWithNalfAlloc<NNumaAwareLockFreeAllocator::TAllocHint::System> {};
struct TWithNalfForceSystemAlloc : TWithNalfAlloc<NNumaAwareLockFreeAllocator::TAllocHint::ForceSystem> {};

using TAllocSwapIncremental = NNumaAwareLockFreeAllocator::TSwapHint<NNumaAwareLockFreeAllocator::TAllocHint::Incremental>;
using TAllocSwapChunked = NNumaAwareLockFreeAllocator::TSwapHint<NNumaAwareLockFreeAllocator::TAllocHint::Chunked>;

template <typename Type, NNumaAwareLockFreeAllocator::TAllocHint::EHint Hint>
struct TNalfAllocator {
    typedef Type value_type;
    typedef Type* pointer;
    typedef const Type* const_pointer;
    typedef Type& reference;
    typedef const Type& const_reference;
    typedef size_t size_type;
    typedef ptrdiff_t difference_type;

    TNalfAllocator() noexcept = default;
    ~TNalfAllocator() noexcept = default;

    template <typename U>
    explicit TNalfAllocator(TNalfAllocator<U, Hint>) noexcept {};
    template <typename U>
    struct rebind { typedef TNalfAllocator<U, Hint> other; };

    static pointer allocate(size_type n, const void* = nullptr) {
        return static_cast<pointer>(NNumaAwareLockFreeAllocator::Allocate(n * sizeof(value_type), Hint));
    }

    static void deallocate(pointer p, size_type = 0U) {
        return NNumaAwareLockFreeAllocator::Free(p);
    }

    static constexpr size_type max_size() noexcept {
        return std::numeric_limits<size_type>::max() / sizeof(value_type);
    }

    template <typename U, typename... Args>
    static void construct(U* p, Args&&... args) {
        ::new ((void*)p) U(std::forward<Args>(args)...);
    }

    template <typename U>
    static void destroy(U* p) {
        return p->~U();
    }

    static pointer address(reference x) noexcept {
        return std::addressof(x);
    }

    static const_pointer address(const_reference x) noexcept {
        return std::addressof(x);
    }
};

template <typename Type>
using TNalfChunkedAllocator = TNalfAllocator<Type, NNumaAwareLockFreeAllocator::TAllocHint::Chunked>;
template <typename Type>
using TNalfIncrementalAllocator = TNalfAllocator<Type, NNumaAwareLockFreeAllocator::TAllocHint::Incremental>;
