#include "stdafx.h"
#include "GroupMoveBehavior.h"
#include "UnitMove/NativeUnitMoveBehavior.h"
#include "UnitMove/ThreatAwareUnitMoveBehavior.h"


///////////////////////////////////////////////////
// Goto state
///////////////////////////////////////////////////

GroupMoveBehavior::Goto::Goto(GroupMoveBehavior& behavior)
  : BehaviorStateT(behavior)
  , m_transitions()
{
}

UnitMoveBehavior* GroupMoveBehavior::instantiateUnitMoveBehavior(BWAPI::Unit* unit) const
{
  UnitMoveBehavior* unitMoveBehavior;
  if (!m_moveOptions.m_threatAware)
  {
    unitMoveBehavior = new NativeUnitMoveBehavior(this, getCurrentPriority(), unit, m_destination, m_moveOptions);
  }
  else
  {
    if (unit->getType().isFlyer())
    {
      unitMoveBehavior = new ThreatAwareUnitMoveBehavior(this, getCurrentPriority(), unit, m_destination, m_moveOptions);
    }
    else
    {
      //TODO: Use a threat aware behavior
      unitMoveBehavior = new NativeUnitMoveBehavior(this, getCurrentPriority(), unit, m_destination, m_moveOptions);
    }
  }

  if(unitMoveBehavior != NULL)
    unitMoveBehavior->zombifyOnTerminate();

  return unitMoveBehavior;
}

void GroupMoveBehavior::Goto::onEnter()
{
  int nbUnits = m_context.getGroup()->size();
  BehaviorWrapper* wrappers = new BehaviorWrapper[nbUnits];

  // Create the unit behavior for each unit
  int i = 0;
  for(PersistentUnitGroup::const_iterator it = m_context.getGroup()->begin(); it != m_context.getGroup()->end(); ++it)
  {
    assert(i<nbUnits);
    wrappers[i] = m_context.addUnitInMotionList(*it);
    ++i;
  }

  // Start each behaviors. 
  // This operation is called outside the first loop because it can cause a callback which could possibly remove some units.
  for(i = 0; i < nbUnits; ++i)
  {
    BehaviorWrapper w = wrappers[i];
    if(!w.behavior->isTerminated())
      w.behavior->start();
  }

  delete[] wrappers;
}

void GroupMoveBehavior::onUnitAddedToGroup(void*, BWAPI::Unit* unit)
{
  if(isExecuting())
  {
    addUnitInMotionList(unit).behavior->start();
  }
}

GroupMoveBehavior::BehaviorWrapper GroupMoveBehavior::addUnitInMotionList(BWAPI::Unit* unit)
{
    assert(!unit->getType().isBuilding());
    // isConnected does not always work if there is an unwalkable tile in the tile position
    //assert(unit->getType().isFlyer() || BWTA::isConnected(unit->getTilePosition(), m_destinationTile));

    BehaviorWrapper wrapper;
    wrapper.behavior = instantiateUnitMoveBehavior(unit);
    wrapper.subscriberId = wrapper.behavior->getTerminateEvent().subscribe(&m_onSubBehaviorTerminated, unit);
    m_unitsInMotion.insert(std::pair<BWAPI::Unit*, BehaviorWrapper>(unit, wrapper));
  return wrapper;
}

void GroupMoveBehavior::onUnitRemovedFromGroup(void*, BWAPI::Unit* unit)
{
  if(isExecuting())
  {
    std::map<BWAPI::Unit*, BehaviorWrapper>::iterator it = m_unitsInMotion.find(unit);
    if(it != m_unitsInMotion.end())
    {
      BehaviorWrapper bw = it->second;
      bw.behavior->getTerminateEvent().unsubscribe(bw.subscriberId);
      bw.behavior->externalTerminate();
      m_unitsInMotion.erase(it);

      if(m_unitsInMotion.empty())
        terminate(Success);
    }
  }
}

void GroupMoveBehavior::onSubBehaviorTerminated(void* data)
{
  BWAPI::Unit* unit = reinterpret_cast<BWAPI::Unit*>(data);
  std::map<BWAPI::Unit*, BehaviorWrapper>::iterator it = m_unitsInMotion.find(unit);
  assert(it != m_unitsInMotion.end());
  BehaviorWrapper bw = it->second;
  bw.behavior->getTerminateEvent().unsubscribe(bw.subscriberId);
  m_unitsInMotion.erase(it);
  
  // Raise onUnitAtDestination event
  getUnitAtDestinationEvent().raise(unit);

  if(m_unitsInMotion.empty())
    terminate(Success);
}