//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

#ifndef ALMOST_SATISFIES_TYPES_H
#define ALMOST_SATISFIES_TYPES_H

#include <functional>
#include <iterator>
#include <ranges>

#include "test_iterators.h"

template <class T, class U = sentinel_wrapper<T>>
class UncheckedRange {
public:
  T begin();
  U end();
};

static_assert(std::ranges::contiguous_range<UncheckedRange<int*, int*>>);

// almost an input_iterator
class InputIteratorNotDerivedFrom {
public:
  using difference_type = long;
  using value_type = int;
  using iterator_category = void;

  InputIteratorNotDerivedFrom& operator++();
  void operator++(int);
  const int& operator*() const;
};

using InputRangeNotDerivedFrom = UncheckedRange<InputIteratorNotDerivedFrom>;

static_assert(std::input_or_output_iterator<InputIteratorNotDerivedFrom>);
static_assert(std::indirectly_readable<InputIteratorNotDerivedFrom>);
static_assert(!std::input_iterator<InputIteratorNotDerivedFrom>);
static_assert(!std::ranges::input_range<InputRangeNotDerivedFrom>);

class InputIteratorNotIndirectlyReadable {
public:
  using difference_type = long;
  using iterator_category = std::input_iterator_tag;

  InputIteratorNotIndirectlyReadable& operator++();
  void operator++(int);
  const int& operator*() const;
};

using InputRangeNotIndirectlyReadable = UncheckedRange<InputIteratorNotIndirectlyReadable>;

static_assert(std::input_or_output_iterator<InputIteratorNotIndirectlyReadable>);
static_assert(!std::indirectly_readable<InputIteratorNotIndirectlyReadable>);
static_assert(!std::input_iterator<InputIteratorNotIndirectlyReadable>);
static_assert(!std::ranges::input_range<InputIteratorNotIndirectlyReadable>);

class InputIteratorNotInputOrOutputIterator {
public:
  using difference_type = long;
  using value_type = int;
  using iterator_category = std::input_iterator_tag;

  int& operator++();
  void operator++(int);
  const int& operator*() const;
};

using InputRangeNotInputOrOutputIterator = UncheckedRange<InputIteratorNotInputOrOutputIterator>;

static_assert(!std::input_or_output_iterator<InputIteratorNotInputOrOutputIterator>);
static_assert(std::indirectly_readable<InputIteratorNotInputOrOutputIterator>);
static_assert(!std::input_iterator<InputIteratorNotInputOrOutputIterator>);
static_assert(!std::ranges::input_range<InputRangeNotInputOrOutputIterator>);

// almost an indirect_unary_predicate
class IndirectUnaryPredicateNotCopyConstructible {
public:
  IndirectUnaryPredicateNotCopyConstructible(const IndirectUnaryPredicateNotCopyConstructible&) = delete;
  bool operator()(int) const;
};

static_assert(std::predicate<IndirectUnaryPredicateNotCopyConstructible, int&>);
static_assert(!std::indirect_unary_predicate<IndirectUnaryPredicateNotCopyConstructible, int*>);

class IndirectUnaryPredicateNotPredicate {
public:
  bool operator()(int&&) const;
};

static_assert(!std::predicate<IndirectUnaryPredicateNotPredicate, int&>);
static_assert(!std::indirect_unary_predicate<IndirectUnaryPredicateNotPredicate, int*>);

// almost a sentinel_for cpp20_input_iterator
class SentinelForNotSemiregular {
public:
  SentinelForNotSemiregular() = delete;
  using difference_type = long;
  SentinelForNotSemiregular& operator++();
  void operator++(int);
  const int& operator*() const;
  friend bool operator==(const SentinelForNotSemiregular&, const cpp20_input_iterator<int*>&);
};

using InputRangeNotSentinelSemiregular = UncheckedRange<cpp20_input_iterator<int*>, SentinelForNotSemiregular>;
using OutputRangeNotSentinelSemiregular = UncheckedRange<cpp20_output_iterator<int*>, SentinelForNotSemiregular>;

