//===----------------------------------------------------------------------===//
//
// 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
// UNSUPPORTED: libcpp-has-no-concepts

// <iterator>

// move_iterator

// template <class Iter1, three_way_comparable_with<Iter1> Iter2>
//   constexpr auto operator<=>(const move_iterator<Iter1>& x, const move_iterator<Iter2>& y)
//     -> compare_three_way_result_t<Iter1, Iter2>;

#include <iterator>
#include <cassert>

#include "test_macros.h"
#include "test_iterators.h"


template<class T, class U> concept HasEquals = requires (T t, U u) { t == u; };
template<class T, class U> concept HasSpaceship = requires (T t, U u) { t <=> u; };

static_assert(!HasSpaceship<std::move_iterator<int*>, std::move_iterator<char*>>);
static_assert( HasSpaceship<std::move_iterator<int*>, std::move_iterator<int*>>);
static_assert( HasSpaceship<std::move_iterator<int*>, std::move_iterator<const int*>>);
static_assert( HasSpaceship<std::move_iterator<const int*>, std::move_iterator<const int*>>);
static_assert(!HasSpaceship<std::move_iterator<forward_iterator<int*>>, std::move_iterator<forward_iterator<int*>>>);
static_assert(!HasSpaceship<std::move_iterator<random_access_iterator<int*>>, std::move_iterator<random_access_iterator<int*>>>);
static_assert(!HasSpaceship<std::move_iterator<contiguous_iterator<int*>>, std::move_iterator<contiguous_iterator<int*>>>);
static_assert( HasSpaceship<std::move_iterator<three_way_contiguous_iterator<int*>>, std::move_iterator<three_way_contiguous_iterator<int*>>>);

static_assert(!HasSpaceship<std::move_iterator<int*>, std::move_sentinel<int*>>);
static_assert(!HasSpaceship<std::move_iterator<three_way_contiguous_iterator<int*>>, std::move_sentinel<three_way_contiguous_iterator<int*>>>);

void test_spaceshippable_but_not_three_way_comparable() {
  struct A {
    using value_type = int;
    using difference_type = int;
    int& operator*() const;
    A& operator++();
    A operator++(int);
    std::strong_ordering operator<=>(const A&) const;
  };
  struct B {
    using value_type = int;
    using difference_type = int;
    int& operator*() const;
    B& operator++();
    B operator++(int);
    std::strong_ordering operator<=>(const B&) const;
    bool operator==(const A&) const;
    std::strong_ordering operator<=>(const A&) const;
  };
  static_assert( std::input_iterator<A>);
  static_assert( std::input_iterator<B>);
  static_assert( HasEquals<A, B>);
  static_assert( HasSpaceship<A, B>);
  static_assert(!std::three_way_comparable_with<A, B>);
  static_assert( HasEquals<std::move_iterator<A>, std::move_iterator<B>>);
  static_assert(!HasSpaceship<std::move_iterator<A>, std::move_iterator<B>>);
}

template <class It, class Jt>
constexpr void test_two()
{
  int a[] = {3, 1, 4};
  const std::move_iterator<It> i1 = std::move_iterator<It>(It(a));
  const std::move_iterator<It> i2 = std::move_iterator<It>(It(a + 2));
  const std::move_iterator<Jt> j1 = std::move_iterator<Jt>(Jt(a));
  const std::move_iterator<Jt> j2 = std::move_iterator<Jt>(Jt(a + 2));
  ASSERT_SAME_TYPE(decltype(i1 <=> i2), std::strong_ordering);
  assert((i1 <=> i1) == std::strong_ordering::equal);
  assert((i1 <=> i2) == std::strong_ordering::less);
  assert((i2 <=> i1) == std::strong_ordering::greater);
  ASSERT_SAME_TYPE(decltype(i1 <=> j2), std::strong_ordering);
  assert((i1 <=> j1) == std::strong_ordering::equal);
  assert((i1 <=> j2) == std::strong_ordering::less);
  assert((i2 <=> j1) == std::strong_ordering::greater);
}

constexpr bool test()
{
  test_two<int*, int*>();
  test_two<int*, const int*>();
  test_two<const int*, int*>();
  test_two<const int*, const int*>();
  test_two<three_way_contiguous_iterator<int*>, three_way_contiguous_iterator<int*>>();
  return true;
}

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

  return 0;
}
