//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

// UNSUPPORTED: c++98, c++03

// <experimental/memory_resource>

// template <class Alloc> class resource_adaptor_imp;

// void * do_allocate(size_t size, size_t align)
// void   do_deallocate(void*, size_t, size_t)

#include <experimental/memory_resource>
#include <type_traits>
#include <memory>
#include <exception>
#include <cassert>

#include "test_macros.h"
#include "test_memory_resource.h"

namespace ex = std::experimental::pmr;

template <class Alloc>
void check_allocate_deallocate()
{
    typedef ex::resource_adaptor<Alloc> R1;
    const std::size_t max_align = alignof(std::max_align_t);

    for (std::size_t s = 1; s < 5012; ++s)
    {
        for(std::size_t align_req = 1; align_req <= (max_align * 2); align_req *= 2)
        {
            const std::size_t align_exp = align_req > max_align
                                                    ? max_align : align_req;
            AllocController P;
            R1 r{Alloc(P)};
            ex::memory_resource & m1 = r;

            void * const ret = m1.allocate(s, align_req);
            assert(P.alive == 1);
            assert(P.alloc_count == 1);
            assert(P.checkAllocAtLeast(ret, s, align_exp));

            assert(((std::size_t)ret % align_exp) == 0);

            m1.deallocate(ret, s, align_req);
            assert(P.alive == 0);
            assert(P.dealloc_count == 1);
            assert(P.checkDeallocMatchesAlloc());
        }
    }
}

void check_alloc_max_size() {
    using Alloc = NullAllocator<char>;
    using R1 = ex::resource_adaptor<Alloc>;
    const std::size_t max_align = alignof(std::max_align_t);

    auto check = [=](std::size_t s, std::size_t align_req) {
        const std::size_t align_exp = align_req > max_align
                                                ? max_align : align_req;
        AllocController P;
        R1 r{Alloc(P)};
        ex::memory_resource & m1 = r;

        void * const ret = m1.allocate(s, align_req);
        assert(P.alive == 1);
        assert(P.alloc_count == 1);
        assert(P.checkAllocAtLeast(ret, s, align_exp));

        m1.deallocate(ret, s, align_req);
        assert(P.alive == 0);
        assert(P.dealloc_count == 1);
        assert(P.checkDeallocMatchesAlloc());
    };

    const std::size_t sizeTypeMax = ~0;
    const std::size_t testSizeStart = sizeTypeMax - (max_align * 3);
    const std::size_t testSizeEnd = sizeTypeMax - max_align;

    for (std::size_t size = testSizeStart; size <= testSizeEnd; ++size) {
        for (std::size_t align=1; align <= (max_align * 2); align *= 2) {
            check(size, align);
        }
    }

#ifndef TEST_HAS_NO_EXCEPTIONS
    for (std::size_t size = sizeTypeMax; size > testSizeEnd; --size) {
        AllocController P;
        R1 r{Alloc(P)};
        ex::memory_resource & m1 = r;

        try {
            m1.allocate(size);
            assert(false);
        } catch (std::exception const&) {
        }
    }
#endif
}

int main(int, char**)
{
    check_allocate_deallocate<CountingAllocator<char>>();
    check_allocate_deallocate<MinAlignedAllocator<char>>();
    check_alloc_max_size();

  return 0;
}
