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

namespace folly {

template <typename... Holders>
class any_badge;

/**
 * Badge pattern allows us to abstract over friend classes and make friend
 * feature more scoped. Using this simple technique we can specify a badge tag
 * on specific functions we want to gate for particular caller contexts (badge
 * holders). Badge can only be constructed by the specified badge holder binding
 * the tagged functions to that call site.
 *
 * As a rule, it is poor form to pass a badge to arbitrary code. It is poor form
 * for virtual, type-erased, or template functions to accept badges or to be
 * passed badges.
 *
 * Example:
 *   class ProtectedClass: {
 *     ...
 *    public:
 *     ...
 *     static void func(folly::badge<FriendClass>);
 *   };
 *
 *   void FriendClass::callProtectedFunc() {
 *     ProtectedClass::func({});               // compiles
 *   }
 *   void OtherClass::callProtectedFunc() {
 *     ProtectedClass::func({});               // does not compile!
 *   }
 *
 *     See: https://awesomekling.github.io/Serenity-C++-patterns-The-Badge/
 *
 */
template <typename Holder>
class badge {
 public:
  friend Holder;
  /* implicit */ constexpr badge(any_badge<Holder>) noexcept {}

 private:
  /* implicit */ constexpr badge() noexcept {}
};

/**
 * For cases when multiple badge holders need to call a function we can
 * use folly::any_badge over each individual holder allowed.
 * We allow subsets of badges to lift into supersets:
 * folly::any_badge<A, B> lifts into folly::any_badge<A, B, C>.
 *
 * Example:
 *   class ProtectedClass: {
 *    public:
 *     static void func(folly::any_badge<FriendClass, OtherFriendClass>);
 *    };
 *
 *   void FriendClass::callProtectedFunc() {
 *     ProtectedClass::func(folly::badge<FriendClass>{});      // compiles
 *   }
 *   void OtherFriendClass::callProtectedFunc() {
 *     ProtectedClass::func(folly::badge<OtherFriendClass>{}); // compiles
 *   }
 */
template <typename... Holders>
class any_badge {
 public:
  template <
      typename Holder,
      typename = std::enable_if_t<folly::IsOneOf<Holder, Holders...>::value>>
  /* implicit */ constexpr any_badge(badge<Holder>) noexcept {}

// Fedora 34 and 35 ship a gcc with the following regression:
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=104008
#if __GNUC__ == 11 && __GNUC_MINOR__ <= 2
  template <typename... OtherHolders>
  /* implicit */ constexpr any_badge(any_badge<OtherHolders...>) noexcept {}
#else
  template <
      typename... OtherHolders,
      typename = std::enable_if_t<folly::StrictConjunction<
          folly::IsOneOf<OtherHolders, Holders...>...>::value>>
  /* implicit */ constexpr any_badge(any_badge<OtherHolders...>) noexcept {}
#endif
};

} // namespace folly
