// Copyright 2014 Open Source Robotics Foundation, Inc.
//
// 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.

#ifndef RCLCPP__EXECUTORS_HPP_
#define RCLCPP__EXECUTORS_HPP_

#include <future>
#include <memory>

#include "rclcpp/executors/multi_threaded_executor.hpp"
#include "rclcpp/executors/single_threaded_executor.hpp"
#include "rclcpp/experimental/executors/events_executor/events_executor.hpp"
#include "rclcpp/executors/events_cbg_executor/events_cbg_executor.hpp"
#include "rclcpp/node.hpp"
#include "rclcpp/utilities.hpp"
#include "rclcpp/visibility_control.hpp"

namespace rclcpp
{

/**
 * @brief Create a default single-threaded executor and execute all available work exhaustively.
 * @param node_ptr Shared pointer to the base interface of the node to spin.
 * @param max_duration max duration to spin
 *
 * This method is deprecated because it can lead to very bad performance if used in a loop:
 * each call will create a new executor and register the node, which is an expensive operation.
 * It's recommended to always manually instantiate an executor and call the methods with
 * the same name on it.
 * For example:
 *     SingleThreadedExecutor executor;
 *     executor.add_node(node_ptr);
 *     executor.spin_all(max_duration);
 * If you are using a non-default context, this should be passed to the executor's constructor.
 */
[[deprecated("use SingleThreadedExecutor::spin_all instead")]]
RCLCPP_PUBLIC
void
spin_all(
  const rclcpp::node_interfaces::NodeBaseInterface::SharedPtr & node_ptr,
  std::chrono::nanoseconds max_duration);

/**
 * @brief Create a default single-threaded executor and execute all available work exhaustively.
 * @param node_ptr Shared pointer to the node to spin.
 * @param max_duration max duration to spin
 *
 * This method is deprecated because it can lead to very bad performance if used in a loop:
 * each call will create a new executor and register the node, which is an expensive operation.
 * It's recommended to always manually instantiate an executor and call the methods with
 * the same name on it.
 * For example:
 *     SingleThreadedExecutor executor;
 *     executor.add_node(node_ptr);
 *     executor.spin_all(max_duration);
 * If you are using a non-default context, this should be passed to the executor's constructor.
 */
[[deprecated("use SingleThreadedExecutor::spin_all instead")]]
RCLCPP_PUBLIC
void
spin_all(const rclcpp::Node::SharedPtr & node_ptr, std::chrono::nanoseconds max_duration);

/**
 * @brief Create a default single-threaded executor and execute any immediately available work.
 * @param node_ptr Shared pointer to the base interface of the node to spin.
 *
 * This method is deprecated because it can lead to very bad performance if used in a loop:
 * each call will create a new executor and register the node, which is an expensive operation.
 * It's recommended to always manually instantiate an executor and call the methods with
 * the same name on it.
 * For example:
 *     SingleThreadedExecutor executor;
 *     executor.add_node(node_ptr);
 *     executor.spin_some();
 * If you are using a non-default context, this should be passed to the executor's constructor.
 */
[[deprecated("use SingleThreadedExecutor::spin_some instead")]]
RCLCPP_PUBLIC
void
spin_some(const rclcpp::node_interfaces::NodeBaseInterface::SharedPtr & node_ptr);

/**
 * @brief Create a default single-threaded executor and execute any immediately available work.
 * @param node_ptr Shared pointer to the node to spin.
 *
 * This method is deprecated because it can lead to very bad performance if used in a loop:
 * each call will create a new executor and register the node, which is an expensive operation.
 * It's recommended to always manually instantiate an executor and call the methods with
 * the same name on it.
 * For example:
 *     SingleThreadedExecutor executor;
 *     executor.add_node(node_ptr);
 *     executor.spin_some();
 * If you are using a non-default context, this should be passed to the executor's constructor.
 */
[[deprecated("use SingleThreadedExecutor::spin_some instead")]]
RCLCPP_PUBLIC
void
spin_some(const rclcpp::Node::SharedPtr & node_ptr);

/// Create a default single-threaded executor and spin the specified node.
/** \param[in] node_ptr Shared pointer to the node to spin. */
RCLCPP_PUBLIC
void
spin(const rclcpp::node_interfaces::NodeBaseInterface::SharedPtr & node_ptr);

RCLCPP_PUBLIC
void
spin(const rclcpp::Node::SharedPtr & node_ptr);

namespace executors
{

using rclcpp::executors::MultiThreadedExecutor;
using rclcpp::executors::SingleThreadedExecutor;
using rclcpp::executors::EventsCBGExecutor;

/// Spin (blocking) until the future is complete, it times out waiting, or rclcpp is interrupted.
/**
 * \param[in] executor The executor which will spin the node.
 * \param[in] node_ptr The node to spin.
 * \param[in] future The future to wait on. If `SUCCESS`, the future is safe to
 *   access after this function
 * \param[in] timeout Optional timeout parameter, which gets passed to
 *   Executor::spin_node_once.
 *   `-1` is block forever, `0` is non-blocking.
 *   If the time spent inside the blocking loop exceeds this timeout, return a `TIMEOUT` return code.
 * \return The return code, one of `SUCCESS`, `INTERRUPTED`, or `TIMEOUT`.
 */
template<typename FutureT, typename TimeRepT = int64_t, typename TimeT = std::milli>
rclcpp::FutureReturnCode
spin_node_until_future_complete(
  rclcpp::Executor & executor,
  const rclcpp::node_interfaces::NodeBaseInterface::SharedPtr & node_ptr,
  const FutureT & future,
  std::chrono::duration<TimeRepT, TimeT> timeout = std::chrono::duration<TimeRepT, TimeT>(-1))
{
  // TODO(wjwwood): does not work recursively; can't call spin_node_until_future_complete
  // inside a callback executed by an executor.
  executor.add_node(node_ptr);
  auto retcode = executor.spin_until_future_complete(future, timeout);
  executor.remove_node(node_ptr);
  return retcode;
}

template<typename NodeT = rclcpp::Node, typename FutureT, typename TimeRepT = int64_t,
  typename TimeT = std::milli>
rclcpp::FutureReturnCode
spin_node_until_future_complete(
  rclcpp::Executor & executor,
  const std::shared_ptr<NodeT> & node_ptr,
  const FutureT & future,
  std::chrono::duration<TimeRepT, TimeT> timeout = std::chrono::duration<TimeRepT, TimeT>(-1))
{
  return rclcpp::executors::spin_node_until_future_complete(
    executor,
    node_ptr->get_node_base_interface(),
    future,
    timeout);
}

}  // namespace executors

template<typename FutureT, typename TimeRepT = int64_t, typename TimeT = std::milli>
rclcpp::FutureReturnCode
spin_until_future_complete(
  const rclcpp::node_interfaces::NodeBaseInterface::SharedPtr & node_ptr,
  const FutureT & future,
  std::chrono::duration<TimeRepT, TimeT> timeout = std::chrono::duration<TimeRepT, TimeT>(-1))
{
  rclcpp::ExecutorOptions options;
  options.context = node_ptr->get_context();
  rclcpp::executors::SingleThreadedExecutor executor(options);
  return executors::spin_node_until_future_complete<FutureT>(executor, node_ptr, future, timeout);
}

template<typename NodeT = rclcpp::Node, typename FutureT, typename TimeRepT = int64_t,
  typename TimeT = std::milli>
rclcpp::FutureReturnCode
spin_until_future_complete(
  const std::shared_ptr<NodeT> & node_ptr,
  const FutureT & future,
  std::chrono::duration<TimeRepT, TimeT> timeout = std::chrono::duration<TimeRepT, TimeT>(-1))
{
  return rclcpp::spin_until_future_complete(node_ptr->get_node_base_interface(), future, timeout);
}

}  // namespace rclcpp

#endif  // RCLCPP__EXECUTORS_HPP_
