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

#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 Message.
// NOTE: Invoker instances are supposed to be owned by Message handlers, and
// not being used directly from user code.
class Invoker {
protected:
  // Protected ctor, only subclasses can instantiate.
  Invoker(void) noexcept;

public:
  // Dtor.
  virtual ~Invoker(void);

  // Enumeration of possible results of an invocation.
  enum class Result { NoMatch, Invoked };

  // Type alias for Result.
  using result_t = Result;

  // Tells if Msg can be used to invoke F.
  virtual bool match(const Message &Msg) const noexcept = 0;

  // Invokes F with Msg if Msg can be used to invoke F.
  virtual result_t operator()(const Message &Msg) const noexcept = 0;

  // Instantiates an implementation of Invoker with the given function.
  // NOTE: As there is no empty Message, no Invoker wraps a function without an
  // argument.
  template <typename T, typename... Ts>
  static std::unique_ptr<Invoker>
  wrap(std::function<void(T, Ts...) noexcept> &&F) noexcept;
};

// Nested namespace with Invoker implementation and helper templates, consider
// it private.
namespace {

// Implementation of the Invoker interface, for functions with different
// signature.
// NOTE: As there is no empty Message, no Invoker wraps a function without an
// argument.
template <typename Fun> class InvokerImpl;

// Empty struct just 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 Seq.
template <size_t... S> struct GenSeq<0, S...> { using Type = Seq<S...>; };

// Specialization of InvokerImpl template for std::function.
// NOTE: 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 Messages.
  using args_t = std::tuple<const T &, const Ts &...>;

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

  // The wrapped function.
  const function_t F;

  // Helper function invoking F by unpacking Args with the help of actual
  // template arguments.
  // PRE: sizeof...(S) == std::tuple_size<args_t>::value
  template <size_t... S>
  inline void invokeFunction(Seq<S...>, const args_t &Args) const noexcept;

public:
  // Ctor.
  InvokerImpl(function_t &&F) noexcept : F(F) {
    ASSERT(bool(F)); // Sanity check.
  }

  // Dtor.
  ~InvokerImpl(void) = default;

  // Tells if Msg can be used to invoke F.
  bool match(const Message &Msg) const noexcept override {
    return Matcher::doesStronglyMatch(Msg);
  };

  // Invokes F with Msg if Msg can be used to invoke F.
  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>
std::unique_ptr<Invoker>
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)));
}

} // End namespace rosa

#endif // ROSA_CORE_INVOKER_HPP

