/*
 * 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 <tuple>
#include <utility>

#include <folly/Utility.h>
#include <folly/functional/Invoke.h>

namespace folly {

namespace detail {
namespace partial {

// helper type to make sure that the templated constructor in Partial does
// not accidentally act as copy or move constructor
struct PartialConstructFromCallable {};

template <typename F, typename... StoredArg>
class Partial {
  using Tuple = std::tuple<StoredArg...>;
  using Indexes = std::index_sequence_for<StoredArg...>;

  template <typename Self, std::size_t... I, typename... Args>
  static auto invokeForward(
      Self&& self, std::index_sequence<I...>, Args&&... args)
      -> invoke_result_t<
          like_t<Self&&, F>,
          like_t<Self&&, StoredArg>...,
          Args&&...> {
    return invoke(
        std::forward<Self>(self).f_,
        std::get<I>(std::forward<Self>(self).stored_args_)...,
        std::forward<Args>(args)...);
  }

 public:
  template <typename Callable, typename... Args>
  Partial(PartialConstructFromCallable, Callable&& callable, Args&&... args)
      : f_(std::forward<Callable>(callable)),
        stored_args_(std::forward<Args>(args)...) {}

  template <typename... CArgs>
  auto operator()(CArgs&&... cargs) & -> decltype(invokeForward(
                                          std::declval<Partial&>(),
                                          Indexes{},
                                          std::declval<CArgs>()...)) {
    return invokeForward(*this, Indexes{}, std::forward<CArgs>(cargs)...);
  }
  template <typename... CArgs>
  auto operator()(CArgs&&... cargs) const& -> decltype(invokeForward(
                                               std::declval<const Partial&>(),
                                               Indexes{},
                                               std::declval<CArgs>()...)) {
    return invokeForward(*this, Indexes{}, std::forward<CArgs>(cargs)...);
  }
  template <typename... As>
  auto operator()(As&&... a) && -> decltype(invokeForward(
                                    std::declval<Partial&&>(),
                                    Indexes{},
                                    std::declval<As>()...)) {
    return invokeForward(std::move(*this), Indexes{}, std::forward<As>(a)...);
  }
  template <typename... As>
  auto operator()(As&&... as) const&& -> decltype(invokeForward(
                                          std::declval<const Partial&&>(),
                                          Indexes{},
                                          std::declval<As>()...)) {
    return invokeForward(std::move(*this), Indexes{}, std::forward<As>(as)...);
  }

 private:
  // the stored callable
  F f_;
  // the stored arguments, these will be forwarded along with the actual
  // argumnets to the callable above
  Tuple stored_args_;
};

} // namespace partial
} // namespace detail

/**
 * Partially applies arguments to a callable
 *
 * `partial` takes a callable and zero or more additional arguments and returns
 * a callable object, which when called with zero or more arguments, will invoke
 * the original callable with the additional arguments passed to `partial`,
 * followed by those passed to the call.
 *
 * E.g. `partial(Foo, 1, 2)(3)` is equivalent to `Foo(1, 2, 3)`.
 *
 * `partial` can be used to bind a class method to an instance:
 * `partial(&Foo::method, foo_pointer)` returns a callable object that can be
 * invoked in the same way as `foo_pointer->method`. In case the first
 * argument in a call to `partial` is a member pointer, the second argument
 * can be a reference, pointer or any object that can be dereferenced to
 * an object of type Foo (like `std::shared_ptr` or `std::unique_ptr`).
 *
 * `partial` is similar to `std::bind`, but you don't have to use placeholders
 * to have arguments passed on. Any number of arguments passed to the object
 * returned by `partial` when called will be added to those passed to `partial`
 * and passed to the original callable.
 */
template <typename F, typename... Args>
auto partial(F&& f, Args&&... args) -> detail::partial::Partial< //
                                        typename std::decay<F>::type,
                                        typename std::decay<Args>::type...> {
  return {
      detail::partial::PartialConstructFromCallable{},
      std::forward<F>(f),
      std::forward<Args>(args)...};
}

} // namespace folly
