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

// std::ranges::begin
// std::ranges::cbegin

#include <ranges>

#include <cassert>
#include <utility>
#include "test_macros.h"
#include "test_iterators.h"

using RangeBeginT = decltype(std::ranges::begin);
using RangeCBeginT = decltype(std::ranges::cbegin);

static int globalBuff[8];

static_assert(!std::is_invocable_v<RangeBeginT, int (&&)[10]>);
static_assert( std::is_invocable_v<RangeBeginT, int (&)[10]>);
static_assert(!std::is_invocable_v<RangeBeginT, int (&&)[]>);
static_assert( std::is_invocable_v<RangeBeginT, int (&)[]>);
static_assert(!std::is_invocable_v<RangeCBeginT, int (&&)[10]>);
static_assert( std::is_invocable_v<RangeCBeginT, int (&)[10]>);
static_assert(!std::is_invocable_v<RangeCBeginT, int (&&)[]>);
static_assert( std::is_invocable_v<RangeCBeginT, int (&)[]>);

struct Incomplete;
static_assert(!std::is_invocable_v<RangeBeginT, Incomplete(&&)[]>);
static_assert(!std::is_invocable_v<RangeBeginT, const Incomplete(&&)[]>);
static_assert(!std::is_invocable_v<RangeCBeginT, Incomplete(&&)[]>);
static_assert(!std::is_invocable_v<RangeCBeginT, const Incomplete(&&)[]>);

static_assert(!std::is_invocable_v<RangeBeginT, Incomplete(&&)[10]>);
static_assert(!std::is_invocable_v<RangeBeginT, const Incomplete(&&)[10]>);
static_assert(!std::is_invocable_v<RangeCBeginT, Incomplete(&&)[10]>);
static_assert(!std::is_invocable_v<RangeCBeginT, const Incomplete(&&)[10]>);

// This case is IFNDR; we handle it SFINAE-friendly.
LIBCPP_STATIC_ASSERT(!std::is_invocable_v<RangeBeginT, Incomplete(&)[]>);
LIBCPP_STATIC_ASSERT(!std::is_invocable_v<RangeBeginT, const Incomplete(&)[]>);
LIBCPP_STATIC_ASSERT(!std::is_invocable_v<RangeCBeginT, Incomplete(&)[]>);
LIBCPP_STATIC_ASSERT(!std::is_invocable_v<RangeCBeginT, const Incomplete(&)[]>);

// This case is IFNDR; we handle it SFINAE-friendly.
LIBCPP_STATIC_ASSERT(!std::is_invocable_v<RangeBeginT, Incomplete(&)[10]>);
LIBCPP_STATIC_ASSERT(!std::is_invocable_v<RangeBeginT, const Incomplete(&)[10]>);
LIBCPP_STATIC_ASSERT(!std::is_invocable_v<RangeCBeginT, Incomplete(&)[10]>);
LIBCPP_STATIC_ASSERT(!std::is_invocable_v<RangeCBeginT, const Incomplete(&)[10]>);

struct BeginMember {
  int x;
  constexpr const int *begin() const { return &x; }
};

// Ensure that we can't call with rvalues with borrowing disabled.
static_assert( std::is_invocable_v<RangeBeginT, BeginMember &>);
static_assert(!std::is_invocable_v<RangeBeginT, BeginMember &&>);
static_assert( std::is_invocable_v<RangeBeginT, BeginMember const&>);
static_assert(!std::is_invocable_v<RangeBeginT, BeginMember const&&>);
static_assert( std::is_invocable_v<RangeCBeginT, BeginMember &>);
static_assert(!std::is_invocable_v<RangeCBeginT, BeginMember &&>);
static_assert( std::is_invocable_v<RangeCBeginT, BeginMember const&>);
static_assert(!std::is_invocable_v<RangeCBeginT, BeginMember const&&>);

constexpr bool testReturnTypes() {
  {
    int *x[2];
    ASSERT_SAME_TYPE(decltype(std::ranges::begin(x)), int**);
    ASSERT_SAME_TYPE(decltype(std::ranges::cbegin(x)), int* const*);
  }
  {
    int x[2][2];
    ASSERT_SAME_TYPE(decltype(std::ranges::begin(x)), int(*)[2]);
    ASSERT_SAME_TYPE(decltype(std::ranges::cbegin(x)), const int(*)[2]);
  }
  {
    struct Different {
      char*& begin();
      short*& begin() const;
    } x;
    ASSERT_SAME_TYPE(decltype(std::ranges::begin(x)), char*);
    ASSERT_SAME_TYPE(decltype(std::ranges::cbegin(x)), short*);
  }
  return true;
}

constexpr bool testArray() {
  int a[2];
  assert(std::ranges::begin(a) == a);
  assert(std::ranges::cbegin(a) == a);

  int b[2][2];
  assert(std::ranges::begin(b) == b);
  assert(std::ranges::cbegin(b) == b);

  BeginMember c[2];
  assert(std::ranges::begin(c) == c);
  assert(std::ranges::cbegin(c) == c);

  return true;
}

