// { dg-do run { target c++17 } }

// Copyright (C) 2016-2024 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/>.

#include <variant>
#include <string>
#include <vector>
#include <unordered_set>
#include <ext/throw_allocator.h>
#include <testsuite_hooks.h>

using namespace std;

struct AlwaysThrow
{
  AlwaysThrow() = default;

  AlwaysThrow(const AlwaysThrow&)
  { throw nullptr; }

  AlwaysThrow(AlwaysThrow&&)
  { throw nullptr; }

  AlwaysThrow& operator=(const AlwaysThrow&)
  {
    throw nullptr;
    return *this;
  }

  AlwaysThrow& operator=(AlwaysThrow&&)
  {
    throw nullptr;
    return *this;
  }

  bool operator<(const AlwaysThrow&) const { VERIFY(false); }
  bool operator<=(const AlwaysThrow&) const { VERIFY(false); }
  bool operator==(const AlwaysThrow&) const { VERIFY(false); }
  bool operator!=(const AlwaysThrow&) const { VERIFY(false); }
  bool operator>=(const AlwaysThrow&) const { VERIFY(false); }
  bool operator>(const AlwaysThrow&) const { VERIFY(false); }
};

struct DeletedMoves
{
  DeletedMoves() = default;
  DeletedMoves(const DeletedMoves&) = default;
  DeletedMoves(DeletedMoves&&) = delete;
  DeletedMoves& operator=(const DeletedMoves&) = default;
  DeletedMoves& operator=(DeletedMoves&&) = delete;
};

void default_ctor()
{
  variant<monostate, string> v;
  VERIFY(holds_alternative<monostate>(v));
}

void copy_ctor()
{
  variant<monostate, string> v("a");
  VERIFY(holds_alternative<string>(v));
  variant<monostate, string> u(v);
  VERIFY(holds_alternative<string>(u));
  VERIFY(get<string>(u) == "a");
}

void move_ctor()
{
  variant<monostate, string> v("a");
  VERIFY(holds_alternative<string>(v));
  variant<monostate, string> u(std::move(v));
  VERIFY(holds_alternative<string>(u));
  VERIFY(get<string>(u) == "a");
  VERIFY(holds_alternative<string>(v));

  variant<vector<int>, DeletedMoves> d{std::in_place_index<0>, {1, 2, 3, 4}};
  // DeletedMoves is not move constructible, so this uses copy ctor:
  variant<vector<int>, DeletedMoves> e(std::move(d));
  VERIFY(std::get<0>(d).size() == 4);
  VERIFY(std::get<0>(e).size() == 4);
}

void arbitrary_ctor()
{
  variant<int, string> v("a");
  VERIFY(holds_alternative<string>(v));
  VERIFY(get<1>(v) == "a");

  {
    // P0608R3
    variant<string, bool> x = "abc";
    VERIFY(x.index() == 0);
  }

  {
    // P0608R3
    struct U {
      U(char16_t c) : c(c) { }
      char16_t c;
    };
    variant<char, U> x = u'\u2043';
    VERIFY(x.index() == 1);
    VERIFY(std::get<1>(x).c == u'\u2043');

    struct Double {
      Double(double& d) : d(d) { }
      double& d;
    };
    double d = 3.14;
    variant<int, Double> y = d;
    VERIFY(y.index() == 1);
    VERIFY(std::get<1>(y).d == d);
  }

  {
    // P0608R3
    variant<float, int> v1 = 'a';
    VERIFY(std::get<1>(v1) == int('a'));
    variant<float, long> v2 = 0;
    VERIFY(std::get<1>(v2) == 0L);
    struct big_int { big_int(int) { } };
    variant<float, big_int> v3 = 0;
    VERIFY(v3.index() == 1);
  }

  {
    // P1957R2 Converting from T* to bool should be considered narrowing
    struct ConvertibleToBool
    {
      operator bool() const { return true; }
    };
    variant<bool> v1 = ConvertibleToBool();
    VERIFY(std::get<0>(v1) == true);
    variant<bool, int> v2 = ConvertibleToBool();
    VERIFY(std::get<0>(v2) == true);
    variant<int, bool> v3 = ConvertibleToBool();
    VERIFY(std::get<1>(v3) == true);
  }
}

struct ThrowingMoveCtorThrowsCopyCtor
{
  ThrowingMoveCtorThrowsCopyCtor() noexcept = default;
  ThrowingMoveCtorThrowsCopyCtor(ThrowingMoveCtorThrowsCopyCtor&&) {}
  ThrowingMoveCtorThrowsCopyCtor(ThrowingMoveCtorThrowsCopyCtor const&)
  {
    throw 0;
  }

