//===-- rosa/core/MessagingSystem.hpp ---------------------------*- C++ -*-===//
//
//                                 The RoSA Framework
//
// Distributed under the terms and conditions of the Boost Software License 1.0.
// See accompanying file LICENSE.
//
// If you did not receive a copy of the license file, see
// http://www.boost.org/LICENSE_1_0.txt.
//
//===----------------------------------------------------------------------===//
///
/// \file rosa/core/MessagingSystem.hpp
///
/// \author David Juhasz (david.juhasz@tuwien.ac.at)
///
/// \date 2017
///
/// \brief Declaration of an interface extending \c rosa::System with messaging.
///
//===----------------------------------------------------------------------===//

#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 \c rosa::System interface with features to create \c rosa::Agent
/// instancess and register \c rosa::Message objects for them.
class MessagingSystem : public System {
  friend class AgentHandle; ///< \c rosa::AgentHandle is our friend.

public:
  /// Returns an object implementing the \c rosa::MessagingSystem interface.
  ///
  /// \param Name name of the new instance
  ///
  /// \return \c std::unique_ptr for the new instance of
  /// \c rosa::MessagingSystem
  static std::unique_ptr<MessagingSystem>
  createSystem(const std::string &Name) noexcept;

private:
  /// Kind for categorizing \c rosa::Unit instances as *agents*.
  static constexpr AtomValue AgentKind = atom("agent");

protected:
  /// Creates a new instance.
  ///
  /// \note Protected constructor restricts instantiation for subclasses.
  MessagingSystem(void) noexcept = default;

protected:
  /// Creates a \c rosa::Agent instance owned by \p this object and returns a
  /// \c rosa::AgentHandle for it.
  ///
  /// \tparam T type of the actual \c rosa::Agent to instantiate
  /// \tparam Funs types of the functions to instantiate \c rosa::Agent with
  ///
  /// \note \c rosa::Agent requires at least one function for its constructor,
  /// but derived classes may do not need that. That's the reason of allowing
  /// zero \p Funs for this template function.
  ///
  /// \param Name name of the new \c rosa::Unit instance
  /// \param Fs functions to instantiate \c rosa::Unit with
  ///
  /// \return handle for the new \c rosa::Agent instance
  ///
  /// \pre Statically, \p T is a subclass of \c rosa::Agent:
  /// \code
  /// std::is_base_of<Agent, T>::value
  /// \endcode
  template <typename T, typename... Funs>
  AgentHandle createAgent(const std::string &Name, Funs &&... Fs);

  /// Unregisters and destroys a \c rosa::Agent referred by a
  /// \c rosa::AgentHandle.
  ///
  /// The function uses \c rosa::System::destroyUnit.
  ///
  /// \param H refers to the \c rosa::Agent to destroy
  ///
  /// \pre The referred \c rosa::Agent is registered.
  ///
  /// \post The referred \c rosa::Agent is not registered and also destroyed.
  void destroyAgent(const AgentHandle &H) noexcept;

  /// Gives the referenced \c rosa::Agent instance for a \c rosa::AgentHandle.
  ///
  /// \note Intended for derived classes to be able to inspect
  /// \c rosa::AgentHandle instances.
  ///
  /// \param H \c rosa::AgentHandle to take the referenced \c rosa::Agent from
  ///
  /// \return reference to the \c rosa::Agent instance from \p H
  static Agent &unwrapAgent(const AgentHandle &H) noexcept { return H.A; }

  /// Gives the owning \c rosa::MessagingSystem of a \c rosa::Agent instance
  /// for a \c rosa::AgentHandle.
  ///
  /// \note Intended for for derived classes to be able to inspect
  /// \c rosa::AgentHandle instances.
  ///
  /// \param H \c rosa::AgentHandle to take the owning
  /// \c rosa::MessagingSystem from
  ///
  /// \return reference to the \c rosa::MessagingSystem owning the
  /// \c rosa::Agent instance from \p H
  static MessagingSystem &unwrapSystem(const AgentHandle &H) noexcept {
    return H.S;
  }

public:
  /// Sends a \c rosa::message_t instance to the \c rosa::Agent instance
  /// referred by a \c rosa::AgentHandle.
  ///
  /// \note If the given \c rosa::Message object cannot be handled by the
  /// referred \c rosa::Agent instance, the \c rosa::Message object is simply
  /// ignored.
  ///
  /// \param H refers to the \c rosa::Agent instance to send to
  /// \param M message to send
  ///
  /// \pre The referred \c rosa::Agent instance is owned by \p this object and
  /// also registered: \code
  /// &unwrapSystem(H) == this && isUnitRegistered(unwrapAgent(H))
  /// \endcode
  virtual void send(const AgentHandle &H, message_t &&M) noexcept = 0;

  /// Sends a message -- created from given constant lvalue references --
  /// to the \c rosa::Agent instance referred by a \c rosa::AgentHandle.
  ///
  /// \note If the given \c rosa::Message object cannot be handled by the
  /// referred \c rosa::Agent instance, the \c rosa::Message object is simply
  /// ignored.
  ///
  /// \note The message must consists of at least one value.
  ///
  /// \tparam Type type of the first mandatory value
  /// \tparam Types types of any further values
  ///
  /// \param H refers to the \c rosa::Agent instance to send to
  /// \param T the first value to include in the message
  /// \param Ts optional further values to include in the message
  ///
  /// \pre The referred \c rosa::Agent instance is owned by \p this object and
  /// also registered: \code
  /// &unwrapSystem(H) == this && isUnitRegistered(unwrapAgent(H))
  /// \endcode
  template <typename Type, typename... Types>
  void send(const AgentHandle &H, const Type &T, const Types &... Ts) noexcept;

  /// Sends a message -- created from given rvalue references --
  /// to the \c rosa::Agent instance referred by a \c rosa::AgentHandle.
  ///
  /// \note If the given \c rosa::Message object cannot be handled by the
  /// referred \c rosa::Agent instance, the \c rosa::Message object is simply
  /// ignored.
  ///
  /// \note The message must consists of at least one value.
  ///
  /// \tparam Type type of the first mandatory value
  /// \tparam Types types of any further values
  ///
  /// \param H refers to the \c rosa::Agent instance to send to
  /// \param T the first value to include in the message
  /// \param Ts optional further values to include in the message
  ///
  /// \pre The referred \c rosa::Agent instance is owned by \p this object and
  /// also registered: \code
  /// &unwrapSystem(H) == this && isUnitRegistered(unwrapAgent(H))
  /// \endcode
  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
