//===-- rosa/core/MessageHandler.hpp ----------------------------*- C++ -*-===//
//
//                                 The RoSA Framework
//
// Distributed under the terms and conditions of the Boost Software License 1.0.
// See accompanying file LICENSE.
//
// If you did not receive a copy of the license file, see
// http://www.boost.org/LICENSE_1_0.txt.
//
//===----------------------------------------------------------------------===//
///
/// \file rosa/core/MessageHandler.hpp
///
/// \author David Juhasz (david.juhasz@tuwien.ac.at)
///
/// \date 2017
///
/// \brief Facility for combining \c rosa::Invoker instances and applying
///        \c 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 \c rosa::Message instances.
///
/// A \c rosa::MessageHandler stores \c rosa::Invoker instances and tries to
/// apply \c rosa::Message objects to them in the order of definition.The first
/// matching \c rosa::Invoker instance is invoked with the \c rosa::Message
/// object, after which handling of that \c 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 \c rosa::Message with \c rosa::TypeList<uint8_t> invokes the
/// first function, and the second function would never be invoked because any
/// matching \c rosa::Message object had already been handled by the first one.
class MessageHandler {

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

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

  /// Stores \c rosa::Invoker instances.
  const invokers_t Invokers;

  /// Creates a container with \c 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 \c rosa::MessageHandler::invokers_t object storing
  /// \c rosa::Invoker instances created from the \p F and \p Fs...
  template <typename Fun, typename... Funs>
  static inline invokers_t createInvokers(Fun &&F, Funs &&... Fs) noexcept;

  /// Updates an \c rosa::MessageHandler::invokers_t object with a new
  /// \c rosa::Invoker instance and handles further functions recursively.
  ///
  /// \tparam Fun type of the first function
  /// \tparam Funs types of further functions
  ///
  /// \param I \c rosa::MessageHandler::invokers_t to update
  /// \param Pos index at which to store the new \c rosa::Invoker instance
  /// \param F function to wrap and store into \p I at index \p Pos
  /// \param Fs further functions to handle later
  ///
  /// \pre \p 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 \c rosa::MessageHandler::wrapFun.
  ///
  /// \param I \c rosa::MessageHandler::invokers_t which is now complete
  /// \param Pos size of \p I
  ///
  /// \pre \p Pos is the size of \p 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
  /// \c 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 \p this object.
  virtual ~MessageHandler(void);

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

  /// Applies a \c rosa::Message object to the first stored \c rosa::Invoker
  /// that can handle it, and tells if there was any.
  ///
  /// \note This operator finds the first applicable \c rosa::Invoker and
  /// invokes it with the given \c rosa::Message object, while the member
  /// function \c rosa::MessageHandler::canHandle only checks if there is any
  /// \c rosa::Invoker that can be invoked with a given \c rosa::Message object.
  ///
  /// \param Msg \c rosa::Message to use in invoking a matching \c rosa::Invoker
  ///
  /// \return whether there was a matching \c rosa::Invoker found and invoked
  /// with \p 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