  ThrowingMoveCtorThrowsCopyCtor& operator=(ThrowingMoveCtorThrowsCopyCtor&&) noexcept
    = default;
  ThrowingMoveCtorThrowsCopyCtor& operator=(ThrowingMoveCtorThrowsCopyCtor const&) noexcept
    = default;
};

void copy_assign()
{
  variant<monostate, string> v("a");
  VERIFY(holds_alternative<string>(v));
  variant<monostate, string> u;
  u = v;
  VERIFY(holds_alternative<string>(u));
  VERIFY(get<string>(u) == "a");
  {
    std::variant<int, ThrowingMoveCtorThrowsCopyCtor> v1,
      v2 = ThrowingMoveCtorThrowsCopyCtor();
    bool should_throw = false;
    try
      {
	v1 = v2;
      }
    catch(int)
      {
	should_throw = true;
      }
    VERIFY(should_throw);
  }
}

void move_assign()
{
  variant<monostate, string> v("a");
  VERIFY(holds_alternative<string>(v));
  variant<monostate, string> u;
  u = std::move(v);
  VERIFY(holds_alternative<string>(u));
  VERIFY(get<string>(u) == "a");
  VERIFY(holds_alternative<string>(v));

  variant<vector<int>, DeletedMoves> d{std::in_place_index<0>, {1, 2, 3, 4}};
  variant<vector<int>, DeletedMoves> e;
  // DeletedMoves is not move assignable, so this uses copy assignment:
  e = std::move(d);
  VERIFY(std::get<0>(d).size() == 4);
  VERIFY(std::get<0>(e).size() == 4);
}

void arbitrary_assign()
{
  variant<int, string> v;
  v = "a";

  VERIFY(holds_alternative<string>(variant<int, string>("a")));
  VERIFY(get<1>(v) == "a");

  {
    // P0608R3
    using T1 = variant<float, int>;
    T1 v1;
    v1 = 0;
    VERIFY(v1.index() == 1);

    using T2 = variant<float, long>;
    T2 v2;
    v2 = 0;
    VERIFY(v2.index() == 1);

    struct big_int {
      big_int(int) { }
    };
    using T3 = variant<float, big_int>;
    T3 v3;
    v3 = 0;
    VERIFY(v3.index() == 1);
  }

  {
    // P1957R2 Converting from T* to bool should be considered narrowing
    struct ConvertibleToBool
    {
      operator bool() const { return true; }
    };
    variant<bool> v1;
    v1 = ConvertibleToBool();
    VERIFY(std::get<0>(v1) == true);
    variant<bool, int> v2;
    v2 = ConvertibleToBool();
    VERIFY(std::get<0>(v2) == true);
    variant<int, bool> v3;
    v3 = ConvertibleToBool();
    VERIFY(std::get<1>(v3) == true);
  }
}

void dtor()
{
  struct A {
      A(int& called) : called(called) {}
      ~A() {
	  called++;
      }
      int& called;
  };
  {
    int called = 0;
    { variant<string, A> a(in_place_index<1>, called); }
    VERIFY(called == 1);
  }
  {
    int called = 0;
    { variant<string, A> a(in_place_index<0>); }
    VERIFY(called == 0);
  }
}

void in_place_index_ctor()
{
  {
    variant<int, string> v(in_place_index<1>, "a");
    VERIFY(holds_alternative<string>(v));
    VERIFY(get<1>(v) == "a");
  }
  {
    variant<int, string> v(in_place_index<1>, {'a', 'b'});
    VERIFY(holds_alternative<string>(v));
    VERIFY(get<1>(v) == "ab");
  }
}

void in_place_type_ctor()
{
  {
    variant<int, string> v(in_place_type<string>, "a");
    VERIFY(holds_alternative<string>(v));
    VERIFY(get<1>(v) == "a");
  }
  {
    variant<int, string> v(in_place_type<string>, {'a', 'b'});
    VERIFY(holds_alternative<string>(v));
    VERIFY(get<1>(v) == "ab");
  }
}

