//===-- rosa/deluxe/DeluxeSensor.hpp ----------------------------*- C++ -*-===//
//
//                                 The RoSA Framework
//
//===----------------------------------------------------------------------===//
///
/// \file rosa/deluxe/DeluxeSensor.hpp
///
/// \author David Juhasz (david.juhasz@tuwien.ac.at)
///
/// \date 2017-2019
///
/// \brief Specialization of \c rosa::Agent for *sensor* role of the the *deluxe
/// interface*.
///
/// \see \c rosa::deluxe::DeluxeContext
///
//===----------------------------------------------------------------------===//

#ifndef ROSA_DELUXE_DELUXESENSOR_HPP
#define ROSA_DELUXE_DELUXESENSOR_HPP

#include "rosa/core/Agent.hpp"

#include "rosa/deluxe/DeluxeAtoms.hpp"

namespace rosa {
namespace deluxe {

/// Specialization of \c rosa::Agent for *sensor* role of the *deluxe
/// interface*.
///
/// \see \c rosa::deluxe::DeluxeContext
class DeluxeSensor : public Agent {
public:
  /// Template alias for function objects used as data source for
  /// \c rosa::deluxe::DeluxeSensor.
  ///
  /// \note The function used for \c D is to be \c noexcept.
  ///
  /// \tparam T type of data provided by the function
  template <typename T> using D = std::function<T(void)>;

  /// The type of values produced by \p this object.
  ///
  /// That is the type of values \p this object sends to its *master*.
  ///
  /// \see \c rosa::deluxe::DeluxeSensor::master
  const TypeNumber OutputType;

private:
  /// Alias for function objects used as trigger handler for
  /// \c rosa::deluxe::DeluxeSensor.
  ///
  /// \note The function used for \c H is to be \c noexcept.
  ///
  /// \see \c DeluxeSensorTriggerHandlers
  using H = std::function<void(void)>;

  /// \defgroup DeluxeSensorTriggerHandlers Trigger handlers of rosa::deluxe::DeluxeSensor
  ///
  /// \brief Trigger handler functions of \c rosa::deluxe::DeluxeSensor
  ///
  /// The actual data source functions are captured in a lambda expression that
  /// is in turn wrapped in a \c std::function object. The lambda expression
  /// calls the data source function to obtain the next sensory value and sends
  /// it to *master* by calling \c rosa::deluxe::DeluxeSensor::sendToMaster. The
  /// function \c rosa::deluxe::DeluxeSensor::handleTrigger needs only to call
  /// the proper function object.

  /// Handles trigger during normal execution.
  ///
  /// \ingroup DeluxeSensorTriggerHandlers
  ///
  /// The function is used during normal execution. During simulation, the
  /// simulation environment sets \c rosa::deluxe::DeluxeSensor::SFP, which is
  /// used instead of \c rosa::deluxe::DeluxeSensor::FP.
  const H FP;

  /// Handles trigger during simulation.
  ///
  /// \ingroup DeluxeSensorTriggerHandlers
  ///
  /// The function is empty by default. The simulation environment sets it to be
  /// used during simulation.
  H SFP;

  /// The *master* to send values to.
  ///
  /// \note *Masters* are set dynamically, hence it is possible that a
  /// \c rosa::deluxe::DeluxeSensor instance does not have any *master* at a
  /// given moment.
  Optional<AgentHandle> Master;

  /// Wraps a data source function into a trigger handler.
  ///
  /// \see \c DeluxeSensorTriggerHandlers
  ///
  /// \tparam T type of data provided by \p F
  ///
  /// \param F function to generate value with
  ///
  /// \pre \p T matches \c rosa::deluxe::DeluxeSensor::OutputType: \code
  /// OutputType == TypeNumberOf<T>::Value
  /// \endcode
  template <typename T> H triggerHandlerFromDataSource(D<T> &&F) noexcept;

public:
  /// Creates a new instance.
  ///
  /// The constructor instantiates the base-class with functions to handle
  /// messages as defined for the *deluxe interface*.
  ///
  /// \todo Enforce F does not potentially throw exception.
  ///
  /// \tparam T type of data to operate on
  ///
  /// \param Kind kind of the new \c rosa::Unit instance
  /// \param Id unique identifier of the new \c rosa::Unit instance
  /// \param Name name of the new \c rosa::Unit instance
  /// \param S \c rosa::MessagingSystem owning the new instance
  /// \param F function to generate the next value with during normal operation
  ///
  /// \pre Statically, \p T is a built-in type:\code
  /// TypeListContains<BuiltinTypes, T>::Value
  /// \endcode
  /// Dynamically, the instance is created as of kind
  /// \c rosa::deluxe::atoms::SensorKind:
  /// \code
  /// Kind == rosa::deluxe::atoms::SensorKind
  /// \endcode
  template <typename T, typename = std::enable_if_t<
                            TypeListContains<BuiltinTypes, T>::Value>>
  DeluxeSensor(const AtomValue Kind, const id_t Id, const std::string &Name,
               MessagingSystem &S, D<T> &&F) noexcept;

