/***************************************************************************//**
 *
 * \file rosa/core/Invoker.hpp
 *
 * \author David Juhasz (david.juhasz@tuwien.ac.at)
 *
 * \date 2017
 *
 * \brief Facilities for providing actual arguments for functions as
 *        `rosa::Message`objects.
 *
 ******************************************************************************/

#ifndef ROSA_CORE_INVOKER_HPP
#define ROSA_CORE_INVOKER_HPP

#include "rosa/core/MessageMatcher.hpp"

#include "rosa/support/log.h"

#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 `rosa::Message` object.
///
/// \note A `rosa::Invoker` instance is supposed to be owned by a
/// `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 `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 `rosa::Invoker`.
  using invoker_t = std::unique_ptr<const Invoker>;

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

  /// Tells if a `rosa::Message` object can be used to invoke the function
  /// wrapped in `this` object.
  ///
  /// \param Msg `rosa::Message` to check
  ///
  /// \return whether `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 `rosa::Message` object.
  ///
  /// The wrapped function is invoked if the actual `rosa::Message` object can
  /// be used to invoke it.
  ///
  /// \param Msg `rosa::Message` to try to invoke the wrapped function with
  ///
  /// \return whether the wrapped function could be invoked with `Msg`
  virtual result_t operator()(const Message &Msg) const noexcept = 0;

  /// Instantiates an implementation of `rosa::Invoker` with the given function.
  ///
  /// \note As there is no empty `rosa::Message`, no `rosa::Invoker` wraps a
  /// function without any argument.
  ///
  /// \tparam T type of the first mandatory argument
  /// \tparam Ts types of any further arguments
  ///
  /// \param F function to wrap
  ///
  /// \return new `invoker_t` object created from the given function
  template <typename T, typename... Ts>
  static invoker_t wrap(std::function<void(T, Ts...) noexcept> &&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...) noexcept>;

  /// 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 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 `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 `rosa::Message`.
///
/// \param FUN the non-static member function to wrap
///
/// \note Inside the class `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 `rosa::Invoker` and helper
/// templates, consider it private.
namespace {

/// \defgroup Seq
///
///@{

/// Template with an empty struct to store a sequence of numbers in compile time
/// as template arguments.
template <size_t...> struct Seq {};

/// Sequence generator, the general case when counting down by extending the
/// sequence.
template <size_t N, size_t... S> struct GenSeq : GenSeq<N - 1, N - 1, S...> {};

/// Sequence generator, the terminal case when storing the generated sequence
/// into `rosa::Seq`.
template <size_t... S> struct GenSeq<0, S...> { using Type = Seq<S...>; };

///@}

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

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

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

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

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

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

  /// The wrapped function.
  const function_t F;

  /// Invokes `F` by unpacking arguments from a `std::tuple` with the help of
  /// the actual template arguments.
  ///
  /// \tparam S sequence of numbers indexing `std::tuple` for arguments
  ///
  /// \param Args arguments to invoke `F` with
  ///
  /// \pre the length of `S` and size of `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 `F` is valid:\code
  /// bool(F)
  /// \endcode
  InvokerImpl(function_t &&F) noexcept : F(F) {
    ASSERT(bool(F)); // Sanity check.
  }

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

  /// Tells if a `rosa::Message` object can be used to invoke the function
  /// wrapped in `this` object.
  ///
  /// \param Msg `rosa::Message` to check
  ///
  /// \return whether `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 `rosa::Message` object.
  ///
  /// The wrapped function is invoked if the actual `rosa::Message` object can
  /// be used to invoke it.
  ///
  /// \param Msg `rosa::Message` to try to invoke the wrapped function with
  ///
  /// \return whether the wrapped function could be invoked with `Msg`
  result_t operator()(const Message &Msg) const noexcept override {
    if (match(Msg)) {
      LOG_TRACE("Invoking with matching arguments");
      invokeFunction(typename GenSeq<sizeof...(Ts) + 1>::Type(),
                     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...) noexcept>>::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...) noexcept> &&F) noexcept {
  return std::unique_ptr<Invoker>(
      new InvokerImpl<std::function<void(T, Ts...) noexcept>>(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

