// -*- C++ -*-
//===----------------------------------------------------------------------===//
//
// Copyright (C) Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
// This file incorporates work covered by the following copyright and permission
// notice:
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
//
//===----------------------------------------------------------------------===//

// template <bool OtherConst>
// requires(sized_sentinel_for<sentinel_t<maybe-const<Const, Views>>,
//                             iterator_t<maybe-const<OtherConst, Views>>>&&...)
// friend constexpr common_type_t<range_difference_t<maybe-const<OtherConst, Views>>...>
//   operator-(const iterator<OtherConst>&, const sentinel&)
//
// template <bool OtherConst>
// requires(sized_sentinel_for<sentinel_t<maybe-const<Const, Views>>,
//                             iterator_t<maybe-const<OtherConst, Views>>>&&...)
// friend constexpr common_type_t<range_difference_t<maybe-const<OtherConst, Views>>...>
//   operator-(const sentinel&, const iterator<OtherConst>&)

#include "support/test_config.h"
#include "support/utils.h"

#if _ENABLE_STD_RANGES_TESTING

#include <cassert>
#include <concepts>
#include <functional>
#include <ranges>
#include <tuple>

#include "../types.h"

#include <oneapi/dpl/ranges>

namespace dpl_ranges = oneapi::dpl::experimental::ranges;

template <class Base = int*>
struct convertible_forward_sized_iterator {
  Base it_ = nullptr;

  using iterator_category = std::forward_iterator_tag;
  using value_type = int;
  using difference_type = std::intptr_t;

  convertible_forward_sized_iterator() = default;
  constexpr convertible_forward_sized_iterator(Base it) : it_(it) {}

  template <std::convertible_to<Base> U>
  constexpr convertible_forward_sized_iterator(const convertible_forward_sized_iterator<U>& it) : it_(it.it_) {}

  constexpr decltype(*Base{}) operator*() const { return *it_; }

  constexpr convertible_forward_sized_iterator& operator++() {
    ++it_;
    return *this;
  }
  constexpr convertible_forward_sized_iterator operator++(int) { return convertible_forward_sized_iterator(it_++); }

  friend constexpr bool operator==(const convertible_forward_sized_iterator&,
                                   const convertible_forward_sized_iterator&) = default;

  friend constexpr difference_type operator-(const convertible_forward_sized_iterator& x,
                                             const convertible_forward_sized_iterator& y) {
    return x.it_ - y.it_;
  }
};
static_assert(std::forward_iterator<convertible_forward_sized_iterator<>>);

template <class Base>
struct convertible_sized_sentinel {
  Base base_;
  explicit convertible_sized_sentinel() = default;
  constexpr convertible_sized_sentinel(const Base& it) : base_(it) {}

  template <std::convertible_to<Base> U>
  constexpr convertible_sized_sentinel(const convertible_sized_sentinel<U>& other) : base_(other.base_) {}

  template <class U>
    requires(std::convertible_to<Base, U> || std::convertible_to<U, Base>)
  friend constexpr bool operator==(const convertible_sized_sentinel& s, const U& base) {
    return s.base_ == base;
  }
  template <class U>
    requires(std::convertible_to<Base, U> || std::convertible_to<U, Base>)
  friend constexpr auto operator-(const convertible_sized_sentinel& s, const U& i) {
    return s.base_ - i;
  }

  template <class U>
    requires(std::convertible_to<Base, U> || std::convertible_to<U, Base>)
  friend constexpr auto operator-(const U& i, const convertible_sized_sentinel& s) {
    return i - s.base_;
  }
};
static_assert(std::sized_sentinel_for<convertible_sized_sentinel<convertible_forward_sized_iterator<>>,
                                      convertible_forward_sized_iterator<>>);
static_assert(std::sized_sentinel_for<convertible_sized_sentinel<convertible_forward_sized_iterator<const int*>>,
                                      convertible_forward_sized_iterator<int*>>);
static_assert(std::sized_sentinel_for<convertible_sized_sentinel<convertible_forward_sized_iterator<int*>>,
                                      convertible_forward_sized_iterator<const int*>>);

struct ConstCompatibleForwardSized : IntBufferView {
  using IntBufferView::IntBufferView;

  using iterator = convertible_forward_sized_iterator<int*>;
  using const_iterator = convertible_forward_sized_iterator<const int*>;

  constexpr iterator begin() { return {buffer_}; }
  constexpr const_iterator begin() const { return {buffer_}; }
  constexpr convertible_sized_sentinel<iterator> end() { return iterator{buffer_ + size_}; }
  constexpr convertible_sized_sentinel<const_iterator> end() const { return const_iterator{buffer_ + size_}; }
};

// clang-format off
template <class T, class U>
concept HasMinus = std::invocable<std::minus<>,const T&, const U&>;

template <class T>
concept SentinelHasMinus = HasMinus<std::ranges::sentinel_t<T>, std::ranges::iterator_t<T>>;
// clang-format on

