// Copyright (C) 2019 Free Software Foundation, Inc.
//
// This file is part of the GNU ISO C++ Library.  This library is free
// software; you can redistribute it and/or modify it under the
// terms of the GNU General Public License as published by the
// Free Software Foundation; either version 3, or (at your option)
// any later version.

// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License along
// with this library; see the file COPYING3.  If not see
// <http://www.gnu.org/licenses/>.

// { dg-options "-std=gnu++2a" }
// { dg-do run { target c++2a } }

#include <testsuite_hooks.h>
#include <testsuite_allocator.h>

using test_allocator = __gnu_test::uneq_allocator<int>;

struct Arg { };

struct A
{
  A() : nargs(0) { }
  A(float&) : nargs(1) { }
  A(int, void*) : nargs(2) { }

  // These should not be used:
  A(const test_allocator& a);
  A(float&, const test_allocator& a);
  A(int, void*, const test_allocator& a);

  const int nargs;
  const int alloc_id = -1;

  // std::uses_allocator<A, test_allocator> should be false:
  using allocator_type = void*();
};

struct B
{
  // This means std::uses_allocator<B, test_allocator> is true:
  using allocator_type = test_allocator;

  B() : nargs(0) { }
  B(float&) : nargs(1) { }
  B(int, void*) : nargs(2) { }

  B(std::allocator_arg_t, const test_allocator& a)
  : nargs(0), alloc_id(a.get_personality()) { }
  B(std::allocator_arg_t, const test_allocator& a, float&)
  : nargs(1), alloc_id(a.get_personality()) { }
  B(std::allocator_arg_t, const test_allocator& a, int, void*)
  : nargs(2), alloc_id(a.get_personality()) { }
  B(std::allocator_arg_t, const test_allocator& a, B&& b)
  : nargs(b.nargs), alloc_id(a.get_personality()) { }

  // These should not be used:
  B(const test_allocator&);
  B(float&, const test_allocator&, float&);
  B(int, void*, const test_allocator&);
  B(const test_allocator&, float&);
  B(const test_allocator&, int, void*);
  B(B&&);
  B(B&&, const test_allocator&);

  const int nargs;
  const int alloc_id = -1;
};

struct C
{
  C() : nargs(0) { }
  C(float&) : nargs(1) { }
  C(int, void*) : nargs(2) { }

  C(const test_allocator& a)
  : nargs(0), alloc_id(a.get_personality()) { }
  C(float&, const test_allocator& a)
  : nargs(1), alloc_id(a.get_personality()) { }
  C(int, void*, const test_allocator& a)
  : nargs(2), alloc_id(a.get_personality()) { }
  C(C&& c, const test_allocator& a)
  : nargs(c.nargs), alloc_id(a.get_personality()) { }

  C(C&&);

  const int nargs;
  const int alloc_id = -1;
};

namespace std {
  // This means std::uses_allocator<C, test_allocator> is true:
  template<> struct uses_allocator<C, test_allocator> : std::true_type { };
}

test_allocator alloc1(1);
test_allocator alloc2(2);

void
test01()
{
  auto i0 = std::make_obj_using_allocator<int>(alloc1, 2);
  VERIFY( i0 == 2 );

  float f = 0.0f;

  auto a0 = std::make_obj_using_allocator<A>(alloc1);
  VERIFY( a0.nargs == 0 );
  VERIFY( a0.alloc_id == -1 );
  auto a1 = std::make_obj_using_allocator<A>(alloc1, f);
  VERIFY( a1.nargs == 1 );
  VERIFY( a1.alloc_id == -1 );
  auto a2 = std::make_obj_using_allocator<A>(alloc1, 123, nullptr);
  VERIFY( a2.nargs == 2 );
  VERIFY( a2.alloc_id == -1 );

  auto b0 = std::make_obj_using_allocator<B>(alloc1);
  VERIFY( b0.nargs == 0 );
  VERIFY( b0.alloc_id == 1 );
  auto b1 = std::make_obj_using_allocator<B>(alloc2, f);
  VERIFY( b1.nargs == 1 );
  VERIFY( b1.alloc_id == 2 );
  auto b2 = std::make_obj_using_allocator<B>(alloc1, 123, nullptr);
  VERIFY( b2.nargs == 2 );
  VERIFY( b2.alloc_id == 1 );

  auto c0 = std::make_obj_using_allocator<C>(alloc1);
  VERIFY( c0.nargs == 0 );
  VERIFY( c0.alloc_id == 1 );
  auto c1 = std::make_obj_using_allocator<C>(alloc2, f);
  VERIFY( c1.nargs == 1 );
  VERIFY( c1.alloc_id == 2 );
  auto c2 = std::make_obj_using_allocator<C>(alloc1, 123, nullptr);
  VERIFY( c2.nargs == 2 );
  VERIFY( c2.alloc_id == 1 );
}

