#include "stdafx.h"
#include "L2Defend.h"
#include "../Layer2Managers.h"
#include "../../../../PerceptualState/World/CurrentWorld.h"
#include "../../../../PerceptualState/Locations/Dynamic/UnitCoordinatesLocation.h"
#include "../../../../PerceptualState/Locations/Static/AutoPopulated/Region.h"

// TODO: L2Defend should be applicable to a subset of the map, not only the whole map

// TODO: L2Defend doesn't even use Defend::m_location (the location to defend)

// TODO: should use move behavior instead of move() command

void L2Defend::onStartImplementation()
{
  L2GroupTacticalAction::onStartImplementation();
  subscribeAllEnemyUnitAdded();
  m_currentLocation = regionAttackedByEnemy();
  //m_onUnitAddedSubscriberId = Spar->getPerceptualState()->getMap()->getOwnUnitAddedEvent().subscribe(&m_onUnitAddedEventHandler, NULL);
  if(m_currentLocation != NULL)
  {
    m_waiting = false;
    m_currentPosition = (*m_currentLocation->getEnemyUnits().first)->getPosition();
    m_currentTarget = (*m_currentLocation->getEnemyUnits().first);
    m_groupAttackBehavior = new GroupAttackBehavior(this, 100, getGroup(), new UnitCoordinatesLocation((*m_currentLocation->getEnemyUnits().first)));
    m_groupAttackBehavior->zombifyOnTerminate();
    m_onEnemyRemovedSubscriberId = m_currentLocation->getEnemyUnitRemovedEvent().subscribe(&m_onEnemyRemovedEventHandler , NULL);
    m_groupAttackBehavior->start();
  }
  else
  {
    m_waiting = true;
    m_currentPosition = getDefendPosition();
    getGroup()->move(m_currentPosition);
  }  
}


void L2Defend::onCancelImplementation()
{
  L2GroupTacticalAction::onCancelImplementation();
  terminate(Cancelled);
}

void L2Defend::onEndImplementation()
{
  unsubscribeAllEnemyUnitAdded();
  //Spar->getPerceptualState()->getMap()->getOwnUnitAddedEvent().unsubscribe(m_onUnitAddedSubscriberId);
  if (m_currentLocation != NULL)
    m_currentLocation->getEnemyUnitRemovedEvent().unsubscribe(m_onEnemyRemovedSubscriberId);
  L2GroupTacticalAction::onEndImplementation();
}

void L2Defend::onUnitAddedToGroup(void*, BWAPI::Unit* unit)
{
  if(m_groupAttackBehavior == NULL && !m_waiting)
  {
    if (m_currentLocation != NULL)
      m_currentLocation->getEnemyUnitRemovedEvent().unsubscribe(m_onEnemyRemovedSubscriberId);
    m_currentLocation = regionAttackedByEnemy();
    if(m_currentLocation != NULL)
    {
      m_onEnemyRemovedSubscriberId = m_currentLocation->getEnemyUnitRemovedEvent().subscribe(&m_onEnemyRemovedEventHandler , NULL);
      AutoPopulatedLocation::unit_iterator it = m_currentLocation->getEnemyUnits().first;
      m_currentTarget = NULL;
      while(it!=m_currentLocation->getEnemyUnits().second && m_currentTarget == NULL)
      {
        if((*it)->isAccessible())
        {
          m_currentTarget = (*it);
        }
        it++;
      }
      if(m_groupAttackBehavior!=NULL)
      {
        m_groupAttackBehavior->externalTerminate();
      }
      if(m_currentTarget!=NULL)
      {
        m_currentPosition = m_currentTarget->getPosition();
        m_groupAttackBehavior = new GroupAttackBehavior(this, 100, getGroup(), new UnitCoordinatesLocation(m_currentTarget));
        m_groupAttackBehavior->zombifyOnTerminate();
        m_groupAttackBehavior->start();
      }
      else
      {
        m_currentPosition = (*m_currentLocation->getEnemyUnits().first)->getPosition();
        m_currentTarget = (*m_currentLocation->getEnemyUnits().first);
        m_groupAttackBehavior = new GroupAttackBehavior(this, 100, getGroup(), new UnitCoordinatesLocation(m_currentTarget));
        m_groupAttackBehavior->zombifyOnTerminate();
        m_groupAttackBehavior->start();
      }
    }
  }
  
  // TODO: these commands should be in layer 1?

  if (!CurrentWorld::PsionicStorm->isAcquired() && unit->getType() == BWAPI::UnitTypes::Protoss_High_Templar)
  {
    BWAPI::Unit* templar = NULL;
    for (PersistentUnitGroup::const_iterator it = getGroup()->begin(); it != getGroup()->end(); ++it)
    {
      if ((*it)->getType() == BWAPI::UnitTypes::Protoss_High_Templar && (*it)->getOrder() != BWAPI::Orders::ArchonWarp)
      {
        templar = *it;
        break;
      }
    }
    if (templar != NULL)
    {
      unit->useTech(BWAPI::TechTypes::Archon_Warp, templar);
      templar->useTech(BWAPI::TechTypes::Archon_Warp, unit);
    }
  }

  if(m_waiting && m_currentPosition != BWAPI::Positions::Invalid && unit->getOrder() != BWAPI::Orders::ArchonWarp)
  {
    unit->move(m_currentPosition);
  }
  else if(!m_waiting && m_currentPosition != BWAPI::Positions::Invalid && unit->getOrder() != BWAPI::Orders::ArchonWarp)
  {
    unit->attack(m_currentPosition);
  }
}

