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

// template<class T, class Alloc, ...>
// constexpr auto uses_allocator_construction_args(const Alloc& alloc, ...) noexcept;

// UNSUPPORTED: c++03, c++11, c++14, c++17

// test_memory_resource requires RTTI for dynamic_cast
// UNSUPPORTED: no-rtti

#include <concepts>
#include <memory>
#include <ranges>
#include <tuple>
#include <utility>

#include "common.h"
#include "test_allocator.h"

template <class Type, class... Args>
constexpr decltype(auto) test_uses_allocator_construction_args(Args&&... args) {
  static_assert(noexcept(std::uses_allocator_construction_args<Type>(std::forward<Args>(args)...)));
  return std::uses_allocator_construction_args<Type>(std::forward<Args>(args)...);
}

template <template <class> class CV>
constexpr void testOne() {
  Alloc a(12);
  {
    std::same_as<std::tuple<std::allocator_arg_t, const Alloc&>> auto ret =
        test_uses_allocator_construction_args<CV<UsesAllocArgT>>(a);
    assert(std::get<1>(ret).get_data() == 12);
  }
  {
    std::same_as<std::tuple<const Alloc&>> auto ret = test_uses_allocator_construction_args<CV<UsesAllocLast>>(a);
    assert(std::get<0>(ret).get_data() == 12);
  }
  {
    [[maybe_unused]] std::same_as<std::tuple<>> auto ret =
        test_uses_allocator_construction_args<CV<NotAllocatorAware>>(a);
  }
  {
    std::same_as<std::tuple<std::piecewise_construct_t,
                            std::tuple<std::allocator_arg_t, const Alloc&>,
                            std::tuple<const Alloc&>>> auto ret =
        test_uses_allocator_construction_args<CV<std::pair<UsesAllocArgT, UsesAllocLast>>>(
            a, std::piecewise_construct, std::tuple<>{}, std::tuple<>{});
    assert(std::get<1>(std::get<1>(ret)).get_data() == 12);
    assert(std::get<0>(std::get<2>(ret)).get_data() == 12);
  }
  {
    std::same_as<std::tuple<std::piecewise_construct_t,
                            std::tuple<std::allocator_arg_t, const Alloc&>,
                            std::tuple<const Alloc&>>> auto ret =
        test_uses_allocator_construction_args<CV<std::pair<UsesAllocArgT, UsesAllocLast>>>(a);
    assert(std::get<1>(std::get<1>(ret)).get_data() == 12);
    assert(std::get<0>(std::get<2>(ret)).get_data() == 12);
  }
  {
    int val = 0;
    std::same_as<std::tuple<std::piecewise_construct_t,
                            std::tuple<std::allocator_arg_t, const Alloc&, int&>,
                            std::tuple<int&, const Alloc&>>> auto ret =
        test_uses_allocator_construction_args<CV<std::pair<UsesAllocArgT, UsesAllocLast>>>(a, val, val);
    assert(std::get<1>(std::get<1>(ret)).get_data() == 12);
    assert(std::get<1>(std::get<2>(ret)).get_data() == 12);
    assert(&std::get<2>(std::get<1>(ret)) == &val);
    assert(&std::get<0>(std::get<2>(ret)) == &val);
  }
  {
    int val = 0;
    std::same_as<std::tuple<std::piecewise_construct_t,
                            std::tuple<std::allocator_arg_t, const Alloc&, int&&>,
                            std::tuple<int&&, const Alloc&>>> auto ret =
        test_uses_allocator_construction_args<CV<std::pair<UsesAllocArgT, UsesAllocLast>>>(
            a, std::move(val), std::move(val));
    assert(std::get<1>(std::get<1>(ret)).get_data() == 12);
    assert(std::get<1>(std::get<2>(ret)).get_data() == 12);
    assert(&std::get<2>(std::get<1>(ret)) == &val);
    assert(&std::get<0>(std::get<2>(ret)) == &val);
  }
#if TEST_STD_VER >= 23
  {
    std::pair p{3, 4};

    std::same_as<std::tuple<std::piecewise_construct_t,
                            std::tuple<std::allocator_arg_t, const Alloc&, int&>,
                            std::tuple<int&, const Alloc&>>> auto ret =
        test_uses_allocator_construction_args<CV<std::pair<UsesAllocArgT, UsesAllocLast>>>(a, p);
    assert(std::get<1>(std::get<1>(ret)).get_data() == 12);
    assert(std::get<1>(std::get<2>(ret)).get_data() == 12);
    assert(std::get<2>(std::get<1>(ret)) == 3);
    assert(std::get<0>(std::get<2>(ret)) == 4);
  }
#endif
  {
    std::pair p{3, 4};
    std::same_as<std::tuple<std::piecewise_construct_t,
                            std::tuple<std::allocator_arg_t, const Alloc&, const int&>,
                            std::tuple<const int&, const Alloc&>>> auto ret =
        test_uses_allocator_construction_args<CV<std::pair<UsesAllocArgT, UsesAllocLast>>>(a, std::as_const(p));
    assert(std::get<1>(std::get<1>(ret)).get_data() == 12);
    assert(std::get<1>(std::get<2>(ret)).get_data() == 12);
    assert(std::get<2>(std::get<1>(ret)) == 3);
    assert(std::get<0>(std::get<2>(ret)) == 4);
  }
  {
    std::pair p{3, 4};
    std::same_as<std::tuple<std::piecewise_construct_t,
                            std::tuple<std::allocator_arg_t, const Alloc&, int&&>,
                            std::tuple<int&&, const Alloc&>>> auto ret =
        test_uses_allocator_construction_args<CV<std::pair<UsesAllocArgT, UsesAllocLast>>>(a, std::move(p));
    assert(std::get<1>(std::get<1>(ret)).get_data() == 12);
    assert(std::get<1>(std::get<2>(ret)).get_data() == 12);
    assert(std::get<2>(std::get<1>(ret)) == 3);
    assert(std::get<0>(std::get<2>(ret)) == 4);
  }
  {
    // Tests for ensuring forward declarations of uses_allocator_construction_args
    // See https://github.com/llvm/llvm-project/issues/66714.
    {
      using NestedPairType = std::pair<int, std::pair<int, UsesAllocArgT>>;
      std::same_as<std::tuple<
          std::piecewise_construct_t,
          std::tuple<>,
          std::tuple<std::piecewise_construct_t, std::tuple<>, std::tuple<std::allocator_arg_t, const Alloc&>>>> auto
          ret = test_uses_allocator_construction_args<NestedPairType>(a);
      (void)ret;
    }
    {
      using NestedPairType = std::pair<int, std::pair<UsesAllocArgT, int>>;
      std::same_as<std::tuple<
          std::piecewise_construct_t,
          std::tuple<>,
          std::tuple<std::piecewise_construct_t, std::tuple<std::allocator_arg_t, const Alloc&>, std::tuple<>>>> auto
          ret = test_uses_allocator_construction_args<NestedPairType>(a);
      (void)ret;
    }
    {
      using PairType = std::pair<int, int>;
      int up         = 1;
      int vp         = 2;

      std::same_as<std::tuple<std::piecewise_construct_t, std::tuple<int&&>, std::tuple<int&&>>> auto ret =
          test_uses_allocator_construction_args<PairType>(a, std::move(up), std::move(vp));
      (void)ret;
    }
    {
      using PairType = const std::pair<int, int>;
      PairType p(1, 2);

      std::same_as<std::tuple<std::piecewise_construct_t, std::tuple<const int&>, std::tuple<const int&>>> auto ret =
          test_uses_allocator_construction_args<PairType>(a, p);
      (void)ret;
    }
    {
      using PairType = std::pair<int, int>;
      PairType p(1, 2);

      std::same_as<std::tuple<std::piecewise_construct_t, std::tuple<int&&>, std::tuple<int&&>>> auto ret =
          test_uses_allocator_construction_args<PairType>(a, std::move(p));
      (void)ret;
    }
#if TEST_STD_VER >= 23
    {
      using PairType = const std::pair<int, int>;
      PairType p(1, 2);

      std::same_as<std::tuple<std::piecewise_construct_t, std::tuple<const int&&>, std::tuple<const int&&>>> auto ret =
          test_uses_allocator_construction_args<PairType>(a, std::move(p));
      (void)ret;
    }
#endif
  }
#if TEST_STD_VER >= 23
  {
    std::pair p{3, 4};
    std::same_as<std::tuple<std::piecewise_construct_t,
                            std::tuple<std::allocator_arg_t, const Alloc&, const int&&>,
                            std::tuple<const int&&, const Alloc&>>> auto ret =
        test_uses_allocator_construction_args<CV<std::pair<UsesAllocArgT, UsesAllocLast>>>(
            a, std::move(std::as_const(p)));
    assert(std::get<1>(std::get<1>(ret)).get_data() == 12);
    assert(std::get<1>(std::get<2>(ret)).get_data() == 12);
    assert(std::get<2>(std::get<1>(ret)) == 3);
    assert(std::get<0>(std::get<2>(ret)) == 4);
  }
#endif
  {
    ConvertibleToPair ctp{};
    auto ret              = test_uses_allocator_construction_args<CV<std::pair<int, int>>>(a, ctp);
    std::pair<int, int> v = std::get<0>(ret);
    assert(std::get<0>(v) == 1);
    assert(std::get<1>(v) == 2);
  }
  {
    ConvertibleToPair ctp{};
    auto ret              = test_uses_allocator_construction_args<CV<std::pair<int, int>>>(a, std::move(ctp));
    std::pair<int, int> v = std::get<0>(ret);
    assert(std::get<0>(v) == 1);
    assert(std::get<1>(v) == 2);
  }
#if TEST_STD_VER >= 23
  // P2165R4 with LWG3821 applied
  // uses_allocator_construction_args should have overload for pair-like
  {
    // pair-like with explicit ctr should work
    struct Foo {
      int i = 5;
    };
    struct Bar {
      int i;
      constexpr explicit Bar(Foo foo) : i(foo.i) {}
    };

    std::tuple<Foo, Foo> pair_like;
    auto ret  = test_uses_allocator_construction_args<CV<std::pair<Bar, Bar>>>(a, pair_like);
    auto pair = std::make_from_tuple<std::pair<Bar, Bar>>(std::move(ret));
    assert(pair.first.i == 5);
    assert(pair.second.i == 5);
  }
  {
    // subrange should work
    int i = 5;
    std::ranges::subrange<int*, int*> r{&i, &i};
    auto ret  = test_uses_allocator_construction_args<CV<std::pair<int*, int*>>>(a, r);
    auto pair = std::make_from_tuple<std::pair<int*, int*>>(std::move(ret));
    assert(pair.first == &i);
    assert(pair.second == &i);
  }
#endif
}

constexpr bool test() {
  testOne<std::type_identity_t>();

  // LWG 3677. Is a cv-qualified pair specially handled in uses-allocator construction
  testOne<std::add_const_t>();
  testOne<std::add_volatile_t>();
  testOne<std::add_cv_t>();

  return true;
}

int main(int, char**) {
  test();
  static_assert(test());
}
