//===----------------------------------------------------------------------===//
//
// 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-no-concepts
// UNSUPPORTED: gcc-10

// template<class In, class Out>
// concept indirectly_movable_storable;

#include <iterator>

#include "test_macros.h"

struct Empty {};

struct MoveOnlyConvertible;
struct AssignableToMoveOnly;

struct MoveOnly {
  MoveOnly(MoveOnly&&) = default;
  MoveOnly(MoveOnly const&) = delete;
  MoveOnly& operator=(MoveOnly&&) = default;
  MoveOnly& operator=(MoveOnly const&) = delete;
  MoveOnly() = default;

  MoveOnly& operator=(MoveOnlyConvertible const&) = delete;
  MoveOnly& operator=(AssignableToMoveOnly const&);
};

template<class T, class ValueType = T>
struct PointerTo {
  using value_type = ValueType;
  T& operator*() const;
};

// MoveOnlyConvertible is convertible to MoveOnly, but not assignable to it. This is
// implemented by explicitly deleting "operator=(MoveOnlyConvertible)" in MoveOnly.
struct MoveOnlyConvertible {
  operator MoveOnly&() const;
};

// This type can be constructed with a MoveOnly and assigned to a MoveOnly, so it does
// model indirectly_movable_storable.
struct AssignableToMoveOnly {
  AssignableToMoveOnly() = default;
  AssignableToMoveOnly(const MoveOnly&);
};

struct DeletedMoveCtor {
  DeletedMoveCtor(DeletedMoveCtor&&) = delete;
  DeletedMoveCtor& operator=(DeletedMoveCtor&&) = default;
};

struct CommonType { };

struct NotConstructibleFromRefIn {
  struct ValueType {
    operator CommonType&() const;
  };

  struct ReferenceType {
    operator CommonType&() const;
  };

  using value_type = ValueType;
  ReferenceType& operator*() const;
};

template <template <class> class X, template <class> class Y>
struct std::basic_common_reference<NotConstructibleFromRefIn::ValueType,
                                   NotConstructibleFromRefIn::ReferenceType, X, Y> {
  using type = CommonType&;
};

template <template <class> class X, template <class> class Y>
struct std::basic_common_reference<NotConstructibleFromRefIn::ReferenceType,
                                   NotConstructibleFromRefIn::ValueType, X, Y> {
  using type = CommonType&;
};

struct NotAssignableFromRefIn {
  struct ReferenceType;

  struct ValueType {
    ValueType(ReferenceType);
    ValueType& operator=(ReferenceType) = delete;
    operator CommonType&() const;
  };

  struct ReferenceType {
    operator CommonType&() const;
  };

  using value_type = ValueType;
  ReferenceType& operator*() const;
};

template <template <class> class X, template <class> class Y>
struct std::basic_common_reference<NotAssignableFromRefIn::ValueType,
                                   NotAssignableFromRefIn::ReferenceType, X, Y> {
  using type = CommonType&;
};

template <template <class> class X, template <class> class Y>
struct std::basic_common_reference<NotAssignableFromRefIn::ReferenceType,
                                   NotAssignableFromRefIn::ValueType, X, Y> {
  using type = CommonType&;
};

struct AnyWritable {
  template<class T>
  AnyWritable& operator=(T&&);
};

struct AnyOutput {
  using value_type = AnyWritable;
  AnyWritable& operator*() const;
};

static_assert( std::indirectly_movable_storable<int*, int*>);
static_assert( std::indirectly_movable_storable<const int*, int *>);
static_assert( std::indirectly_movable_storable<int*, int[2]>);
static_assert( std::indirectly_movable_storable<Empty*, Empty*>);
static_assert( std::indirectly_movable_storable<MoveOnly*, MoveOnly*>);
static_assert( std::indirectly_movable_storable<PointerTo<MoveOnly>, PointerTo<MoveOnly>>);
// The case when indirectly_writable<iter_rvalue_reference> but not indirectly_writable<iter_value>.
static_assert( std::indirectly_writable<
                 PointerTo<MoveOnly, MoveOnlyConvertible>,
                 std::iter_rvalue_reference_t<
                    PointerTo<MoveOnly, MoveOnlyConvertible>>>);
static_assert(!std::indirectly_movable_storable<PointerTo<MoveOnly, MoveOnlyConvertible>,
                                                PointerTo<MoveOnly, MoveOnlyConvertible>>);
static_assert(!std::indirectly_movable_storable<DeletedMoveCtor*, DeletedMoveCtor*>);
static_assert( std::indirectly_movable_storable<PointerTo<MoveOnly, AssignableToMoveOnly>,
                                                PointerTo<MoveOnly, AssignableToMoveOnly>>);
static_assert(!std::indirectly_movable_storable<NotConstructibleFromRefIn, AnyOutput>);
static_assert(!std::indirectly_movable_storable<NotAssignableFromRefIn, AnyOutput>);
