//===----------------------------------------------------------------------===//
//
// 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, c++20, c++23

// <functional>

// template<auto f> constexpr unspecified not_fn() noexcept;

#include <functional>

#include <bit>
#include <cassert>
#include <concepts>
#include <type_traits>
#include <utility>

#include "test_macros.h"

class BooleanTestable {
  bool val_;

public:
  constexpr explicit BooleanTestable(bool val) : val_(val) {}
  constexpr operator bool() const { return val_; }
  constexpr BooleanTestable operator!() const { return BooleanTestable{!val_}; }
};

LIBCPP_STATIC_ASSERT(std::__boolean_testable<BooleanTestable>);

class FakeBool {
  int val_;

public:
  constexpr FakeBool(int val) : val_(val) {}
  constexpr FakeBool operator!() const { return FakeBool{-val_}; }
  constexpr bool operator==(int other) const { return val_ == other; }
};

template <bool IsNoexcept>
struct MaybeNoexceptFn {
  bool operator()() const noexcept(IsNoexcept); // not defined
};

template <bool IsNoexcept>
struct MaybeNoexceptNegation {
  bool operator!() noexcept(IsNoexcept); // not defined
};

template <bool IsNoexcept>
MaybeNoexceptNegation<IsNoexcept> maybe_noexcept_negation() noexcept {
  return {};
}

constexpr void basic_tests() {
  { // Test constant functions
    auto false_fn = std::not_fn<std::false_type{}>();
    assert(false_fn());

    auto true_fn = std::not_fn<std::true_type{}>();
    assert(!true_fn());

    static_assert(noexcept(std::not_fn<std::false_type{}>()));
    static_assert(noexcept(std::not_fn<std::true_type{}>()));
  }

  { // Test function with one argument
    auto is_odd = std::not_fn<[](auto x) { return x % 2 == 0; }>();
    assert(is_odd(1));
    assert(!is_odd(2));
    assert(is_odd(3));
    assert(!is_odd(4));
    assert(is_odd(5));
  }

  { // Test function with multiple arguments
    auto at_least_10 = [](auto... vals) { return (vals + ... + 0) >= 10; };
    auto at_most_9   = std::not_fn<at_least_10>();
    assert(at_most_9());
    assert(at_most_9(1));
    assert(at_most_9(1, 2, 3, 4, -1));
    assert(at_most_9(3, 3, 2, 1, -2));
    assert(!at_most_9(10, -1, 2));
    assert(!at_most_9(5, 5));
    static_assert(noexcept(std::not_fn<at_least_10>()));
  }

  { // Test function that returns boolean-testable type other than bool
    auto is_product_even = [](auto... vals) { return BooleanTestable{(vals * ... * 1) % 2 == 0}; };
    auto is_product_odd  = std::not_fn<is_product_even>();
    assert(is_product_odd());
    assert(is_product_odd(1, 3, 5, 9));
    assert(is_product_odd(3, 3, 3, 3));
    assert(!is_product_odd(3, 5, 9, 11, 0));
    assert(!is_product_odd(11, 7, 5, 3, 2));
    static_assert(noexcept(std::not_fn<is_product_even>()));
  }

  { // Test function that returns non-boolean-testable type
    auto sum         = [](auto... vals) -> FakeBool { return (vals + ... + 0); };
    auto negated_sum = std::not_fn<sum>();
    assert(negated_sum() == 0);
    assert(negated_sum(3) == -3);
    assert(negated_sum(4, 5, 1, 3) == -13);
    assert(negated_sum(4, 2, 5, 6, 1) == -18);
    assert(negated_sum(-1, 3, 2, -8) == 4);
    static_assert(noexcept(std::not_fn<sum>()));
  }

  { // Test member pointers
    struct MemberPointerTester {
      bool value = true;
      constexpr bool not_value() const { return !value; }
      constexpr bool value_and(bool other) noexcept { return value && other; }
    };

    MemberPointerTester tester;

    auto not_mem_object = std::not_fn<&MemberPointerTester::value>();
    assert(!not_mem_object(tester));
    assert(!not_mem_object(std::as_const(tester)));
    static_assert(noexcept(not_mem_object(tester)));
    static_assert(noexcept(not_mem_object(std::as_const(tester))));

    auto not_nullary_mem_fn = std::not_fn<&MemberPointerTester::not_value>();
    assert(not_nullary_mem_fn(tester));
    static_assert(!noexcept(not_nullary_mem_fn(tester)));

    auto not_unary_mem_fn = std::not_fn<&MemberPointerTester::value_and>();
    assert(not_unary_mem_fn(tester, false));
    static_assert(noexcept(not_unary_mem_fn(tester, false)));
    static_assert(!std::is_invocable_v<decltype(not_unary_mem_fn), const MemberPointerTester&, bool>);
  }
}

