//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

#ifndef ALLOCATORS_H
#define ALLOCATORS_H

#include <memory>
#include <new>
#include <type_traits>
#include <utility>

#include "test_macros.h"

#if TEST_STD_VER >= 11

template <class T>
class A1
{
    int id_;
public:
    explicit A1(int id = 0) TEST_NOEXCEPT : id_(id) {}

    typedef T value_type;

    int id() const {return id_;}

    static bool copy_called;
    static bool move_called;
    static bool allocate_called;
    static std::pair<T*, std::size_t> deallocate_called;

    A1(const A1& a) TEST_NOEXCEPT : id_(a.id()) {copy_called = true;}
    A1(A1&& a)      TEST_NOEXCEPT : id_(a.id()) {move_called = true;}
    A1& operator=(const A1& a) TEST_NOEXCEPT { id_ = a.id(); copy_called = true; return *this;}
    A1& operator=(A1&& a)      TEST_NOEXCEPT { id_ = a.id(); move_called = true; return *this;}

    template <class U>
        A1(const A1<U>& a) TEST_NOEXCEPT : id_(a.id()) {copy_called = true;}
    template <class U>
        A1(A1<U>&& a) TEST_NOEXCEPT : id_(a.id()) {move_called = true;}

    T* allocate(std::size_t n)
    {
        allocate_called = true;
        return (T*)n;
    }

    void deallocate(T* p, std::size_t n)
    {
        deallocate_called = std::pair<T*, std::size_t>(p, n);
    }

    std::size_t max_size() const {return id_;}
};

template <class T> bool A1<T>::copy_called = false;
template <class T> bool A1<T>::move_called = false;
template <class T> bool A1<T>::allocate_called = false;
template <class T> std::pair<T*, std::size_t> A1<T>::deallocate_called;

template <class T, class U>
inline
bool operator==(const A1<T>& x, const A1<U>& y)
{
    return x.id() == y.id();
}

template <class T, class U>
inline
bool operator!=(const A1<T>& x, const A1<U>& y)
{
    return !(x == y);
}

template <class T>
class A2
{
    int id_;
public:
    explicit A2(int id = 0) TEST_NOEXCEPT : id_(id) {}

    typedef T value_type;

    typedef unsigned size_type;
    typedef int difference_type;

    typedef std::true_type propagate_on_container_move_assignment;

    int id() const {return id_;}

    static bool copy_called;
    static bool move_called;
    static bool allocate_called;

    A2(const A2& a) TEST_NOEXCEPT : id_(a.id()) {copy_called = true;}
    A2(A2&& a)      TEST_NOEXCEPT : id_(a.id()) {move_called = true;}
    A2& operator=(const A2& a) TEST_NOEXCEPT { id_ = a.id(); copy_called = true; return *this;}
    A2& operator=(A2&& a)      TEST_NOEXCEPT { id_ = a.id(); move_called = true; return *this;}

    T* allocate(std::size_t, const void* hint)
    {
        allocate_called = true;
        return (T*) const_cast<void *>(hint);
    }
};

template <class T> bool A2<T>::copy_called = false;
template <class T> bool A2<T>::move_called = false;
template <class T> bool A2<T>::allocate_called = false;

template <class T, class U>
inline
bool operator==(const A2<T>& x, const A2<U>& y)
{
    return x.id() == y.id();
}

template <class T, class U>
inline
bool operator!=(const A2<T>& x, const A2<U>& y)
{
    return !(x == y);
}

template <class T>
class A3
{
    int id_;
public:
    explicit A3(int id = 0) TEST_NOEXCEPT : id_(id) {}

    typedef T value_type;

    typedef std::true_type propagate_on_container_copy_assignment;
    typedef std::true_type propagate_on_container_swap;

    int id() const {return id_;}

    static bool copy_called;
    static bool move_called;
    static bool constructed;
    static bool destroy_called;

    A3(const A3& a) TEST_NOEXCEPT : id_(a.id()) {copy_called = true;}
    A3(A3&& a)      TEST_NOEXCEPT : id_(a.id())  {move_called = true;}
    A3& operator=(const A3& a) TEST_NOEXCEPT { id_ = a.id(); copy_called = true; return *this;}
    A3& operator=(A3&& a)      TEST_NOEXCEPT { id_ = a.id(); move_called = true; return *this;}

    template <class U, class ...Args>
    void construct(U* p, Args&& ...args)
    {
        ::new (p) U(std::forward<Args>(args)...);
        constructed = true;
    }

    template <class U>
    void destroy(U* p)
    {
        p->~U();
        destroy_called = true;
    }

    A3 select_on_container_copy_construction() const {return A3(-1);}
};

template <class T> bool A3<T>::copy_called = false;
template <class T> bool A3<T>::move_called = false;
template <class T> bool A3<T>::constructed = false;
template <class T> bool A3<T>::destroy_called = false;

template <class T, class U>
inline
bool operator==(const A3<T>& x, const A3<U>& y)
{
    return x.id() == y.id();
}

template <class T, class U>
inline
bool operator!=(const A3<T>& x, const A3<U>& y)
{
    return !(x == y);
}

template <class T, bool POCCAValue>
class MaybePOCCAAllocator {
    int id_ = 0;
    bool* copy_assigned_into_ = nullptr;

    template<class, bool> friend class MaybePOCCAAllocator;

public:
    typedef std::integral_constant<bool, POCCAValue> propagate_on_container_copy_assignment;
    typedef T value_type;

    template <class U>
    struct rebind {
        typedef MaybePOCCAAllocator<U, POCCAValue> other;
    };

    TEST_CONSTEXPR MaybePOCCAAllocator() = default;
    TEST_CONSTEXPR MaybePOCCAAllocator(int id, bool* copy_assigned_into)
        : id_(id), copy_assigned_into_(copy_assigned_into) {}

    template <class U>
    MaybePOCCAAllocator(const MaybePOCCAAllocator<U, POCCAValue>& that)
        : id_(that.id_), copy_assigned_into_(that.copy_assigned_into_) {}

    MaybePOCCAAllocator(const MaybePOCCAAllocator&) = default;
    TEST_CONSTEXPR_CXX14 MaybePOCCAAllocator& operator=(const MaybePOCCAAllocator& a)
    {
        id_ = a.id();
        if (copy_assigned_into_)
            *copy_assigned_into_ = true;
        return *this;
    }

    TEST_CONSTEXPR_CXX20 T* allocate(std::size_t n)
    {
        return std::allocator<T>().allocate(n);
    }

    TEST_CONSTEXPR_CXX20 void deallocate(T* ptr, std::size_t n)
    {
        std::allocator<T>().deallocate(ptr, n);
    }

    TEST_CONSTEXPR int id() const { return id_; }

    template <class U>
    TEST_CONSTEXPR friend bool operator==(const MaybePOCCAAllocator& lhs, const MaybePOCCAAllocator<U, POCCAValue>& rhs)
    {
        return lhs.id() == rhs.id();
    }

    template <class U>
    TEST_CONSTEXPR friend bool operator!=(const MaybePOCCAAllocator& lhs, const MaybePOCCAAllocator<U, POCCAValue>& rhs)
    {
        return !(lhs == rhs);
    }
};

template <class T>
using POCCAAllocator = MaybePOCCAAllocator<T, /*POCCAValue = */true>;
template <class T>
using NonPOCCAAllocator = MaybePOCCAAllocator<T, /*POCCAValue = */false>;

#endif // TEST_STD_VER >= 11

#endif // ALLOCATORS_H