  /// Destroys \p this object.
  ~DeluxeSensor(void) noexcept;

  /// The *master* of \p this object, if any.
  ///
  /// \see \c rosa::deluxe::DeluxeSensor::registerMaster
  ///
  /// \return the *master* registered for \p this object
  Optional<AgentHandle> master(void) const noexcept;

  /// Registers a *master* for \p this object.
  ///
  /// The new *master* is registered by overwriting the reference to any
  /// already registered *master*. One can clear the registered reference by
  /// passing an *empty* \c rosa::Optional object as actual argument.
  ///
  /// \note The role of the referred *master* is validated by checking its
  /// *kind*.
  ///
  /// \param _Master the *master* to register
  ///
  /// \pre \p Master is empty or of kind \c rosa::deluxe::atoms::AgentKind:
  /// \code
  /// !_Master || unwrapAgent(*_Master).Kind == rosa::deluxe::atoms::AgentKind
  /// \endcode
  void registerMaster(const Optional<AgentHandle> _Master) noexcept;

  /// Clears the simulation trigger handler of \p this object.
  ///
  /// The function assigns \c rosa::deluxe::DeluxeSensor::SFP with \c nullptr.
  void clearSimulationDataSource(void) noexcept;

  /// Tells whether a simulation trigger handler is set for \p this object.
  ///
  /// The function returns whether \c rosa::deluxe::DeluxeSensor::SFP is not
  /// \c nullptr.
  ///
  /// \return if a simulation trigger handler is set for \p this object.
  bool simulationDataSourceIsSet(void) const noexcept;

  /// Registers a simulation data source for \p this object.
  ///
  /// A new simulation trigger handler wrapping \p SF is stored in
  /// \c rosa::deluxe::DeluxeSensor::SFP by overwriting any already registered
  /// simulation data source.
  ///
  /// \todo Enforce SF does not potentially throw exception.
  ///
  /// \tparam T type of data provided by \p SF
  ///
  /// \param SF function to generate value with
  ///
  /// \pre \p T matches \c rosa::deluxe::DeluxeSensor::OutputType: \code
  /// OutputType == TypeNumberOf<T>::Value
  /// \endcode
  template <typename T> void registerSimulationDataSource(D<T> &&SF) noexcept;

private:
  /// Sends a value to the *master* of \p this object.
  ///
  /// \p Value is getting sent to \c rosa::deluxe::DeluxeSensor::Master if it
  /// contains a valid handle for a \c rosa::deluxe::DeluxeAgent. The function
  /// does nothing otherwise.
  ///
  /// \tparam T type of the value to send
  ///
  /// \param Value value to send
  ///
  /// \pre \p T matches \c rosa::deluxe::DeluxeSensor::OutputType: \code
  /// OutputType == TypeNumberOf<T>::Value
  /// \endcode
  template <typename T> void sendToMaster(const T &Value) noexcept;

  /// Generates the next sensory value upon trigger from the system.
  ///
  /// Executes \c rosa::deluxe::DeluxeSensor::FP or
  /// \c rosa::deluxe::DeluxeSensor::SFP if set.
  ///
  /// \note The only argument is a \c rosa::AtomConstant, hence its actual
  /// value is ignored.
  void handleTrigger(atoms::Trigger) noexcept;
};

template <typename T>
DeluxeSensor::H DeluxeSensor::triggerHandlerFromDataSource(D<T> &&F) noexcept {
  ASSERT(OutputType == TypeNumberOf<T>::Value);
  return [this, F](void) noexcept { sendToMaster(F()); };
}

template <typename T, typename>
DeluxeSensor::DeluxeSensor(const AtomValue Kind, const id_t Id,
                           const std::string &Name, MessagingSystem &S,
                           D<T> &&F) noexcept
    : Agent(Kind, Id, Name, S, THISMEMBER(handleTrigger)),
      OutputType(TypeNumberOf<T>::Value),
      FP(triggerHandlerFromDataSource(std::move(F))),
      SFP(nullptr) {
  ASSERT(Kind == atoms::SensorKind);
  LOG_TRACE("DeluxeSensor is created.");
}

template <typename T>
void DeluxeSensor::registerSimulationDataSource(D<T> &&SF) noexcept {
  ASSERT(OutputType == TypeNumberOf<T>::Value);
  SFP = triggerHandlerFromDataSource(std::move(SF));
}

template <typename T>
void DeluxeSensor::sendToMaster(const T &Value) noexcept {
  ASSERT(OutputType == TypeNumberOf<T>::Value);

  // There is a handle and the referred *master* is in a valid state.
  if (Master && *Master) {
    Master->sendMessage(Message::create(atoms::Slave::Value, Id, Value));
  }
}

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

#endif // ROSA_DELUXE_DELUXESENSOR_HPP
