/*******************************************************************************
 *
 * File:     System.hpp
 *
 * Contents: Declaration of System interface.
 *
 * Copyright 2017
 *
 * Author: David Juhasz (david.juhasz@tuwien.ac.at)
 *
 ******************************************************************************/

#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 <memory>
#include <string>

namespace rosa {

// Base interface for actual agent-systems, the class provides facilities to
// keep track of Units of the system.
// NOTE: Any subclasses are 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 SystemBase
// implenenting a base feature-set.
class System {
public:
  // Signature of creator functions for Units.
  template <typename T, typename S>
  using UnitCreator = std::function<T *(const id_t, S &)noexcept>;

  // Returns an object implementing the interface defined by the class.
  static std::unique_ptr<System> createSystem(const std::string &Name) noexcept;

protected:
  // Protected ctor, only subclasses can instantiate.
  System(void) noexcept = default;

  // No copy and move.
  System(const System&) = delete;
  System(System&&) = delete;
  System &operator=(const System&) = delete;
  System &operator=(System&&) = delete;

public:
  // Dtor.
  // NOTE: Any implementation makes sure that only a cleaned System can be
  // destroyed.
  virtual ~System(void) = default;

protected:
  // Tells the next Id to be used for a newly created Unit.
  // NOTE: Never returs nthe same value twice.
  virtual id_t nextId(void) noexcept = 0;

  // Tells if the System has been marked clean and ready for destruction.
  virtual bool isSystemCleaned(void) const noexcept = 0;

  // Marks the System cleaned. Can be called only once when the System does not
  // have any live Units.
  // PRE: !isSystemCleaned() && empty()
  // POST: isSystemCleaned()
  virtual void markCleaned(void) noexcept = 0;

  // Registers the given Unit instance to the System.
  // PRE: !isUnitRegistered(U)
  // POST: isUnitRegistered(U)
  virtual void registerUnit(Unit &U) noexcept = 0;

  // Unregisters and destroys the given Unit.
  // PRE: isUnitRegistered(U)
  // POST: !isUnitRegistered(U) && 'U is destroyed'
  virtual void destroyUnit(Unit &U) noexcept = 0;

  // Returns if the given Unit is registered in the System.
  virtual bool isUnitRegistered(const Unit &U) const noexcept = 0;

  // Creates a Unit instance with the given UnitCreator and registers the new
  // Unit instance
  // NOTE: The type argument S must be the actual subclass whose instance
  // creates the Unit. That cannot be statically enforced, it is the
  // reponsibility of the caller to provide the proper System subclass.
  // STATIC PRE:
  // std::is_base_of<Unit, T>::value && std::is_base_of<System, S>::value
  // PRE: !isSystemCleaned()
  template <typename T, typename S> T &createUnit(UnitCreator<T, S> C) noexcept;

public:
  // Tells the textual name of the System.
  // NOTE: The returned reference remains valid as long as the originating
  // System is not destroyed.
  virtual const std::string &name(void) const noexcept = 0;

  // Returns the number of Units constructed in the System so far,
  // including those being already destroyed.
  virtual size_t numberOfConstructedUnits(void) const noexcept = 0;

  // Returns the number of live Units, that have been constructed and not
  // destroyed yet.
  virtual size_t numberOfLiveUnits(void) const noexcept = 0;

  // Returns if the System has no live Units.
  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

