//===-- rosa/app/Application.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/app/Application.hpp
///
/// \author David Juhasz (david.juhasz@tuwien.ac.at)
///
/// \date 2017-2020
///
/// \brief Public interface for the *application interface* for working with
/// agent
///        systems.
///
//===----------------------------------------------------------------------===//

#ifndef ROSA_APP_APPLICATION_HPP
#define ROSA_APP_APPLICATION_HPP

#include "rosa/app/AppSystem.hpp"

#include "rosa/support/types.hpp"

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

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

namespace rosa {
namespace app {

/// Defines the *application interface*.
///
/// \todo The classes \c rosa::app::AppSensor and \c
/// rosa::app::AppAgent share some common features in relation to their
/// *slave* role in the *application interface*. But their definitions are
/// completely independent. It could be investigated how to lift their common
/// parts into a new *application slave* class, which would serve as base for
/// both, to avoid code duplication.
class Application {

  /// 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::app::Application::getSystem.
  std::shared_ptr<AppSystem> System;

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

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

  /// Returns a new instance of \c rosa::app::Application.
  ///
  /// \param Name name of the underlying \c rosa::AppSystem
  ///
  /// \return \c std::unique_ptr for the new instance of
  /// \c rosa::app::Application with a new, empty \c rosa::AppSystem
  static std::unique_ptr<Application> 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
  Application(const std::string &Name) noexcept;

public:
  /// Destroys \p this object.
  ~Application(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;

private:
  /// 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 Instantiation fails if any of the type arguments \p MT and \p T
  /// is not an instance of \c rosa::app::AppTuple or \p T is \c
  /// rosa::app::EmptyAppTuple.
  ///
  /// \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::app::Application::registerSensorValues is used to
  /// register an alternative simulation data source with \c
  /// rosa::app::AppSensor::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::app::AppSensor::AppSensor.
  ///
  /// \return \c rosa::AgentHandle for the new *sensor*
  template <
      typename MT, typename T,
      typename = std::enable_if<TypeListAllAppTuple<TypeList<MT, T>>::Value &&
                                !std::is_same<T, EmptyAppTuple>::value>>
  AgentHandle createSensorImpl(const std::string &Name,
                               std::function<void(std::pair<MT, bool>)> &&MF,
                               std::function<T(void)> &&F) noexcept;

public:
  /// 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
  ///
  /// \note Instantiation fails if type argument \p T is neither a built-in type
  /// nor an instance of \c rosa::app::AppTuple with at least one element.
  ///
  /// \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::app::Application::registerSensorValues is used to register
  /// an alternative simulation data source with
  /// \c rosa::app::AppSensor::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::app::AppSensor::AppSensor.
  ///
  /// \return \c rosa::AgentHandle for the new *sensor*
  template <typename T, typename = std::enable_if_t<
                            TypeListContains<BuiltinTypes, T>::Value ||
                            (IsAppTuple<T>::Value &&
                             !std::is_same<T, EmptyAppTuple>::value)>>
  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 The type arguments \p MT and \p T must be either all built-in types
  /// or all instances of \c rosa::app::AppTuple. Moreover, \p T cannot be
  /// \c rosa::app::EmptyAppTuple. Instantiation fails if these conditions
  /// do not hold.
  ///
  /// \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::app::Application::registerSensorValues is used to
  /// register an alternative simulation data source with \c
  /// rosa::app::AppSensor::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::app::AppSensor::AppSensor.
  ///
  /// \return \c rosa::AgentHandle for the new *sensor*
  template <typename MT, typename T,
            typename = std::enable_if<
                TypeListSubsetOf<TypeList<MT, T>, BuiltinTypes>::Value ||
                (TypeListAllAppTuple<TypeList<MT, T>>::Value &&
                 !std::is_same<T, EmptyAppTuple>::value)>>
  AgentHandle createSensor(
      const std::string &Name, std::function<void(std::pair<MT, bool>)> &&MF,
      std::function<T(void)> &&F = [](void) { return T(); }) noexcept;

private:
  /// 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
  ///
  /// \note Instantiation fails if any of the type arguments \p MT, \p T, \p
  /// Ts..., and \p As... is not an instance of \c rosa::app::AppTuple or
  /// any of \p T and \p As... is \c rosa::app::EmptyAppTuple.
  ///
  /// \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::app::AppAgent::AppAgent.
  ///
  /// \return \c rosa::AgentHandle for the new *agent*
  template <typename MT, typename T, typename... Ts, typename... As,
            typename = std::enable_if_t<
                TypeListAllAppTuple<TypeList<MT, T, Ts..., As...>>::Value &&
                !std::is_same<T, EmptyAppTuple>::value &&
                (true && ... && (!std::is_same<As, EmptyAppTuple>::value))>>
  AgentHandle createAgentImpl(
      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;

public:
  /// 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
  ///
  /// \note The type arguments \p T and \p As... must be either all built-in
  /// types or all instances of \c rosa::app::AppTuple. Moreover, none of
  /// them can be \c rosa::app::EmptyAppTuple. Instantiation fails if
  /// these conditions do not hold.
  ///
  /// \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::app::AppAgent::AppAgent.
  ///
  /// \return \c rosa::AgentHandle for the new *agent*
  template <typename T, typename... As,
            typename = std::enable_if_t<
                TypeListSubsetOf<TypeList<T, As...>, BuiltinTypes>::Value ||
                (TypeListAllAppTuple<TypeList<T, As...>>::Value &&
                 !std::is_same<T, EmptyAppTuple>::value &&
                 (true && ... && (!std::is_same<As, EmptyAppTuple>::value)))>>
  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
  ///
  /// \note The type arguments \p MT, \p T, and \p As... must be either all
  /// built-in types or all instances of \c rosa::app::AppTuple. Moreover,
  /// none of \p T and \p As... can be \c rosa::app::EmptyAppTuple.
  /// Instantiation fails if these conditions do not hold.
  ///
  /// \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::app::AppAgent::AppAgent.
  ///
  /// \return \c rosa::AgentHandle for the new *agent*
  template <typename MT, typename T, typename... As,
            typename = std::enable_if_t<
                TypeListSubsetOf<TypeList<MT, T, As...>, BuiltinTypes>::Value ||
                (TypeListAllAppTuple<TypeList<MT, T, As...>>::Value &&
                 !std::is_same<T, EmptyAppTuple>::value &&
                 (true && ... && (!std::is_same<As, EmptyAppTuple>::value)))>>
  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
  ///
  /// \note The type arguments \p T, \p Ts, and \p As... must be either all
  /// built-in types or all instances of \c rosa::app::AppTuple. Moreover,
  /// none of \p T and \p As... can be \c rosa::app::EmptyAppTuple.
  /// Instantiation fails if these conditions do not hold.
  ///
  /// \param Name name of the new *agent*
  /// \param F function for the new *agent* to process input values and
  /// generate output with
  ///
  /// \note \p F does not produce master-output for a given position if the
  /// corresponding type is \c rosa::app::EmptyAppTuple. It is not
  /// possible to disable master-output at any position by using built-in types.
  ///
  /// \see \c rosa::app::AppAgent::AppAgent.
  ///
  /// \return \c rosa::AgentHandle for the new *agent*
  template <
      typename T, typename... Ts, typename... As,
      typename = std::enable_if_t<
          TypeListSubsetOf<TypeList<T, Ts..., As...>, BuiltinTypes>::Value ||
          (TypeListAllAppTuple<TypeList<T, Ts..., As...>>::Value &&
           !std::is_same<T, EmptyAppTuple>::value &&
           (true && ... && (!std::is_same<As, EmptyAppTuple>::value)))>>
  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
  ///
  /// \note The type arguments \p MT, \p T, \p Ts, and \p As... must be either
  /// all built-in types or all instances of \c rosa::app::AppTuple.
  /// Moreover, none of \p T and \p As... can be \c
  /// rosa::app::EmptyAppTuple. Instantiation fails if these conditions
  /// do not hold.
  ///
  /// \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
  ///
  /// \note \p F does not produce master-output for a given position if the
  /// corresponding type is \c rosa::app::EmptyAppTuple. It is not
  /// possible to disable master-output at any position by using built-in types.
  ///
  /// \see \c rosa::app::AppAgent::AppAgent.
  ///
  /// \return \c rosa::AgentHandle for the new *agent*
  template <typename MT, typename T, typename... Ts, typename... As,
            typename = std::enable_if_t<
                TypeListSubsetOf<TypeList<MT, T, Ts..., As...>,
                                 BuiltinTypes>::Value ||
                (TypeListAllAppTuple<TypeList<MT, T, Ts..., As...>>::Value &&
                 !std::is_same<T, EmptyAppTuple>::value &&
                 (true && ... && (!std::is_same<As, EmptyAppTuple>::value)))>>
  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;

  /// Returns the current execution policy of the referred \p Unit
  ///
  /// \see \c rosa::app::AppExecutionPolicy
  ///
  /// \note The referred \p Unit is either *sensor* or *agent*.
  ///
  /// \note The returned reference is valid only as long as \c
  /// rosa::app::Application::setExecutionPolicy() is not called with the
  /// *unit* referred by \p Unit and the *unit* is not destroyed.
  ///
  /// \param Unit the *unit* whose execution policy is to be obtained
  ///
  /// \return the \c rosa::app::AppExecutionPolicy from \p Unit if \p Unit
  /// is valid
  Optional<const AppExecutionPolicy &>
  getExecutionPolicy(AgentHandle Unit) const noexcept;

  /// Sets the current execution policy of the referred \p Unit to \p
  /// ExecutionPolicy.
  ///
  /// \see \c rosa::app::AppExecutionPolicy
  ///
  /// \note The referred \p Unit is either *sensor* or *agent*.
  ///
  /// \param Unit the *unit* whose execution policy is to be set
  /// \param ExecutionPolicy the new execution policy for \p Unit
  ///
  /// \return how successful setting \p ExecutionPolicy for \p Unit was
  ///
  /// \note The function may return the following
  /// \c rosa::app::Application::ErrorCode values:
  /// `ErrorCode`                 | Comment
  /// -----------                 | -------
  /// `NoError`                   | Success
  /// `NotUnit`                   | Referred \p Unit is not valid
  /// `UnsuitableExecutionPolicy` | \p ExecutionPolicy cannot handle \p Unit
  ErrorCode setExecutionPolicy(
      AgentHandle Unit,
      std::unique_ptr<AppExecutionPolicy> &&ExecutionPolicy) noexcept;

  /// Connects 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::app::Application::ErrorCode values:
  /// `ErrorCode`        | Comment
  /// -----------        | -------
  /// `NoError`          | Success
  /// `NotAgent`         | Referred \p Agent is not \c rosa::app::AppAgent
  /// `NotSensor`        | Referred \p Sensor is not \c rosa::app::AppSensor
  /// `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 thanthe 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::app::Application::ErrorCode values:
  /// `ErrorCode`        | Comment
  /// -----------        | -------
  /// `NoError`          | Success
  /// `NotAgent`         | Referred \p Master or \p Slave is not \c
  /// rosa::app::AppAgent `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::app::Application::registerSensorValues,
  /// \c rosa::app::Application::simulate
  ///
  /// Need to clear simulation data sources from all the *sensors*.
  void initializeSimulation(void) noexcept;

public:
  /// Registers a stream providing values for a *sensor* during
  /// simulation.
  ///
  /// \tparam Iterator type of iterator providing values for \p Sensor
  /// \tparam T type that can be matched to values \p Sensor is operating on,
  /// always use default!
  ///
  /// \note Instantiation fails if type argument \p T is neither a built-in type
  /// nor a tuple (i.e., can be converted to \c rosa::app::AppTuple).
  ///
  /// \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::app::Application::ErrorCode values:
  /// `ErrorCode`             | Comment
  /// -----------             | -------
  /// `NoError`               | Success
  /// `TypeMismatch`          | \p T does not match the type of values
  /// generated by \p Sensor
  /// `NotSensor`             | Referred \p Sensor is not \c
  /// rosa::app::AppSensor
  /// `AlreadyHasValueStream` | \p Sensor already has simulation data source set
  template <typename Iterator, typename T = typename Iterator::value_type,
            typename = std::enable_if_t<
                TypeListContains<BuiltinTypes, T>::Value || IsTuple<T>::Value>>
  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::app::Application::AppUnits 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::app::Application, consider it private.
namespace {

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

/// Convenience template alias for \c MapToEmptyAppTuple.
template <typename T> using empty_app_t = typename MapToEmptyAppTuple<T>::Type;

/// Converts a \c std::tuple of \c rosa::Optional built-in types into a
/// corresponding \c std::tuple of \c rosa::Optional with each actual value
/// wrapped in \c rosa::app::AppTuple.
///
/// \tparam Ts types of the values
/// \tparam S0 indices for accessing values in \p Values
///
/// \param Values the \c std::tuple of \c rosa::Optional with built-in values
///
/// \note The second argument provides indices statically as template arguments
/// \p S0..., so its actual value is ignored.
///
/// \return a \c std::tuple of \c rosa::Optional corresponding to \p Values
/// with each actual value wrapped in \c rosa::app::AppTuple
///
/// \pre Statically, all type arguments \p Ts... are built-in types and the
/// provided indices \p S0... match the length of \p Ts...: \code
/// TypeListSubsetOf<TypeList<Ts...>, BuiltinTypes>::Value &&
/// sizeof...(Ts) == sizeof...(S0)
/// \endcode
template <typename... Ts, size_t... S0>
std::tuple<Optional<AppTuple<Ts>>...>
wrapBuiltinInAppTuple(const std::tuple<Optional<Ts>...> &Values,
                      Seq<S0...>) noexcept {
  STATIC_ASSERT((TypeListSubsetOf<TypeList<Ts...>, BuiltinTypes>::Value),
                "not built-in types");
  STATIC_ASSERT(sizeof...(Ts) == sizeof...(S0), "inconsistent type arguments");

  return std::make_tuple(
      std::get<S0>(Values)
          ? Optional<AppTuple<Ts>>(make_app_tuple<Ts>(*std::get<S0>(Values)))
          : Optional<AppTuple<Ts>>()...);
}

} // End namespace

template <typename MT, typename T, typename>
AgentHandle
Application::createSensorImpl(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));
  AppUnits.emplace(H);
  return H;
}

template <typename T, typename>
AgentHandle Application::createSensor(const std::string &Name,
                                      std::function<T(void)> &&F) noexcept {
  auto EmptyMF = std::function<void(std::pair<EmptyAppTuple, bool>)>(
      [](std::pair<EmptyAppTuple, bool>) {});

  if constexpr (TypeListContains<BuiltinTypes, T>::Value) {
    using OutputType = AppTuple<T>;
    return createSensorImpl(
        Name, std::move(EmptyMF),
        std::function<OutputType(void)>(
            [F{std::move(F)}](void) { return OutputType(F()); }));

  } else if constexpr (IsAppTuple<T>::Value &&
                       !std::is_same<T, EmptyAppTuple>::value) {
    return createSensorImpl(Name, std::move(EmptyMF), std::move(F));

  } else {
    ASSERT(false && "Unexpected type argument");
  }
}

template <typename MT, typename T, typename>
AgentHandle
Application::createSensor(const std::string &Name,
                          std::function<void(std::pair<MT, bool>)> &&MF,
                          std::function<T(void)> &&F) noexcept {

  if constexpr (TypeListSubsetOf<TypeList<MT, T>, BuiltinTypes>::Value) {
    using MasterInputType = AppTuple<MT>;
    using OutputType = AppTuple<T>;
    return createSensorImpl(
        Name,
        std::function<void(std::pair<MasterInputType, bool>)>(
            [MF{std::move(MF)}](std::pair<MasterInputType, bool> Arg) {
              MF({std::get<0>(Arg.first), Arg.second});
            }),
        std::function<OutputType(void)>(
            [F{std::move(F)}](void) { return OutputType(F()); }));

  } else if constexpr (TypeListAllAppTuple<TypeList<MT, T>>::Value &&
                       !std::is_same<T, EmptyAppTuple>::value) {
    return createSensorImpl(Name, std::move(MF), std::move(F));

  } else {
    ASSERT(false && "Unexpected type arguments");
  }
}

template <typename MT, typename T, typename... Ts, typename... As, typename>
AgentHandle Application::createAgentImpl(
    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));
  AppUnits.emplace(H);
  return H;
}

template <typename T, typename... As, typename>
AgentHandle Application::createAgent(
    const std::string &Name,
    std::function<Optional<T>(std::pair<As, bool>...)> &&F) noexcept {

  using NoMasterOutputType = std::tuple<Optional<empty_app_t<As>>...>;
  auto EmptyMF =
      std::function<NoMasterOutputType(std::pair<EmptyAppTuple, bool>)>(
          [](std::pair<EmptyAppTuple, bool>) { return NoMasterOutputType(); });

  if constexpr (TypeListSubsetOf<TypeList<T, As...>, BuiltinTypes>::Value) {
    using OutputType = AppTuple<T>;
    return createAgentImpl(
        Name, std::move(EmptyMF),
        std::function<
            std::tuple<Optional<OutputType>, Optional<empty_app_t<As>>...>(
                std::pair<AppTuple<As>, bool>...)>(
            [F{std::move(F)}](std::pair<AppTuple<As>, bool>... Args) {
              const auto Result = F({std::get<0>(Args.first), Args.second}...);
              return std::tuple_cat(
                  wrapBuiltinInAppTuple(std::tuple(Result), seq_t<1>()),
                  NoMasterOutputType());
            }));

  } else if constexpr (TypeListAllAppTuple<TypeList<T, As...>>::Value &&
                       !std::is_same<T, EmptyAppTuple>::value &&
                       (true && ... &&
                        (!std::is_same<As, EmptyAppTuple>::value))) {
    return createAgentImpl(
        Name, std::move(EmptyMF),
        std::function<std::tuple<Optional<T>, Optional<empty_app_t<As>>...>(
            std::pair<As, bool>...)>(
            [F{std::move(F)}](std::pair<As, bool>... Args) {
              const auto Result = F(Args...);
              return std::tuple_cat(std::tuple(Result), NoMasterOutputType());
            }));

  } else {
    ASSERT(false && "Unexpected type arguments");
  }
}

template <typename MT, typename T, typename... As, typename>
AgentHandle Application::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<empty_app_t<As>>...>;

