//===-- rosa/core/System.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/core/System.hpp
///
/// \author David Juhasz (david.juhasz@tuwien.ac.at)
///
/// \date 2017-2019
///
/// \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 \c rosa::Unit instances owned
/// by a \c 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 \c rosa::SystemBase
/// implenenting a base feature-set.
class System {
public:
  /// Signature of creator functions for \c rosa::Unit instances.
  ///
  /// \note The function is to be \c noexcept.
  ///
  /// \tparam T type derived from \c rosa::Unit
  /// \tparam S type derived from \c rosa::System
  template <typename T, typename S>
  using UnitCreator = std::function<T *(const id_t, S &)>;

  /// Returns an object implementing the \c rosa::System interface.
  ///
  /// \param Name name of the new instance
  ///
  /// \return \c std::unique_ptr for a new instance of \c 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 \c rosa::System.
  ///@{
  System(const System &) = delete;
  System(System &&) = delete;
  System &operator=(const System &) = delete;
  System &operator=(System &&) = delete;
  ///@}

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

  /// Tells whether \p this object is the same as \p Other.
  ///
  /// \note Whenever checking equality of two objects, use the one with the
  /// more specialized static type on the left-hand side of the operator. The
  /// static type of the object on the right-hand side is better to be
  /// \c rosa::System, ambiguous conversion might happen otherwise.
  ///
  /// \param Other another \c rosa::System instance to compare to
  ///
  /// \return whether \p this object and \p Other is the same
  virtual bool operator==(const System &Other) const noexcept = 0;

  /// Tells whether \p this object is not the same as \p Other.
  ///
  /// \note Whenever checking inequality of two objects, use the one with the
  /// more specialized static type on the left-hand side of the operator. The
  /// static type of the object on the right-hand side is better to be
  /// \c rosa::System, ambiguous conversion might happen otherwise.
  ///
  /// \param Other another \c rosa::System instance to compare to
  ///
  /// \return whether \p this object and \p Other is not the same
  bool operator!=(const System &Other) const noexcept {
    return !operator==(Other);
  }

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

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

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

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

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

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

  /// Creates a \c rosa::Unit instance with the given
  /// \c rosa::System::UnitCreator and registers the new instance.
  ///
  /// \tparam T type of the actual \c rosa::Unit to instantiate
  /// \tparam S type of the actual \c rosa::System instantiating
  ///
  /// \param C function creating an instance of type \p T
  ///
  /// \note \p S must be the actual subclass that wants to instantiate
  /// \c rosa::Unit. That cannot be statically enforced, it is the
  /// reponsibility of the caller to provide the proper \c rosa::System
  /// subclass.
  ///
  /// \pre Statically, \p T is a subclass of \c rosa::Unit and \p S is a
  /// subclass of \c rosa::System:
  /// \code
  /// std::is_base_of<Unit, T>::value && std::is_base_of<System, S>::value
  /// \endcode
  /// Dynamically, \p 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 \p this object
  ///
  /// \note The returned reference remains valid as long as \p this object is
  /// not destroyed.
  ///
  /// \return name of \p this object
  virtual const std::string &name(void) const noexcept = 0;

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

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

  /// Tells if \p this object has no live \c rosa::Unit instances.
  ///
  /// \return whether \p this object has any live \c 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
