//===----------------------------------------------------------------------===//
//
// 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 SUPPORT_CONTAINER_TEST_TYPES_H
#define SUPPORT_CONTAINER_TEST_TYPES_H

// container_test_types.h - A set of types used for testing STL containers.
// The types container within this header are used to test the requirements in
// [container.requirements.general]. The header is made up of 3 main components:
//
// * test-types: 'CopyInsertable', 'MoveInsertable' and 'EmplaceConstructible' -
//    These test types are used to test the container requirements of the same
//    name. These test types use the global 'AllocatorConstructController' to
//    assert that they are only constructed by the containers allocator.
//
// * test-allocator: 'ContainerTestAllocator' - This test allocator is used to
//    test the portions of [container.requirements.general] that pertain to the
//    containers allocator. The three primary jobs of the test allocator are:
//      1. Enforce that 'a.construct(...)' and 'a.destroy(...)' are only ever
//         instantiated for 'Container::value_type'.
//      2. Provide a mechanism of checking calls to 'a.construct(Args...)'.
//         Including controlling when and with what types 'a.construct(...)'
//         may be called with.
//      3. Support the test types internals by controlling the global
//        'AllocatorConstructController' object.
//
// * 'AllocatorConstructController' - This type defines an interface for testing
//   the construction of types using an allocator. This type is used to communicate
//   between the test author, the containers allocator, and the types
//   being constructed by the container.
//   The controller's primary functions are:
//     1. Allow calls to 'a.construct(p, args...)' to be checked by a test.
//        The test uses 'cc->expect<Args...>()' to specify that the allocator
//        should expect one call to 'a.construct' with the specified argument
//        types.
//     2. Controlling the value of 'cc->isInAllocatorConstruct()' within the
//        'construct' method. The test-types use this value to assert that
//         they are being constructed by the allocator.
//
//   'AllocatorConstructController' enforces the Singleton pattern since the
//    test-types, test-allocator and test need to share the same controller
//    object. A pointer to the global controller is returned by
//   'getConstructController()'.
//
//----------------------------------------------------------------------------
/*
 * Usage: The following example checks that 'unordered_map::emplace(Args&&...)'
 *        with 'Args = [CopyInsertable<1> const&, CopyInsertible<2>&&]'
 *        calls 'alloc.construct(value_type*, Args&&...)' with the same types.
 *
 * // Typedefs for container
 * using Key = CopyInsertible<1>;
 * using Value = CopyInsertible<2>;
 * using ValueTp = std::pair<const Key, Value>;
 * using Alloc = ContainerTestAllocator<ValueTp, ValueTp>;
 * using Map = std::unordered_map<Key, Value, std::hash<Key>, std::equal_to<Key>, Alloc>;
 *
 * // Get the global controller, reset it, and construct an allocator with
 * // the controller.
 * ConstructController* cc = getConstructController();
 * cc->reset();
 *
 * // Create a Map and a Key and Value to insert. Note that the test-allocator
 * // does not need to be given 'cc'.
 * Map m;
 * const Key k(1);
 * Value v(1);
 *
 * // Tell the controller to expect a construction from the specified types.
 * cc->expect<Key const&, Value&&>();
 *
 * // Emplace the objects into the container. 'Alloc.construct(p, UArgs...)'
 * // will assert 'cc->check<UArgs&&>()' is true which will consume
 * // the call to 'cc->expect<...>()'.
 * m.emplace(k, std::move(v));
 *
 * // Assert that the "expect" was consumed by a matching "check" call within
 * // Alloc.
 * assert(!cc->unexpected());
 *
 */

#include <functional>
#include <cassert>

#include "test_macros.h"

#if TEST_STD_VER < 11
#error This header requires C++11 or greater
#endif

