/***************************************************************************//**
 *
 * \file rosa/core/AbstractAgent.hpp
 *
 * \author David Juhasz (david.juhasz@tuwien.ac.at)
 *
 * \date 2017
 *
 * \brief Declaration of an abstract interface for *Agents*.
 *
 ******************************************************************************/

#ifndef ROSA_CORE_ABSTRACTAGENT_HPP
#define ROSA_CORE_ABSTRACTAGENT_HPP

#include "rosa/core/Message.hpp"
#include "rosa/core/forward_declarations.h"

#include "rosa/support/debug.hpp"

#include <memory>

namespace rosa {

/// Abstract class declaring an interface for *Agents*.
///
/// \tparam Ref type of the derived class implementing `rosa::AbstractAgent` for
///             referencing `this` object
///
/// \note `Ref` is reference for `rosa::AbstractAgent`, whose actual value must
/// be a class derived from `rosa::AbstractAgent<Ref>`.
///
/// \note It can be statically checked if `Ref` is derived from
/// `rosa::AbstractAgent<Ref>`, but the static assertion cannot be defined
/// directly in the class body. That is because a class `C` derived from
/// `rosa::AbstractAgent<C>` is not complete when the static assertion in the
/// definition of `rosa::AbstractAgent<C>` would be evaluated. Thus, the static
/// assertion is placed in the constructor of `rosa::AbstractAgent`.
template <typename Ref> class AbstractAgent {
protected:
  /// Creates a new instance of `rosa::AbstractAgent<Ref>`.
  ///
  /// \note The constructor is protected, thus restricting class instantiation
  /// for derived classes only.
  ///
  /// \pre `Ref` is derived from `rosa::AbstractAgent<Ref>`:\code
  /// std::is_base_of<AbstractAgent<Ref>, Ref>::value
  /// \endcode
  AbstractAgent(void) noexcept;

public:
  /// Destroys `this` object.
  virtual ~AbstractAgent(void) = default;

  /// Tells if `this` object is in a valid state.
  ///
  /// \return if `this` object is in a valid state
  virtual operator bool(void) const noexcept = 0;

  /// Tells if a given reference refers to `this` object.
  ///
  /// \param R reference to another object
  ///
  /// \return if `R` refers to `this` object
  virtual bool operator==(const Ref &R) const noexcept = 0;

  /// Returns a reference to `this` object.
  ///
  /// \return a reference to `this` object
  virtual Ref self(void) noexcept = 0;

  /// Sends a `rosa::message_t` instance to `this` object.
  ///
  /// \param M message to send
  ///
  /// \pre `this` object is in a valid state:\code
  /// bool(*this)
  /// \endcode
  virtual void sendMessage(message_t &&M) noexcept = 0;

  /// Sends a message -- created from given constant lvalue references -- to
  /// `this` object.
  ///
  /// \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 T the first value to include in the message
  /// \param Ts optional further values to include in the message
  ///
  /// \pre `this` object is in a valid state:\code
  /// bool(*this)
  /// \endcode
  template <typename Type, typename... Types>
  void send(const Type &T, const Types &... Ts) noexcept;

  /// Sends a message -- created from given rvalue references -- to `this`
  /// object.
  ///
  /// \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 T the first value to include in the message
  /// \param Ts optional further values to include in the message
  ///
  /// \pre `this` object is in a valid state:\code
  /// bool(*this)
  /// \endcode
  template <typename Type, typename... Types>
  void send(Type &&T, Types &&... Ts) noexcept;
};

template <typename Ref> AbstractAgent<Ref>::AbstractAgent(void) noexcept {
  STATIC_ASSERT((std::is_base_of<AbstractAgent<Ref>, Ref>::value),
                "not derived Agent"); // Sanity check.
}

template <typename Ref>
template <typename Type, typename... Types>
void AbstractAgent<Ref>::send(const Type &T, const Types &... Ts) noexcept {
  sendMessage(Message::create<Type, Types...>(T, Ts...));
}

template <typename Ref>
template <typename Type, typename... Types>
void AbstractAgent<Ref>::send(Type &&T, Types &&... Ts) noexcept {
  sendMessage(Message::create<Type, Types...>(std::move(T), std::move(Ts)...));
}

} // End namespace rosa

#endif // ROSA_CORE_ABSTRACTAGENT_HPP

