/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

#include <type_traits>

#include <folly/Portability.h>
#include <folly/Traits.h>
#include <folly/functional/Invoke.h>
#include <folly/functional/traits.h>

namespace folly {

//  match_empty_function_protocol
//  match_empty_function_protocol_fn
//
//  Evaluates the empty-function protocol for a given value, returning true if
//  the value matches the empty-function protocol and false otherwise.
//
//  A value matches the empty-function protocol when all of the following
//  conditions hold:
//  * its type admits explicit construction with the special value nullptr,
//  * its type admits equality-comparison with the special value nullptr,
//  * it compares-equal-to the special value nullptr.
//
//  In particular, default-constructed values of the following native and
//  standard-library types match the empty-function protocol:
//  * pointer-to-function
//  * pointer-to-member-function
//  * std::function
//  * std::move_only_function
//
//  Likewise, move-constructed-from and move-assigned-from values of the
//  following standard-library types match the empty-function protocol:
//  * std::function
//  * std::move_only_function
//  However, it is unspecified whether self-move-assigned-from values of the
//  above types match the empty-function protocol.
struct match_empty_function_protocol_fn {
 private:
  template <typename T>
  using detect_from_eq_nullptr =
      decltype(static_cast<bool>(static_cast<T const&>(T(nullptr)) == nullptr));

  template <typename T>
  static constexpr bool cx_matches_v =
      require_sizeof<T> && is_detected_v<detect_from_eq_nullptr, T>;

 public:
  template <typename T, std::enable_if_t<!cx_matches_v<T>, int> = 0>
  constexpr bool operator()(T const&) const noexcept {
    return false;
  }
  template <typename T, std::enable_if_t<cx_matches_v<T>, int> = 0>
  constexpr bool operator()(T const& t) const noexcept {
    return static_cast<bool>(t == nullptr);
  }
};
inline constexpr match_empty_function_protocol_fn
    match_empty_function_protocol{};

//  ----

//  match_static_lambda_protocol_v
//
//  Evaluates the static-lambda protocol for a given type.
//
//  A value type T matches the static-lambda protocol when all of the following
//  conditions hold:
//  * T is empty
//  * T is trivially-copyable
//
//  In particular, capture-free lambdas match the static-lambda protocol. Static
//  lambdas, introduced in C++23, are capture-free and therefore match the
//  static-lambda protocol as well.
//
//  In particular, certain function-objects in the standard library match the
//  static-lambda protocol:
//  * std::identity
//  * std::less, std::equal_to, std::greater
//  * std::hash
//  * std::default_delete
template <typename F>
static constexpr bool match_static_lambda_protocol_v = ( //
    std::is_empty<F>::value && //
    std::is_trivially_copyable_v<F> && //
    true);

//  ----

namespace detail {

template <typename S>
struct match_safely_invocable_as_protocol_impl_ {
  using traits = function_traits<S>;

  using sig_r = typename traits::result_type;
  static constexpr bool sig_nx = traits::is_nothrow;

  template <typename F>
  using fun_cvref_0 = conditional_t<std::is_reference<F>::value, F, F&>;
  template <typename F>
  using fun_cvref = fun_cvref_0<typename traits::template value_like<F>>;

  template <typename F>
  struct fun_r_ {
    template <typename... A>
    using apply = invoke_result_t<F, A...>;
  };
  template <typename F>
  using fun_r =
      typename traits::template arguments<fun_r_<fun_cvref<F>>::template apply>;

  template <typename F>
  struct fun_inv_ {
    template <typename... A>
    using apply = is_invocable_r<sig_r, F, A...>;
  };
  template <typename F>
  struct fun_inv_nx_ {
    template <typename... A>
    using apply = is_nothrow_invocable_r<sig_r, F, A...>;
  };

  template <typename F>
  using is_invocable_r_ = typename traits::template arguments<
      conditional_t<sig_nx, fun_inv_nx_<F>, fun_inv_<F>>::template apply>;

  template <typename F, typename FCVR = fun_cvref<F>>
  static constexpr bool is_invocable_r_v = std::is_reference<FCVR>::value
      ? is_invocable_r_<F>::value
      : is_invocable_r_<F&>::value && is_invocable_r_<F&&>::value;
};

template <
    typename Fun,
    typename Sig,
    typename Impl = match_safely_invocable_as_protocol_impl_<Sig>,
    typename FunR = typename Impl::template fun_r<Fun>,
    typename SigR = typename Impl::sig_r,
    typename FunQ = typename Impl::template fun_cvref<Fun>,
    bool SigNx = Impl::sig_nx,
    // forbid reference-to-expired-temporary
    typename = std::enable_if_t<
        !std::is_reference<SigR>::value || std::is_reference<FunR>::value>,
    // forbid object slicing: derived maybe-ref to base non-ref conversion
    typename = std::enable_if_t<
        std::is_same<decay_t<SigR>, decay_t<FunR>>::value ||
        std::is_reference<SigR>::value ||
        !std::is_base_of<decay_t<SigR>, decay_t<FunR>>::value>,
    // forbid mismatched cvref
    // forbid pointer with signature cvref
    // forbid noexcept wrapping non-noexcept
    // forbid non-convertible return-type
    // forbid noexcept wrapping non-noexcept return-type conversion
    typename = std::enable_if_t<Impl::template is_invocable_r_v<FunQ>>>
using match_safely_invocable_as_protocol_detect_1_ = void;

template <typename Fun, typename Sig>
using match_safely_invocable_as_protocol_detect_ =
    match_safely_invocable_as_protocol_detect_1_<Fun, Sig>;

} // namespace detail

//  match_safely_invocable_as_protocol_v
//
//  Evaluates the safely-invocable-as protocol for a given type with respect to
//  a given signature. This protocol determines whether a given type honors a
//  set of implied constraints imposed by the given function type (signature).
//
//  A type F matches the safely-invocable-as protocol for a given signature S
//  when all of the following conditions hold:
//  * invocation compatibility
//    * argument-type-list conversions
//    * return-type conversion
//    * cvref compatibility
//    * noexcept compatibility
//  * no reference-to-temporary in return-type conversion
//  * no object slicing in return-type conversion
//
//  The meaning of invocation compatibility above is as per the is-callable-from
//  table in the documentation of std::move_only_function (C++23), extended with
//  volatile qualifications. See:
//    http://eel.is/c++draft/func.wrap.move
template <typename F, typename Sig>
inline constexpr bool match_safely_invocable_as_protocol_v =
    is_detected_v<detail::match_safely_invocable_as_protocol_detect_, F, Sig>;

} // namespace folly
