/***************************************************************************//**
 *
 * \file rosa/core/MessageHandler.hpp
 *
 * \author David Juhasz (david.juhasz@tuwien.ac.at)
 *
 * \date 2017
 *
 * \brief Facility for combining `rosa::Invoker` instances and applying
 *        `rosa::Message` intances to them.
 *
 ******************************************************************************/

#ifndef ROSA_CORE_MESSAGEHANDLER_HPP
#define ROSA_CORE_MESSAGEHANDLER_HPP

#include "rosa/core/Invoker.hpp"

#include "rosa/support/log.h"

#include <vector>

namespace rosa {

/// Handles `rosa::Message` instances.
///
/// A `rosa::MessageHandler` stores `rosa::Invoker` instances and tries to
/// apply `rosa::Message` objects to them in the order of definition.The first
/// matching `rosa::Invoker` instance is invoked with the `rosa::Message`
/// object, after which handling of that `rosa::Message` object is completed.
///
/// For example, consider the following snippet: \code
/// rosa::MessageHandler {
///   rosa::Invoker::F<uint8_t>([](uint8_t) { /* ... */ }),
///   rosa::Invoker::F<uint8_t>([](uint8_t) { /* Never invoked */ })
/// };
/// \endcode
/// Applying a `rosa::Message` with `rosa::TypeList<uint8_t>` invokes the first
/// function, and the second function would never be invoked because any
/// matching `rosa::Message` object had already been handled by the first one.
class MessageHandler {

  /// Type alias to bring `rosa::Invoker::invoker_t` to the local scope.
  using invoker_t = Invoker::invoker_t;

  /// Type alias for a `std::vector` storing `rosa::Invoker` instances.
  using invokers_t = std::vector<invoker_t>;

  /// Stores `rosa::Invoker` instances.
  const invokers_t Invokers;

  /// Creates a container with `rosa::Invoker` instances from functions.
  ///
  /// \tparam Fun type of the mandatory first function
  /// \tparam Funs types of further functions
  ///
  /// \param F the mandatory first function
  /// \param Fs optional further functions
  ///
  /// \return `invokers_t` object storing `rosa::Invoker` instances created
  /// from the `F` and `Fs...`
  template <typename Fun, typename... Funs>
  static inline invokers_t createInvokers(Fun &&F, Funs &&... Fs) noexcept;

  /// Updates an `invokers_t` object with a new `rosa::Invoker` instance and
  /// handles further functions recursively.
  ///
  /// \tparam Fun type of the first function
  /// \tparam Funs types of further functions
  ///
  /// \param I `invokers_t` to update
  /// \param Pos index at which to store the new `rosa::Invoker` instance
  /// \param F function to wrap and store into `I` at index `Pos`
  /// \param Fs further functions to handle later
  ///
  /// \pre `Pos` is a valid index:\code
  /// Pos < I.size()
  /// \endcode
  template <typename Fun, typename... Funs>
  static inline void wrapFun(invokers_t &I, const size_t Pos, Fun &&F,
                             Funs &&... Fs) noexcept;

  /// Terminal case for the template `rosa::MessageHandler::wrapFun`.
  ///
  /// \param I `invokers_t` which is now complete
  /// \param Pos size of `I`
  ///
  /// \pre `Pos` is the size of `I`:\code
  /// Pos == I.size();
  /// \endcode
  static inline void wrapFun(invokers_t &I, const size_t Pos) noexcept;

public:
  /// Creates an instance.
  ///
  /// The constructor stores the given functions into the new
  /// `rosa::MessageHandler` instance.
  ///
  /// \tparam Fun type of the mandatory first function
  /// \tparam Funs types of further functions
  ///
  /// \param F the first function to store
  /// \param Fs optional further functions to store
  template<typename Fun, typename... Funs>
  MessageHandler(Fun &&F, Funs &&... Fs) noexcept;

  /// Destroys `this` object.
  virtual ~MessageHandler(void);

  /// Tells if a `rosa::Message` object can be handled by `this` object.
  ///
  /// \param Msg `rosa::Message`to check
  ///
  /// \return whether `this` object stores a `rosa::Invoker` instance that can
  /// handle `Msg`
  bool canHandle(const Message &Msg) const noexcept;

  /// Applies a `rosa::Message` object to the first stored `rosa::Invoker` that
  /// can handle it, and tells if there was any.
  ///
  /// \note This operator finds the first applicable `rosa::Invoker` and invokes
  /// it with the given `rosa::Message` object, while the member function
  /// `rosa::MessageHandler::canHandle` only checks if there is any
  /// `rosa::Invoker` that can be invoked with `rosa::Message` object.
  ///
  /// \param Msg `rosa::Message` to use in invoking a matching `rosa::Invoker`
  ///
  /// \return whether there was a matching `rosa::Invoker` found and invoked
  /// with `Msg`.
  bool operator()(const Message &Msg) const noexcept;
};

template <typename Fun, typename... Funs>
MessageHandler::MessageHandler(Fun &&F, Funs &&... Fs) noexcept
    : Invokers(createInvokers(std::move(F), std::move(Fs)...)) {
  LOG_TRACE("MessageHandler is created");
}

template <typename Fun, typename... Funs>
MessageHandler::invokers_t
MessageHandler::createInvokers(Fun &&F, Funs &&... Fs) noexcept {
  // Create a container with the required size and get all the functions
  // wrapped.
  invokers_t I(1 + sizeof...(Funs));
  wrapFun(I, 0, std::move(F), std::move(Fs)...);
  return I;
}

template <typename Fun, typename... Funs>
void MessageHandler::wrapFun(invokers_t &I, const size_t Pos, Fun &&F,
                             Funs &&... Fs) noexcept {
  ASSERT(Pos < I.size()); // Sanity check.
  // Wrap the current function and continue with the rest.
  I[Pos] = Invoker::wrap(std::move(F));
  wrapFun(I, Pos + 1, std::move(Fs)...);
}

void MessageHandler::wrapFun(invokers_t &I, const size_t Pos) noexcept {
  ASSERT(Pos == I.size()); // Sanity check.
  // Nothing to do here.
}

} // End namespace rosa

#endif // ROSA_CORE_MESSAGEHANDLER_HPP

