#pragma once
#include "TacticalAction.h"
#include "../../../../Utils/Process/GroupProcess.h"
#include "../../../../Utils/PersistentUnitGroup.h"

class UnitManager;

/**
 * @brief Base class for group tactical actions.
 */
class GroupTacticalAction : public GroupProcess, public virtual TacticalAction
{
public:
  GroupTacticalAction(const Process* parent, unsigned int priority)
    : GroupProcess(parent, &m_group)
    , Process(parent)
    , TacticalAction(parent, priority)
    , m_group("Action-group")
    , m_initialGroup(NULL)
    , m_initialUnitAddedEventHandler(*this)
    , m_initialUnitRemovedEventHandler(*this)
    , m_initialGroupDestroyedEventHandler(*this)
  {
  }

  GroupTacticalAction(const Process* parent, unsigned int priority, const PersistentUnitGroup* initialGroup)
    : GroupProcess(Spar, &m_group)
    , Process(parent)
    , TacticalAction(parent, priority)
    , m_group("Action-group")
    , m_initialGroup(initialGroup)
    , m_initialUnitAddedEventHandler(*this)
    , m_initialUnitRemovedEventHandler(*this)
    , m_initialGroupDestroyedEventHandler(*this)
  {
    for (PersistentUnitGroup::const_iterator it = m_initialGroup->begin(); it != m_initialGroup->end(); ++it)
    {
      m_group.insert(*it);
    }
    m_initialUnitAddedID = m_initialGroup->getUnitAddedEvent().subscribe(&m_initialUnitAddedEventHandler, NULL);
    m_initialUnitRemovedID = m_initialGroup->getUnitRemovedEvent().subscribe(&m_initialUnitRemovedEventHandler, NULL);
    m_initialGroupDestroyedID = m_initialGroup->getGroupDestroyedEvent().subscribe(&m_initialGroupDestroyedEventHandler, NULL);
  }

  ~GroupTacticalAction()
  {
    if (m_initialGroup != NULL)
    {
      m_initialGroup->getUnitAddedEvent().unsubscribe(m_initialUnitAddedID);
      m_initialGroup->getUnitRemovedEvent().unsubscribe(m_initialUnitRemovedID);
      m_initialGroup->getGroupDestroyedEvent().unsubscribe(m_initialGroupDestroyedID);
    }
  }

  bool addUnit(BWAPI::Unit* unit, bool force = false)
  {
    bool success = true;
    if (isWaitingToExecute())
    {
      success = m_group.insert(unit);
    }
    else if (isExecuting())
    {
      success = addActionToUnit(unit, force);
      if (success)
      {
        m_group.insert(unit);
      }
    }
    return success;
  }

  static int transfer(GroupTacticalAction* source, GroupTacticalAction* destination, const int nbUnits)
  {
    return transfer(source, destination, nbUnits, True<BWAPI::Unit*>());
  }

  template <class Predicate>
  static int transfer(GroupTacticalAction* source, GroupTacticalAction* destination, const int nbUnits, Predicate predicate)
  {
    const PersistentUnitGroup* sourceGroup = nbUnits > 0 ? source->getGroup() : destination->getGroup();
    const int absN = std::abs(nbUnits);
    int i = 0;
    PersistentUnitGroup::const_iterator it = sourceGroup->begin();
    while (i < absN && it != sourceGroup->end())
    {
      PersistentUnitGroup::const_iterator next = ++(PersistentUnitGroup::const_iterator(it));
      BWAPI::Unit* unit = *it;
      if (predicate(unit) && destination->addUnit(unit, true))
      {
        ++i;
      }
      //source->removeUnit(unit);

      it = next;
    }
    return nbUnits > 0 ? i : -i;
  }

  /////////
  // GUI //
  /////////
  virtual UnitRecordGroup _getUnitsInvolved() const
  {
    return UnitRecordGroup(getGroup()->begin(), getGroup()->end());
  }

protected:
  friend class Layer2UnitData;

  void removeUnit(BWAPI::Unit* unit)
  {
    // Condition instead?
   
    if (isWaitingToExecute())
    {
      m_group.remove(unit);
    }
    else if (isExecuting())
    {
      removeActionFromUnit(unit);
      m_group.remove(unit);
    }
  }

  virtual void _output(std::ostream& out) const;

  void onUnitAddedToInitialGroup(void*, BWAPI::Unit* unit)
  {
    m_group.insert(unit);
  }

  void onUnitRemovedFromInitialGroup(void*, BWAPI::Unit* unit)
  {
    m_group.remove(unit);
  }

  void onInitialGroupDestroyed(void*)
  {
    m_initialGroup->getUnitAddedEvent().unsubscribe(m_initialUnitAddedID);
    m_initialGroup->getUnitRemovedEvent().unsubscribe(m_initialUnitRemovedID);
    m_initialGroup = NULL;
  }

  virtual void onStart();
  virtual void onStartInterface();
  virtual void onEnd();
  virtual void onEndInterface();

private:
  bool addActionToUnit(BWAPI::Unit* unit, bool force);

  void removeActionFromUnit(BWAPI::Unit* unit);

  EVENT_HANDLER1(GroupTacticalAction, onUnitAddedToInitialGroup, BWAPI::Unit*) m_initialUnitAddedEventHandler;
  PersistentUnitGroup::OnUnitAdded::SubscriberID m_initialUnitAddedID;
  EVENT_HANDLER1(GroupTacticalAction, onUnitRemovedFromInitialGroup, BWAPI::Unit*) m_initialUnitRemovedEventHandler;
  PersistentUnitGroup::OnUnitRemoved::SubscriberID m_initialUnitRemovedID;
  EVENT_HANDLER(GroupTacticalAction, onInitialGroupDestroyed) m_initialGroupDestroyedEventHandler;
  PersistentUnitGroup::OnGroupDestroyed::SubscriberID m_initialGroupDestroyedID;

  // Initial units sent to action
  const PersistentUnitGroup* m_initialGroup;
  PersistentUnitGroup m_group;
};
