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

// <algorithm>

// UNSUPPORTED: c++03, c++11, c++14, c++17

// template<class T, class Proj = identity,
//          indirect_strict_weak_order<projected<const T*, Proj>> Comp = ranges::less>
//   constexpr const T& ranges::max(const T& a, const T& b, Comp comp = {}, Proj proj = {});
//
// template<copyable T, class Proj = identity,
//          indirect_strict_weak_order<projected<const T*, Proj>> Comp = ranges::less>
//   constexpr T ranges::max(initializer_list<T> r, Comp comp = {}, Proj proj = {});
//
// template<input_range R, class Proj = identity,
//          indirect_strict_weak_order<projected<iterator_t<R>, Proj>> Comp = ranges::less>
//   requires indirectly_copyable_storable<iterator_t<R>, range_value_t<R>*>
//   constexpr range_value_t<R>
//     ranges::max(R&& r, Comp comp = {}, Proj proj = {});

#include <algorithm>
#include <array>
#include <cassert>
#include <functional>
#include <ranges>

#include "almost_satisfies_types.h"
#include "test_iterators.h"
#include "test_macros.h"

template <class T>
concept HasMaxR = requires { std::ranges::max(std::declval<T>()); };

struct NoLessThanOp {};
struct NotTotallyOrdered {
  int i;
  bool operator<(const NotTotallyOrdered& o) const { return i < o.i; }
};

struct Movable {
  Movable& operator=(Movable&&) = default;
  Movable(Movable&&) = default;
  Movable(const Movable&) = delete;
};

static_assert(!HasMaxR<int>);

static_assert(HasMaxR<int(&)[10]>);
static_assert(HasMaxR<int(&&)[10]>);
static_assert(!HasMaxR<NoLessThanOp(&)[10]>);
static_assert(!HasMaxR<NotTotallyOrdered(&)[10]>);
static_assert(!HasMaxR<Movable(&)[10]>);

static_assert(HasMaxR<std::initializer_list<int>>);
static_assert(!HasMaxR<std::initializer_list<NoLessThanOp>>);
static_assert(!HasMaxR<std::initializer_list<NotTotallyOrdered>>);
static_assert(!HasMaxR<std::initializer_list<Movable>>);
static_assert(!HasMaxR<InputRangeNotDerivedFrom>);
static_assert(!HasMaxR<InputRangeNotIndirectlyReadable>);
static_assert(!HasMaxR<InputRangeNotInputOrOutputIterator>);
static_assert(!HasMaxR<InputRangeNotSentinelSemiregular>);
static_assert(!HasMaxR<InputRangeNotSentinelEqualityComparableWith>);

template <class T, class U = T>
concept HasMax2 = requires { std::ranges::max(std::declval<T>(), std::declval<U>()); };

static_assert(HasMax2<int>);
static_assert(!HasMax2<int, long>);

static_assert(std::is_same_v<decltype(std::ranges::max(1, 2)), const int&>);

constexpr void test_2_arguments() {
  assert(std::ranges::max(1, 2) == 2);
  assert(std::ranges::max(2, 1) == 2);
  // test comparator
  assert(std::ranges::max(1, 2, std::ranges::greater{}) == 1);
  // test projection
  assert(std::ranges::max(1, 2, std::ranges::less{}, [](int i){ return i == 1 ? 10 : i; }) == 1);

  { // check that std::invoke is used
    struct S { int i; };
    S a[3] = { S{2}, S{1}, S{3} };
    decltype(auto) ret = std::ranges::max(a[0], a[1], {}, &S::i);
    ASSERT_SAME_TYPE(decltype(ret), const S&);
    assert(&ret == &a[0]);
    assert(ret.i == 2);
  }

  { // check that pointers are compared and not a range
    int i[1];
    int* a[] = {i, i + 1};
    auto ret = std::ranges::max(a[0], a[1]);
    assert(ret == i + 1);
  }

  { // test predicate and projection count
    int compares = 0;
    int projections = 0;
    auto comparator = [&](int x, int y) {
      ++compares;
      return x < y;
    };
    auto projection = [&](int x) {
      ++projections;
      return x;
    };
    auto ret = std::ranges::max(1, 2, comparator, projection);
    assert(ret == 2);
    assert(compares == 1);
    assert(projections == 2);
  }

  { // check that the first argument is returned
    struct S { int check; int other; };
    auto ret = std::ranges::max(S {0, 1}, S {0, 2}, {}, &S::check);
    assert(ret.other == 1);
  }
}