  if constexpr (TypeListSubsetOf<TypeList<MT, T, As...>, BuiltinTypes>::Value) {
    using MasterInputType = AppTuple<MT>;
    using OutputType = AppTuple<T>;
    return createAgentImpl(
        Name,
        std::function<NoMasterOutputType(std::pair<MasterInputType, bool>)>(
            [MF{std::move(MF)}](std::pair<MasterInputType, bool> Arg) {
              MF({std::get<0>(Arg.first), Arg.second});
              return NoMasterOutputType();
            }),
        std::function<
            std::tuple<Optional<OutputType>, Optional<empty_app_t<As>>...>(
                std::pair<AppTuple<As>, bool>...)>(
            [F{std::move(F)}](std::pair<AppTuple<As>, bool>... Args) {
              const auto Result = F({std::get<0>(Args.first), Args.second}...);
              return std::tuple_cat(
                  wrapBuiltinInAppTuple(std::tuple(Result), seq_t<1>()),
                  NoMasterOutputType());
            }));

  } else if constexpr (TypeListAllAppTuple<TypeList<MT, T, As...>>::Value &&
                       !std::is_same<T, EmptyAppTuple>::value &&
                       (true && ... &&
                        (!std::is_same<As, EmptyAppTuple>::value))) {
    return createAgentImpl(
        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<empty_app_t<As>>...>(
            std::pair<As, bool>...)>(
            [F{std::move(F)}](std::pair<As, bool>... Args) {
              const auto Result = F(Args...);
              return std::tuple_cat(std::tuple(Result), NoMasterOutputType());
            }));

  } else {
    ASSERT(false && "Unexpected type arguments");
  }
}