namespace detail {
// TypeID - Represent a unique identifier for a type. TypeID allows equality
// comparisons between different types.
struct TypeID {
  friend bool operator==(TypeID const& LHS, TypeID const& RHS)
  {return LHS.m_id == RHS.m_id; }
  friend bool operator!=(TypeID const& LHS, TypeID const& RHS)
  {return LHS.m_id != RHS.m_id; }
private:
  explicit constexpr TypeID(const int* xid) : m_id(xid) {}
  const int* const m_id;
  template <class T> friend class TypeInfo;
};

// TypeInfo - Represent information for the specified type 'T', including a
// unique TypeID.
template <class T>
class TypeInfo {
public:
  typedef T value_type;
  typedef TypeID ID;
  static  ID const& GetID() { static ID id(&dummy_addr); return id; }

private:
  static const int dummy_addr;
};

template <class L, class R>
inline bool operator==(TypeInfo<L> const&, TypeInfo<R> const&)
{ return std::is_same<L, R>::value; }

template <class L, class R>
inline bool operator!=(TypeInfo<L> const& lhs, TypeInfo<R> const& rhs)
{ return !(lhs == rhs); }

template <class T>
const int TypeInfo<T>::dummy_addr = 42;

// makeTypeID - Return the TypeID for the specified type 'T'.
template <class T>
inline constexpr TypeID const& makeTypeID() { return TypeInfo<T>::GetID(); }

template <class ...Args>
struct ArgumentListID {};

// makeArgumentID - Create and return a unique identifier for a given set
// of arguments.
template <class ...Args>
inline constexpr TypeID const& makeArgumentID() {
  return makeTypeID<ArgumentListID<Args...>>();
}

} // namespace detail

//===----------------------------------------------------------------------===//
//                        AllocatorConstructController
//===----------------------------------------------------------------------===//

struct AllocatorConstructController {
  const detail::TypeID* m_expected_args;
  bool m_allow_constructions;
  bool m_allow_unchecked;
  int m_expected_count;

  void clear() {
    m_expected_args = nullptr;
    m_expected_count = -1;
  }

  // Check for and consume an expected construction added by 'expect'.
  // Return true if the construction was expected and false otherwise.
  // This should only be called by 'Allocator.construct'.
  bool check(detail::TypeID const& tid) {
    if (!m_expected_args) {
      assert(m_allow_unchecked);
      return m_allow_unchecked;
    }
    bool res = *m_expected_args == tid;
    if (m_expected_count == -1 || --m_expected_count == -1)
      m_expected_args = nullptr;
    return res;
  }

  // Return true iff there is an unchecked construction expression.
  bool unchecked() {
    return m_expected_args != nullptr;
  }

  // Expect a call to Allocator::construct with Args that match 'tid'.
  void expect(detail::TypeID const& tid) {
    assert(!unchecked());
    m_expected_args = &tid;
  }

  template <class ...Args>
  void expect(int times = 1) {
    assert(!unchecked());
    assert(times > 0);
    m_expected_count = times - 1;
    m_expected_args = &detail::makeArgumentID<Args...>();
  }
  template <class ...Args>
  bool check() {
    return check(detail::makeArgumentID<Args...>());
  }


  // Return true iff the program is currently within a call to "Allocator::construct"
  bool isInAllocatorConstruct() const {
    return m_allow_constructions;
  }

  void inAllocatorConstruct(bool value = true) {
    m_allow_constructions = value;
  }

  void allowUnchecked(bool value = true) {
    m_allow_unchecked = value;
  }

  void reset() {
    m_allow_constructions = false;
    m_expected_args = nullptr;
    m_allow_unchecked = false;
    m_expected_count = -1;
  }

private:
  friend AllocatorConstructController* getConstructController();
  AllocatorConstructController()  { reset(); }
  AllocatorConstructController(AllocatorConstructController const&);
  AllocatorConstructController& operator=(AllocatorConstructController const&);
};

typedef AllocatorConstructController ConstructController;

// getConstructController - Return the global allocator construction controller.
inline ConstructController* getConstructController() {
  static ConstructController c;
  return &c;
}

template <class ...Args>
struct ExpectConstructGuard {
  ExpectConstructGuard(int N)  {
    auto CC = getConstructController();
    assert(!CC->unchecked());
    CC->expect<Args...>(N);
  }

  ~ExpectConstructGuard() {
    assert(!getConstructController()->unchecked());
  }
};

//===----------------------------------------------------------------------===//
//                       ContainerTestAllocator
//===----------------------------------------------------------------------===//