void emplace()
{
  variant<int, string> v;
  v.emplace<0>(1);
  VERIFY(get<0>(v) == 1);
  v.emplace<string>("a");
  VERIFY(get<string>(v) == "a");
  v.emplace<1>({'a', 'b'});
  VERIFY(get<1>(v) == "ab");
  v.emplace<string>({'a', 'c'});
  VERIFY(get<string>(v) == "ac");
  {
    variant<int, AlwaysThrow> v;
    AlwaysThrow a;
    try { v.emplace<1>(a); } catch (nullptr_t) { }
    VERIFY(v.valueless_by_exception());
    v.emplace<0>(42);
    VERIFY(!v.valueless_by_exception());
  }
  {
    variant<int, AlwaysThrow> v;
    try { v.emplace<1>(AlwaysThrow{}); } catch (nullptr_t) { }
    VERIFY(v.valueless_by_exception());
    v.emplace<0>(42);
    VERIFY(!v.valueless_by_exception());
  }
  VERIFY(&v.emplace<0>(1) == &std::get<0>(v));
  VERIFY(&v.emplace<int>(1) == &std::get<int>(v));
  VERIFY(&v.emplace<1>("a") == &std::get<1>(v));
  VERIFY(&v.emplace<string>("a") == &std::get<string>(v));
  {
    variant<vector<int>> v;
    VERIFY(&v.emplace<0>({1,2,3}) == &std::get<0>(v));
    VERIFY(&v.emplace<vector<int>>({1,2,3}) == &std::get<vector<int>>(v));
  }

  {
    // Ensure no copies of the vector are made, only moves.
    // See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=87431#c21

    // static_assert(__detail::__variant::_Never_valueless_alt<vector<AlwaysThrow>>::value);
    variant<int, DeletedMoves, vector<AlwaysThrow>> v;
    v.emplace<2>(1);
    v.emplace<vector<AlwaysThrow>>(1);
    v.emplace<0>(0);

    // To test the emplace(initializer_list<U>, Args&&...) members we
    // can't use AlwaysThrow because elements in an initialier_list
    // are always copied. Use throw_allocator instead.
    using Vector = vector<int, __gnu_cxx::throw_allocator_limit<int>>;
    // static_assert(__detail::__variant::_Never_valueless_alt<Vector>::value);
    variant<int, DeletedMoves, Vector> vv;
    Vector::allocator_type::set_limit(1);
    vv.emplace<2>(1, 1);
    Vector::allocator_type::set_limit(1);
    vv.emplace<Vector>(1, 1);
    Vector::allocator_type::set_limit(1);
    vv.emplace<0>(0);
    Vector::allocator_type::set_limit(1);
    vv.emplace<2>({1, 2, 3});
    Vector::allocator_type::set_limit(1);
    vv.emplace<Vector>({1, 2, 3, 4});
    try {
      Vector::allocator_type::set_limit(0);
      vv.emplace<2>(1, 1);
      VERIFY(false);
    } catch (const __gnu_cxx::forced_error&) {
    }
    VERIFY(vv.valueless_by_exception());
  }
}

void test_get()
{
  VERIFY(get<1>(variant<int, string>("a")) == "a");
  VERIFY(get<string>(variant<int, string>("a")) == "a");
  {
    bool caught = false;

    try
      {
	get<0>(variant<int, string>("a"));
      }
    catch (const bad_variant_access&)
      {
	caught = true;
      }
    VERIFY(caught);
  }
  {
    bool caught = false;

    try
      {
	get<int>(variant<int, string>("a"));
      }
    catch (const bad_variant_access&)
      {
	caught = true;
      }
    VERIFY(caught);
  }
}

void test_relational()
{
  VERIFY((variant<int, string>(2) < variant<int, string>(3)));
  VERIFY((variant<int, string>(3) == variant<int, string>(3)));
  VERIFY((variant<int, string>(3) > variant<int, string>(2)));
  VERIFY((variant<int, string>(3) <= variant<int, string>(3)));
  VERIFY((variant<int, string>(2) <= variant<int, string>(3)));
  VERIFY((variant<int, string>(3) >= variant<int, string>(3)));
  VERIFY((variant<int, string>(3) >= variant<int, string>(2)));
  VERIFY((variant<int, string>(2) != variant<int, string>(3)));

  VERIFY((variant<int, string>(2) < variant<int, string>("a")));
  VERIFY((variant<string, int>(2) > variant<string, int>("a")));

  {
    variant<int, AlwaysThrow> v, w;
    try
      {
	AlwaysThrow a;
	v = a;
      }
    catch (nullptr_t) { }
    VERIFY(v.valueless_by_exception());
    VERIFY(v < w);
    VERIFY(v <= w);
    VERIFY(!(v == w));
    VERIFY(v == v);
    VERIFY(v != w);
    VERIFY(w > v);
    VERIFY(w >= v);
  }
}

void test_swap()
{
  variant<int, string> a("a"), b("b");
  a.swap(b);
  VERIFY(get<1>(a) == "b");
  VERIFY(get<1>(b) == "a");
  swap(a, b);
  VERIFY(get<1>(a) == "a");
  VERIFY(get<1>(b) == "b");
}