constexpr void test_perfect_forwarding_call_wrapper() {
  { // Make sure we call the correctly cv-ref qualified operator()
    // based on the value category of the not_fn<NTTP> unspecified-type.
    struct X {
      constexpr FakeBool operator()() & { return 1; }
      constexpr FakeBool operator()() const& { return 2; }
      constexpr FakeBool operator()() && { return 3; }
      constexpr FakeBool operator()() const&& { return 4; }
    };

    auto f  = std::not_fn<X{}>();
    using F = decltype(f);
    assert(static_cast<F&>(f)() == -2);
    assert(static_cast<const F&>(f)() == -2);
    assert(static_cast<F&&>(f)() == -2);
    assert(static_cast<const F&&>(f)() == -2);
  }

  // Call to `not_fn<NTTP>` unspecified-type's operator() should always result in call to the const& overload of the underlying function object.
  {
    { // Make sure unspecified-type is still callable when we delete the & overload.
      struct X {
        FakeBool operator()() & = delete;
        FakeBool operator()() const&;
        FakeBool operator()() &&;
        FakeBool operator()() const&&;
      };

      using F = decltype(std::not_fn<X{}>());
      static_assert(std::invocable<F&>);
      static_assert(std::invocable<const F&>);
      static_assert(std::invocable<F>);
      static_assert(std::invocable<const F>);
    }

    { // Make sure unspecified-type is not callable when we delete the const& overload.
      struct X {
        FakeBool operator()() &;
        FakeBool operator()() const& = delete;
        FakeBool operator()() &&;
        FakeBool operator()() const&&;
      };

      using F = decltype(std::not_fn<X{}>());
      static_assert(!std::invocable<F&>);
      static_assert(!std::invocable<const F&>);
      static_assert(!std::invocable<F>);
      static_assert(!std::invocable<const F>);
    }

    { // Make sure unspecified-type is still callable when we delete the && overload.
      struct X {
        FakeBool operator()() &;
        FakeBool operator()() const&;
        FakeBool operator()() && = delete;
        FakeBool operator()() const&&;
      };

      using F = decltype(std::not_fn<X{}>());
      static_assert(std::invocable<F&>);
      static_assert(std::invocable<const F&>);
      static_assert(std::invocable<F>);
      static_assert(std::invocable<const F>);
    }

    { // Make sure unspecified-type is still callable when we delete the const&& overload.
      struct X {
        FakeBool operator()() &;
        FakeBool operator()() const&;
        FakeBool operator()() &&;
        FakeBool operator()() const&& = delete;
      };

      using F = decltype(std::not_fn<X{}>());
      static_assert(std::invocable<F&>);
      static_assert(std::invocable<const F&>);
      static_assert(std::invocable<F>);
      static_assert(std::invocable<const F>);
    }
  }

  { // Test perfect forwarding
    auto f = [](int& val) {
      val = 5;
      return false;
    };

    auto not_f = std::not_fn<f>();
    int val    = 0;
    assert(not_f(val));
    assert(val == 5);

    using NotF = decltype(not_f);
    static_assert(std::invocable<NotF, int&>);
    static_assert(!std::invocable<NotF, int>);
  }
}

constexpr void test_return_type() {
  { // Test constructors and assignment operators
    struct IsPowerOfTwo {
      constexpr bool operator()(unsigned int x) const { return std::has_single_bit(x); }
    };

    auto is_not_power_of_2 = std::not_fn<IsPowerOfTwo{}>();
    assert(is_not_power_of_2(5));
    assert(!is_not_power_of_2(4));

    auto moved = std::move(is_not_power_of_2);
    assert(moved(5));
    assert(!moved(4));

    auto copied = is_not_power_of_2;
    assert(copied(7));
    assert(!copied(8));

    moved = std::move(copied);
    assert(copied(9));
    assert(!copied(16));

    copied = moved;
    assert(copied(11));
    assert(!copied(32));
  }

  { // Make sure `not_fn<NTTP>` unspecified-type's operator() is SFINAE-friendly.
    using F = decltype(std::not_fn<[](int x) { return !x; }>());
    static_assert(!std::is_invocable<F>::value);
    static_assert(std::is_invocable<F, int>::value);
    static_assert(!std::is_invocable<F, void*>::value);
    static_assert(!std::is_invocable<F, int, int>::value);
  }

  { // Test noexceptness
    auto always_noexcept = std::not_fn<MaybeNoexceptFn<true>{}>();
    static_assert(noexcept(always_noexcept()));

    auto never_noexcept = std::not_fn<MaybeNoexceptFn<false>{}>();
    static_assert(!noexcept(never_noexcept()));

    auto always_noexcept_negation = std::not_fn<maybe_noexcept_negation<true>>();
    static_assert(noexcept(always_noexcept_negation()));

    auto never_noexcept_negation = std::not_fn<maybe_noexcept_negation<false>>();
    static_assert(!noexcept(never_noexcept_negation()));
  }

  { // Test calling volatile wrapper
    using NotFn = decltype(std::not_fn<std::false_type{}>());
    static_assert(!std::invocable<volatile NotFn&>);
    static_assert(!std::invocable<const volatile NotFn&>);
    static_assert(!std::invocable<volatile NotFn>);
    static_assert(!std::invocable<const volatile NotFn>);
  }
}

constexpr bool test() {
  basic_tests();
  test_perfect_forwarding_call_wrapper();
  test_return_type();

  return true;
}

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

  return 0;
}