void L2Defend::onUnitRemovedFromGroup(void*, BWAPI::Unit*)
{
  // Nothing to do since the unit is also removed from m_groupAttackBehavior's group (since the same group is used)
}

void L2Defend::onEnemyAdded(void*, UnitRecord* unit)
{
  Region* region = unit->getRegion();
  assert(region != NULL);

  if(m_waiting && region->getNbOwnBuildings() > 0){
    m_waiting = false;
    m_currentTarget = unit;
    m_currentPosition = unit->getPosition();
    m_currentLocation = unit->getRegion();
    m_groupAttackBehavior = new GroupAttackBehavior(this, 100, getGroup(), new UnitCoordinatesLocation(m_currentTarget));
    m_groupAttackBehavior->zombifyOnTerminate();
    m_onEnemyRemovedSubscriberId = m_currentLocation->getEnemyUnitRemovedEvent().subscribe(&m_onEnemyRemovedEventHandler , NULL);
    m_groupAttackBehavior->start();
  }
}

void L2Defend::onEnemyRemoved(void*, UnitRecord* unit, BWAPI::UnitType, BWAPI::Player*)
{
  if(m_currentLocation->getNbEnemyUnits() == 0){
    if(m_groupAttackBehavior!=NULL)
    {
      m_groupAttackBehavior->externalTerminate();
    }
    m_currentLocation->getEnemyUnitRemovedEvent().unsubscribe(m_onEnemyRemovedSubscriberId);
    //m_currentLocation->getEnemyUnitAddedEvent().unsubscribe(m_onEnemyAddedSubscriberId);
    m_currentLocation = regionAttackedByEnemy();

    if(m_currentLocation != NULL)
    {
      assert(m_currentLocation->getEnemyUnits().first != m_currentLocation->getEnemyUnits().second);
      m_onEnemyRemovedSubscriberId = m_currentLocation->getEnemyUnitRemovedEvent().subscribe(&m_onEnemyRemovedEventHandler , NULL);
      m_currentPosition = (*(m_currentLocation->getEnemyUnits().first))->getPosition();
      m_currentTarget = *m_currentLocation->getEnemyUnits().first;
      m_groupAttackBehavior = new GroupAttackBehavior(this, 100, getGroup(), new UnitCoordinatesLocation(m_currentTarget));
      m_groupAttackBehavior->zombifyOnTerminate();
      m_groupAttackBehavior->start();
    }
    else
    {
      m_waiting = true;
      m_currentPosition = getDefendPosition();
      getGroup()->move(m_currentPosition);
    }
  }
  else if(unit==m_currentTarget)
  {
    assert(m_currentLocation->getEnemyUnits().first != m_currentLocation->getEnemyUnits().second);
    AutoPopulatedLocation::unit_iterator it = m_currentLocation->getEnemyUnits().first;
    m_currentTarget = NULL;
    while(it!=m_currentLocation->getEnemyUnits().second && m_currentTarget == NULL)
    {
      if((*it)->isAccessible())
      {
        m_currentTarget = (*it);
      }
      it++;
    }
    if(m_groupAttackBehavior!=NULL)
    {
      m_groupAttackBehavior->externalTerminate();
    }
    if(m_currentTarget!=NULL)
    {
      m_currentPosition = m_currentTarget->getPosition();
      m_groupAttackBehavior = new GroupAttackBehavior(this, 100, getGroup(), new UnitCoordinatesLocation(m_currentTarget));
      m_groupAttackBehavior->zombifyOnTerminate();
      m_groupAttackBehavior->start();
    }
    else
    {
      m_currentPosition = (*m_currentLocation->getEnemyUnits().first)->getPosition();
      m_currentTarget = (*m_currentLocation->getEnemyUnits().first);
      m_groupAttackBehavior = new GroupAttackBehavior(this, 100, getGroup(), new CoordinatesLocation(m_currentTarget->getPosition()));
      m_groupAttackBehavior->zombifyOnTerminate();
      m_groupAttackBehavior->start();
    }
  }
}