void test_visit()
{
  {
    struct Visitor
    {
      int operator()(int, float) {
	  return 0;
      }
      int operator()(int, double) {
	  return 1;
      }
      int operator()(char, float) {
	  return 2;
      }
      int operator()(char, double) {
	  return 3;
      }
      int operator()(int, float) const {
	  return 5;
      }
      int operator()(int, double) const {
	  return 6;
      }
      int operator()(char, float) const {
	  return 7;
      }
      int operator()(char, double) const {
	  return 8;
      }
    } visitor1;
    VERIFY(visit(visitor1, variant<int, char>(1), variant<float, double>(1.0f)) == 0);
    VERIFY(visit(visitor1, variant<int, char>(1), variant<float, double>(1.0)) == 1);
    VERIFY(visit(visitor1, variant<int, char>('a'), variant<float, double>(1.0f)) == 2);
    VERIFY(visit(visitor1, variant<int, char>('a'), variant<float, double>(1.0)) == 3);

    const auto& visitor2 = visitor1;
    VERIFY(visit(visitor2, variant<int, char>(1), variant<float, double>(1.0f)) == 5);
    VERIFY(visit(visitor2, variant<int, char>(1), variant<float, double>(1.0)) == 6);
    VERIFY(visit(visitor2, variant<int, char>('a'), variant<float, double>(1.0f)) == 7);
    VERIFY(visit(visitor2, variant<int, char>('a'), variant<float, double>(1.0)) == 8);
  }

  {
    struct Visitor
    {
      int operator()(int, float) && {
	  return 0;
      }
      int operator()(int, double) && {
	  return 1;
      }
      int operator()(char, float) && {
	  return 2;
      }
      int operator()(char, double) && {
	  return 3;
      }
    };
    VERIFY(visit(Visitor{}, variant<int, char>(1), variant<float, double>(1.0f)) == 0);
    VERIFY(visit(Visitor{}, variant<int, char>(1), variant<float, double>(1.0)) == 1);
    VERIFY(visit(Visitor{}, variant<int, char>('a'), variant<float, double>(1.0f)) == 2);
    VERIFY(visit(Visitor{}, variant<int, char>('a'), variant<float, double>(1.0)) == 3);
  }
}

struct Hashable
{
  Hashable(const char* s) : s(s) { }
  // Non-trivial special member functions:
  Hashable(const Hashable&) { }
  Hashable(Hashable&&) noexcept { }
  ~Hashable() { }

  string s;

  bool operator==(const Hashable& rhs) const noexcept
  { return s == rhs.s; }
};

namespace std {
  template<> struct hash<Hashable> {
    size_t operator()(const Hashable& h) const noexcept
    { return hash<std::string>()(h.s); }
  };
}

void test_hash()
{
  unordered_set<variant<int, Hashable>> s;
  VERIFY(s.emplace(3).second);
  VERIFY(s.emplace("asdf").second);
  VERIFY(s.emplace().second);
  VERIFY(s.size() == 3);
  VERIFY(!s.emplace(3).second);
  VERIFY(!s.emplace("asdf").second);
  VERIFY(!s.emplace().second);
  VERIFY(s.size() == 3);
  {
    struct A
    {
      operator Hashable()
      {
        throw nullptr;
      }
    };
    variant<int, Hashable> v;
    try
      {
        v.emplace<1>(A{});
      }
    catch (nullptr_t)
      {
      }
    VERIFY(v.valueless_by_exception());
    VERIFY(s.insert(v).second);
    VERIFY(s.size() == 4);
    VERIFY(!s.insert(v).second);
  }
}

void test_valueless_by_exception()
{
  {
    AlwaysThrow a;
    bool caught = false;
    try
      {
	variant<int, AlwaysThrow> v(a);
      }
    catch (nullptr_t)
      {
	caught = true;
      }
    VERIFY(caught);
  }
  {
    AlwaysThrow a;
    bool caught = false;
    try
      {
	variant<int, AlwaysThrow> v(a);
      }
    catch (nullptr_t)
      {
	caught = true;
      }
    VERIFY(caught);
  }
  {
    variant<int, AlwaysThrow> v;
    bool caught = false;
    try
      {
	AlwaysThrow a;
	v = a;
      }
    catch (nullptr_t)
      {
	caught = true;
      }
    VERIFY(caught);
    VERIFY(v.valueless_by_exception());
  }
  {
    variant<int, AlwaysThrow> v;
    bool caught = false;
    try
      {
	v = AlwaysThrow{};
      }
    catch (nullptr_t)
      {
	caught = true;
      }
    VERIFY(caught);
    VERIFY(v.valueless_by_exception());
  }
}

int main()
{
  default_ctor();
  copy_ctor();
  move_ctor();
  arbitrary_ctor();
  in_place_index_ctor();
  in_place_type_ctor();
  copy_assign();
  move_assign();
  arbitrary_assign();
  dtor();
  emplace();
  test_get();
  test_relational();
  test_swap();
  test_visit();
  test_hash();
  test_valueless_by_exception();
}
