/*
 * 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.
 */

#include <folly/functional/protocol.h>

#include <folly/Traits.h>
#include <folly/portability/GTest.h>

struct ProtocolTest : testing::Test {
  template <typename F>
  static constexpr bool empty_function(F const& f) {
    return folly::match_empty_function_protocol(f);
  }

  template <typename F>
  static constexpr bool static_lambda_v =
      folly::match_static_lambda_protocol_v<F>;

  template <typename F, typename S>
  static constexpr bool safely_invocable_v =
      folly::match_safely_invocable_as_protocol_v<F, S>;
};

TEST_F(ProtocolTest, match_empty_function_protocol) {
  constexpr auto lambda = [] {};
  struct nada {
    void go() {}
  };

  using sig = void();
  using mfnp = void (nada::*)();

  EXPECT_FALSE((std::bool_constant<empty_function(lambda)>{}));

  EXPECT_TRUE(empty_function(static_cast<sig*>(nullptr)));
  EXPECT_FALSE(empty_function(static_cast<sig*>(lambda)));

  EXPECT_TRUE(empty_function(static_cast<mfnp>(nullptr)));
  EXPECT_FALSE(empty_function(static_cast<mfnp>(&nada::go)));

  EXPECT_TRUE(empty_function(std::function<sig>{}));
  EXPECT_TRUE(empty_function(std::function<sig>{nullptr}));
  EXPECT_FALSE(empty_function(std::function<sig>{lambda}));
}

TEST_F(ProtocolTest, match_static_lambda_protocol) {
  struct not_empty {
    int dummy;
  };
  EXPECT_FALSE(static_lambda_v<not_empty>);

  struct not_trivially_copyable {
    not_trivially_copyable(not_trivially_copyable const&);
    not_trivially_copyable& operator=(not_trivially_copyable const&);
  };
  EXPECT_FALSE(static_lambda_v<not_trivially_copyable>);

  struct empty_trivially_copyable {
    empty_trivially_copyable() = delete;
  };
  EXPECT_TRUE(static_lambda_v<empty_trivially_copyable>);

  auto lambda = [] {};
  EXPECT_TRUE(static_lambda_v<decltype(lambda)>);

  EXPECT_TRUE(static_lambda_v<std::default_delete<int>>);
  EXPECT_TRUE(static_lambda_v<std::less<int>>);
  EXPECT_TRUE(static_lambda_v<std::greater<int>>);
  EXPECT_TRUE(static_lambda_v<std::equal_to<int>>);
  EXPECT_TRUE(static_lambda_v<std::hash<int>>);
}

TEST_F(ProtocolTest, match_safely_invocable_as_protocol) {
  // non-const non-noexcept invocation
  {
    struct fun {
      [[maybe_unused]] void operator()();
    };
    EXPECT_TRUE((safely_invocable_v<fun, void()>));
    EXPECT_FALSE((safely_invocable_v<fun, void() const>));
    EXPECT_FALSE((safely_invocable_v<fun, void() noexcept>));
  }

  // non-const noexcept-invocation
  {
    struct fun {
      [[maybe_unused]] void operator()() noexcept;
    };
    EXPECT_TRUE((safely_invocable_v<fun, void()>));
    EXPECT_FALSE((safely_invocable_v<fun, void() const>));
    EXPECT_TRUE((safely_invocable_v<fun, void() noexcept>));
  }

  // const non-noexcept invocation
  {
    struct fun {
      [[maybe_unused]] void operator()() const;
    };
    EXPECT_TRUE((safely_invocable_v<fun, void()>));
    EXPECT_TRUE((safely_invocable_v<fun, void() const>));
    EXPECT_FALSE((safely_invocable_v<fun, void() noexcept>));
  }

  // return-type conversions
  {
    struct foo {};
    struct bar : foo {};
    struct wiz_x {
      /* implicit */ [[maybe_unused]] wiz_x(bar);
    };
    struct wiz_nx {
      /* implicit */ [[maybe_unused]] wiz_nx(bar) noexcept;
    };
    struct fun {
      [[maybe_unused]] bar operator()() noexcept;
    };
    EXPECT_TRUE((safely_invocable_v<fun, bar()>));
    EXPECT_FALSE((safely_invocable_v<fun, foo()>)); // slicing
    EXPECT_TRUE((safely_invocable_v<fun, void()>));
    EXPECT_FALSE((safely_invocable_v<fun, bar const&()>)); // dangling-ref
    EXPECT_FALSE((safely_invocable_v<fun, foo const&()>)); // dangling-ref
    EXPECT_TRUE((safely_invocable_v<fun, wiz_x()>));
    EXPECT_TRUE((safely_invocable_v<fun, wiz_nx()>));
    EXPECT_FALSE((safely_invocable_v<fun, wiz_x() noexcept>));
    EXPECT_TRUE((safely_invocable_v<fun, wiz_nx() noexcept>));
  }

  // return-type ref conversions
  {
    struct foo {};
    struct bar : foo {};
    struct fun {
      [[maybe_unused]] bar&& operator()() noexcept;
    };
    using bar_rr = bar&&; // workaround for clang-format fail case
    using foo_rr = foo&&; // workaround for clang-format fail case
    EXPECT_TRUE((safely_invocable_v<fun, bar()>));
    EXPECT_TRUE((safely_invocable_v<fun, bar_rr()>));
    EXPECT_TRUE((safely_invocable_v<fun, bar const&()>));
    EXPECT_FALSE((safely_invocable_v<fun, foo()>)); // slicing
    EXPECT_TRUE((safely_invocable_v<fun, foo_rr()>)); // maybe not slicing
    EXPECT_TRUE((safely_invocable_v<fun, foo const&()>)); // maybe not slicing
    EXPECT_TRUE((safely_invocable_v<fun, void()>));
  }

  // cvref compatibility
  {
    struct fun {
      [[maybe_unused]] void operator()() const&&;
    };
    EXPECT_FALSE((safely_invocable_v<fun, void()>));
    EXPECT_FALSE((safely_invocable_v<fun, void() const>));
    EXPECT_FALSE((safely_invocable_v<fun, void() volatile>));
    EXPECT_FALSE((safely_invocable_v<fun, void() const volatile>));
    EXPECT_FALSE((safely_invocable_v<fun, void()&>));
    EXPECT_FALSE((safely_invocable_v<fun, void() const&>));
    EXPECT_FALSE((safely_invocable_v<fun, void() volatile&>));
    EXPECT_FALSE((safely_invocable_v<fun, void() const volatile&>));
    EXPECT_TRUE((safely_invocable_v<fun, void() &&>));
    EXPECT_TRUE((safely_invocable_v<fun, void() const&&>));
    EXPECT_FALSE((safely_invocable_v<fun, void() volatile&&>));
    EXPECT_FALSE((safely_invocable_v<fun, void() const volatile&&>));
  }
}
