//===----------------------------------------------------------------------===//
//
// 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++03, c++11, c++14
// TODO: Change to XFAIL once https://github.com/llvm/llvm-project/issues/40340 is fixed
// UNSUPPORTED: availability-pmr-missing

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

// <memory_resource>

// template <class T> class polymorphic_allocator

// template <class U1, class U2, class ...Args1, class ...Args2>
// void polymorphic_allocator<T>::construct(pair<T1, T2>*, piecewise_construct_t
//                                          tuple<Args1...> x, tuple<Args2...>)

// The standard specifies a transformation to uses-allocator construction as
// follows:
//  - If uses_allocator_v<T1,memory_resource*> is false and
//      is_constructible_v<T,Args1...> is true, then xprime is x.
//  - Otherwise, if uses_allocator_v<T1,memory_resource*> is true and
//      is_constructible_v<T1,allocator_arg_t,memory_resource*,Args1...> is true,
//      then xprime is
//      tuple_cat(make_tuple(allocator_arg, this->resource()), std::move(x)).
//  - Otherwise, if uses_allocator_v<T1,memory_resource*> is true and
//      is_constructible_v<T1,Args1...,memory_resource*> is true, then xprime is
//      tuple_cat(std::move(x), make_tuple(this->resource())).
//  - Otherwise the program is ill formed.
//
// The use of "xprime = tuple_cat(..., std::move(x), ...)" causes all of the
// objects in 'x' to be copied into 'xprime'. If 'x' contains any types which
// are stored by value this causes an unnecessary copy to occur. To prevent this
//  libc++ changes this call into
// "xprime = forward_as_tuple(..., std::get<Idx>(std::move(x))..., ...)".
// 'xprime' contains references to the values in 'x' instead of copying them.

// This test checks the number of copies incurred to the elements in
// 'tuple<Args1...>' and 'tuple<Args2...>'.

#include <memory_resource>
#include <type_traits>
#include <utility>
#include <tuple>
#include <cassert>
#include <cstdlib>
#include "test_std_memory_resource.h"

template <class T>
struct TestHarness {
  TestResource R;
  std::pmr::memory_resource* M         = &R;
  std::pmr::polymorphic_allocator<T> A = M;
  bool constructed                     = false;
  T* ptr;

  TestHarness() : ptr(A.allocate(1)) {}

  template <class... Args>
  void construct(Args&&... args) {
    A.construct(ptr, std::forward<Args>(args)...);
    constructed = true;
  }

  ~TestHarness() {
    if (constructed)
      A.destroy(ptr);
    A.deallocate(ptr, 1);
  }
};

struct CountCopies {
  int count;
  CountCopies() : count(0) {}
  CountCopies(CountCopies const& o) : count(o.count + 1) {}
};

struct CountCopiesAllocV1 {
  typedef std::pmr::polymorphic_allocator<char> allocator_type;
  std::pmr::memory_resource* alloc;
  int count;
  CountCopiesAllocV1() : alloc(nullptr), count(0) {}
  CountCopiesAllocV1(std::allocator_arg_t, allocator_type const& a, CountCopiesAllocV1 const& o)
      : alloc(a.resource()), count(o.count + 1) {}

  CountCopiesAllocV1(CountCopiesAllocV1 const& o) : count(o.count + 1) {}
};

struct CountCopiesAllocV2 {
  typedef std::pmr::polymorphic_allocator<char> allocator_type;
  std::pmr::memory_resource* alloc;
  int count;
  CountCopiesAllocV2() : alloc(nullptr), count(0) {}
  CountCopiesAllocV2(CountCopiesAllocV2 const& o, allocator_type const& a) : alloc(a.resource()), count(o.count + 1) {}

  CountCopiesAllocV2(CountCopiesAllocV2 const& o) : count(o.count + 1) {}
};

int main(int, char**) {
  {
    using T = CountCopies;
    using U = CountCopiesAllocV1;
    using P = std::pair<T, U>;

    std::tuple<T> t1;
    std::tuple<U> t2;

    TestHarness<P> h;
    h.construct(std::piecewise_construct, t1, t2);
    P const& p = *h.ptr;
    assert(p.first.count == 2);
    assert(p.second.count == 2);
    assert(p.second.alloc == h.M);
  }
  {
    using T = CountCopiesAllocV1;
    using U = CountCopiesAllocV2;
    using P = std::pair<T, U>;

    std::tuple<T> t1;
    std::tuple<U> t2;

    TestHarness<P> h;
    h.construct(std::piecewise_construct, std::move(t1), std::move(t2));
    P const& p = *h.ptr;
    assert(p.first.count == 2);
    assert(p.first.alloc == h.M);
    assert(p.second.count == 2);
    assert(p.second.alloc == h.M);
  }
  {
    using T = CountCopiesAllocV2;
    using U = CountCopiesAllocV1;
    using P = std::pair<T, U>;

    std::tuple<T> t1;
    std::tuple<U> t2;

    TestHarness<P> h;
    h.construct(std::piecewise_construct, std::move(t1), std::move(t2));
    P const& p = *h.ptr;
    assert(p.first.count == 2);
    assert(p.first.alloc == h.M);
    assert(p.second.count == 2);
    assert(p.second.alloc == h.M);
  }
  {
    using T = CountCopiesAllocV2;
    using U = CountCopies;
    using P = std::pair<T, U>;

    std::tuple<T> t1;
    std::tuple<U> t2;

    TestHarness<P> h;
    h.construct(std::piecewise_construct, t1, t2);
    P const& p = *h.ptr;
    assert(p.first.count == 2);
    assert(p.first.alloc == h.M);
    assert(p.second.count == 2);
  }

  return 0;
}