void test() {
  int buffer1[5] = {1, 2, 3, 4, 5};

  {
    // simple-view
    dpl_ranges::zip_view v{ForwardSizedNonCommon(buffer1)};
    static_assert(!std::ranges::common_range<decltype(v)>);
    static_assert(simple_view<decltype(v)>);

    auto it = v.begin();
    auto st = v.end();
    assert(st - it == 5);
    assert(st - std::ranges::next(it, 1) == 4);

    assert(it - st == -5);
    assert(std::ranges::next(it, 1) - st == -4);
    static_assert(SentinelHasMinus<decltype(v)>);
  }

  {
    // shortest range
    dpl_ranges::zip_view v(std::views::iota(0, 3), ForwardSizedNonCommon(buffer1));
    static_assert(!std::ranges::common_range<decltype(v)>);
    auto it = v.begin();
    auto st = v.end();
    assert(st - it == 3);
    assert(st - std::ranges::next(it, 1) == 2);

    assert(it - st == -3);
    assert(std::ranges::next(it, 1) - st == -2);
    static_assert(SentinelHasMinus<decltype(v)>);
  }

  {
    // underlying sentinel does not model sized_sentinel_for
    dpl_ranges::zip_view v(std::views::iota(0), SizedRandomAccessView(buffer1));
    static_assert(!std::ranges::common_range<decltype(v)>);
    static_assert(!SentinelHasMinus<decltype(v)>);
  }

  {
    // const incompatible:
    // underlying const sentinels cannot subtract underlying iterators
    // underlying sentinels cannot subtract underlying const iterators
    dpl_ranges::zip_view v(NonSimpleForwardSizedNonCommon{buffer1});
    static_assert(!std::ranges::common_range<decltype(v)>);
    static_assert(!simple_view<decltype(v)>);

    using Iter = std::ranges::iterator_t<decltype(v)>;
    using ConstIter = std::ranges::iterator_t<const decltype(v)>;
    static_assert(!std::is_same_v<Iter, ConstIter>);
    using Sentinel = std::ranges::sentinel_t<decltype(v)>;
    using ConstSentinel = std::ranges::sentinel_t<const decltype(v)>;
    static_assert(!std::is_same_v<Sentinel, ConstSentinel>);

    static_assert(HasMinus<Iter, Sentinel>);
    static_assert(HasMinus<Sentinel, Iter>);
    static_assert(HasMinus<ConstIter, ConstSentinel>);
    static_assert(HasMinus<ConstSentinel, ConstIter>);
    auto it = v.begin();
    auto const_it = std::as_const(v).begin();
    auto st = v.end();
    auto const_st = std::as_const(v).end();
    assert(it - st == -5);
    assert(st - it == 5);
    assert(const_it - const_st == -5);
    assert(const_st - const_it == 5);

    static_assert(!HasMinus<Iter, ConstSentinel>);
    static_assert(!HasMinus<ConstSentinel, Iter>);
    static_assert(!HasMinus<ConstIter, Sentinel>);
    static_assert(!HasMinus<Sentinel, ConstIter>);
  }

  {
    // const compatible allow non-const to const conversion
    dpl_ranges::zip_view v(ConstCompatibleForwardSized{buffer1});
    static_assert(!std::ranges::common_range<decltype(v)>);
    static_assert(!simple_view<decltype(v)>);

    using Iter = std::ranges::iterator_t<decltype(v)>;
    using ConstIter = std::ranges::iterator_t<const decltype(v)>;
    static_assert(!std::is_same_v<Iter, ConstIter>);
    using Sentinel = std::ranges::sentinel_t<decltype(v)>;
    using ConstSentinel = std::ranges::sentinel_t<const decltype(v)>;
    static_assert(!std::is_same_v<Sentinel, ConstSentinel>);

    static_assert(HasMinus<Iter, Sentinel>);
    static_assert(HasMinus<Sentinel, Iter>);
    static_assert(HasMinus<ConstIter, ConstSentinel>);
    static_assert(HasMinus<ConstSentinel, ConstIter>);
    static_assert(HasMinus<Iter, ConstSentinel>);
    static_assert(HasMinus<ConstSentinel, Iter>);
    static_assert(HasMinus<ConstIter, Sentinel>);
    static_assert(HasMinus<Sentinel, ConstIter>);

    auto it = v.begin();
    auto const_it = std::as_const(v).begin();
    auto st = v.end();
    auto const_st = std::as_const(v).end();

    assert(it - st == -5);
    assert(st - it == 5);
    assert(const_it - const_st == -5);
    assert(const_st - const_it == 5);
    assert(it - const_st == -5);
    assert(const_st - it == 5);
    assert(const_it - st == -5);
    assert(st - const_it == 5);
  }
}
#endif //_ENABLE_STD_RANGES_TESTING

int main() {
#if _ENABLE_STD_RANGES_TESTING
    test();
#endif
    return TestUtils::done(_ENABLE_STD_RANGES_TESTING);
}
