//===-- rosa/deluxe/DeluxeExecutionPolicy.h ---------------------*- C++ -*-===//
//
//                                 The RoSA Framework
//
//===----------------------------------------------------------------------===//
///
/// \file rosa/deluxe/DeluxeExecutionPolicy.h
///
/// \author David Juhasz (david.juhasz@tuwien.ac.at)
///
/// \date 2019
///
/// \brief Public interface of *execution policies* in the *deluxe interface*.
///
//===----------------------------------------------------------------------===//

#ifndef ROSA_DELUXE_DELUXEEXECUTIONPOLICY_H
#define ROSA_DELUXE_DELUXEEXECUTIONPOLICY_H

#include "rosa/core/AgentHandle.hpp"

#include <memory>
#include <ostream>
#include <set>
#include <vector>

namespace rosa {
namespace deluxe {

// Forward declaration of DeluxeSystem. Do not include the corresponding header
// in this file because of cyclic dependency.
class DeluxeSystem;

/// *Execution policy* that controls how *agents* and *sensors* call their
/// processing functions.
///
/// An *execution policy* can be applied to a deluxe *unit* only if \c
/// deluxe::rosa::DeluxeExecutionPolicy::canHandle() allows it. Each deluxe
/// *unit* must have a compatible *execution policy* associated to it, and the
/// *unit* queries \c rosa::deluxe::DeluxeExecutionPolicy::shouldProcess() on each
/// triggering and calls its processing funtion only if it is allowed by the
/// *execution policy*.
///
/// \see rosa::deluxe::DeluxeExecutionPolicy::decimation()
/// \see rosa::deluxe::DeluxeExecutionPolicy::awaitAll()
/// \see rosa::deluxe::DeluxeExecutionPolicy::awaitAny()
///
/// \todo Extend the interface with query functions about what kind of
/// execution policy is behind the interface. This can be done in relation
/// to the existing factory functions; for example, if the actual object is
/// decimation and with what rate.
class DeluxeExecutionPolicy {
protected:

  /// Protected constructor, only implementations can instantiate the class.
  DeluxeExecutionPolicy(void) noexcept = default;

private:
  /// No instance can be copy-constructed, move-constructed, copied, and moved.
  ///
  ///@{
  DeluxeExecutionPolicy(const DeluxeExecutionPolicy &) = delete;
  DeluxeExecutionPolicy(DeluxeExecutionPolicy &&) = delete;
  DeluxeExecutionPolicy &operator=(const DeluxeExecutionPolicy &) = delete;
  DeluxeExecutionPolicy &operator=(DeluxeExecutionPolicy &&) = delete;
  ///@}

public:
  /// Virtual destructor for subclasses.
  virtual ~DeluxeExecutionPolicy(void) noexcept = default;

  /// Creates an *execution policy* that allows execution with decimation of
  /// triggering.
  ///
  //// *Decimation* can handle both *agents* and *sensors*.
  /// Processing functions are executed only on every \p D <sup>th</sup>
  /// triggering. In the case of *sensors* in simulation, the simulation data
  /// source is read on each triggering as it provides values with respect to
  /// the highest execution frequency, but output is generated by the *sensor*
  /// only on every \p D <sup>th</sup> triggering.
  ///
  /// \note A rate of \c 0 is allowed as actual argument and is treated as rate
  /// \c 1 (i.e., execute processing functions on each triggering).
  ///
  /// \param D the rate of *decimation*
  ///
  /// \return an *execution policy* implementing *decimation* with rate \p D
  static std::unique_ptr<DeluxeExecutionPolicy> decimation(const size_t D);

  /// Creates an *execution policy* that allows execution only if all defined
  /// *slave* positions has new input.
  ///
  /// *Await all* can handle only *agents* and only if the particular *agent*
  /// has at least as many *slave* positions as the largest position defined in
  /// \p S. Processing functions are executed only if new input has been
  /// received for all defined *slave* positions.
  ///
  /// \param S set of *slave* positions to await input from
  ///
  /// \return an *execution policy* implementing *awaiting all* input from set
  /// \p S
  static std::unique_ptr<DeluxeExecutionPolicy>
  awaitAll(const std::set<size_t> &S);

