/*******************************************************************************
 *
 * File:     MessagingSystem.hpp
 *
 * Contents: Declaration of the class MessagingSystem.
 *
 * Copyright 2017
 *
 * Author: David Juhasz (david.juhasz@tuwien.ac.at)
 *
 ******************************************************************************/

#ifndef ROSA_CORE_MESSAGINGSYSTEM_HPP
#define ROSA_CORE_MESSAGINGSYSTEM_HPP

#include "rosa/core/AgentHandle.hpp"
#include "rosa/core/System.hpp"

#include "rosa/support/atom.hpp"

namespace rosa {

// Extends the System interface with features to create Agents and register
// Messages for them.
class MessagingSystem : public System {
  friend class AgentHandle; // AgentHandle is our friend.

public:
  // Returns an object implementing the interface defined by the class.
  static std::unique_ptr<MessagingSystem>
  createSystem(const std::string &Name) noexcept;

private:
  // Kind used for Unit categorization of Agents.
  static constexpr AtomValue AgentKind = atom("agent");

protected:
  // Ctor.
  MessagingSystem(void) noexcept = default;

protected:
  // Creates an Agent owned by the MessagingSystem and returns a handle for it.
  // NOTE: Agent requires at least one Fun for its constructor, but derived
  // classes may do not need that. That's the reason of allowing even zero Funs
  // for this template function.
  // STATIC PRE: std::is_base_of<Agent, T>::value
  template <typename T, typename... Funs>
  AgentHandle createAgent(const std::string &Name, Funs &&... Fs);

  // Gives the wrapped Agent from the given AgentHandle for derived classes
  // to be able to inspect the AgentHandle.
  static inline Agent &unwrapAgent(const AgentHandle &H) noexcept {
    return H.A;
  }

  // Gives the original owning MessagingSystem of the wrapped Agent from the
  // given AgentHandle for derived classes to be able to inspect the
  // AgentHandle.
  static inline MessagingSystem &unwrapSystem(const AgentHandle &H) noexcept {
    return H.S;
  }

public:
  // Sends the given Message to the Agent referred by the given AgentHandle.
  // NOTE: If the given Message cannot be handled by the referred Agent, the
  // Message is simply ignored.
  // PRE: &unwrapSystem(H) == this && isUnitRegistered(unwrapAgent(H))
  virtual void send(const AgentHandle &H, message_t &&M) noexcept = 0;

  // Convenience template, which creates the Message from the given constant
  // lvalue references and sends to the Agent.
  // PRE: &unwrapSystem(H) == this && isUnitRegistered(unwrapAgent(H))
  template <typename Type, typename... Types>
  void send(const AgentHandle &H, const Type &T, const Types &... Ts) noexcept;

  // Convenience template, which creates the Message from the given rvalue
  // references and sends to the Agent.
  // PRE: &unwrapSystem(H) == this && isUnitRegistered(unwrapAgent(H))
  template <typename Type, typename... Types>
  void send(const AgentHandle &H, Type &&T, Types &&... Ts) noexcept;
};

template <typename T, typename... Funs>
AgentHandle MessagingSystem::createAgent(const std::string &Name,
                                         Funs &&... Fs) {
  STATIC_ASSERT((std::is_base_of<Agent, T>::value), "not an Agent");
  Agent &A = createUnit<T, MessagingSystem>([&](const id_t Id,
                                                MessagingSystem &S) noexcept {
    return new T(AgentKind, Id, Name, S, std::move(Fs)...);
  });
  return {A};
}

template <typename Type, typename... Types>
void MessagingSystem::send(const AgentHandle &H, const Type &T,
                           const Types &... Ts) noexcept {
  send(H, Message::create<Type, Types...>(T, Ts...));
}

template <typename Type, typename... Types>
void MessagingSystem::send(const AgentHandle &H, Type &&T,
                           Types &&... Ts) noexcept {
  send(H, Message::create<Type, Types...>(std::move(T), std::move(Ts)...));
}

} // End namespace rosa

#endif // ROSA_CORE_MESSAGINGSYSTEM_HPP

