#pragma once

#include <uf/config.h>
#include <cstddef>
#include <new>
#include <type_traits>

#define UF_MEMORYPOOL_USE_STL_ALLOCATOR 1

namespace uf {
	namespace allocator {
		extern UF_API bool override;

		void* UF_API allocate( size_t n);
		void  UF_API deallocate( void* p, size_t n = 0 );

		void* UF_API malloc_m( size_t n );
		void  UF_API free_m( void* p, size_t n = 0 );
	}

	template <class T>
	struct Allocator {
		typedef T value_type;

		Allocator() = default;
		template <class U> constexpr Allocator( const Allocator<U>& ) noexcept {}

		T* allocate( size_t n ) {
			void* p = uf::allocator::allocate( n * sizeof(T) );
			if ( !p ) throw std::bad_alloc();
			return static_cast<T*>(p);
		}

		void deallocate( T* p, size_t n ) noexcept {
			uf::allocator::deallocate( p, n * sizeof(T) );
		}
	};

	template <class T, class U>
	bool operator==( const uf::Allocator<T>&, const uf::Allocator<U>& ) { return true; }

	template <class T, class U>
	bool operator!=( const uf::Allocator<T>&, const uf::Allocator<U>& ) { return false; }

	// will never ever use the pool
	template <class T>
	struct Mallocator {
		typedef T value_type;

		Mallocator() = default;
		template <class U> constexpr Mallocator( const Mallocator<U>& ) noexcept {}

		T* allocate( size_t n ) {
			void* p = uf::allocator::malloc_m( n * sizeof(T) );
			if ( !p ) throw std::bad_alloc();
			return static_cast<T*>( p );
		}

		void deallocate(T* p, size_t n) noexcept {
			uf::allocator::free_m( p, n * sizeof(T) );
		}
	};

	template <class T, class U>
	bool operator==(const uf::Mallocator<T>&, const uf::Mallocator<U>&) { return true; }

	template <class T, class U>
	bool operator!=(const uf::Mallocator<T>&, const uf::Mallocator<U>&) { return false; }
}