/***************************************************************************//**
 *
 * \file rosa/core/System.hpp
 *
 * \author David Juhasz (david.juhasz@tuwien.ac.at)
 *
 * \date 2017
 *
 * \brief Declaration of *System* interface.
 *
 ******************************************************************************/

#ifndef ROSA_CORE_SYSTEM_HPP
#define ROSA_CORE_SYSTEM_HPP

#include "rosa/config/config.h"

#include "rosa/core/forward_declarations.h"

#include "rosa/support/debug.hpp"
#include "rosa/support/log.h"

#include <functional>
#include <memory>
#include <string>

namespace rosa {

/// Base interface for actual agent-systems.
///
/// The class provides facilities to keep track of `rosa::Unit` instances owned
/// by `this` `rosa::System`.
///
/// \note Any subclass is supposed to provide thread-safe implementation.
///
/// \note The class declares only an interface to avoid trouble with multiple
/// inheritance in various subclasses as in derived interfaces and derived
/// implementations.
///
/// \note Actual implementations are supposed to derive from `rosa::SystemBase`
/// implenenting a base feature-set.
class System {
public:
  /// Signature of creator functions for `rosa::Unit` instances.
  ///
  /// \tparam T type derived from `rosa::Unit`
  /// \tparam S type derived from `rosa::System`
  template <typename T, typename S>
  using UnitCreator = std::function<T *(const id_t, S &)noexcept>;

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

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

  /// No copying and moving of `rosa::System`.
  ///@{
  System(const System&) = delete;
  System(System&&) = delete;
  System &operator=(const System&) = delete;
  System &operator=(System&&) = delete;
  ///@}

public:
  /// Destroys `this` object.
  ///
  /// \note Any implementation makes sure that a `rosa::System` can be
  /// destroyed only if it is marked *cleaned*
  /// \see rosa::System::isSystemCleaned
  virtual ~System(void) = default;

protected:
  /// Tells the next unique identifier to be used for a newly created
  /// `rosa::Unit`.
  ///
  /// \return `id_t` which is unique within the context of `this` object.
  ///
  /// \note Never returs the same value twice.
  virtual id_t nextId(void) noexcept = 0;

  /// Tells if `this` object has been marked cleaned and is ready for
  /// destruction.
  ///
  /// \return if `this` object is marked clean.
  virtual bool isSystemCleaned(void) const noexcept = 0;

  /// Marks `this` object cleaned.
  ///
  /// \note Can be called only once when the System does not have any live
  /// `rosa::Unit` instances.
  ///
  /// \pre `this` object has not yet been marked as cleaned and it has no
  /// `rosa::Unit` instances registered:\code
  /// !isSystemCleaned() && empty()
  /// \endcode
  ///
  /// \post `this` object is marked cleaned:\code
  /// isSystemCleaned()
  /// \encode
  virtual void markCleaned(void) noexcept = 0;

  /// Registers a `rosa::Unit` instance to `this` object.
  ///
  /// \param U `rosa::Unit` to register
  ///
  /// \pre `this` object has not yet been marked as cleaned and `U` is not
  /// registered yet:\code
  /// !isSystemCleaned() && !isUnitRegistered(U)
  /// \endcode
  ///
  /// \post `U` is registered:\code
  /// isUnitRegistered(U)
  /// \endcode
  virtual void registerUnit(Unit &U) noexcept = 0;

  /// Unregisters and destroys a registered `rosa::Unit` instance.
  ///
  /// \param U `rosa::Unit` to destroy
  ///
  /// \pre `U` is registered:\code
  /// isUnitRegistered(U)
  /// \endcode
  ///
  /// \post `U` is not registered:\code
  /// !isUnitRegistered(U)
  /// \endcode Moreover, `U` is destroyed.
  virtual void destroyUnit(Unit &U) noexcept = 0;

  /// Tells if a `rosa::Unit` is registered in `this` object.
  ///
  /// \param U `rosa::Unit` to check
  ///
  /// \return whether `U` is registered in `this` object
  virtual bool isUnitRegistered(const Unit &U) const noexcept = 0;

  /// Creates a `rosa::Unit` instance with the given `rosa::System::UnitCreator`
  /// and registers the new instance.
  ///
  /// \tparam T type of the actual `rosa::Unit` to instantiate
  /// \tparam S type of the actual `rosa::System` instantiating
  ///
  /// \param C function creating an instance of type `T`
  ///
  /// \note `S` must be the actual subclass that wants to instantiate
  /// `rosa::Unit`. That cannot be statically enforced, it is the
  /// reponsibility of the caller to provide the proper `rosa::System` subclass.
  ///
  /// \pre Statically, `T` is a subclass of `rosa::Unit` and `S` is a subclass
  /// of `rosa::System`:\code
  /// std::is_base_of<Unit, T>::value && std::is_base_of<System, S>::value
  /// \endcode Dynamically, `this` object has not yet been marked cleaned:\code
  /// !isSystemCleaned()
  /// \endcode
  template <typename T, typename S> T &createUnit(UnitCreator<T, S> C) noexcept;

public:
  /// Tells the name of `this` object
  ///
  /// \note The returned reference remains valid as long as `this` object is not
  /// destroyed.
  ///
  /// \return name of `this` object
  virtual const std::string &name(void) const noexcept = 0;

  /// Tells the number of `rosa::Unit` instances constructed in the context of
  /// `this`object so far, including those being already destroyed.
  ///
  /// \return number of `rosa::Unit`instances created so far
  virtual size_t numberOfConstructedUnits(void) const noexcept = 0;

  /// Tells the number of live `rosa::Unit` instances in the context `this`
  /// object, those being constructed and not destroyed yet.
  ///
  /// \return number of `rosa::Unit` instances alive
  virtual size_t numberOfLiveUnits(void) const noexcept = 0;

  /// Tells if `this` object has no live `rosa::Unit` instances.
  ///
  /// \return whether `this` object has any live `rosa::Unit` instances
  virtual bool empty(void) const noexcept = 0;
};

template <typename T, typename S>
T &System::createUnit(UnitCreator<T, S> C) noexcept {
  STATIC_ASSERT((std::is_base_of<Unit, T>::value), "not a Unit");
  STATIC_ASSERT((std::is_base_of<System, S>::value), "not a System");
  if (isSystemCleaned()) {
    ROSA_CRITICAL("Trying to create a Unit in a cleaned System (" + name() +
                  ")");
  }
  const id_t Id = nextId();
  T *U = C(Id, static_cast<S &>(*this));
  registerUnit(*U);
  LOG_TRACE("Unit created and registered (" + U->FullName + ")");
  return *U;
}

} // End namespace rosa

#endif // ROSA_CORE_SYSTEM_HPP

