//===----------------------------------------------------------------------===//
//
// 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, c++17

// <variant>

// template <class... Types> class variant;

// template <class... Types> requires (three_way_comparable<Types> && ...)
//   constexpr std::common_comparison_category_t<
//     std::compare_three_way_result_t<Types>...>
//   operator<=>(const variant<Types...>& t, const variant<Types...>& u);

#include <cassert>
#include <limits>
#include <type_traits>
#include <utility>
#include <variant>

#include "test_macros.h"
#include "test_comparisons.h"

#ifndef TEST_HAS_NO_EXCEPTIONS
// MakeEmptyT throws in operator=(&&), so we can move to it to create valueless-by-exception variants.
struct MakeEmptyT {
  MakeEmptyT() = default;
  MakeEmptyT(MakeEmptyT&&) { throw 42; }
  MakeEmptyT& operator=(MakeEmptyT&&) { throw 42; }
};
inline bool operator==(const MakeEmptyT&, const MakeEmptyT&) {
  assert(false);
  return false;
}
inline std::weak_ordering operator<=>(const MakeEmptyT&, const MakeEmptyT&) {
  assert(false);
  return std::weak_ordering::equivalent;
}

template <class Variant>
void makeEmpty(Variant& v) {
  Variant v2(std::in_place_type<MakeEmptyT>);
  try {
    v = std::move(v2);
    assert(false);
  } catch (...) {
    assert(v.valueless_by_exception());
  }
}

void test_empty() {
  {
    using V = std::variant<int, MakeEmptyT>;
    V v1;
    V v2;
    makeEmpty(v2);
    assert(testOrder(v1, v2, std::weak_ordering::greater));
  }
  {
    using V = std::variant<int, MakeEmptyT>;
    V v1;
    makeEmpty(v1);
    V v2;
    assert(testOrder(v1, v2, std::weak_ordering::less));
  }
  {
    using V = std::variant<int, MakeEmptyT>;
    V v1;
    makeEmpty(v1);
    V v2;
    makeEmpty(v2);
    assert(testOrder(v1, v2, std::weak_ordering::equivalent));
  }
}
#endif // TEST_HAS_NO_EXCEPTIONS

template <class T1, class T2, class Order>
constexpr bool test_with_types() {
  using V = std::variant<T1, T2>;
  AssertOrderReturn<Order, V>();
  { // same index, same value
    constexpr V v1(std::in_place_index<0>, T1{1});
    constexpr V v2(std::in_place_index<0>, T1{1});
    assert(testOrder(v1, v2, Order::equivalent));
  }
  { // same index, value < other_value
    constexpr V v1(std::in_place_index<0>, T1{0});
    constexpr V v2(std::in_place_index<0>, T1{1});
    assert(testOrder(v1, v2, Order::less));
  }
  { // same index, value > other_value
    constexpr V v1(std::in_place_index<0>, T1{1});
    constexpr V v2(std::in_place_index<0>, T1{0});
    assert(testOrder(v1, v2, Order::greater));
  }
  { // LHS.index() < RHS.index()
    constexpr V v1(std::in_place_index<0>, T1{0});
    constexpr V v2(std::in_place_index<1>, T2{0});
    assert(testOrder(v1, v2, Order::less));
  }
  { // LHS.index() > RHS.index()
    constexpr V v1(std::in_place_index<1>, T2{0});
    constexpr V v2(std::in_place_index<0>, T1{0});
    assert(testOrder(v1, v2, Order::greater));
  }

  return true;
}

constexpr bool test_three_way() {
  assert((test_with_types<int, double, std::partial_ordering>()));
  assert((test_with_types<int, long, std::strong_ordering>()));

  {
    using V              = std::variant<int, double>;
    constexpr double nan = std::numeric_limits<double>::quiet_NaN();
    {
      constexpr V v1(std::in_place_type<int>, 1);
      constexpr V v2(std::in_place_type<double>, nan);
      assert(testOrder(v1, v2, std::partial_ordering::less));
    }
    {
      constexpr V v1(std::in_place_type<double>, nan);
      constexpr V v2(std::in_place_type<int>, 2);
      assert(testOrder(v1, v2, std::partial_ordering::greater));
    }
    {
      constexpr V v1(std::in_place_type<double>, nan);
      constexpr V v2(std::in_place_type<double>, nan);
      assert(testOrder(v1, v2, std::partial_ordering::unordered));
    }
  }

  return true;
}

// SFINAE tests
template <class T, class U = T>
concept has_three_way_op = requires (T& t, U& u) { t <=> u; };

// std::three_way_comparable is a more stringent requirement that demands
// operator== and a few other things.
using std::three_way_comparable;

struct HasSimpleOrdering {
  constexpr bool operator==(const HasSimpleOrdering&) const;
  constexpr bool operator<(const HasSimpleOrdering&) const;
};

struct HasOnlySpaceship {
  constexpr bool operator==(const HasOnlySpaceship&) const = delete;
  constexpr std::weak_ordering operator<=>(const HasOnlySpaceship&) const;
};

struct HasFullOrdering {
  constexpr bool operator==(const HasFullOrdering&) const;
  constexpr std::weak_ordering operator<=>(const HasFullOrdering&) const;
};

// operator<=> must resolve the return types of all its union types'
// operator<=>s to determine its own return type, so it is detectable by SFINAE
static_assert(!has_three_way_op<HasSimpleOrdering>);
static_assert(!has_three_way_op<std::variant<int, HasSimpleOrdering>>);

static_assert(!three_way_comparable<HasSimpleOrdering>);
static_assert(!three_way_comparable<std::variant<int, HasSimpleOrdering>>);

static_assert(has_three_way_op<HasOnlySpaceship>);
static_assert(!has_three_way_op<std::variant<int, HasOnlySpaceship>>);

static_assert(!three_way_comparable<HasOnlySpaceship>);
static_assert(!three_way_comparable<std::variant<int, HasOnlySpaceship>>);

static_assert( has_three_way_op<HasFullOrdering>);
static_assert( has_three_way_op<std::variant<int, HasFullOrdering>>);

static_assert( three_way_comparable<HasFullOrdering>);
static_assert( three_way_comparable<std::variant<int, HasFullOrdering>>);

int main(int, char**) {
  test_three_way();
  static_assert(test_three_way());

#ifndef TEST_HAS_NO_EXCEPTIONS
  test_empty();
#endif // TEST_HAS_NO_EXCEPTIONS

  return 0;
}
