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

// constexpr iterator begin();

#include <ranges>

#include <cassert>
#include "test_iterators.h"
#include "types.h"

struct Range : std::ranges::view_base {
  using Iterator = forward_iterator<int*>;
  using Sentinel = sentinel_wrapper<Iterator>;
  constexpr explicit Range(int* b, int* e) : begin_(b), end_(e) { }
  constexpr Iterator begin() const { return Iterator(begin_); }
  constexpr Sentinel end() const { return Sentinel(Iterator(end_)); }

private:
  int* begin_;
  int* end_;
};

// A range that isn't a forward_range, used to test filter_view
// when we don't cache the result of begin()
struct InputRange : std::ranges::view_base {
  using Iterator = cpp17_input_iterator<int*>;
  using Sentinel = sentinel_wrapper<Iterator>;
  constexpr explicit InputRange(int* b, int* e) : begin_(b), end_(e) { }
  constexpr Iterator begin() const { return Iterator(begin_); }
  constexpr Sentinel end() const { return Sentinel(Iterator(end_)); }

private:
  int* begin_;
  int* end_;
};

struct TrackingPred : TrackInitialization {
  using TrackInitialization::TrackInitialization;
  constexpr bool operator()(int i) const { return i % 2 == 0; }
};

template <typename Range>
constexpr void general_tests() {
  int buff[] = {1, 2, 3, 4, 5, 6, 7, 8};

  // Check the return type of `.begin()`
  {
    Range range(buff, buff + 1);
    auto pred = [](int) { return true; };
    std::ranges::filter_view view(range, pred);
    using FilterIterator = std::ranges::iterator_t<decltype(view)>;
    ASSERT_SAME_TYPE(FilterIterator, decltype(view.begin()));
  }

  // begin() over an empty range
  {
    Range range(buff, buff);
    auto pred = [](int) { return true; };
    std::ranges::filter_view view(range, pred);
    auto it = view.begin();
    assert(base(it.base()) == buff);
    assert(it == view.end());
  }

  // begin() over a 1-element range
  {
    {
      Range range(buff, buff + 1);
      auto pred = [](int i) { return i == 1; };
      std::ranges::filter_view view(range, pred);
      auto it = view.begin();
      assert(base(it.base()) == buff);
    }
    {
      Range range(buff, buff + 1);
      auto pred = [](int) { return false; };
      std::ranges::filter_view view(range, pred);
      auto it = view.begin();
      assert(base(it.base()) == buff + 1);
      assert(it == view.end());
    }
  }

  // begin() over a 2-element range
  {
    {
      Range range(buff, buff + 2);
      auto pred = [](int i) { return i == 1; };
      std::ranges::filter_view view(range, pred);
      auto it = view.begin();
      assert(base(it.base()) == buff);
    }
    {
      Range range(buff, buff + 2);
      auto pred = [](int i) { return i == 2; };
      std::ranges::filter_view view(range, pred);
      auto it = view.begin();
      assert(base(it.base()) == buff + 1);
    }
    {
      Range range(buff, buff + 2);
      auto pred = [](int) { return false; };
      std::ranges::filter_view view(range, pred);
      auto it = view.begin();
      assert(base(it.base()) == buff + 2);
      assert(it == view.end());
    }
  }

  // begin() over a N-element range
  {
    for (int k = 1; k != 8; ++k) {
      Range range(buff, buff + 8);
      auto pred = [=](int i) { return i == k; };
      std::ranges::filter_view view(range, pred);
      auto it = view.begin();
      assert(base(it.base()) == buff + (k - 1));
    }
    {
      Range range(buff, buff + 8);
      auto pred = [](int) { return false; };
      std::ranges::filter_view view(range, pred);
      auto it = view.begin();
      assert(base(it.base()) == buff + 8);
      assert(it == view.end());
    }
  }

  // Make sure we do not make a copy of the predicate when we call begin()
  // (we should be passing it to ranges::find_if using std::ref)
  {
    bool moved = false, copied = false;
    Range range(buff, buff + 2);
    std::ranges::filter_view view(range, TrackingPred(&moved, &copied));
    moved = false;
    copied = false;
    [[maybe_unused]] auto it = view.begin();
    assert(!moved);
    assert(!copied);
  }

  // Test with a non-const predicate
  {
    Range range(buff, buff + 8);
    auto pred = [](int i) mutable { return i % 2 == 0; };
    std::ranges::filter_view view(range, pred);
    auto it = view.begin();
    assert(base(it.base()) == buff + 1);
  }

  // Test with a predicate that takes by non-const reference
  {
    Range range(buff, buff + 8);
    auto pred = [](int& i) { return i % 2 == 0; };
    std::ranges::filter_view view(range, pred);
    auto it = view.begin();
    assert(base(it.base()) == buff + 1);
  }
}

template <typename ForwardRange>
constexpr void cache_tests() {
  int buff[] = {1, 2, 3, 4, 5, 6, 7, 8};

  // Make sure that we cache the result of begin() on subsequent calls
  // (only applies to forward_ranges)
  ForwardRange range(buff, buff + 8);
  int called = 0;
  auto pred = [&](int i) { ++called; return i == 3; };

  std::ranges::filter_view view(range, pred);
  assert(called == 0);
  for (int k = 0; k != 3; ++k) {
    auto it = view.begin();
    assert(base(it.base()) == buff + 2);
    assert(called == 3);
  }
}

constexpr bool test() {
  general_tests<Range>();
  general_tests<InputRange>(); // test when we don't cache the result
  cache_tests<Range>();
  return true;
}

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

  return 0;
}
