//===----------------------------------------------------------------------===//
//
// 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: !c++experimental

// constexpr iterator& operator--();
//              requires ref-is-glvalue && bidirectional_range<Base> &&
//                       bidirectional_range<range_reference_t<Base>> &&
//                       common_range<range_reference_t<Base>>;
// constexpr iterator operator--(int);
//              requires ref-is-glvalue && bidirectional_range<Base> &&
//                       bidirectional_range<range_reference_t<Base>> &&
//                       common_range<range_reference_t<Base>>;

#include <cassert>
#include <ranges>
#include <type_traits>

#include "../types.h"

template <class T>
concept CanPreDecrement = requires(T& t) { --t; };

template <class T>
concept CanPostDecrement = requires(T& t) { t--; };

constexpr void noDecrementTest(auto&& jv) {
  auto iter = jv.begin();
  static_assert(!CanPreDecrement<decltype(iter)>);
  static_assert(!CanPostDecrement<decltype(iter)>);
}

constexpr bool test() {
  int buffer[4][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}};

  {
    // outer == ranges::end
    std::ranges::join_view jv(buffer);
    auto iter = std::next(jv.begin(), 16);
    for (int i = 16; i != 0; --i) {
      assert(*--iter == i);
    }
  }

  {
    // outer == ranges::end
    std::ranges::join_view jv(buffer);
    auto iter = std::next(jv.begin(), 13);
    for (int i = 13; i != 0; --i) {
      assert(*--iter == i);
    }
  }

  {
    // outer != ranges::end
    std::ranges::join_view jv(buffer);
    auto iter = std::next(jv.begin(), 12);
    for (int i = 12; i != 0; --i) {
      assert(*--iter == i);
    }
  }

  {
    // outer != ranges::end
    std::ranges::join_view jv(buffer);
    auto iter = std::next(jv.begin());
    for (int i = 1; i != 0; --i) {
      assert(*--iter == i);
    }
  }

  {
    int small[2][1] = {{1}, {2}};
    std::ranges::join_view jv(small);
    auto iter = std::next(jv.begin(), 2);
    for (int i = 2; i != 0; --i) {
      assert(*--iter == i);
    }
  }

  {
#if defined(__GNUG__) && !defined(__clang__)
    // This seems to be a gcc bug where evaluating the following code
    // at compile time results in wrong array index
    if (!std::is_constant_evaluated()) {
#endif
      // skip empty inner
      BidiCommonInner inners[4] = {buffer[0], {nullptr, 0}, {nullptr, 0}, buffer[1]};
      std::ranges::join_view jv(inners);
      auto iter = jv.end();
      for (int i = 8; i != 0; --i) {
        assert(*--iter == i);
      }
#if defined(__GNUG__) && !defined(__clang__)
    }
#endif
  }

  {
    // basic type checking
    std::ranges::join_view jv(buffer);
    auto iter1 = std::ranges::next(jv.begin(), 4);
    using iterator = decltype(iter1);

    decltype(auto) iter2 = --iter1;
    static_assert(std::same_as<decltype(iter2), iterator&>);
    assert(&iter1 == &iter2);

    std::same_as<iterator> decltype(auto) iter3 = iter1--;
    assert(iter3 == std::next(iter1));
  }

  {
    // !ref-is-glvalue
    BidiCommonInner inners[2] = {buffer[0], buffer[1]};
    InnerRValue<BidiCommonOuter<BidiCommonInner>> outer{inners};
    std::ranges::join_view jv(outer);
    noDecrementTest(jv);
  }

  {
    // !bidirectional_range<Base>
    BidiCommonInner inners[2] = {buffer[0], buffer[1]};
    SimpleForwardCommonOuter<BidiCommonInner> outer{inners};
    std::ranges::join_view jv(outer);
    noDecrementTest(jv);
  }

  {
    // !bidirectional_range<range_reference_t<Base>>
    ForwardCommonInner inners[2] = {buffer[0], buffer[1]};
    std::ranges::join_view jv(inners);
    noDecrementTest(jv);
  }

  {
    // LWG3313 `join_view::iterator::operator--` is incorrectly constrained
    // `join_view::iterator` should not have `operator--` if
    // !common_range<range_reference_t<Base>>
    BidiNonCommonInner inners[2] = {buffer[0], buffer[1]};
    std::ranges::join_view jv(inners);
    auto iter = jv.begin();
    static_assert(!CanPreDecrement<decltype(iter)>);
    static_assert(!CanPostDecrement<decltype(iter)>);
  }

  return true;
}

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

  return 0;
}
