#pragma once

#include "../../C/Baselib_Memory.h"
#include "../../Cpp/Algorithm.h"

// Internal, to enable override of default C Api implementation for unit-tests
#ifndef detail_BASELIB_PAGE_ALLOCATOR_TEST_IMPL
#define detail_BASELIB_PAGE_ALLOCATOR_TEST_IMPL 0
#endif

namespace baselib
{
    BASELIB_CPP_INTERFACE
    {
        namespace detail
        {
            // Default memory allocation methods
            struct page_allocator_impl
            {
                static constexpr auto Baselib_Memory_AllocatePagesEx = ::Baselib_Memory_AllocatePagesEx;
                static constexpr auto Baselib_Memory_ReleasePages      = ::Baselib_Memory_ReleasePages;
                static constexpr auto Baselib_Memory_SetPageStateEx  = ::Baselib_Memory_SetPageStateEx;
            };

            // Test memory allocation methods
            struct page_allocator_impl_test
            {
                static Baselib_Memory_PageAllocation Baselib_Memory_AllocatePagesEx(uint64_t pageSize, uint64_t pageCount, uint64_t alignmentInMultipleOfPageSize, Baselib_Memory_PageState pageState, uint32_t pageStateExt, Baselib_ErrorState* errorState);
                static void Baselib_Memory_ReleasePages(Baselib_Memory_PageAllocation pageAllocation, Baselib_ErrorState* errorState);
                static void Baselib_Memory_SetPageStateEx(void* addressOfFirstPage, uint64_t pageSize, uint64_t pageCount, Baselib_Memory_PageState pageState, uint32_t pageStateExt, Baselib_ErrorState* errorState);
            };

            typedef enum Memory_PageState : int
            {
                Memory_PageState_Reserved             = Baselib_Memory_PageState_Reserved,
                Memory_PageState_NoAccess             = Baselib_Memory_PageState_NoAccess,
                Memory_PageState_ReadOnly             = Baselib_Memory_PageState_ReadOnly,
                Memory_PageState_ReadWrite            = Baselib_Memory_PageState_ReadWrite,
                Memory_PageState_ReadOnly_Executable  = Baselib_Memory_PageState_ReadOnly_Executable | Baselib_Memory_PageState_ReadOnly,
                Memory_PageState_ReadWrite_Executable = Baselib_Memory_PageState_ReadWrite_Executable | Baselib_Memory_PageState_ReadWrite,
            } Memory_PageState;

            template<uint32_t alignment>
            class page_allocator
            {
                // Use test memory allocation implementation if detail_BASELIB_HEAP_ALLOCATOR_TEST_IMPL is true
                using Impl = typename std::conditional<detail_BASELIB_PAGE_ALLOCATOR_TEST_IMPL, page_allocator_impl_test, page_allocator_impl>::type;

                const size_t m_PageSize;
                const size_t m_PageSizeAligned;

                FORCE_INLINE constexpr size_t PagedCountFromSize(size_t size) const
                {
                    return (size + (m_PageSize - 1)) / m_PageSize;
                }

                FORCE_INLINE size_t DefaultPageSize() const
                {
                    Baselib_Memory_PageSizeInfo info;
                    Baselib_Memory_GetPageSizeInfo(&info);
                    return static_cast<size_t>(info.defaultPageSize);
                }

            public:
                page_allocator() : page_allocator(DefaultPageSize()) {}
                page_allocator(size_t page_size) : m_PageSize(page_size), m_PageSizeAligned(page_size > alignment ? page_size : alignment) {}

                void* allocate(size_t size, int state, Baselib_ErrorState *error_state_ptr, uint32_t ext_page_state) const
                {
                    Baselib_Memory_PageAllocation pa = Impl::Baselib_Memory_AllocatePagesEx(m_PageSize, PagedCountFromSize(size), m_PageSizeAligned / m_PageSize, (Baselib_Memory_PageState)state, ext_page_state, error_state_ptr);
                    return pa.ptr;
                }

                bool deallocate(void* ptr, size_t size, Baselib_ErrorState *error_state_ptr) const
                {
                    Impl::Baselib_Memory_ReleasePages({ptr, m_PageSize, PagedCountFromSize(size)}, error_state_ptr);
                    return (error_state_ptr->code == Baselib_ErrorCode_Success);
                }

                constexpr size_t optimal_size(size_t size) const
                {
                    return (size + m_PageSizeAligned - 1) & ~(m_PageSizeAligned - 1);
                }

                bool set_page_state(void* ptr, size_t size, int state, Baselib_ErrorState *error_state_ptr, uint32_t ext_page_state) const
                {
                    Impl::Baselib_Memory_SetPageStateEx(ptr, m_PageSize, PagedCountFromSize(size), (Baselib_Memory_PageState)state, ext_page_state, error_state_ptr);
                    return (error_state_ptr->code == Baselib_ErrorCode_Success);
                }
            };
        }
    }
}

#undef detail_BASELIB_PAGE_ALLOCATOR_TEST_IMPL