template <typename T, typename... Ts, typename... As, typename>
AgentHandle
Application::createAgent(const std::string &Name,
                         std::function<std::tuple<Optional<T>, Optional<Ts>...>(
                             std::pair<As, bool>...)> &&F) noexcept {

  if constexpr (TypeListSubsetOf<TypeList<T, Ts..., As...>,
                                 BuiltinTypes>::Value) {
    using MasterOutputType = std::tuple<Optional<AppTuple<Ts>>...>;
    using OutputType = AppTuple<T>;
    return createAgentImpl(
        Name,
        std::function<MasterOutputType(std::pair<EmptyAppTuple, bool>)>(
            [](std::pair<EmptyAppTuple, bool>) { return MasterOutputType(); }),
        std::function<
            std::tuple<Optional<OutputType>, Optional<AppTuple<Ts>>...>(
                std::pair<AppTuple<As>, bool>...)>(
            [F{std::move(F)}](std::pair<AppTuple<As>, bool>... Args) {
              const auto Result = F({std::get<0>(Args.first), Args.second}...);
              return wrapBuiltinInAppTuple(Result, seq_t<1 + sizeof...(Ts)>());
            }));

  } else if constexpr (TypeListAllAppTuple<TypeList<T, Ts..., As...>>::Value &&
                       !std::is_same<T, EmptyAppTuple>::value &&
                       (true && ... &&
                        (!std::is_same<As, EmptyAppTuple>::value))) {
    using MasterOutputType = std::tuple<Optional<Ts>...>;
    return createAgentImpl(
        Name,
        std::function<MasterOutputType(std::pair<EmptyAppTuple, bool>)>(
            [](std::pair<EmptyAppTuple, bool>) { return MasterOutputType(); }),
        std::function<std::tuple<Optional<T>, Optional<Ts>...>(
            std::pair<As, bool>...)>(
            [F{std::move(F)}](std::pair<As, bool>... Args) {
              const auto Output = F(Args...);
              return Output;
            }));

  } else {
    ASSERT(false && "Unexpected type arguments");
  }
}

