//===-- rosa/core/Invoker.hpp -----------------------------------*- C++ -*-===//
//
//                                 The RoSA Framework
//
//===----------------------------------------------------------------------===//
///
/// \file rosa/core/Invoker.hpp
///
/// \author David Juhasz (david.juhasz@tuwien.ac.at)
///
/// \date 2017-2019
///
/// \brief Facilities for providing actual arguments for functions as
///        \c rosa::Messageobjects.
///
//===----------------------------------------------------------------------===//

#ifndef ROSA_CORE_INVOKER_HPP
#define ROSA_CORE_INVOKER_HPP

#include "rosa/core/MessageMatcher.hpp"

#include "rosa/support/log.h"
#include "rosa/support/sequence.hpp"

#include <functional>
#include <memory>

namespace rosa {

/// Wraps a function and provides a simple interface to invoke the stored
/// function by passing actual arguments as a \c rosa::Message object.
///
/// \note A \c rosa::Invoker instance is supposed to be owned by a
/// \c rosa::MessageHandler instance, and not being used directly from user
/// code.
class Invoker {
protected:
  /// Creates an instance.
  ///
  /// \note Protected constructor restricts instantiation to derived classes.
  Invoker(void) noexcept;

public:
  /// Destroys \p this object.
  virtual ~Invoker(void);

  /// Possible results of an invocation.
  enum class Result {
    NoMatch, ///< The wrapped function could not be invoked
    Invoked  ///< The wrapped function has been invoked
  };

  /// Type alias for a smart-pointer for \c rosa::Invoker.
  using invoker_t = std::unique_ptr<const Invoker>;

  /// Type alias for \c rosa::Invoker::Result.
  using result_t = Result;

  /// Tells if a \c rosa::Message object can be used to invoke the function
  /// wrapped in \p this object.
  ///
  /// \param Msg \c rosa::Message to check
  ///
  /// \return whether \p Msg can be used to invoke the wrapped function
  virtual bool match(const Message &Msg) const noexcept = 0;

  /// Tries to invoke the wrapped function with a \c rosa::Message object.
  ///
  /// The wrapped function is invoked if the actual \c rosa::Message object can
  /// be used to invoke it.
  ///
  /// \param Msg \c rosa::Message to try to invoke the wrapped function with
  ///
  /// \return whether the wrapped function could be invoked with \p Msg
  virtual result_t operator()(const Message &Msg) const noexcept = 0;

  /// Instantiates an implementation of \c rosa::Invoker with the given
  /// function.
  ///
  /// \note As there is no empty \c rosa::Message, no \c rosa::Invoker wraps a
  /// function without any argument.
  ///
  /// \todo Enforce F does not potentially throw exception.
  ///
  /// \tparam T type of the first mandatory argument
  /// \tparam Ts types of any further arguments
  ///
  /// \param F function to wrap
  ///
  /// \return new \c rosa::Invoker::invoker_t object created from the given
  /// function
  template <typename T, typename... Ts>
  static invoker_t wrap(std::function<void(T, Ts...)> &&F) noexcept;

  /// Convenience template alias for casting callable stuff to function objects
  /// for wrapping.
  ///
  /// \tparam Ts types of arguments
  ///
  /// \todo Should make it possible to avoid using an explicit conversion for
  /// the arguments of wrap.
  template <typename... Ts> using F = std::function<void(Ts...)>;


