/*******************************************************************************
 *
 * File:     MessageHandler.hpp
 *
 * Contents: Definition of MessageHandler interface.
 *
 * Copyright 2017
 *
 * Author: David Juhasz (david.juhasz@tuwien.ac.at)
 *
 ******************************************************************************/

#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 Message instances. A MessageHandler stores Invokers and tries to
// apply Messages to them in the order of definition. The first matching
// Invoker is invoked with the Message, after which handling of Message is
// completed.
//
// For example, consider the following snippet:
//
// MessageHandler {
//   Invoker::F<uint8_t>([](uint8_t) { /* ... */ }),
//   Invoker::F<uint8_t>([](uint8_t) { /* Never invoked */ })
// };
//
// Applying a Message with TypeList<uint8_t> invokes the first function, and
// the second function would never be invoked because any matching Message had
// already been handled by the first one.
class MessageHandler {

  // Using invoker_t from Invoker.
  using invoker_t = Invoker::invoker_t;

  // Type alias for a vector storing Invokers.
  using invokers_t = std::vector<invoker_t>;

  // Stores the Invokers of this MessageHandler.
  const invokers_t Invokers;

  // Creates a container with Invokers from the given functions.
  template <typename Fun, typename... Funs>
  static inline invokers_t createInvokers(Fun &&F, Funs &&... Fs) noexcept;

  // Wraps F into an Invoker and stores it into I at position Pos.
  // PRE: Pos < I.size()
  template <typename Fun, typename... Funs>
  static inline void wrapFun(invokers_t &I, const size_t Pos, Fun &&F,
                             Funs &&... Fs) noexcept;

  // Terminal case for function wrapper.
  // PRE: Pos == I.size();
  static inline void wrapFun(invokers_t &I, const size_t Pos) noexcept;

public:
  // Ctor, stores the given functions into the new MessageHandler instance.
  template<typename Fun, typename... Funs>
  MessageHandler(Fun &&F, Funs &&... Fs) noexcept;

  virtual ~MessageHandler(void);

  // Tells if there is any Invoker that can handle Msg.
  bool canHandle(const Message &Msg) const noexcept;

  // Applies Msg to the first Invoker which can handle Msg, and tells if there
  // was any.
  // NOTE: This operator finds the first applicable Invoker and invokes it with
  // Msg, while the member function canHandle only checks if there is any
  // Invoker that can be 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