BWAPI::Position L2Defend::getDefendPosition() const
{
  Map* map = Spar->getPerceptualState()->getMap();
  if (map->getNbOwnUnits(BWAPI::UnitTypes::Protoss_Nexus) > 0)
  {
    return (*map->getOwnUnits(BWAPI::UnitTypes::Protoss_Nexus).first)->getPosition();
  }
  else if (map->getNbOwnBuildings() > 0)
  {
    return (*map->getOwnBuildings().first)->getPosition();
  }
  else
  {
    return m_location->getCurrentCenter();
    //return BWAPI::Position(getGroup()->getCenter());
  }
}

Region* L2Defend::regionAttackedByEnemy()
{
  // TODO: some kind of estimate of the opponent's strength would be cool...

  Region* region = NULL;
  size_t maxUnits = boost::numeric::bounds<size_t>::lowest();
  std::pair<Map::region_iterator, Map::region_iterator> pi = Spar->getPerceptualState()->getMap()->getRegions();
  for(Map::region_iterator i = pi.first ; i!=pi.second ; ++i)
  {
    if((*i)->getNbOwnBuildings() > 0)
    {
      size_t nbEnemyUnits = (*i)->getNbEnemyUnits();
      if (nbEnemyUnits > maxUnits)
      {
        maxUnits = nbEnemyUnits;
        region = *i;
      }
    } 
  }
  return region;
}

//void L2Defend::onUnitAdded(void*, UnitRecord* unit)
//{
//  if(unit->getType().isBuilding())
//  {
//    Region* location = unit->getLocation();
//    if (region == NULL)
//    {
//      region = dynamic_cast<Region*>(location->getParent());
//      assert(region != NULL);
//    }
//    subscribeEnemyUnitAdded(region);
//  }
//}

//void L2Defend::subscribeEnemyUnitAdded(AutoPopulatedLocation* cl)
//{
//  typedef Event<boost::tuples::tuple<UnitRecord*>>::SubscriberID ID;
//  if(m_mapSubscription.find(cl) == m_mapSubscription.end())
//  {
//    m_mapSubscription.insert(std::make_pair(cl,cl->getEnemyUnitAddedEvent().subscribe(&m_onEnemyAddedEventHandler, NULL)));
//  }
//}


void L2Defend::subscribeAllEnemyUnitAdded()
{
  std::pair<Map::region_iterator, Map::region_iterator> pi = Spar->getPerceptualState()->getMap()->getRegions();
  for(Map::region_iterator i = pi.first ; i!=pi.second ; ++i)
  {
    m_mapSubscription.insert(std::make_pair(*i,(*i)->getEnemyUnitAddedEvent().subscribe(&m_onEnemyAddedEventHandler, NULL)));
  }
}


void L2Defend::unsubscribeAllEnemyUnitAdded()
{
  typedef std::map<AutoPopulatedLocation*,Event<boost::tuples::tuple<UnitRecord*>>::SubscriberID>::iterator MapIterator;
  for(MapIterator it = m_mapSubscription.begin() ; it != m_mapSubscription.end() ; ++it)
  {
    (it->first)->getEnemyUnitAddedEvent().unsubscribe(it->second);
  }
}