void 
test02()
{
  decltype(auto) b
    = std::make_obj_using_allocator<const B>(alloc1, 123, nullptr);
  static_assert( std::is_const_v<decltype(b)> );
  VERIFY( b.nargs == 2 );
  VERIFY( b.alloc_id == 1 );

  decltype(auto) c = std::make_obj_using_allocator<const C>(alloc1);
  static_assert( std::is_const_v<decltype(c)> );
  VERIFY( c.nargs == 0 );
  VERIFY( c.alloc_id == 1 );
}

void
test03()
{
  B b;
  decltype(auto) ref = std::make_obj_using_allocator<B&>(alloc1, b);
  static_assert( std::is_same_v<decltype(ref), B&> );
  VERIFY( &ref == &b );
  VERIFY( ref.nargs == 0 );
  VERIFY( ref.alloc_id == -1 );
  const B& cref = std::make_obj_using_allocator<const B&>(alloc1, b);
  static_assert( std::is_same_v<decltype(cref), const B&> );
  VERIFY( &cref == &b );
  VERIFY( cref.nargs == 0 );
  VERIFY( cref.alloc_id == -1 );
}

void
test04()
{
  struct D
  {
    D(std::allocator_arg_t) { }
    D(std::allocator_arg_t, int) { }

    // These should not be used:
    D(std::allocator_arg_t, const test_allocator&);
    D(std::allocator_arg_t, const test_allocator&, int);

    ~D() { }
  };

  D d1 = std::make_obj_using_allocator<D>(alloc1, std::allocator_arg);

  struct E
  {
    using allocator_type = test_allocator;

    E(std::allocator_arg_t, const test_allocator&) { }
    E(std::allocator_arg_t, int, const test_allocator&) { }

    // These should not be used:
    E(std::allocator_arg_t);
    E(std::allocator_arg_t, int);

    ~E() { }
  };

  E e1 = std::make_obj_using_allocator<E>(alloc1, std::allocator_arg);
  E e2 = std::make_obj_using_allocator<E>(alloc2, std::allocator_arg, 2);
}

