//===-- rosa/deluxe/DeluxeSystem.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/deluxe/DeluxeSystem.hpp
///
/// \author David Juhasz (david.juhasz@tuwien.ac.at)
///
/// \date 2017-2019
///
/// \brief Specialization of \c rosa::MessagingSystem for the *deluxe
/// interface*.
///
/// \see \c rosa::deluxe::DeluxeContext
///
//===----------------------------------------------------------------------===//

#ifndef ROSA_DELUXE_DELUXESYSTEM_HPP
#define ROSA_DELUXE_DELUXESYSTEM_HPP

#include "rosa/core/MessagingSystem.hpp"

#include "rosa/deluxe/DeluxeAgent.hpp"
#include "rosa/deluxe/DeluxeSensor.hpp"

namespace rosa {
namespace deluxe {

/// Implements and extends the \c rosa::MessagingSystem interface to be
/// used by \c rosa::deluxe::DeluxeContext.
///
/// The class is a specialization of \c rosa::MessagingSystem, where objects
/// of two specialized subtypes of \c rosa::Agent, \c rosa::deluxe::DeluxeSensor
/// and \c rosa::deluxe::DeluxeAgent, constitute a system. The class extends the
/// \c rosa::MessagingSystem interface with features required to implement the
/// *deluxe interface*.
///
/// \see rosa::deluxe::DeluxeContext
class DeluxeSystem : public MessagingSystem {

  friend class DeluxeContext;
  friend class DeluxeExecutionPolicy;

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

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

public:
  /// Creates a \c rosa::deluxe::DeluxeSensor instance owned by \p this object
  /// and returns a \p rosa::AgentHandle for it.
  ///
  /// \tparam MT type of master-input the new \c rosa::deluxe::DeluxeSensor
  /// receives
  /// \tparam T type of data the new \c rosa::deluxe::DeluxeSensor operates on
  ///
  /// \note Type arguments \p MT and \p T must be instances of \c
  /// rosa::deluxe::DeluxeTuple.
  ///
  /// \param Name name of the new \c rosa::deluxe::DeluxeSensor
  /// \param MF function to process master-input values
  /// \param F function to generate the next value with during normal operation
  ///
  /// \see \c rosa::deluxe::DeluxeSensor::DeluxeSensor.
  ///
  /// \return \c rosa::AgentHandle for new \c rosa::deluxe::DeluxeSensor
  template <typename MT, typename T>
  AgentHandle createSensor(const std::string &Name,
                           std::function<void(std::pair<MT, bool>)> &&MF,
                           std::function<T(void)> &&F) noexcept;

  /// Creates a \c rosa::deluxe::DeluxeAgent instance owned by \p this object
  /// and returns a \c rosa::AgentHandle for it.
  ///
  /// \tparam MT type of master-input the new \c rosa::deluxe::DeluxeAgent
  /// receives
  /// \tparam T type of data the new \c rosa::deluxe::DeluxeAgent outputs
  /// \tparam Ts types of master-output the new \c rosa::deluxe::DeluxeAgent
  /// produces
  /// \tparam As types of inputs the new \c rosa::deluxe::DeluxeAgent takes
  ///
  /// \note Type arguments \p MT, \p T, \p Ts..., and \p As... must be
  /// instances of \c rosa::deluxe::DeluxeTuple.
  ///
  /// \param Name name of the new \c rosa::deluxe::DeluxeAgent
  /// \param MF function for the new \c rosa::deluxe::DeluxeAgent to process
  /// master-input values and generate master-output with
  /// \param F function for the new \c rosa::deluxe::DeluxeAgent to process
  /// input values and generate output and master-output with
  ///
  /// \see \c rosa::deluxe::DeluxeAgent::DeluxeAgent.
  ///
  /// \return \c rosa::AgentHandle for new \c rosa::deluxe::DeluxeAgent
  template <typename MT, typename T, typename... Ts, typename... As>
  AgentHandle createAgent(
      const std::string &Name,
      std::function<std::tuple<Optional<Ts>...>(std::pair<MT, bool>)> &&MF,
      std::function<std::tuple<Optional<T>, Optional<Ts>...>(
          std::pair<As, bool>...)> &&F) noexcept;

protected:
  /// Tells whether a \c rosa::AgentHandle refers to a
  /// \c rosa::deluxe::DeluxeSensor owned by \p this object.
  ///
  /// \param H \c rosa::AgentHandle to check
  ///
  /// \return whether \p H refers to a \c rosa::deluxe::DeluxeSensor owned by
  /// \p this object
  virtual bool isDeluxeSensor(const AgentHandle &H) const noexcept = 0;

  /// Extracts a const qualified \c rosa::deluxe::DeluxeSensor reference from a
  /// const qualified \c rosa::AgentHandle if possible.
  ///
  /// The function returns a \c rosa::Optional object containing a const
  /// qualified reference to a \c rosa::deluxe::DeluxeSensor object extracted
  /// from a const qualified \c rosa::AgentHandle instance if the referred
  /// object is of type \c rosa::deluxeDeluxeSensor and owned by \p this object.
  /// The returned \c rosa::Optional object is empty otherwise.
  ///
  /// \see rosa::deluxe::DeluxeSystem::isDeluxeSensor
  ///
  /// \param H \c rosa::AgentHandle to extract a \c rosa::deluxe::DeluxeSensor
  /// from
  ///
  /// \return const qualified reference to \c rosa::deluxe::DeluxeSensor if
  /// \p H refers to an object which is of that type and is owned by \p this
  /// object
  Optional<const DeluxeSensor &> getDeluxeSensor(const AgentHandle &H) const
      noexcept;

