// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef BASE_TEST_GMOCK_CALLBACK_SUPPORT_H_
#define BASE_TEST_GMOCK_CALLBACK_SUPPORT_H_

#include <functional>
#include <tuple>
#include <type_traits>
#include <utility>

#include "base/functional/callback.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "testing/gmock/include/gmock/gmock.h"

namespace base {

namespace gmock_callback_support_internal {

// Small helper to get the `I`th argument.
template <size_t I, typename... Args>
decltype(auto) get(Args&&... args) {
  return std::get<I>(std::forward_as_tuple(std::forward<Args>(args)...));
}

// Invokes `cb` with the arguments stored in `tuple`. Both `cb` and `tuple` are
// perfectly forwarded, allowing callers to specify whether they should be
// passed by move or copy.
template <typename Callback, typename Tuple, size_t... Is>
decltype(auto) RunImpl(Callback&& cb,
                       Tuple&& tuple,
                       std::index_sequence<Is...>) {
  return std::forward<Callback>(cb).Run(
      std::get<Is>(std::forward<Tuple>(tuple))...);
}

// Invokes `cb` with the arguments stored in `tuple`. Both `cb` and `tuple` are
// perfectly forwarded, allowing callers to specify whether they should be
// passed by move or copy. Needs to dispatch to the three arguments version to
// be able to construct a `std::index_sequence` of the corresponding size.
template <typename Callback, typename Tuple>
decltype(auto) RunImpl(Callback&& cb, Tuple&& tuple) {
  return RunImpl(std::forward<Callback>(cb), std::forward<Tuple>(tuple),
                 std::make_index_sequence<
                     std::tuple_size<std::remove_reference_t<Tuple>>::value>());
}

// Invoked when the arguments to a OnceCallback are copy constructible. In this
// case the returned lambda will pass the arguments to the provided callback by
// copy, allowing it to be used multiple times.
template <size_t I,
          typename Tuple,
          std::enable_if_t<std::is_copy_constructible<Tuple>::value, int> = 0>
auto RunOnceCallbackImpl(Tuple&& tuple) {
  return
      [tuple = std::forward<Tuple>(tuple)](auto&&... args) -> decltype(auto) {
        return RunImpl(
            std::move(::base::gmock_callback_support_internal::get<I>(args...)),
            tuple);
      };
}

// Invoked when the arguments to a OnceCallback are not copy constructible. In
// this case the returned lambda will pass the arguments to the provided
// callback by move, allowing it to be only used once.
template <size_t I,
          typename Tuple,
          std::enable_if_t<!std::is_copy_constructible<Tuple>::value, int> = 0>
auto RunOnceCallbackImpl(Tuple&& tuple) {
  // Mock actions need to be copyable, but `tuple` is not. Wrap it in in a
  // `scoped_refptr` to allow it to be copied.
  auto tuple_ptr = base::MakeRefCounted<base::RefCountedData<Tuple>>(
      std::forward<Tuple>(tuple));
  return [tuple_ptr =
              std::move(tuple_ptr)](auto&&... args) mutable -> decltype(auto) {
    // Since running the action will move out of the arguments, `tuple_ptr` is
    // nulled out, so that attempting to run it twice will result in a run-time
    // crash.
    return RunImpl(
        std::move(::base::gmock_callback_support_internal::get<I>(args...)),
        std::move(std::exchange(tuple_ptr, nullptr)->data));
  };
}

// Invoked for RepeatingCallbacks. In this case the returned lambda will pass
// the arguments to the provided callback by copy, allowing it to be used
// multiple times. Move-only arguments are not supported.
template <size_t I, typename Tuple>
auto RunRepeatingCallbackImpl(Tuple&& tuple) {
  return
      [tuple = std::forward<Tuple>(tuple)](auto&&... args) -> decltype(auto) {
        return RunImpl(::base::gmock_callback_support_internal::get<I>(args...),
                       tuple);
      };
}

}  // namespace gmock_callback_support_internal

namespace test {

// Matchers for base::{Once,Repeating}Callback and
// base::{Once,Repeating}Closure.
MATCHER(IsNullCallback, "a null callback") {
  return (arg.is_null());
}

MATCHER(IsNotNullCallback, "a non-null callback") {
  return (!arg.is_null());
}

// The Run[Once]Closure() action invokes the Run() method on the closure
// provided when the action is constructed. Function arguments passed when the
// action is run will be ignored.
ACTION_P(RunClosure, closure) {
  closure.Run();
}

// This action can be invoked at most once. Any further invocation will trigger
// a CHECK failure.
inline auto RunOnceClosure(base::OnceClosure cb) {
  // Mock actions need to be copyable, but OnceClosure is not. Wrap the closure
  // in a base::RefCountedData<> to allow it to be copied.
  using RefCountedOnceClosure = base::RefCountedData<base::OnceClosure>;
  scoped_refptr<RefCountedOnceClosure> copyable_cb =
      base::MakeRefCounted<RefCountedOnceClosure>(std::move(cb));
  return [copyable_cb](auto&&...) {
    CHECK(copyable_cb->data);
    std::move(copyable_cb->data).Run();
  };
}

// The Run[Once]Closure<N>() action invokes the Run() method on the N-th
// (0-based) argument of the mock function.
template <size_t I>
auto RunClosure() {
  return [](auto&&... args) -> decltype(auto) {
    return ::base::gmock_callback_support_internal::get<I>(args...).Run();
  };
}

template <size_t I>
auto RunOnceClosure() {
  return [](auto&&... args) -> decltype(auto) {
    return std::move(::base::gmock_callback_support_internal::get<I>(args...))
        .Run();
  };
}

// The Run[Once]Callback<N>(p1, p2, ..., p_k) action invokes the Run() method on
// the N-th (0-based) argument of the mock function, with arguments p1, p2, ...,
// p_k.
//
// Notes:
//
//   1. The arguments are passed by value by default.  If you need to
//   pass an argument by reference, wrap it inside ByRef().  For example,
//
//     RunCallback<1>(5, string("Hello"), ByRef(foo))
//
//   passes 5 and string("Hello") by value, and passes foo by reference.
//
//   2. If the callback takes an argument by reference but ByRef() is
//   not used, it will receive the reference to a copy of the value,
//   instead of the original value.  For example, when the 0-th
//   argument of the callback takes a const string&, the action
//
//     RunCallback<0>(string("Hello"))
//
//   makes a copy of the temporary string("Hello") object and passes a
//   reference of the copy, instead of the original temporary object,
//   to the callback.  This makes it easy for a user to define an
//   RunCallback action from temporary values and have it performed later.
//
//   3. In order to facilitate re-use of the `RunOnceCallback()` action,
//   the arguments are copied during each run if possible. If this can't
//   be done (e.g. one of the arguments is move-only), the arguments will
//   be passed by move. However, since moving potentially invalidates the
//   arguments, the resulting action is only allowed to run once in this
//   case. Attempting to run it twice will result in a runtime crash.
//   Using move-only arguments with `RunCallback()` is not supported.
template <size_t I, typename... RunArgs>
auto RunOnceCallback(RunArgs&&... run_args) {
  return ::base::gmock_callback_support_internal::RunOnceCallbackImpl<I>(
      std::make_tuple(std::forward<RunArgs>(run_args)...));
}

template <size_t I, typename... RunArgs>
auto RunCallback(RunArgs&&... run_args) {
  return ::base::gmock_callback_support_internal::RunRepeatingCallbackImpl<I>(
      std::make_tuple(std::forward<RunArgs>(run_args)...));
}

}  // namespace test
}  // namespace base

#endif  // BASE_TEST_GMOCK_CALLBACK_SUPPORT_H_