void
test05()
{
  using std::pair;
  std::piecewise_construct_t p;
  std::tuple<> t0;
  float f = 0.0f;
  std::tuple<float&> t1(f);
  std::tuple<int, void*> t2{};

  auto aa00 = std::make_obj_using_allocator<pair<A, A>>(alloc1, p, t0, t0);
  VERIFY( aa00.first.nargs == 0 );
  VERIFY( aa00.first.alloc_id == -1 );
  VERIFY( aa00.second.nargs == 0 );
  VERIFY( aa00.second.alloc_id == -1 );
  auto ab00 = std::make_obj_using_allocator<pair<A, B>>(alloc1, p, t0, t0);
  VERIFY( ab00.first.nargs == 0 );
  VERIFY( ab00.first.alloc_id == -1 );
  VERIFY( ab00.second.nargs == 0 );
  VERIFY( ab00.second.alloc_id == 1 );
  auto bc00 = std::make_obj_using_allocator<pair<B, C>>(alloc2, p, t0, t0);
  VERIFY( bc00.first.nargs == 0 );
  VERIFY( bc00.first.alloc_id == 2 );
  VERIFY( bc00.second.nargs == 0 );
  VERIFY( bc00.second.alloc_id == 2 );
  auto cb00 = std::make_obj_using_allocator<pair<C, B>>(alloc2, p, t0, t0);
  VERIFY( cb00.first.nargs == 0 );
  VERIFY( cb00.first.alloc_id == 2 );
  VERIFY( cb00.second.nargs == 0 );
  VERIFY( cb00.second.alloc_id == 2 );
  auto cc00
    = std::make_obj_using_allocator<pair<C, const C>>(alloc1, p, t0, t0);
  VERIFY( cc00.first.nargs == 0 );
  VERIFY( cc00.first.alloc_id == 1 );
  VERIFY( cc00.second.nargs == 0 );
  VERIFY( cc00.second.alloc_id == 1 );

  auto aa21 = std::make_obj_using_allocator<pair<A, A>>(alloc1, p, t2, t1);
  VERIFY( aa21.first.nargs == 2 );
  VERIFY( aa21.first.alloc_id == -1 );
  VERIFY( aa21.second.nargs == 1 );
  VERIFY( aa21.second.alloc_id == -1 );
  auto ab21 = std::make_obj_using_allocator<pair<A, B>>(alloc1, p, t2, t1);
  VERIFY( ab21.first.nargs == 2 );
  VERIFY( ab21.first.alloc_id == -1 );
  VERIFY( ab21.second.nargs == 1 );
  VERIFY( ab21.second.alloc_id == 1 );
  auto bc11 = std::make_obj_using_allocator<pair<B, C>>(alloc2, p, t1, t1);
  VERIFY( bc11.first.nargs == 1 );
  VERIFY( bc11.first.alloc_id == 2 );
  VERIFY( bc11.second.nargs == 1 );
  VERIFY( bc11.second.alloc_id == 2 );
  auto cb12 = std::make_obj_using_allocator<pair<C, B>>(alloc2, p, t1, t2);
  VERIFY( cb12.first.nargs == 1 );
  VERIFY( cb12.first.alloc_id == 2 );
  VERIFY( cb12.second.nargs == 2 );
  VERIFY( cb12.second.alloc_id == 2 );
  auto cc22
    = std::make_obj_using_allocator<pair<C, const C>>(alloc1, p, t2, t1);
  VERIFY( cc22.first.nargs == 2 );
  VERIFY( cc22.first.alloc_id == 1 );
  VERIFY( cc22.second.nargs == 1 );
  VERIFY( cc22.second.alloc_id == 1 );
}