  /// Convenience template for preparing non-static member functions into
  /// function objects for wrapping.
  ///
  /// \tparam C type whose non-static member the function is
  /// \tparam Ts types of arguments
  ///
  /// \see \c THISMEMBER
  template <typename C, typename... Ts>
  static inline F<Ts...> M(C *O, void (C::*Fun)(Ts...) noexcept) noexcept;
};

/// Convenience preprocessor macro for the typical use of \c rosa::Invoker::M.
/// It can be used inside a class to turn a non-static member function into a
/// function object capturing this pointer, so using the actual object when
/// handling a \c rosa::Message.
///
/// \param FUN the non-static member function to wrap
///
/// \note Inside the class \c MyClass, use\code
/// THISMEMBER(fun)
/// \endcode instead of\code
/// Invoker::M(this, &MyClass::fun)
/// \endcode
#define THISMEMBER(FUN)                                                        \
  Invoker::M(this, &std::decay<decltype(*this)>::type::FUN)

/// Nested namespace with implementation of \c rosa::Invoker and helper
/// templates, consider it private.
namespace {

/// \defgroup InvokerImpl Implementation for rosa::Invoker
///
/// Implements the \c rosa::Invoker interface for functions with different
/// signatures.
///
///@{

/// Declaration of \c rosa::InvokerImpl implementing \c rosa::Invoker.
///
/// \tparam Fun function to wrap
template <typename Fun> class InvokerImpl;

/// Implementation of \c rosa::InvokerImpl for \c std::function.
///
/// \tparam T type of the first mandatory argument
/// \tparam Ts types of further arguments
///
/// \note As there is no empty \c rosa::Message, no \c rosa::Invoker wraps a
/// function without any argument, i.e., no
/// \c std::function<void(void)>.
template <typename T, typename... Ts>
class InvokerImpl<std::function<void(T, Ts...)>> final
    : public Invoker {

  /// Type alias for the stored function.
  using function_t = std::function<void(T, Ts...)>;

  /// Type alias for correctly typed argument-tuples as obtained from
  /// \c rosa::Message.
  using args_t = std::tuple<const T &, const Ts &...>;

  /// Alias for \c rosa::MessageMatcher for the arguments of the stored
  /// function.
  using Matcher = MsgMatcher<T, Ts...>;

  /// The wrapped function.
  const function_t F;

  /// Invokes \c InvokerImpl::F by unpacking arguments from a \c std::tuple with
  /// the help of the actual template arguments.
  ///
  /// \tparam S sequence of numbers indexing \c std::tuple for arguments
  ///
  /// \param Args arguments to invoke \c InvokerImpl::F with
  ///
  /// \pre the length of \p S and size of \p Args are matching:\code
  /// sizeof...(S) == std::tuple_size<args_t>::value
  /// \endcode
  template <size_t... S>
  inline void invokeFunction(Seq<S...>, const args_t &Args) const noexcept;

public:
  /// Creates an instance.
  ///
  /// \param F function to wrap
  ///
  /// \pre \p F is valid:\code
  /// bool(F)
  /// \endcode
  InvokerImpl(function_t &&F) noexcept : F(F) {
    ASSERT(bool(F)); // Sanity check.
  }

  /// Destroys \p this object.
  ~InvokerImpl(void) = default;

  /// Tells if a \c rosa::Message object can be used to invoke the function
  /// wrapped in \p this object.
  ///
  /// \param Msg \c rosa::Message to check
  ///
  /// \return whether \p Msg can be used to invoke the wrapped function
  bool match(const Message &Msg) const noexcept override {
    return Matcher::doesStronglyMatch(Msg);
  };

  /// Tries to invoke the wrapped function with a \c rosa::Message object.
  ///
  /// The wrapped function is invoked if the actual \c rosa::Message object can
  /// be used to invoke it.
  ///
  /// \param Msg \c rosa::Message to try to invoke the wrapped function with
  ///
  /// \return whether the wrapped function could be invoked with \p Msg
  result_t operator()(const Message &Msg) const noexcept override {
    if (match(Msg)) {
      LOG_TRACE("Invoking with matching arguments");
      invokeFunction(seq_t<sizeof...(Ts) + 1>(), Matcher::extractedValues(Msg));
      return result_t::Invoked;
    } else {
      LOG_TRACE("Tried to invoke with non-matching arguments");
      return result_t::NoMatch;
    }
  }
};

template <typename T, typename... Ts>
template <size_t... S>
void InvokerImpl<std::function<void(T, Ts...)>>::invokeFunction(
    Seq<S...>, const args_t &Args) const noexcept {
  ASSERT(sizeof...(S) == std::tuple_size<args_t>::value); // Sanity check.
  F(std::get<S>(Args)...);
}

///@}

} // End namespace

template <typename T, typename... Ts>
Invoker::invoker_t
Invoker::wrap(std::function<void(T, Ts...)> &&F) noexcept {
  return std::unique_ptr<Invoker>(
      new InvokerImpl<std::function<void(T, Ts...)>>(std::move(F)));
}

template <typename C, typename... Ts>
Invoker::F<Ts...> Invoker::M(C *O, void (C::*Fun)(Ts...) noexcept) noexcept {
  return [ O, Fun ](Ts... Vs) noexcept->void { (O->*Fun)(Vs...); };
}
} // End namespace rosa

#endif // ROSA_CORE_INVOKER_HPP