static_assert(std::input_or_output_iterator<SentinelForNotSemiregular>);
static_assert(!std::semiregular<SentinelForNotSemiregular>);
static_assert(!std::sentinel_for<SentinelForNotSemiregular, cpp20_input_iterator<int*>>);

// almost a sentinel_for cpp20_input_iterator
class SentinelForNotWeaklyEqualityComparableWith {
public:
  using difference_type = long;
  SentinelForNotWeaklyEqualityComparableWith& operator++();
  void operator++(int);
  const int& operator*() const;
};

using InputRangeNotSentinelEqualityComparableWith =
  UncheckedRange<cpp20_input_iterator<int*>, SentinelForNotWeaklyEqualityComparableWith>;
using OutputRangeNotSentinelEqualityComparableWith =
  UncheckedRange<cpp20_output_iterator<int*>, SentinelForNotWeaklyEqualityComparableWith>;

static_assert(std::input_or_output_iterator<SentinelForNotWeaklyEqualityComparableWith>);
static_assert(std::semiregular<SentinelForNotWeaklyEqualityComparableWith>);
static_assert(!std::sentinel_for<SentinelForNotWeaklyEqualityComparableWith, cpp20_input_iterator<int*>>);

class WeaklyIncrementableNotMovable {
public:
  using difference_type = long;
  WeaklyIncrementableNotMovable& operator++();
  void operator++(int);
  WeaklyIncrementableNotMovable(const WeaklyIncrementableNotMovable&) = delete;
};

static_assert(!std::movable<WeaklyIncrementableNotMovable>);
static_assert(!std::weakly_incrementable<WeaklyIncrementableNotMovable>);

// almost a forward_iterator
class ForwardIteratorNotDerivedFrom {
public:
  using difference_type = long;
  using value_type = int;
  using iterator_category = std::input_iterator_tag;

  ForwardIteratorNotDerivedFrom& operator++();
  ForwardIteratorNotDerivedFrom operator++(int);
  const int& operator*() const;
  bool operator==(const ForwardIteratorNotDerivedFrom&) const = default;
};

using ForwardRangeNotDerivedFrom = UncheckedRange<ForwardIteratorNotDerivedFrom>;

static_assert(std::input_iterator<ForwardIteratorNotDerivedFrom>);
static_assert(std::incrementable<ForwardIteratorNotDerivedFrom>);
static_assert(std::sentinel_for<ForwardIteratorNotDerivedFrom, ForwardIteratorNotDerivedFrom>);
static_assert(!std::forward_iterator<ForwardIteratorNotDerivedFrom>);

class ForwardIteratorNotIncrementable {
public:
  using difference_type = long;
  using value_type = int;
  using iterator_category = std::forward_iterator_tag;

  ForwardIteratorNotIncrementable& operator++();
  int operator++(int);
  const int& operator*() const;
  bool operator==(const ForwardIteratorNotIncrementable&) const = default;
};

using ForwardRangeNotIncrementable = UncheckedRange<ForwardIteratorNotIncrementable>;

static_assert(std::input_iterator<ForwardIteratorNotIncrementable>);
static_assert(!std::incrementable<ForwardIteratorNotIncrementable>);
static_assert(std::sentinel_for<ForwardIteratorNotIncrementable, ForwardIteratorNotIncrementable>);
static_assert(!std::forward_iterator<ForwardIteratorNotIncrementable>);

using ForwardRangeNotSentinelSemiregular = UncheckedRange<forward_iterator<int*>, SentinelForNotSemiregular>;
using ForwardRangeNotSentinelEqualityComparableWith =
    UncheckedRange<forward_iterator<int*>, SentinelForNotWeaklyEqualityComparableWith>;

class BidirectionalIteratorNotDerivedFrom {
public:
  using difference_type = long;
  using value_type = int;
  using iterator_category = std::forward_iterator_tag;