// ContainerTestAllocator - A STL allocator type that only allows 'construct'
// and 'destroy' to be called for 'AllowConstructT' types. ContainerTestAllocator
// uses the 'AllocatorConstructionController' interface.
template <class T, class AllowConstructT>
class ContainerTestAllocator
{
  struct InAllocatorConstructGuard {
    ConstructController *m_cc;
    bool m_old;
    InAllocatorConstructGuard(ConstructController* cc) : m_cc(cc) {
      if (m_cc) {
        m_old = m_cc->isInAllocatorConstruct();
        m_cc->inAllocatorConstruct(true);
      }
    }
    ~InAllocatorConstructGuard() {
      if (m_cc) m_cc->inAllocatorConstruct(m_old);
    }
  private:
    InAllocatorConstructGuard(InAllocatorConstructGuard const&);
    InAllocatorConstructGuard& operator=(InAllocatorConstructGuard const&);
  };

public:
    typedef T value_type;

    int construct_called;
    int destroy_called;
    ConstructController* controller;

    ContainerTestAllocator() TEST_NOEXCEPT
        : controller(getConstructController()) {}

    explicit ContainerTestAllocator(ConstructController* c)
       : controller(c)
    {}

    template <class U>
    ContainerTestAllocator(ContainerTestAllocator<U, AllowConstructT> other) TEST_NOEXCEPT
      : controller(other.controller)
    {}

    T* allocate(std::size_t n)
    {
        return static_cast<T*>(::operator new(n*sizeof(T)));
    }

    void deallocate(T* p, std::size_t)
    {
        return ::operator delete(static_cast<void*>(p));
    }

    template <class Up, class ...Args>
    void construct(Up* p, Args&&... args) {
      static_assert((std::is_same<Up, AllowConstructT>::value),
                    "Only allowed to construct Up");
      assert(controller->check<Args&&...>());
      {
        InAllocatorConstructGuard g(controller);
        ::new ((void*)p) Up(std::forward<Args>(args)...);
      }
    }

    template <class Up>
    void destroy(Up* p) {
      static_assert((std::is_same<Up, AllowConstructT>::value),
                    "Only allowed to destroy Up");
      {
        InAllocatorConstructGuard g(controller);
        p->~Up();
      }
    }

    friend bool operator==(ContainerTestAllocator, ContainerTestAllocator) {return true;}
    friend bool operator!=(ContainerTestAllocator x, ContainerTestAllocator y) {return !(x == y);}
};


namespace test_detail {
typedef ContainerTestAllocator<int, int> A1;
typedef std::allocator_traits<A1> A1T;
typedef ContainerTestAllocator<float, int> A2;
typedef std::allocator_traits<A2> A2T;

static_assert(std::is_same<A1T::rebind_traits<float>, A2T>::value, "");
static_assert(std::is_same<A2T::rebind_traits<int>, A1T>::value, "");
} // end namespace test_detail

//===----------------------------------------------------------------------===//
//  'CopyInsertable', 'MoveInsertable' and 'EmplaceConstructible' test types
//===----------------------------------------------------------------------===//

template <int Dummy = 0>
struct CopyInsertable {
  int data;
  mutable bool copied_once;
  bool constructed_under_allocator;

  explicit CopyInsertable(int val) : data(val), copied_once(false),
                                     constructed_under_allocator(false) {
    if (getConstructController()->isInAllocatorConstruct()) {
      copied_once = true;
      constructed_under_allocator = true;
    }
  }

  CopyInsertable() : data(0), copied_once(false), constructed_under_allocator(true)
  {
    assert(getConstructController()->isInAllocatorConstruct());
  }

  CopyInsertable(CopyInsertable const& other) : data(other.data),
                                                copied_once(true),
                                                constructed_under_allocator(true) {
    assert(getConstructController()->isInAllocatorConstruct());
    assert(other.copied_once == false);
    other.copied_once = true;
  }

  CopyInsertable(CopyInsertable& other) : data(other.data), copied_once(true),
                                          constructed_under_allocator(true) {
    assert(getConstructController()->isInAllocatorConstruct());
    assert(other.copied_once == false);
    other.copied_once = true;
  }

  CopyInsertable(CopyInsertable&& other) : CopyInsertable(other) {}

  // Forgive pair for not downcasting this to an lvalue in its constructors.
  CopyInsertable(CopyInsertable const && other) : CopyInsertable(other) {}


  template <class ...Args>
  CopyInsertable(Args&&...) {
    assert(false);
  }

  ~CopyInsertable() {
    assert(constructed_under_allocator == getConstructController()->isInAllocatorConstruct());
  }

  void reset(int value) {
    data = value;
    copied_once = false;
    constructed_under_allocator = false;
  }
};