struct BeginMemberReturnsInt {
  int begin() const;
};
static_assert(!std::is_invocable_v<RangeBeginT, BeginMemberReturnsInt const&>);

struct BeginMemberReturnsVoidPtr {
  const void *begin() const;
};
static_assert(!std::is_invocable_v<RangeBeginT, BeginMemberReturnsVoidPtr const&>);

struct EmptyBeginMember {
  struct iterator {};
  iterator begin() const;
};
static_assert(!std::is_invocable_v<RangeBeginT, EmptyBeginMember const&>);

struct PtrConvertibleBeginMember {
  struct iterator { operator int*() const; };
  iterator begin() const;
};
static_assert(!std::is_invocable_v<RangeBeginT, PtrConvertibleBeginMember const&>);

struct NonConstBeginMember {
  int x;
  constexpr int *begin() { return &x; }
};
static_assert( std::is_invocable_v<RangeBeginT,  NonConstBeginMember &>);
static_assert(!std::is_invocable_v<RangeBeginT,  NonConstBeginMember const&>);
static_assert(!std::is_invocable_v<RangeCBeginT, NonConstBeginMember &>);
static_assert(!std::is_invocable_v<RangeCBeginT, NonConstBeginMember const&>);

struct EnabledBorrowingBeginMember {
  constexpr int *begin() const { return &globalBuff[0]; }
};
template<>
inline constexpr bool std::ranges::enable_borrowed_range<EnabledBorrowingBeginMember> = true;

struct BeginMemberFunction {
  int x;
  constexpr const int *begin() const { return &x; }
  friend int *begin(BeginMemberFunction const&);
};

struct EmptyPtrBeginMember {
  struct Empty {};
  Empty x;
  constexpr const Empty *begin() const { return &x; }
};

constexpr bool testBeginMember() {
  BeginMember a;
  assert(std::ranges::begin(a) == &a.x);
  assert(std::ranges::cbegin(a) == &a.x);
  static_assert(!std::is_invocable_v<RangeBeginT, BeginMember&&>);
  static_assert(!std::is_invocable_v<RangeCBeginT, BeginMember&&>);

  NonConstBeginMember b;
  assert(std::ranges::begin(b) == &b.x);
  static_assert(!std::is_invocable_v<RangeCBeginT, NonConstBeginMember&>);

  EnabledBorrowingBeginMember c;
  assert(std::ranges::begin(c) == &globalBuff[0]);
  assert(std::ranges::cbegin(c) == &globalBuff[0]);
  assert(std::ranges::begin(std::move(c)) == &globalBuff[0]);
  assert(std::ranges::cbegin(std::move(c)) == &globalBuff[0]);

  BeginMemberFunction d;
  assert(std::ranges::begin(d) == &d.x);
  assert(std::ranges::cbegin(d) == &d.x);

  EmptyPtrBeginMember e;
  assert(std::ranges::begin(e) == &e.x);
  assert(std::ranges::cbegin(e) == &e.x);

  return true;
}


struct BeginFunction {
  int x;
  friend constexpr const int *begin(BeginFunction const& bf) { return &bf.x; }
};
static_assert( std::is_invocable_v<RangeBeginT,  BeginFunction const&>);
static_assert(!std::is_invocable_v<RangeBeginT,  BeginFunction &&>);
static_assert(!std::is_invocable_v<RangeBeginT,  BeginFunction &>);
static_assert( std::is_invocable_v<RangeCBeginT, BeginFunction const&>);
static_assert( std::is_invocable_v<RangeCBeginT, BeginFunction &>);

struct BeginFunctionReturnsInt {
  friend int begin(BeginFunctionReturnsInt const&);
};
static_assert(!std::is_invocable_v<RangeBeginT, BeginFunctionReturnsInt const&>);

struct BeginFunctionReturnsVoidPtr {
  friend void *begin(BeginFunctionReturnsVoidPtr const&);
};
static_assert(!std::is_invocable_v<RangeBeginT, BeginFunctionReturnsVoidPtr const&>);

struct BeginFunctionReturnsPtrConvertible {
  struct iterator { operator int*() const; };
  friend iterator begin(BeginFunctionReturnsPtrConvertible const&);
};
static_assert(!std::is_invocable_v<RangeBeginT, BeginFunctionReturnsPtrConvertible const&>);

struct BeginFunctionByValue {
  friend constexpr int *begin(BeginFunctionByValue) { return &globalBuff[1]; }
};
static_assert(!std::is_invocable_v<RangeCBeginT, BeginFunctionByValue>);

struct BeginFunctionEnabledBorrowing {
  friend constexpr int *begin(BeginFunctionEnabledBorrowing) { return &globalBuff[2]; }
};
template<>
inline constexpr bool std::ranges::enable_borrowed_range<BeginFunctionEnabledBorrowing> = true;