  BidirectionalIteratorNotDerivedFrom& operator++();
  BidirectionalIteratorNotDerivedFrom operator++(int);
  BidirectionalIteratorNotDerivedFrom& operator--();
  BidirectionalIteratorNotDerivedFrom operator--(int);
  int& operator*() const;

  bool operator==(const BidirectionalIteratorNotDerivedFrom&) const = default;
};

using BidirectionalRangeNotDerivedFrom = UncheckedRange<BidirectionalIteratorNotDerivedFrom>;
using BidirectionalRangeNotSentinelSemiregular =
    UncheckedRange<bidirectional_iterator<int*>, SentinelForNotSemiregular>;
using BidirectionalRangeNotSentinelWeaklyEqualityComparableWith =
    UncheckedRange<bidirectional_iterator<int*>, SentinelForNotWeaklyEqualityComparableWith>;

static_assert(std::forward_iterator<BidirectionalIteratorNotDerivedFrom>);
static_assert(!std::bidirectional_iterator<BidirectionalIteratorNotDerivedFrom>);
static_assert(!std::ranges::bidirectional_range<BidirectionalRangeNotDerivedFrom>);

class BidirectionalIteratorNotDecrementable {
public:
  using difference_type = long;
  using value_type = int;
  using iterator_category = std::bidirectional_iterator_tag;

  BidirectionalIteratorNotDecrementable& operator++();
  BidirectionalIteratorNotDecrementable operator++(int);
  int& operator*() const;

  bool operator==(const BidirectionalIteratorNotDecrementable&) const = default;
};

using BidirectionalRangeNotDecrementable = UncheckedRange<BidirectionalIteratorNotDecrementable>;

static_assert(std::forward_iterator<BidirectionalIteratorNotDecrementable>);
static_assert(!std::bidirectional_iterator<BidirectionalIteratorNotDecrementable>);
static_assert(!std::ranges::bidirectional_range<BidirectionalRangeNotDecrementable>);

class PermutableNotForwardIterator {
public:
  using difference_type = long;
  using value_type = int;
  using iterator_category = std::input_iterator_tag;

  PermutableNotForwardIterator& operator++();
  void operator++(int);
  int& operator*() const;
};

using PermutableRangeNotForwardIterator = UncheckedRange<PermutableNotForwardIterator>;

static_assert(std::input_iterator<PermutableNotForwardIterator>);
static_assert(!std::forward_iterator<PermutableNotForwardIterator>);
static_assert(!std::permutable<PermutableNotForwardIterator>);

class PermutableNotSwappable {
public:
  class NotSwappable {
    NotSwappable(NotSwappable&&) = delete;
  };

  using difference_type = long;
  using value_type = NotSwappable;
  using iterator_category = std::contiguous_iterator_tag;

  PermutableNotSwappable& operator++();
  PermutableNotSwappable operator++(int);
  NotSwappable& operator*() const;

  bool operator==(const PermutableNotSwappable&) const = default;
};

using PermutableRangeNotSwappable = UncheckedRange<PermutableNotSwappable>;

static_assert(std::input_iterator<PermutableNotSwappable>);
static_assert(std::forward_iterator<PermutableNotSwappable>);
static_assert(!std::permutable<PermutableNotSwappable>);
static_assert(!std::indirectly_swappable<PermutableNotSwappable>);

class OutputIteratorNotInputOrOutputIterator {
public:
  using difference_type = long;
  using value_type = int;
  using iterator_category = std::input_iterator_tag;

  int& operator++();
  void operator++(int);
  int& operator*();
};

using OutputRangeNotInputOrOutputIterator = UncheckedRange<InputIteratorNotInputOrOutputIterator>;

static_assert(!std::input_or_output_iterator<OutputIteratorNotInputOrOutputIterator>);
static_assert(std::indirectly_writable<OutputIteratorNotInputOrOutputIterator, int>);
static_assert(!std::output_iterator<OutputIteratorNotInputOrOutputIterator, int>);
static_assert(!std::ranges::input_range<OutputRangeNotInputOrOutputIterator>);