  /// Creates an *execution policy* that allows execution if any of the defined
  /// *slave* positions has new input.
  ///
  /// *Await any* can handle only *agents* and only if the particular *agent*
  /// has at least as many *slave* positions as the largest position defined in
  /// \p S. Processing functions are executed if new input has been received for
  /// any of the defined *slave* positions.
  ///
  /// \param S set of *slave* positions to await input from
  ///
  /// \return an *execution policy* implementing *awaiting any* input from set
  /// \p S
  static std::unique_ptr<DeluxeExecutionPolicy>
  awaitAny(const std::set<size_t> &S);

  /// Tells if \p this object can handle the deluxe *unit* referred by \p H.
  ///
  /// The *execution policy* implemented by \p this object is applicable to the
  /// given deluxe *unit* referred by \p H only if the function returns \c true.
  ///
  /// \param H reference to the *unit* to check
  /// \param S the system owning the *unit* referred by \p H
  ///
  /// \return if \p this object can handle the *unit* referred by \p H
  virtual bool canHandle(const AgentHandle H, const DeluxeSystem &S) const
      noexcept = 0;

  /// Tells if processing function should be executed on the current triggering.
  ///
  /// The function is to be called on each triggering of the deluxe *unit*.
  /// Decision about execution of processing function is done by \p this object
  /// according to the implemented *execution policy*.
  ///
  /// \param InputChanged flags indicating whether new input has been received
  /// at *slave* positions
  ///
  /// \return if to execute processing function
  virtual bool shouldProcess(const std::vector<bool> &InputChanged) noexcept = 0;

  /// Dumps \p this object into textual representation.
  ///
  /// \return textual representation of \p this object
  virtual std::string dump(void) const noexcept = 0;

protected:
  /// Tells whether the *unit* referred by \p H is a \c
  /// rosa::deluxe::DeluxeAgent.
  ///
  /// \param H reference to the *unit* to check
  /// \param S the system owning the *unit* referred by \p H
  ///
  /// \return if the *unit* referred by \p H is a \c rosa::deluxe::DeluxeAgent
  bool isDeluxeAgent(const AgentHandle H, const DeluxeSystem &S) const noexcept;

  /// Tells the number of inputs handled by the *unit* referred by \p H.
  ///
  /// If \p H refers to a \c rosa::deluxe::DeluxeAgent, the function returns the
  /// number of inputs (i.e., *slave* positions) of the *agent*. Otherwise, the
  /// function returns \c 0.
  ///
  /// \param H reference to the *unit* to check
  /// \param S the system owning the *unit* referred by \p H
  ///
  /// \return the number of inputs handled by the *unit* referred by \p H
  size_t numberOfDeluxeAgentInputs(const AgentHandle H,
                                   const DeluxeSystem &S) const noexcept;
};

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

namespace std {

/// Converts a \c rosa::deluxe::DeluxeExecutionPolicy into \c std::string.
///
/// \param EP \c rosa::deluxe::DeluxeExecutionPolicy to convert
///
/// \return \c std::string representing \p EP
string to_string(const rosa::deluxe::DeluxeExecutionPolicy &EP);

/// Dumps a \c rosa::deluxe::DeluxeExecutionPolicy to a given \c std::ostream.
///
/// \param [in,out] OS output stream to dump to
/// \param EP \c rosa::deluxe::DeluxeExecutionPolicy to dump
///
/// \return \p OS after dumping \p EP to it
ostream &operator<<(ostream &OS, const rosa::deluxe::DeluxeExecutionPolicy &EP);

} // End namespace std

#endif // ROSA_DELUXE_DELUXEEXECUTIONPOLICY_H