void
test06()
{
  using std::pair;
  float f = 0.0f;

  auto aa00 = std::make_obj_using_allocator<pair<A, A>>(alloc1);
  VERIFY( aa00.first.nargs == 0 );
  VERIFY( aa00.first.alloc_id == -1 );
  VERIFY( aa00.second.nargs == 0 );
  VERIFY( aa00.second.alloc_id == -1 );
  auto ab00 = std::make_obj_using_allocator<pair<A, B>>(alloc1);
  VERIFY( ab00.first.nargs == 0 );
  VERIFY( ab00.first.alloc_id == -1 );
  VERIFY( ab00.second.nargs == 0 );
  VERIFY( ab00.second.alloc_id == 1 );
  auto bc00 = std::make_obj_using_allocator<pair<B, C>>(alloc2);
  VERIFY( bc00.first.nargs == 0 );
  VERIFY( bc00.first.alloc_id == 2 );
  VERIFY( bc00.second.nargs == 0 );
  VERIFY( bc00.second.alloc_id == 2 );
  auto cb00 = std::make_obj_using_allocator<pair<C, B>>(alloc2);
  VERIFY( cb00.first.nargs == 0 );
  VERIFY( cb00.first.alloc_id == 2 );
  VERIFY( cb00.second.nargs == 0 );
  VERIFY( cb00.second.alloc_id == 2 );
  auto cc00 = std::make_obj_using_allocator<pair<C, const C>>(alloc1);
  VERIFY( cc00.first.nargs == 0 );
  VERIFY( cc00.first.alloc_id == 1 );
  VERIFY( cc00.second.nargs == 0 );
  VERIFY( cc00.second.alloc_id == 1 );

  auto aa11 = std::make_obj_using_allocator<pair<A, A>>(alloc1, f, f);
  VERIFY( aa11.first.nargs == 1 );
  VERIFY( aa11.first.alloc_id == -1 );
  VERIFY( aa11.second.nargs == 1 );
  VERIFY( aa11.second.alloc_id == -1 );
  auto aba1 = std::make_obj_using_allocator<pair<A, B>>(alloc1, A{}, f);
  VERIFY( aba1.first.nargs == 0 );
  VERIFY( aba1.first.alloc_id == -1 );
  VERIFY( aba1.second.nargs == 1 );
  VERIFY( aba1.second.alloc_id == 1 );
  auto bc11 = std::make_obj_using_allocator<pair<B, C>>(alloc2, f, f);
  VERIFY( bc11.first.nargs == 1 );
  VERIFY( bc11.first.alloc_id == 2 );
  VERIFY( bc11.second.nargs == 1 );
  VERIFY( bc11.second.alloc_id == 2 );
  auto cb1b = std::make_obj_using_allocator<pair<C, B>>(alloc2, f, B{});
  VERIFY( cb1b.first.nargs == 1 );
  VERIFY( cb1b.first.alloc_id == 2 );
  VERIFY( cb1b.second.nargs == 0 );
  VERIFY( cb1b.second.alloc_id == 2 );
  auto cccc
    = std::make_obj_using_allocator<pair<C, const C>>(alloc1, C{}, C{});
  VERIFY( cccc.first.nargs == 0 );
  VERIFY( cccc.first.alloc_id == 1 );
  VERIFY( cccc.second.nargs == 0 );
  VERIFY( cccc.second.alloc_id == 1 );

  pair<float&, A> p1a(f, A{});
  pair<float&, float&> p11(f, f);
  auto aa1a = std::make_obj_using_allocator<pair<A, A>>(alloc1, p1a);
  VERIFY( aa1a.first.nargs == 1 );
  VERIFY( aa1a.first.alloc_id == -1 );
  VERIFY( aa1a.second.nargs == 0 );
  VERIFY( aa1a.second.alloc_id == -1 );
  auto ab11 = std::make_obj_using_allocator<pair<A, B>>(alloc1, p11);
  VERIFY( ab11.first.nargs == 1 );
  VERIFY( ab11.first.alloc_id == -1 );
  VERIFY( ab11.second.nargs == 1 );
  VERIFY( ab11.second.alloc_id == 1 );
  auto cb11 = std::make_obj_using_allocator<pair<C, B>>(alloc2, p11);
  VERIFY( cb11.first.nargs == 1 );
  VERIFY( cb11.first.alloc_id == 2 );
  VERIFY( cb11.second.nargs == 1 );
  VERIFY( cb11.second.alloc_id == 2 );

  auto bcbc = std::make_obj_using_allocator<pair<B, C>>(alloc2, pair<B, C>());
  VERIFY( bcbc.first.nargs == 0 );
  VERIFY( bcbc.first.alloc_id == 2 );
  VERIFY( bcbc.second.nargs == 0 );
  VERIFY( bcbc.second.alloc_id == 2 );

  auto cc11 = std::make_obj_using_allocator<pair<C, B>>(alloc2, std::move(p11));
  VERIFY( cc11.first.nargs == 1 );
  VERIFY( cc11.first.alloc_id == 2 );
  VERIFY( cc11.second.nargs == 1 );
  VERIFY( cc11.second.alloc_id == 2 );
}

void
test07()
{
  using nested_pair = std::pair<const std::pair<B, const B>, C>;
  auto p = std::make_obj_using_allocator<const nested_pair>(alloc1);
  VERIFY( p.first.first.alloc_id == 1 );
  VERIFY( p.first.second.alloc_id == 1 );
  VERIFY( p.second.alloc_id == 1 );
}

void
test08()
{
  // LWG DR 3187.
  // P0591R4 reverted DR 2586 fixes to scoped_allocator_adaptor::construct()

  struct X {
    using allocator_type = std::allocator<X>;
    X(std::allocator_arg_t, allocator_type&&) { }
    X(const allocator_type&) { }
  };

  std::allocator<X> a;
  std::make_obj_using_allocator<X>(a);
}

int
main()
{
  test01();
  test02();
  test03();
  test04();
  test05();
  test06();
  test07();
  test08();
}