  /// Extracts a \c rosa::deluxe::DeluxeSensor reference from a
  /// \c rosa::AgentHandle if possible.
  ///
  /// The function returns a \c rosa::Optional object containing a reference to
  /// a \c rosa::deluxe::DeluxeSensor object extracted from a
  /// \c rosa::AgentHandle instance if the referred object is of type
  /// \c rosa::deluxeDeluxeSensor and owned by \p this object. The returned
  /// \c rosa::Optional object is empty otherwise.
  ///
  /// \see rosa::deluxe::DeluxeSystem::isDeluxeSensor
  ///
  /// \param H \c rosa::AgentHandle to extract a \c rosa::deluxe::DeluxeSensor
  /// from
  ///
  /// \return reference to \c rosa::deluxe::DeluxeSensor if \p H refers to an
  /// object which is of that type and is owned by \p this object
  Optional<DeluxeSensor &> getDeluxeSensor(AgentHandle &H) const noexcept;

  /// Tells whether a \c rosa::AgentHandle refers to a
  /// \c rosa::deluxe::DeluxeAgent owned by \p this object.
  ///
  /// \param H \c rosa::AgentHandle to check
  ///
  /// \return whether \p H refers to a \c rosa::deluxe::DeluxeAgent owned by
  /// \p this object
  virtual bool isDeluxeAgent(const AgentHandle &H) const noexcept = 0;

  /// Extracts a const qualified \c rosa::deluxe::DeluxeAgent reference from a
  /// const qualified \c rosa::AgentHandle if possible.
  ///
  /// The function returns a \c rosa::Optional object containing a const
  /// qualified reference to a \c rosa::deluxe::DeluxeAgent object extracted
  /// from a const qualified \c rosa::AgentHandle instance if the referred
  /// object is of type \c rosa::deluxeDeluxeAgent and owned by \p this object.
  /// The returned \c rosa::Optional object is empty otherwise.
  ///
  /// \see rosa::deluxe::DeluxeSystem::isDeluxeAgent
  ///
  /// \param H \c rosa::AgentHandle to extract a \c rosa::deluxe::DeluxeAgent
  /// from
  ///
  /// \return const qualified reference to \c rosa::deluxe::DeluxeAgent if \p H
  /// refers to an object which is of that type and is owned by \p this object
  Optional<const DeluxeAgent &> getDeluxeAgent(const AgentHandle &H) const
      noexcept;

  /// Extracts a \c rosa::deluxe::DeluxeAgent reference from a
  /// \c rosa::AgentHandle if possible.
  ///
  /// The function returns a \c rosa::Optional object containing a reference to
  /// a \c rosa::deluxe::DeluxeAgent object extracted from a
  /// \c rosa::AgentHandle instance if the referred object is of type
  /// \c rosa::deluxeDeluxeAgent and owned by \p this object. The returned
  /// \c rosa::Optional object is empty otherwise.
  ///
  /// \see rosa::deluxe::DeluxeSystem::isDeluxeAgent
  ///
  /// \param H \c rosa::AgentHandle to extract a \c rosa::deluxe::DeluxeAgent
  /// from
  ///
  /// \return reference to \c rosa::deluxe::DeluxeAgent if \p H refers to an
  /// object which is of that type and is owned by \p this object
  Optional<DeluxeAgent &> getDeluxeAgent(AgentHandle &H) const noexcept;
};

template <typename MT, typename T>
AgentHandle
DeluxeSystem::createSensor(const std::string &Name,
                           std::function<void(std::pair<MT, bool>)> &&MF,
                           std::function<T(void)> &&F) noexcept {
  Agent &DS = createUnit<DeluxeSensor, MessagingSystem>(
      [&](const id_t Id, MessagingSystem &S) {
        return new DeluxeSensor(atoms::SensorKind, Id, Name, S, std::move(MF),
                                std::move(F));
      });
  return {DS};
}

template <typename MT, typename T, typename... Ts, typename... As>
AgentHandle DeluxeSystem::createAgent(
    const std::string &Name,
    std::function<std::tuple<Optional<Ts>...>(std::pair<MT, bool>)> &&MF,
    std::function<std::tuple<Optional<T>, Optional<Ts>...>(
        std::pair<As, bool>...)> &&F) noexcept {
  Agent &DA = createUnit<DeluxeAgent, DeluxeSystem>(
      [&](const id_t Id, DeluxeSystem &S) {
        return new DeluxeAgent(atoms::AgentKind, Id, Name, S, std::move(MF),
                               std::move(F));
      });
  return {DA};
}

} // End namespace deluxe
} // End namespace rosa

#endif // ROSA_DELUXE_DELUXESYSTEM_HPP
