#pragma once
#include "Process.h"
#include "../PersistentUnitGroup.h"

/**
 * @brief A group process is a process controlling a group of units.
 */
class GroupProcess : public virtual Process
{
public:
  /**
   * @brief Creates a new group process
   * @param parent The parent of this process.
   * @param group The group associated with this group process. Note that the group must exist
   *              for the entire group process life span, as its units not copied (allowing one
   *              to externally feed units into the group).
   */
  GroupProcess(const Process* parent, const PersistentUnitGroup* group)
    : Process(parent)
    , m_group(group)
    , m_unitAddedEventHandler(*this)
    , m_unitRemovedEventHandler(*this)
#ifdef _DEBUG_COMPONENT
    , m_groupDestroyedEventHandler(*this)
#endif
  {
  }

  /**
   * @brief Destroys the group process. This involved unregistering the event handlers from the group.
   */
  ~GroupProcess()
  {
    m_group->getUnitAddedEvent().unsubscribe(m_unitAddedID);
    m_group->getUnitRemovedEvent().unsubscribe(m_unitRemovedID);
#ifdef _DEBUG_COMPONENT
    m_group->getGroupDestroyedEvent().unsubscribe(m_groupDestroyedID);
#endif
  }

  /**
   * @return The group of units controlled by this group process.
   */
  const PersistentUnitGroup* getGroup() const
  {
    return m_group;
  }

protected:
  /**
   * @brief Handler to be implemented by derived classes when a unit is added to the group process.
   * @param unit The unit added to the group.
   */
  virtual void onUnitAddedToGroup(void*, BWAPI::Unit* unit) = 0;
  /**
   * @brief Handler to be implemented by derived classes when a unit is removed from the group process.
   * @param unit The unit removed from the group.
   */
  virtual void onUnitRemovedFromGroup(void*, BWAPI::Unit* unit)= 0;

#ifdef _DEBUG_COMPONENT
  /**
   * @brief Handler printing an error message if the group is destroyed before the group process is.
   */
  void onGroupDestroyed(void*)
  {
    if (!isTerminated())
    {
      //terminate(GroupDestroyed); // Prevent crash?
      SPAR_LOG(LogTypes.GENERAL_ERROR, "ERROR GroupProcess::onGroupDestroyed: Process \"%s\" is not terminated but its group \"%s\" has been destroyed", 
        toString(this).c_str(),
        toString(m_group).c_str());
    }
  }
#endif

  /**
   * @brief Connects the event handlers to the group's events when the group process starts.
   * @note Process::onStart() must be called by the derived class (to avoid multiple calls)!
   */
  virtual void onStart()
  {
    // WARNING: Process::onStart() is intended to be called by the derived class!

    m_unitAddedID = m_group->getUnitAddedEvent().subscribe(&m_unitAddedEventHandler, NULL);
    m_unitRemovedID = m_group->getUnitRemovedEvent().subscribe(&m_unitRemovedEventHandler, NULL);
#ifdef _DEBUG_COMPONENT
    m_groupDestroyedID = m_group->getGroupDestroyedEvent().subscribe(&m_groupDestroyedEventHandler, NULL);
#endif
  }

  /**
   * @brief Deconnects the event handlers from the group's events when the group process ends.
   * @note Process::onEnd() must be called by the derived class (to avoid multiple calls)!
   */
  virtual void onEnd()
  {
    // WARNING: Process::onEnd() is intended to be called by the derived class!

    m_group->getUnitAddedEvent().unsubscribe(m_unitAddedID);
    m_group->getUnitRemovedEvent().unsubscribe(m_unitRemovedID);
#ifdef _DEBUG_COMPONENT
    m_group->getGroupDestroyedEvent().unsubscribe(m_groupDestroyedID);
#endif
  }

private:
  EVENT_HANDLER1(GroupProcess, onUnitAddedToGroup, BWAPI::Unit*) m_unitAddedEventHandler;
  PersistentUnitGroup::OnUnitAdded::SubscriberID m_unitAddedID;
  EVENT_HANDLER1(GroupProcess, onUnitRemovedFromGroup, BWAPI::Unit*) m_unitRemovedEventHandler;
  PersistentUnitGroup::OnUnitRemoved::SubscriberID m_unitRemovedID;
#ifdef _DEBUG_COMPONENT
  EVENT_HANDLER(GroupProcess, onGroupDestroyed) m_groupDestroyedEventHandler;
  PersistentUnitGroup::OnGroupDestroyed::SubscriberID m_groupDestroyedID;
#endif

  /**
   * @brief The units currently assigned to this group process.
   *        Note that units can be externally added to the group.
   */
  const PersistentUnitGroup* const m_group;
};