struct BeginFunctionReturnsEmptyPtr {
  struct Empty {};
  Empty x;
  friend constexpr const Empty *begin(BeginFunctionReturnsEmptyPtr const& bf) { return &bf.x; }
};

struct BeginFunctionWithDataMember {
  int x;
  int begin;
  friend constexpr const int *begin(BeginFunctionWithDataMember const& bf) { return &bf.x; }
};

struct BeginFunctionWithPrivateBeginMember {
  int y;
  friend constexpr const int *begin(BeginFunctionWithPrivateBeginMember const& bf) { return &bf.y; }
private:
  const int *begin() const;
};

constexpr bool testBeginFunction() {
  BeginFunction a{};
  const BeginFunction aa{};
  static_assert(!std::invocable<RangeBeginT, decltype((a))>);
  assert(std::ranges::cbegin(a) == &a.x);
  assert(std::ranges::begin(aa) == &aa.x);
  assert(std::ranges::cbegin(aa) == &aa.x);

  BeginFunctionByValue b{};
  const BeginFunctionByValue bb{};
  assert(std::ranges::begin(b) == &globalBuff[1]);
  assert(std::ranges::cbegin(b) == &globalBuff[1]);
  assert(std::ranges::begin(bb) == &globalBuff[1]);
  assert(std::ranges::cbegin(bb) == &globalBuff[1]);

  BeginFunctionEnabledBorrowing c{};
  const BeginFunctionEnabledBorrowing cc{};
  assert(std::ranges::begin(std::move(c)) == &globalBuff[2]);
  assert(std::ranges::cbegin(std::move(c)) == &globalBuff[2]);
  assert(std::ranges::begin(std::move(cc)) == &globalBuff[2]);
  assert(std::ranges::cbegin(std::move(cc)) == &globalBuff[2]);

  BeginFunctionReturnsEmptyPtr d{};
  const BeginFunctionReturnsEmptyPtr dd{};
  static_assert(!std::invocable<RangeBeginT, decltype((d))>);
  assert(std::ranges::cbegin(d) == &d.x);
  assert(std::ranges::begin(dd) == &dd.x);
  assert(std::ranges::cbegin(dd) == &dd.x);

  BeginFunctionWithDataMember e{};
  const BeginFunctionWithDataMember ee{};
  static_assert(!std::invocable<RangeBeginT, decltype((e))>);
  assert(std::ranges::begin(ee) == &ee.x);
  assert(std::ranges::cbegin(e) == &e.x);
  assert(std::ranges::cbegin(ee) == &ee.x);

  BeginFunctionWithPrivateBeginMember f{};
  const BeginFunctionWithPrivateBeginMember ff{};
  static_assert(!std::invocable<RangeBeginT, decltype((f))>);
  assert(std::ranges::cbegin(f) == &f.y);
  assert(std::ranges::begin(ff) == &ff.y);
  assert(std::ranges::cbegin(ff) == &ff.y);

  return true;
}


ASSERT_NOEXCEPT(std::ranges::begin(std::declval<int (&)[10]>()));
ASSERT_NOEXCEPT(std::ranges::cbegin(std::declval<int (&)[10]>()));

struct NoThrowMemberBegin {
  ThrowingIterator<int> begin() const noexcept; // auto(t.begin()) doesn't throw
} ntmb;
static_assert(noexcept(std::ranges::begin(ntmb)));
static_assert(noexcept(std::ranges::cbegin(ntmb)));

struct NoThrowADLBegin {
  friend ThrowingIterator<int> begin(NoThrowADLBegin&) noexcept;  // auto(begin(t)) doesn't throw
  friend ThrowingIterator<int> begin(const NoThrowADLBegin&) noexcept;
} ntab;
static_assert(noexcept(std::ranges::begin(ntab)));
static_assert(noexcept(std::ranges::cbegin(ntab)));

struct NoThrowMemberBeginReturnsRef {
  ThrowingIterator<int>& begin() const noexcept; // auto(t.begin()) may throw
} ntmbrr;
static_assert(!noexcept(std::ranges::begin(ntmbrr)));
static_assert(!noexcept(std::ranges::cbegin(ntmbrr)));

struct BeginReturnsArrayRef {
    auto begin() const noexcept -> int(&)[10];
} brar;
static_assert(noexcept(std::ranges::begin(brar)));
static_assert(noexcept(std::ranges::cbegin(brar)));

// Test ADL-proofing.
struct Incomplete;
template<class T> struct Holder { T t; };
static_assert(!std::is_invocable_v<RangeBeginT, Holder<Incomplete>*>);
static_assert(!std::is_invocable_v<RangeBeginT, Holder<Incomplete>*&>);
static_assert(!std::is_invocable_v<RangeCBeginT, Holder<Incomplete>*>);
static_assert(!std::is_invocable_v<RangeCBeginT, Holder<Incomplete>*&>);

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

  testArray();
  static_assert(testArray());

  testBeginMember();
  static_assert(testBeginMember());

  testBeginFunction();
  static_assert(testBeginFunction());

  return 0;
}
