//===-- rosa/deluxe/DeluxeContext.hpp ---------------------------*- C++ -*-===//
//
//                                 The RoSA Framework
//
//===----------------------------------------------------------------------===//
///
/// \file rosa/deluxe/DeluxeContext.hpp
///
/// \author David Juhasz (david.juhasz@tuwien.ac.at)
///
/// \date 2017-2019
///
/// \brief Public interface for the *deluxe interface* for working with agent
///        systems.
///
//===----------------------------------------------------------------------===//

#ifndef ROSA_DELUXE_DELUXECONTEXT_HPP
#define ROSA_DELUXE_DELUXECONTEXT_HPP

#include "rosa/deluxe/DeluxeSystem.hpp"

#include "rosa/support/types.hpp"

#include <iterator>
#include <memory>
#include <set>

/// Local helper macro to log and return a
/// \c rosa::deluxe::DeluxeContext::ErrorCode value.
///
/// Creates a debug message with the stringified value and returns the value.
///
/// \param Err \c rosa::deluxe::DeluxeContext::ErrorCode value to log and
/// return
#define DCRETERROR(Err)                                                        \
  {                                                                            \
    LOG_DEBUG(#Err);                                                           \
    return Err;                                                                \
  }

namespace rosa {
namespace deluxe {

/// Defines the *deluxe interface*.
///
/// \todo The classes \c rosa::deluxe::DeluxeSensor and \c
/// rosa::deluxe::DeluxeAgent share some common features in relation to their
/// *slave* role in the *deluxe interface*. But their definitions are completely
/// independent. It could be investigated how to lift their common parts into a
/// new *deluxe slave* class, which would serve as base for both, to avoid code
/// duplication.
///
/// \todo In the master-to-slave communication, the type \c rosa::unit_t
/// indicates no master-output in the *master* and no master-input in the
/// *slave*. That works fine, but does not allow \c rosa::unit_t to be used in
/// actual master-to-slave communication. It would make sense to use \c
/// rosa::none_t as the extreme type instead. That would need some adjustment of
/// code because \c rosa::none_t is not part of \c rosa::BuiltinTypes.
class DeluxeContext {

  /// A system owned by \p this object.
  ///
  /// \note The reference is kept in a \c std::shared_ptr because of the member
  /// function \c rosa::deluxe::DeluxeContext::getSystem.
  std::shared_ptr<DeluxeSystem> System;

  /// References to all *sensors* and *agents* created by \p this object.
  std::set<AgentHandle> DeluxeUnits;

public:

  /// Errors that may be resulted by some of the member functions of the class.
  enum struct ErrorCode {
    NoError,
    TypeMismatch,
    NotSensor,
    NotAgent,
    WrongPosition,
    AlreadyHasSlave,
    AlreadyHasMaster,
    AlreadyHasValueStream
  };

  /// Returns a new instance of \c rosa::deluxe::DeluxeContext.
  ///
  /// \param Name name of the underlying \c rosa::DeluxeSystem
  ///
  /// \return \c std::unique_ptr for the new instance of
  /// \c rosa::deluxe::DeluxeContext with a new, empty \c rosa::DeluxeSystem
  static std::unique_ptr<DeluxeContext>
  create(const std::string &Name) noexcept;

private:
  /// Creates a new instance.
  ///
  /// \note Private constructor restricts instantiation to member functions of
  /// the class.
  ///
  /// \param Name name of the underlying \c rosa::MessagingSystem
  DeluxeContext(const std::string &Name) noexcept;

public:
  /// Destroys \p this object.
  ~DeluxeContext(void) noexcept;

  /// Returns a reference for the underlying \c rosa::MessagingSystem.
  ///
  /// \note One cannot do much with a \c rosa::MessagingSystem currently, this
  /// is for future use.
  ///
  /// \return reference for the underlying \c rosa::MessagingSystem.
  std::weak_ptr<MessagingSystem> getSystem(void) const noexcept;

  /// Creates a new *sensor* in the context of \p this object.
  ///
  /// The new *sensor* does not receive master-input.
  ///
  /// \tparam T type of data the new *sensor* operates on
  ///
  /// \param Name name of the new *sensor*
  /// \param F function for the new *sensor* to generate the next value with
  /// during normal operation
  ///
  /// \note \p F is not used during simulation, in which case
  /// \c rosa::deluxe::DeluxeContext::registerSensorValues is used to register
  /// an alternative simulation data source with
  /// \c rosa::deluxe::DeluxeSensor::registerSimulationDataSource. One may
  /// safely keep relying on the default value of \p F as long as only
  /// simulation of the system is to be done.
  ///
  /// \see \c rosa::deluxe::DeluxeSensor::DeluxeSensor.
  ///
  /// \return \c rosa::AgentHandle for the new *sensor*
  template <typename T>
  AgentHandle createSensor(
      const std::string &Name,
      std::function<T(void)> &&F = [](void) { return T(); }) noexcept;

  /// Creates a new *sensor* in the context of \p this object.
  ///
  /// The new *sensor* handles master-input by \p MF.
  ///
  /// \tparam MT type of master-input the new *sensor* handles
  /// \tparam T type of data the new *sensor* operates on
  ///
  /// \note If \p MT is \c rosa::UnitType
  ///
  /// \param Name name of the new *sensor*
  /// \param MF function for the new *sensors* to process master-input values with
  /// \param F function for the new *sensor* to generate the next value with
  /// during normal operation
  ///
  /// \note \p F is not used during simulation, in which case
  /// \c rosa::deluxe::DeluxeContext::registerSensorValues is used to register
  /// an alternative simulation data source with
  /// \c rosa::deluxe::DeluxeSensor::registerSimulationDataSource. One may
  /// safely keep relying on the default value of \p F as long as only
  /// simulation of the system is to be done.
  ///
  /// \see \c rosa::deluxe::DeluxeSensor::DeluxeSensor.
  ///
  /// \return \c rosa::AgentHandle for the new *sensor*
  template <typename MT, typename T>
  AgentHandle createSensor(
      const std::string &Name, std::function<void(std::pair<MT, bool>)> &&MF,
      std::function<T(void)> &&F = [](void) { return T(); }) noexcept;

  /// Creates a new *agent* in the context of \p this object.
  ///
  /// The new *agent* neither receives master-input nor produces master-output.
  ///
  /// \tparam T type of data the new *agent* outputs
  /// \tparam As types of inputs the new *agent* takes
  ///
  /// \param Name name of the new *agent*
  /// \param F function for the new *agent* to process input values and
  /// generate output with
  ///
  /// \see \c rosa::deluxe::DeluxeAgent::DeluxeAgent.
  ///
  /// \return \c rosa::AgentHandle for the new *agent*
  template <typename T, typename... As>
  AgentHandle
  createAgent(const std::string &Name,
              std::function<Optional<T>(std::pair<As, bool>...)> &&F) noexcept;

  /// Creates a new *agent* in the context of \p this object.
  ///
  /// The new *agent* receives master-input by \p MF but does not produce
  /// master-output.
  ///
  /// \tparam MT type of master-input the new *agent* handles
  /// \tparam T type of data the new *agent* outputs
  /// \tparam As types of inputs the new *agent* takes
  ///
  /// \param Name name of the new *agent*
  /// \param MF function for the new *agent* to process master-input values
  /// with
  /// \param F function for the new *agent* to process input values and
  /// generate output with
  ///
  /// \see \c rosa::deluxe::DeluxeAgent::DeluxeAgent.
  ///
  /// \return \c rosa::AgentHandle for the new *agent*
  template <typename MT, typename T, typename... As>
  AgentHandle
  createAgent(const std::string &Name,
              std::function<void(std::pair<MT, bool>)> &&MF,
              std::function<Optional<T>(std::pair<As, bool>...)> &&F) noexcept;

  /// Creates a new *agent* in the context of \p this object.
  ///
  /// The new *agent* does not receive master-input but produces master-output.
  ///
  /// \tparam T type of data the new *agent* outputs
  /// \tparam Ts types of master-output the new *agent* produces
  /// \tparam As types of inputs the new *agent* takes
  ///
  /// \param Name name of the new *agent*
  /// \param F function for the new *agent* to process input values and
  /// generate output with
  ///
  /// \see \c rosa::deluxe::DeluxeAgent::DeluxeAgent.
  ///
  /// \return \c rosa::AgentHandle for the new *agent*
  template <typename T, typename... Ts, typename... As>
  AgentHandle
  createAgent(const std::string &Name,
              std::function<std::tuple<Optional<T>, Optional<Ts>...>(
                  std::pair<As, bool>...)> &&F) noexcept;

  /// Creates a new *agent* in the context of \p this object.
  ///
  /// The new *agent* receives master-input by \p MF and produces master-output.
  ///
  /// \tparam MT type of master-input the new *agent* handles
  /// \tparam T type of data the new *agent* outputs
  /// \tparam Ts types of master-output the new *agent* produces
  /// \tparam As types of inputs the new *agent* takes
  ///
  /// \param Name name of the new *agent*
  /// \param MF function for the new *agent* to process master-input values
  /// with
  /// \param F function for the new *agent* to process input values and
  /// generate output with
  ///
  /// \see \c rosa::deluxe::DeluxeAgent::DeluxeAgent.
  ///
  /// \return \c rosa::AgentHandle for the new *agent*
  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;

  /// Connectes a *sensor* to an *agent* in the context of \p this object.
  ///
  /// \param Agent the *agent* to connect to
  /// \param Pos the index of slot of \p Agent to connect \p Sensor to
  /// \param Sensor the *sensor* to connect
  /// \param Description optional textual description of the connection
  ///
  /// \return how successfull connecting \p Sensor to \p Agent at slot index
  /// \p Pos was
  ///
  /// \note The function may return the following
  /// \c rosa::deluxe::DeluxeContext::ErrorCode values:
  /// `ErrorCode`        | Comment
  /// -----------        | -------
  /// `NoError`          | Success
  /// `NotAgent`         | Referred \p Agent is not \c rosa::deluxe::DeluxeAgent
  /// `NotSensor`        | Referred \p Sensor is not \c rosa::deluxe::DeluxeSensor
  /// `WrongPosition`    | \p Pos is not a valid input position of \p Agent
  /// `TypeMismatch`     | Expected input type at position \p Pos of \p Agent is other than the output type of \p Sensor or expected master-input of \p Sensor is other than master-output at position \p Pos of \p Agent if any
  /// `AlreadyHasSlave`  | \p Agent at position \p Pos already has a *slave* registered
  /// `AlreadyHasMaster` | \p Sensor already has a *master* registered
  ErrorCode connectSensor(AgentHandle Agent, const size_t Pos,
                          AgentHandle Sensor,
                          const std::string &Description = "") noexcept;

  /// Connectes two *agents* in the context of \p this object.
  ///
  /// \param Master the *agent* to connect to
  /// \param Pos the index of slot of \p Master to connect \p Slave to
  /// \param Slave the *agent* to connect
  /// \param Description optional textual description of the connection
  ///
  /// \return how succesfull connecting \p Slave to \p Master at slot index
  /// \p Pos was
  ///
  /// \note The function may return the following
  /// \c rosa::deluxe::DeluxeContext::ErrorCode values:
  /// `ErrorCode`        | Comment
  /// -----------        | -------
  /// `NoError`          | Success
  /// `NotAgent`         | Referred \p Master or \p Slave is not \c rosa::deluxe::DeluxeAgent
  /// `WrongPosition`    | \p Pos is not a valid input position of \p Master
  /// `TypeMismatch`     | Expected input type at position \p Pos of \p Master is other than the output type of \p Slave or expected master-input of \p Slave is other than master-output at position \p Pos of \p Master if any
  /// `AlreadyHasSlave`  | \p Master at position \p Pos already has a *slave* registered
  /// `AlreadyHasMaster` | \p Slave already has a *master* registered
  ErrorCode connectAgents(AgentHandle Master, const size_t Pos,
                          AgentHandle Slave,
                          const std::string &Description = "") noexcept;

  /// Initializes \c this object and others managed by \p this object for
  /// setting up and performing simulation.
  ///
  /// \see \c rosa::deluxe::DeluxeContext::registerSensorValues,
  /// \c rosa::deluxe::DeluxeContext::simulate
  ///
  /// Need to clear simulation data sources from all the *sensors*.
  void initializeSimulation(void) noexcept;

  /// Registers a stream providing values for a *sensor* during simulation.
  ///
  /// \tparam Iterator type of iterator providing values for \p Sensor
  /// \tparam T type of values \p Sensor is operating on, always use default!
  ///
  /// \param Sensor the *sensor* to register values for
  /// \param Start provides values for \p Sensor
  /// \param End denotes the end of stream of values
  /// \param Default value to be used when input stream is depleted during
  /// simulation
  ///
  /// \return how successful registering \p Source for \p Sensor
  ///
  /// \note The function may return the following
  /// \c rosa::deluxe::DeluxeContext::ErrorCode values:
  /// `ErrorCode`             | Comment
  /// -----------             | -------
  /// `NoError`               | Success
  /// `TypeMismatch`          | \p Sensor generates values of a type other than \p T
  /// `NotSensor`             | Referred \p Sensor is not \c rosa::deluxe::DeluxeSensor
  /// `AlreadyHasValueStream` | \p Sensor already has simulation data source set
  template <typename Iterator, typename T = typename Iterator::value_type>
  ErrorCode registerSensorValues(AgentHandle Sensor, Iterator &&Start,
                                 const Iterator &End, T Default = {}) noexcept;

  /// Performs the system contained by \p this object.
  ///
  /// The function performs \p NumCycles cycle of simulation. In each cycle,
  /// all the *agents* and *sensors* registered in
  /// \c rosa::deluxe::DeluxeContext::DeluxeUnits are trigged for execution.
  ///
  /// \param NumCycles number of cycles to perform
  ///
  /// \pre All the *sensors* in the system contained by \p this object generate
  /// their output from simulation data sources.
  void simulate(const size_t NumCycles) const noexcept;
};

/// Anonymous namespace with helper features for implementing
/// \c rosa::deluxe::DeluxeContext, consider it private.
namespace {

/// Maps any type \p T to \c rosa::unit_t.
template <typename T> struct MapToUnit { using Type = unit_t; };

} // End namespace

template <typename T>
AgentHandle DeluxeContext::createSensor(const std::string &Name,
                                        std::function<T(void)> &&F) noexcept {
  return createSensor(Name,
                      std::function<void(std::pair<unit_t, bool>)>(
                          [](std::pair<unit_t, bool>) {}),
                      std::move(F));
}

template <typename MT, typename T>
AgentHandle
DeluxeContext::createSensor(const std::string &Name,
                            std::function<void(std::pair<MT, bool>)> &&MF,
                            std::function<T(void)> &&F) noexcept {
  AgentHandle H = System->createSensor(Name, std::move(MF), std::move(F));
  DeluxeUnits.emplace(H);
  return H;
}

template <typename T, typename... As>
AgentHandle DeluxeContext::createAgent(
    const std::string &Name,
    std::function<Optional<T>(std::pair<As, bool>...)> &&F) noexcept {
  using NoMasterOutputType =
      std::tuple<Optional<typename MapToUnit<As>::Type>...>;
  return createAgent(
      Name,
      std::function<NoMasterOutputType(std::pair<unit_t, bool>)>(
          [](std::pair<unit_t, bool>) { return NoMasterOutputType(); }),
      std::function<
          std::tuple<Optional<T>, Optional<typename MapToUnit<As>::Type>...>(
              std::pair<As, bool>...)>(
          [F{std::move(F)}](std::pair<As, bool>... Args) {
            return std::tuple_cat(std::make_tuple(F(Args...)),
                                  NoMasterOutputType());
          }));
}

template <typename MT, typename T, typename... As>
AgentHandle DeluxeContext::createAgent(
    const std::string &Name, std::function<void(std::pair<MT, bool>)> &&MF,
    std::function<Optional<T>(std::pair<As, bool>...)> &&F) noexcept {
  using NoMasterOutputType =
      std::tuple<Optional<typename MapToUnit<As>::Type>...>;
  return createAgent(
      Name,
      std::function<NoMasterOutputType(std::pair<MT, bool>)>(
          [MF{std::move(MF)}](std::pair<MT, bool> Arg) {
            MF(Arg);
            return NoMasterOutputType();
          }),
      std::function<
          std::tuple<Optional<T>, Optional<typename MapToUnit<As>::Type>...>(
              std::pair<As, bool>...)>(
          [F{std::move(F)}](std::pair<As, bool>... Args) {
            return std::tuple_cat(std::make_tuple(F(Args...)),
                                  NoMasterOutputType());
          }));
}

template <typename T, typename... Ts, typename... As>
AgentHandle DeluxeContext::createAgent(
    const std::string &Name,
    std::function<std::tuple<Optional<T>, Optional<Ts>...>(
        std::pair<As, bool>...)> &&F) noexcept {
  using MasterOutputType = std::tuple<Optional<Ts>...>;
  return createAgent(
      Name,
      std::function<MasterOutputType(std::pair<unit_t, bool>)>(
          [](std::pair<unit_t, bool>) { return MasterOutputType(); }),
      std::move(F));
}

template <typename MT, typename T, typename... Ts, typename... As>
AgentHandle DeluxeContext::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 {
  AgentHandle H = System->createAgent(Name, std::move(MF), std::move(F));
  DeluxeUnits.emplace(H);
  return H;
}

template <typename Iterator, typename T>
DeluxeContext::ErrorCode
DeluxeContext::registerSensorValues(AgentHandle Sensor, Iterator &&Start,
                                    const Iterator &End, T Default) noexcept {
  // Get the type of values provided by \p Iterator.
  STATIC_ASSERT((std::is_same<T, typename Iterator::value_type>::value),
                "type mismatch");

  // Make sure preconditions are met.
  if (!System->isDeluxeSensor(Sensor)) {
    DCRETERROR(ErrorCode::NotSensor);
  }
  auto S = System->getDeluxeSensor(Sensor);
  ASSERT(S); // Sanity check.
  if (S->OutputType != TypeNumberOf<T>::Value) {
    DCRETERROR(ErrorCode::TypeMismatch);
  } else if (S->simulationDataSourceIsSet()) {
    DCRETERROR(ErrorCode::AlreadyHasValueStream);
  }

  // Register input stream.
  // \note Need to capture parameters by value so having local copies.
  S->registerSimulationDataSource(
      std::function<T(void)>([=](void) mutable noexcept->T {
        if (Start != End) {
          LOG_TRACE_STREAM << "Reading next value for sensor '" << S->FullName
                           << "': " << *Start << '\n';
          return *Start++;
        } else {
          LOG_TRACE_STREAM << "Providing default value for sensor '"
                           << S->FullName << "': " << Default << '\n';
          return Default;
        }
      }));
  return ErrorCode::NoError;
}

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

// Undef local macro if not used in the corresponding implementation.
#ifndef ROSA_LIB_DELUXE_DELUXECONTEXT_CPP
#undef DCRETERROR
#endif

#endif // ROSA_DELUXE_DELUXECONTEXT_HPP
