//===-- rosa/core/AbstractAgent.hpp -----------------------------*- C++ -*-===//
//
//                                 The RoSA Framework
//
//===----------------------------------------------------------------------===//
///
/// \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 \c rosa::AbstractAgent
/// for referencing \p this object
///
/// \note \p Ref is reference for \c rosa::AbstractAgent, whose actual value
/// must be a class derived from \c rosa::AbstractAgent<Ref>.
///
/// \note It can be statically checked if \p Ref is derived from
/// \c rosa::AbstractAgent<Ref>, but the static assertion cannot be defined
/// directly in the class body. That is because a class \c C derived from
/// \c rosa::AbstractAgent<C> is not complete when the static assertion in the
/// definition of \c rosa::AbstractAgent<C> would be evaluated. Thus, the static
/// assertion is placed in the constructor of \c rosa::AbstractAgent.
template <typename Ref> class AbstractAgent {
protected:
  /// Creates a new instance of \c rosa::AbstractAgent<Ref>.
  ///
  /// \note The constructor is protected, thus restricting class instantiation
  /// for derived classes only.
  ///
  /// \pre \p Ref is derived from \c rosa::AbstractAgent<Ref>:\code
  /// std::is_base_of<AbstractAgent<Ref>, Ref>::value
  /// \endcode
  AbstractAgent(void) noexcept;

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

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

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

  /// Compares \p this object to a given reference.
  ///
  /// The operator can be used to sort references. Standard containers storing
  /// entries in an ordered way use \c std::less for comparing entries, which
  /// utilizes this operator.
  ///
  /// \param R reference to another object
  ///
  /// \return if \p this object compares less to \p R
  virtual bool operator<(const Ref &R) const noexcept = 0;

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

  /// Sends a \c rosa::message_t instance to \p this object.
  ///
  /// \param M message to send
  ///
  /// \pre \p 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
  /// \p 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 \p 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 \p 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 \p 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