template <typename MT, typename T, typename... Ts, typename... As, typename>
AgentHandle Application::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 {

  if constexpr (TypeListSubsetOf<TypeList<MT, T, Ts..., As...>,
                                 BuiltinTypes>::Value) {
    using MasterInputType = AppTuple<MT>;
    using MasterOutputType = std::tuple<Optional<AppTuple<Ts>>...>;
    using OutputType = AppTuple<T>;
    return createAgentImpl(
        Name,
        std::function<MasterOutputType(std::pair<MasterInputType, bool>)>(
            [MF{std::move(MF)}](std::pair<MasterInputType, bool> Arg) {
              const auto Result = MF({std::get<0>(Arg.first), Arg.second});
              return wrapBuiltinInAppTuple(Result, seq_t<sizeof...(Ts)>());
            }),
        std::function<
            std::tuple<Optional<OutputType>, Optional<AppTuple<Ts>>...>(
                std::pair<AppTuple<As>, bool>...)>(
            [F{std::move(F)}](std::pair<AppTuple<As>, bool>... Args) {
              const auto Result = F({std::get<0>(Args.first), Args.second}...);
              return wrapBuiltinInAppTuple(Result, seq_t<1 + sizeof...(Ts)>());
            }));

  } else if constexpr (TypeListAllAppTuple<
                           TypeList<MT, T, Ts..., As...>>::Value &&
                       !std::is_same<T, EmptyAppTuple>::value &&
                       (true && ... &&
                        (!std::is_same<As, EmptyAppTuple>::value))) {
    using MasterOutputType = std::tuple<Optional<Ts>...>;
    return createAgentImpl(
        Name,
        std::function<MasterOutputType(std::pair<MT, bool>)>(
            [MF{std::move(MF)}](std::pair<MT, bool> Arg) {
              const auto Output = MF(Arg);
              return Output;
            }),
        std::function<std::tuple<Optional<T>, Optional<Ts>...>(
            std::pair<As, bool>...)>(
            [F{std::move(F)}](std::pair<As, bool>... Args) {
              const auto Output = F(Args...);
              return Output;
            }));

  } else {
    ASSERT(false && "Unexpected type arguments");
  }
}

template <typename Iterator, typename T, typename>
Application::ErrorCode
Application::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->isAppSensor(Sensor)) {
    APPRETERROR(ErrorCode::NotSensor);
  }
  auto S = System->getAppSensor(Sensor);
  ASSERT(S); // Sanity check.

  if (S->simulationDataSourceIsSet()) {
    APPRETERROR(ErrorCode::AlreadyHasValueStream);
  }

  if constexpr (TypeListContains<BuiltinTypes, T>::Value) {
    if (S->OutputType != TypeToken<T>::Value) {
      APPRETERROR(ErrorCode::TypeMismatch);
    }

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

  } else if constexpr (IsTuple<T>::Value) {

    using TT = matching_app_tuple_t<T>;
    if (std::is_same<T, EmptyAppTuple>::value || S->OutputType != TT::TT) {
      APPRETERROR(ErrorCode::TypeMismatch);
    }

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

  } else {
    ASSERT(false && "Unexpected type argument");
  }

  return ErrorCode::NoError;
}

} // End namespace app
} // End namespace rosa

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

#endif // ROSA_APP_APPLICATION_HPP