template <int ID>
bool operator==(CopyInsertable<ID> const& L, CopyInsertable<ID> const& R) {
  return L.data == R.data;
}


template <int ID>
bool operator!=(CopyInsertable<ID> const& L, CopyInsertable<ID> const& R) {
  return L.data != R.data;
}

template <int ID>
bool operator <(CopyInsertable<ID> const& L, CopyInsertable<ID> const& R) {
  return L.data < R.data;
}


#ifdef _LIBCUDACXX_BEGIN_NAMESPACE_STD
_LIBCUDACXX_BEGIN_NAMESPACE_STD
#else
namespace std {
#endif
  template <int ID>
  struct hash< ::CopyInsertable<ID> > {
    typedef ::CopyInsertable<ID> argument_type;
    typedef size_t result_type;

    size_t operator()(argument_type const& arg) const {
      return arg.data;
    }
  };
  template <class T, class Alloc>
  class vector;
  template <class T, class Alloc>
  class deque;
  template <class T, class Alloc>
  class list;
  template <class _Key, class _Value, class _Less, class _Alloc>
  class map;
  template <class _Key, class _Value, class _Less, class _Alloc>
  class multimap;
  template <class _Value, class _Less, class _Alloc>
  class set;
  template <class _Value, class _Less, class _Alloc>
  class multiset;
  template <class _Key, class _Value, class _Hash, class _Equals, class _Alloc>
  class unordered_map;
  template <class _Key, class _Value, class _Hash, class _Equals, class _Alloc>
  class unordered_multimap;
  template <class _Value, class _Hash, class _Equals, class _Alloc>
  class unordered_set;
  template <class _Value, class _Hash, class _Equals, class _Alloc>
  class unordered_multiset;

#ifdef _LIBCUDACXX_END_NAMESPACE_STD
_LIBCUDACXX_END_NAMESPACE_STD
#else
} // end namespace std
#endif

// TCT - Test container type
namespace TCT {

template <class T = CopyInsertable<1>>
using vector = std::vector<T, ContainerTestAllocator<T, T> >;
template <class T = CopyInsertable<1>>
using deque = std::deque<T, ContainerTestAllocator<T, T> >;
template <class T = CopyInsertable<1>>
using list = std::list<T, ContainerTestAllocator<T, T> >;

template <class Key = CopyInsertable<1>, class Value = CopyInsertable<2>,
          class ValueTp = std::pair<const Key, Value> >
using unordered_map =
      std::unordered_map<Key, Value, std::hash<Key>, std::equal_to<Key>,
                              ContainerTestAllocator<ValueTp, ValueTp> >;

template <class Key = CopyInsertable<1>, class Value = CopyInsertable<2>,
          class ValueTp = std::pair<const Key, Value> >
using map =
      std::map<Key, Value, std::less<Key>,
                              ContainerTestAllocator<ValueTp, ValueTp> >;

template <class Key = CopyInsertable<1>, class Value = CopyInsertable<2>,
          class ValueTp = std::pair<const Key, Value> >
using unordered_multimap =
      std::unordered_multimap<Key, Value, std::hash<Key>, std::equal_to<Key>,
                                   ContainerTestAllocator<ValueTp, ValueTp> >;

template <class Key = CopyInsertable<1>, class Value = CopyInsertable<2>,
          class ValueTp = std::pair<const Key, Value> >
using multimap =
      std::multimap<Key, Value, std::less<Key>,
                              ContainerTestAllocator<ValueTp, ValueTp> >;

template <class Value = CopyInsertable<1> >
using unordered_set =
  std::unordered_set<Value, std::hash<Value>, std::equal_to<Value>,
                               ContainerTestAllocator<Value, Value> >;

template <class Value = CopyInsertable<1> >
using set =
    std::set<Value, std::less<Value>, ContainerTestAllocator<Value, Value> >;

template <class Value = CopyInsertable<1> >
using unordered_multiset =
    std::unordered_multiset<Value, std::hash<Value>, std::equal_to<Value>,
                                    ContainerTestAllocator<Value, Value> >;

template <class Value = CopyInsertable<1> >
using multiset =
    std::multiset<Value, std::less<Value>, ContainerTestAllocator<Value, Value> >;

} // end namespace TCT

#endif // SUPPORT_CONTAINER_TEST_TYPES_H