class OutputIteratorNotIndirectlyWritable {
public:
  using difference_type = long;
  using iterator_category = std::input_iterator_tag;

  OutputIteratorNotIndirectlyWritable& operator++();
  void operator++(int);
  const int& operator*() const;
};

using OutputRangeNotIndirectlyWritable = UncheckedRange<OutputIteratorNotIndirectlyWritable>;

static_assert(std::input_or_output_iterator<OutputIteratorNotIndirectlyWritable>);
static_assert(!std::indirectly_writable<OutputIteratorNotIndirectlyWritable, int>);
static_assert(!std::output_iterator<OutputIteratorNotIndirectlyWritable, int>);
static_assert(!std::ranges::output_range<OutputIteratorNotIndirectlyWritable, int>);

class IndirectBinaryPredicateNotIndirectlyReadable {
public:
  using difference_type = long;
  using iterator_category = std::input_iterator_tag;

  int& operator++();
  void operator++(int);
  const int& operator*() const;
};

using InputRangeIndirectBinaryPredicateNotIndirectlyReadable
     = UncheckedRange<cpp20_input_iterator<int*>, IndirectBinaryPredicateNotIndirectlyReadable>;

static_assert(!std::indirect_binary_predicate<std::ranges::equal_to, IndirectBinaryPredicateNotIndirectlyReadable, int*>);

class RandomAccessIteratorNotDerivedFrom {
  using Self = RandomAccessIteratorNotDerivedFrom;

public:
  using value_type = int;
  using difference_type = long;
  using pointer = int*;
  using reference = int&;
  // Deliberately not using the `std::random_access_iterator_tag` category.
  using iterator_category = std::bidirectional_iterator_tag;

  reference operator*() const;
  reference operator[](difference_type) const;

  Self& operator++();
  Self& operator--();
  Self operator++(int);
  Self operator--(int);

  Self& operator+=(difference_type);
  Self& operator-=(difference_type);
  friend Self operator+(Self, difference_type);
  friend Self operator+(difference_type, Self);
  friend Self operator-(Self, difference_type);
  friend difference_type operator-(Self, Self);

  auto operator<=>(const Self&) const = default;
};

static_assert(std::bidirectional_iterator<RandomAccessIteratorNotDerivedFrom>);
static_assert(!std::random_access_iterator<RandomAccessIteratorNotDerivedFrom>);

using RandomAccessRangeNotDerivedFrom = UncheckedRange<RandomAccessIteratorNotDerivedFrom>;

class RandomAccessIteratorBadIndex {
  using Self = RandomAccessIteratorBadIndex;

public:
  using value_type = int;
  using difference_type = long;
  using pointer = int*;
  using reference = int&;
  using iterator_category = std::random_access_iterator_tag;

  reference operator*() const;
  // Deliberately returning a type different from `reference`.
  const int& operator[](difference_type) const;

  Self& operator++();
  Self& operator--();
  Self operator++(int);
  Self operator--(int);

  Self& operator+=(difference_type);
  Self& operator-=(difference_type);
  friend Self operator+(Self, difference_type);
  friend Self operator+(difference_type, Self);
  friend Self operator-(Self, difference_type);
  friend difference_type operator-(Self, Self);

  auto operator<=>(const Self&) const = default;
};

static_assert(std::bidirectional_iterator<RandomAccessIteratorBadIndex>);
static_assert(!std::random_access_iterator<RandomAccessIteratorBadIndex>);

using RandomAccessRangeBadIndex = UncheckedRange<RandomAccessIteratorBadIndex>;

template <class Iter>
class ComparatorNotCopyable {
public:
  ComparatorNotCopyable(ComparatorNotCopyable&&) = default;
  ComparatorNotCopyable& operator=(ComparatorNotCopyable&&) = default;
  ComparatorNotCopyable(const ComparatorNotCopyable&) = delete;
  ComparatorNotCopyable& operator=(const ComparatorNotCopyable&) = delete;

  bool operator()(Iter&, Iter&) const;
};

#endif // ALMOST_SATISFIES_TYPES_H