constexpr void test_initializer_list() {
  { // test projection
    auto proj = [](int i) { return i == 5 ? 100 : i; };
    int ret = std::ranges::max({7, 6, 9, 3, 5, 1, 2, 4}, {}, proj);
    assert(ret == 5);
  }

  { // test comparator
    int ret = std::ranges::max({7, 6, 9, 3, 5, 1, 2, 4}, std::ranges::greater{});
    assert(ret == 1);
  }

  { // check that complexity requirements are met
    int compares = 0;
    int projections = 0;
    auto comparator = [&](int a, int b) {
      ++compares;
      return a < b;
    };
    auto projection = [&](int a) {
      ++projections;
      return a;
    };
    std::same_as<int> decltype(auto) ret = std::ranges::max({1, 2, 3}, comparator, projection);
    assert(ret == 3);
    assert(compares == 2);
    assert(projections == 4);
  }

  { // check that std::invoke is used
    struct S { int i; };
    std::same_as<S> decltype(auto) ret = std::ranges::max({ S{2}, S{1}, S{3} }, {}, &S::i);
    assert(ret.i == 3);
  }

  { // check that the first largest element is returned
    { // where the first element is the largest
      struct S { int check; int other; };
      auto ret = std::ranges::max({ S{1, 1}, S{0, 2}, S{1, 3} }, {}, &S::check);
      assert(ret.check == 1);
      assert(ret.other == 1);
    }
    { // where the first element isn't the largest
      struct S { int check; int other; };
      auto ret = std::ranges::max({ S{0, 1}, S{1, 2}, S{1, 3} }, {}, &S::check);
      assert(ret.check == 1);
      assert(ret.other == 2);
    }
  }
}

template <class It, class Sent = It>
constexpr void test_range_types() {
  std::iter_value_t<It> a[] = {7, 6, 9, 3, 5, 1, 2, 4};
  auto range = std::ranges::subrange(It(a), Sent(It(a + 8)));
  auto ret = std::ranges::max(range);
  assert(ret == 9);
}

constexpr void test_range() {
  // check that all range types work
  {
    struct NonTrivialInt {
      int val_;
      constexpr NonTrivialInt(int val) : val_(val) {}
      constexpr NonTrivialInt(const NonTrivialInt& other) : val_(other.val_) {}
      constexpr NonTrivialInt& operator=(const NonTrivialInt& other) {
        val_ = other.val_;
        return *this;
      }

      constexpr ~NonTrivialInt() {}

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

    auto call_with_sentinels = []<class Iter> {
      if constexpr (std::forward_iterator<Iter>)
        test_range_types<Iter, Iter>();
      test_range_types<Iter, sentinel_wrapper<Iter>>();
      test_range_types<Iter, sized_sentinel<Iter>>();
    };

    types::for_each(types::cpp20_input_iterator_list<int*>{}, call_with_sentinels);
    types::for_each(types::cpp20_input_iterator_list<NonTrivialInt*>{}, call_with_sentinels);
  }

  int a[] = {7, 6, 9, 3, 5, 1, 2, 4};
  { // test projection
    auto proj = [](int& i) { return i == 5 ? 100 : i; };
    int ret = std::ranges::max(a, std::ranges::less{}, proj);
    assert(ret == 5);
  }

  { // test comparator
    int ret = std::ranges::max(a, std::ranges::greater{});
    assert(ret == 1);
  }

  { // check that predicate and projection call counts are correct
    int compares = 0;
    int projections = 0;
    auto comparator = [&](int x, int y) {
      ++compares;
      return x < y;
    };
    auto projection = [&](int x) {
      ++projections;
      return x;
    };
    std::same_as<int> decltype(auto) ret = std::ranges::max(std::array{1, 2, 3}, comparator, projection);
    assert(ret == 3);
    assert(compares == 2);
    assert(projections == 4);
  }

  { // check that std::invoke is used
    struct S { int i; };
    S b[3] = { S{2}, S{1}, S{3} };
    std::same_as<S> decltype(auto) ret = std::ranges::max(b, {}, &S::i);
    assert(ret.i == 3);
  }

  { // check that the first largest element is returned
    { // where the first element is the largest
      struct S { int check; int other; };
      S b[] = { S{1, 1}, S{0, 2}, S{1, 3} };
      auto ret = std::ranges::max(b, {}, &S::check);
      assert(ret.check == 1);
      assert(ret.other == 1);
    }
    { // where the first element isn't the largest
      struct S { int check; int other; };
      S b[] = { S{0, 1}, S{1, 2}, S{1, 3} };
      auto ret = std::ranges::max(b, {}, &S::check);
      assert(ret.check == 1);
      assert(ret.other == 2);
    }
  }
}

constexpr bool test() {
  test_2_arguments();
  test_initializer_list();
  test_range();

  return true;
}

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

  return 0;
}
