#include "stdafx.h"
#include "ManageArmyProcess.h"
#include "../Layer3Script.h"
#include "../L3BaseLocation.h"
#include "../../../../PerceptualState/Locations/Static/AutoPopulated/Region.h"
#include "../../../../PerceptualState/PerceptualState.h"

static const int NB_PROBES_INITIAL_SCOUT = 9;
static const int NB_FRAMES_BETWEEN_SCOUTS = 3000;

template <class LocationIterator>
typename LocationIterator::value_type getRandomElementFromSequence(LocationIterator first, size_t n)
{
  const int randomIndex = rand() % n;
  for (int i = 0; i < randomIndex; ++i, ++first);
  return *first;
}

bool containsUnitType(const PersistentUnitGroup* group, BWAPI::UnitType type)
{
  for (PersistentUnitGroup::const_iterator it = group->begin(); it != group->end(); ++it)
  {
    if ((*it)->getType() == type)
      return true;
  }
  return false;
}

struct AllowTransferUnit
{
  bool operator()(BWAPI::Unit* unit) const
  {
    // No observers or high templars without psionic storm

    return unit->getType() != BWAPI::UnitTypes::Protoss_Observer &&
      (unit->getType() != BWAPI::UnitTypes::Protoss_High_Templar || (CurrentWorld::PsionicStorm->isAcquired() && unit->getEnergy() >= 70));
  }
};

ManageArmyProcess::ManageArmyProcess(const Process* parent)
  : Layer3Process(parent, Scheduler::SCHEDULING_MEDIUM_PRIORITY, 10, 4)
  , m_attackMoveEndedEventHandler(*this)
  , m_scoutArrivedEventHandler(*this)
  //, m_highTemplars("High Templars")
  //, m_attackingObservers("Attacking Observers")
  //, m_defendingObservers("Defending Observers")
  //, m_scoutingObservers("Scouting Observers")
  , m_attackAction(*this)
  , m_defendAction(*this)
  , m_randomScouts(*this)
  , m_enemyBaseScouts(*this)
  , m_corsairsHarass(*this)
  , m_mainRegion(NULL)
  , m_goalRegionAttack(NULL)
  , m_currentRegionAttack(NULL)
  , m_lastTriggeredScoutFrame(boost::numeric::bounds<int>::lowest())
{
}

void ManageArmyProcess::onStart()
{
  Layer3Process::onStart();

  // Set m_regionWrappers and m_mainRegion
  std::pair<Map::region_iterator, Map::region_iterator> regions = Spar->getPerceptualState()->getMap()->getRegions();
  for (Map::region_iterator it = regions.first; it != regions.second; ++it)
  {
    Region* region = *it;
    if(region->getAdjacentRegions().size() > 0) // remove Island
    {
      RegionWrapper* rw = new RegionWrapper(region);
      if(Layer3Script::getInstance().getMainBase()->getParent() == rw->region)
        m_mainRegion = rw;
      m_regionWrappers.insert(std::make_pair(region, rw));
    }
  }
  assert(m_mainRegion != NULL);

  // Defend action at first chokepoint
  //BaseLocation* mainBase = Layer3Script::getInstance().getMainBase();
  //assert(mainBase != NULL);
  //Region* region = mainBase->getParent();
  //Chokepoint* chokepoint = region->getChokepoints().empty() ? NULL : (*region->getChokepoints().begin()).first;
  //if (chokepoint != NULL)
  //{
    m_defendAction = Layer3Script::getInstance().getLayer2().instantiateDefend(
      this,
      PRIORITY_DEFEND, 
      &PersistentUnitGroup::EmptyGroup, 
      Spar->getPerceptualState()->getMap());
  //}
  //else
  //{
  //  m_defendAction = Layer3Script::getInstance().getLayer2().instantiateDefend(
  //    this,
  //    PRIORITY_DEFEND, 
  //    &PersistentUnitGroup::EmptyGroup, 
  //    Spar->getPerceptualState()->getMap());
  //}
}

void ManageArmyProcess::run()
{
/*
// Unit Power Tests
  bool ownUnit = true;
  bool canBeDetected = true;
  bool groundOnlyRatio = 0.5;

  UnitPower a0 = getUnitPower(BWAPI::UnitTypes::Protoss_Arbiter, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower a1 = getUnitPower(BWAPI::UnitTypes::Protoss_Archon, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower a2 = getUnitPower(BWAPI::UnitTypes::Protoss_Carrier, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower a3 = getUnitPower(BWAPI::UnitTypes::Protoss_Corsair, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower a4 = getUnitPower(BWAPI::UnitTypes::Protoss_Dark_Templar, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower a5 = getUnitPower(BWAPI::UnitTypes::Protoss_Dark_Archon, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower a6 = getUnitPower(BWAPI::UnitTypes::Protoss_Dragoon, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower a7 = getUnitPower(BWAPI::UnitTypes::Protoss_High_Templar, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower a8 = getUnitPower(BWAPI::UnitTypes::Protoss_Interceptor, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower a9 = getUnitPower(BWAPI::UnitTypes::Protoss_Observer, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower a10 = getUnitPower(BWAPI::UnitTypes::Protoss_Photon_Cannon, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower a11= getUnitPower(BWAPI::UnitTypes::Protoss_Probe, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower a12= getUnitPower(BWAPI::UnitTypes::Protoss_Reaver, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower a13= getUnitPower(BWAPI::UnitTypes::Protoss_Scarab, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower a14= getUnitPower(BWAPI::UnitTypes::Protoss_Scout, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower a15= getUnitPower(BWAPI::UnitTypes::Protoss_Shuttle, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower a16= getUnitPower(BWAPI::UnitTypes::Protoss_Zealot, ownUnit, canBeDetected, groundOnlyRatio);

  UnitPower b1 = getUnitPower(BWAPI::UnitTypes::Terran_Battlecruiser, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower b2 = getUnitPower(BWAPI::UnitTypes::Terran_Bunker, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower b3 = getUnitPower(BWAPI::UnitTypes::Terran_Dropship, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower b4 = getUnitPower(BWAPI::UnitTypes::Terran_Firebat, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower b5 = getUnitPower(BWAPI::UnitTypes::Terran_Ghost, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower b6 = getUnitPower(BWAPI::UnitTypes::Terran_Goliath, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower b7 = getUnitPower(BWAPI::UnitTypes::Terran_Marine, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower b8 = getUnitPower(BWAPI::UnitTypes::Terran_Medic, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower b9 = getUnitPower(BWAPI::UnitTypes::Terran_Missile_Turret, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower b10 = getUnitPower(BWAPI::UnitTypes::Terran_Science_Vessel, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower b11= getUnitPower(BWAPI::UnitTypes::Terran_SCV, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower b12= getUnitPower(BWAPI::UnitTypes::Terran_Siege_Tank_Siege_Mode, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower b13= getUnitPower(BWAPI::UnitTypes::Terran_Siege_Tank_Tank_Mode, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower b14= getUnitPower(BWAPI::UnitTypes::Terran_Valkyrie, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower b15= getUnitPower(BWAPI::UnitTypes::Terran_Vulture, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower b16= getUnitPower(BWAPI::UnitTypes::Terran_Vulture_Spider_Mine, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower b17= getUnitPower(BWAPI::UnitTypes::Terran_Wraith, ownUnit, canBeDetected, groundOnlyRatio);
  
  UnitPower c1 = getUnitPower(BWAPI::UnitTypes::Zerg_Broodling, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower c2 = getUnitPower(BWAPI::UnitTypes::Zerg_Cocoon, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower c3 = getUnitPower(BWAPI::UnitTypes::Zerg_Defiler, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower c4 = getUnitPower(BWAPI::UnitTypes::Zerg_Devourer, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower c5 = getUnitPower(BWAPI::UnitTypes::Zerg_Drone, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower c6 = getUnitPower(BWAPI::UnitTypes::Zerg_Egg, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower c7 = getUnitPower(BWAPI::UnitTypes::Zerg_Guardian, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower c8 = getUnitPower(BWAPI::UnitTypes::Zerg_Hydralisk, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower c9 = getUnitPower(BWAPI::UnitTypes::Zerg_Larva, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower c10 = getUnitPower(BWAPI::UnitTypes::Zerg_Lurker, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower c11= getUnitPower(BWAPI::UnitTypes::Zerg_Mutalisk, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower c12= getUnitPower(BWAPI::UnitTypes::Zerg_Overlord, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower c13= getUnitPower(BWAPI::UnitTypes::Zerg_Queen, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower c14= getUnitPower(BWAPI::UnitTypes::Zerg_Scourge, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower c15= getUnitPower(BWAPI::UnitTypes::Zerg_Sunken_Colony, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower c16= getUnitPower(BWAPI::UnitTypes::Zerg_Ultralisk, ownUnit, canBeDetected, groundOnlyRatio);
  UnitPower c17= getUnitPower(BWAPI::UnitTypes::Zerg_Zergling, ownUnit, canBeDetected, groundOnlyRatio);
*/

  assert(m_mainRegion != NULL);
  assert(m_defendAction != NULL);

  BaseLocation* enemyBaseLocation = getEnemyMainBaseLocation();
  if (m_enemyBaseScouts.empty() && 
      BWAPI::Broodwar->getFrameCount() - NB_FRAMES_BETWEEN_SCOUTS > m_lastTriggeredScoutFrame &&
      Spar->getPerceptualState()->getMap()->getNbOwnUnits(BWAPI::UnitTypes::Protoss_Probe) >= NB_PROBES_INITIAL_SCOUT &&
      (enemyBaseLocation == NULL || BWAPI::Broodwar->getFrameCount() - 4000 > enemyBaseLocation->getParent()->getNewestVisibleTile().second))
  {
    m_lastTriggeredScoutFrame = BWAPI::Broodwar->getFrameCount();
    executeScout(NULL);
  }

  if (m_defendAction->isWaitingToExecute())
    m_defendAction->start();

  bool canBeDetected = false;
  const double groundRatio = 0.5;
  double minSuperiorityRatio = 1.5;
  double retreatSuperiorityRatio = 1.2;

  // Always attack if we have more than 180 units
  if(BWAPI::Broodwar->self()->supplyUsed() >= 180*2)
  {
    minSuperiorityRatio = -1;
    retreatSuperiorityRatio = -1;
  }
  
  // TODO: Transfer to attack only if bases are safe

  const PersistentUnitGroup* army = m_defendAction->getGroup();
  UnitPower armyPower;
  UnitPower unitPower;
  bool alreadyFighting = m_attackAction != NULL;
  bool canSendReinforcement = army->size() >= 12 || (army->size() >= 6 && alreadyFighting);
  if(canSendReinforcement)
  {
    for(PersistentUnitGroup::const_iterator it = army->begin(); it != army->end(); ++it)
    {
      unitPower = getUnitPower((*it)->getType(), true, true, 0.5);
      armyPower.AttackPower += unitPower.AttackPower;
      armyPower.HitPointPower += unitPower.HitPointPower;
    }
  }

  RegionWrapper* target = NULL;
  if(alreadyFighting)
  {
    army = m_attackAction->getGroup();
    for(PersistentUnitGroup::const_iterator it = army->begin(); it != army->end(); ++it)
    {
      unitPower = getUnitPower((*it)->getType(), true, true, 0.5);
      armyPower.AttackPower += unitPower.AttackPower;
      armyPower.HitPointPower += unitPower.HitPointPower;
    }
    
    UnitPower regionPower = getRegionEnnemyPower(m_currentRegionAttack->region, canBeDetected, groundRatio);

    if(getSuperiorityRatio(armyPower, regionPower) < retreatSuperiorityRatio)
    {
      // TODO: ... retreat;
    } else {
      if(canSendReinforcement)
      {
        GroupTacticalAction::transfer(m_defendAction, m_attackAction, m_defendAction->getGroup()->size(), AllowTransferUnit());
      }
    }
  } else {
    if(armyPower.AttackPower > 0)
      target = getAttackLocation(m_mainRegion, armyPower, canBeDetected, groundRatio, minSuperiorityRatio);

    // did we found a valid target
    if (target != NULL)
    {
      m_goalRegionAttack = target;
      m_currentRegionAttack = m_goalRegionAttack;
      if(m_currentRegionAttack->fromRegion != NULL)
        while(m_currentRegionAttack->fromRegion->fromRegion != NULL)
          m_currentRegionAttack = m_currentRegionAttack->fromRegion;

      m_attackAction = Layer3Script::getInstance().getLayer2().instantiateAttackMove(
        this,
        PRIORITY_ATTACK_MOVE,
        &PersistentUnitGroup::EmptyGroup,
        m_currentRegionAttack->region);
      m_attackAction->getTerminateEvent().subscribe(&m_attackMoveEndedEventHandler, NULL);
      m_attackAction->start();
      GroupTacticalAction::transfer(m_defendAction, m_attackAction, m_defendAction->getGroup()->size(), AllowTransferUnit());
    }
  }
}

BaseLocation* ManageArmyProcess::getEnemyMainBaseLocation() const
{
  BaseLocation* main = NULL;

  const Map* map = Spar->getPerceptualState()->getMap();
  const std::set<BaseLocation*>& enemyPossibleStartLocations = map->getEnemyPossibleStartBaseLocations();
  for (std::set<BaseLocation*>::const_iterator it = enemyPossibleStartLocations.begin(); it != enemyPossibleStartLocations.end(); ++it)
  {
    if ((*it)->hasEnemyResourceDepot() && (main == NULL || (*it)->getNbEnemyUnits() > main->getNbEnemyUnits()))
    {
      main = (*it);
    }
  }
  return main;
}

StaticLocation* ManageArmyProcess::getNextCorsairHarassLocation(BWAPI::Unit* corsair) const
{
  unused(corsair); // Harass location could depend on the corsair's position

  StaticLocation* harassLocation = NULL;
  if (m_corsairsHarass.empty())
  {
    // First corsair harasses enemy's main region if identified
    BaseLocation* enemyMain = getEnemyMainBaseLocation();
    if (enemyMain != NULL)
      harassLocation = enemyMain->getParent();
  }
  if (harassLocation == NULL)
  {
    harassLocation = Spar->getPerceptualState()->getMap();
  }

  return harassLocation;
}

void ManageArmyProcess::addUnit(BWAPI::Unit* unit)
{
  // Subscribe Defend Ended ?
  if (!Spar->isTerminated())
  {
    if(unit->getType() == BWAPI::UnitTypes::Protoss_Corsair)
    {
      // The new corsair could be added to an existing group as well.
      // We should hook the harass behavior's "in-danger" event to retreat the corsair when appropriate.
      Location* location = getNextCorsairHarassLocation(unit);

      PersistentUnitGroup temp("Temporary-corsair", &unit, &unit + 1);
      Harass* corsairHarass = Layer3Script::getInstance().getLayer2().instantiateHarass(
        this,
        PRIORITY_ATTACK_MOVE, 
        &temp, 
        location);
      m_corsairsHarass.add(corsairHarass);
      corsairHarass->start();
    }
    //else if(unit->getType() == BWAPI::UnitTypes::Protoss_High_Templar)
    //{
    //  if(m_highTemplars.size() > 0)
    //  {
    //    BWAPI::Unit* target = (*m_highTemplars.begin());

    //    assert(unit->canIssueCommand(BWAPI::UnitCommand::useTech(unit, BWAPI::TechTypes::Archon_Warp, target)));
    //    unit->useTech(BWAPI::TechTypes::Archon_Warp, target);
    //    m_highTemplars.remove(m_highTemplars.begin());
    //  } 
    //  else
    //  {
    //    m_highTemplars.insert(unit);
    //  }
    //} 
    else if(unit->getType() == BWAPI::UnitTypes::Protoss_Observer)
    {
      if(m_attackAction != NULL && !containsUnitType(m_attackAction->getGroup(), BWAPI::UnitTypes::Protoss_Observer))
      {
        //m_attackingObservers.insert(unit);
        m_attackAction->addUnit(unit);
      }
      else if (m_defendAction != NULL && !containsUnitType(m_defendAction->getGroup(), BWAPI::UnitTypes::Protoss_Observer))
      {
        //m_defendingObservers.insert(unit);
        m_defendAction->addUnit(unit);
      }
      else
      {
        //m_scoutingObservers.insert(unit);
        executeScout(unit);
      }
    } 
    else
    {
      assert(m_defendAction != NULL);

      // TODO: add to attack action sometimes?
      m_defendAction->addUnit(unit);
    }
  }
}

void ManageArmyProcess::onAttackMoveEnded(void*)
{
  bool canBeDetected = false;
  const double groundRatio = 0.5;
  const double minSuperiorityRatio = 1.2;

  UnitPower armyPower;
  UnitPower unitPower;
  const PersistentUnitGroup* attackGroup = m_attackAction->getGroup();
  for(PersistentUnitGroup::const_iterator it = attackGroup->begin(); it != attackGroup->end(); ++it)
  {
    unitPower = getUnitPower((*it)->getType(), true, true, 0.5);
    armyPower.AttackPower += unitPower.AttackPower;
    armyPower.HitPointPower += unitPower.HitPointPower;
  }
  if(armyPower.AttackPower > 0)
  {
    RegionWrapper* target = getAttackLocation(m_currentRegionAttack, armyPower, canBeDetected, groundRatio, minSuperiorityRatio);
    if (target != NULL)
    {
      m_goalRegionAttack = target;
      m_currentRegionAttack = m_goalRegionAttack;
      if(m_currentRegionAttack->fromRegion != NULL)
        while(m_currentRegionAttack->fromRegion->fromRegion != NULL)
          m_currentRegionAttack = m_currentRegionAttack->fromRegion;

      m_attackAction = Layer3Script::getInstance().getLayer2().instantiateAttackMove(
        this,
        PRIORITY_ATTACK_MOVE, 
        attackGroup, 
        m_currentRegionAttack->region);
      m_attackAction->getTerminateEvent().subscribe(&m_attackMoveEndedEventHandler, NULL);
      m_attackAction->start();
    }
  }
}

double ManageArmyProcess::getSuperiorityRatio(const UnitPower& ownPower, const UnitPower& ennemyPower) const
{
  if(ownPower.AttackPower <= 0)
  {
    if(ennemyPower.AttackPower <= 0)
      return 1;
    else
      return 0;
  } else if(ennemyPower.AttackPower <= 0)
    return boost::numeric::bounds<double>::highest();

  double ownFrameSurvival = (ownPower.HitPointPower / ennemyPower.AttackPower);
  double enemyFrameSurvival = (ennemyPower.HitPointPower / ownPower.AttackPower);

  return ownFrameSurvival / enemyFrameSurvival;
}

ManageArmyProcess::RegionWrapper* ManageArmyProcess::getAttackLocation(RegionWrapper* fromRegion, UnitPower armyPower, bool canBeDetected, double groundRatio, double minSuperiorityRatio)
{
  // Choose target
  // 1 - Region location with the biggest advantage, accessible without crossing enemy region
  // 2 - Random region if no enemy regions were found
  // TODO: 3 - possible enemy start locations not yet explored (if nothing scouted yet)
  // TODO: 4 - least recently explored base location (instead of random)

  assert(armyPower.AttackPower > 0);
  RegionWrapper* target = NULL;
  bool enemyBaseFound = false;

  {
    double maxAdvantage = boost::numeric::bounds<double>::lowest();
    std::list<RegionWrapper*> openNodes = std::list<RegionWrapper*>();
    openNodes.push_back(fromRegion);
    int currentFrame = BWAPI::Broodwar->getFrameCount();
    RegionWrapper* currentRegionWrapper;
    Region* currentRegion;

    fromRegion->lastVisitedFrame = currentFrame;
    fromRegion->fromRegion = NULL;
    while(openNodes.size() > 0)
    {
      currentRegionWrapper = *openNodes.begin();
      openNodes.erase(openNodes.begin());
      assert(currentRegionWrapper->lastVisitedFrame == currentFrame);
      {
        currentRegion = currentRegionWrapper->region;
        currentRegionWrapper->lastVisitedFrame = currentFrame;
        size_t totalNb = currentRegion->getNbEnemyUnits();

        if (totalNb > 0)
        {
          enemyBaseFound = true;
          
          UnitPower regionPower = getRegionEnnemyPower(currentRegion, canBeDetected, groundRatio);

          double superiorityRatio = getSuperiorityRatio(armyPower, regionPower);

          if(superiorityRatio > minSuperiorityRatio && superiorityRatio > maxAdvantage)
          {
            maxAdvantage = superiorityRatio;
            target = currentRegionWrapper;
          }
        } else {
          const std::set<std::pair<Region*, Chokepoint*>> adjacents = currentRegion->getAdjacentRegions();
          for(std::set<std::pair<Region*, Chokepoint*>>::const_iterator it = adjacents.begin(); it != adjacents.end(); ++it)
          {
            stdext::hash_map<Region*, RegionWrapper*>::const_iterator wrapperIt = m_regionWrappers.find(it->first);
            assert(wrapperIt != m_regionWrappers.end());
            if(wrapperIt != m_regionWrappers.end())
            {
              if(wrapperIt->second->lastVisitedFrame < currentFrame)
              {
                wrapperIt->second->lastVisitedFrame = currentFrame;
                wrapperIt->second->fromRegion = currentRegionWrapper;
                openNodes.push_back(wrapperIt->second);
              }
            }
          }
        }
      }
    }
  }

  if (enemyBaseFound)
    return target;

  {
    const int randomIndex = rand() % m_regionWrappers.size();
    stdext::hash_map<Region*, RegionWrapper*>::const_iterator it = m_regionWrappers.begin();
    for (int i = 0; i < randomIndex; ++i, ++it);

    target = it->second;
  }

  assert(target != NULL);
  return target;
}

ManageArmyProcess::UnitPower ManageArmyProcess::getRegionEnnemyPower(Region* region, bool canBeDetected, double groundOnlyRatio) const
{
  UnitPower regionPower;
  UnitPower unitPower;
  std::pair<Region::unit_iterator, Region::unit_iterator> enemyUnits = region->getEnemyUnits();
  for(Region::unit_iterator it = enemyUnits.first; it != enemyUnits.second; ++it)
  {
    unitPower = getUnitPower((*it)->getType(), false, canBeDetected, groundOnlyRatio);
    regionPower.AttackPower += unitPower.AttackPower;
    regionPower.HitPointPower += unitPower.HitPointPower;
  }
  return regionPower;
}

ManageArmyProcess::UnitPower ManageArmyProcess::getUnitPower(BWAPI::UnitType unitType, bool ownUnit, bool canBeDetected, double groundOnlyRatio) const
{
  //BWAPI::UnitType unitType = unit->getType();
  double attackPower = 0; // approximation of damage per frame
  double hitPointPower = 0;

  double damageFactor = 1;
  double hitPointFactor = 1;
  double rangeFactor = 0.6;
  double maxRange = 384;
  double maxSplashRadius = 100;
  double splashFactor = 0.5;
  double cloakFactor = 1.25;
  if(canBeDetected)
    cloakFactor = 5;
  double flyerFactor = 1 + groundOnlyRatio * groundOnlyRatio;


  // Hardcoded power values
  if(unitType.isBuilding() 
    && unitType != BWAPI::UnitTypes::Protoss_Photon_Cannon
    && unitType != BWAPI::UnitTypes::Zerg_Sunken_Colony
    && unitType != BWAPI::UnitTypes::Terran_Bunker)
  {
    // no power
  } else if(unitType == BWAPI::UnitTypes::Protoss_Probe)
  {
    // no power
  } else if (unitType == BWAPI::UnitTypes::Protoss_High_Templar)
  {
    if(ownUnit)
    {
      // Useless without storm
      if(CurrentWorld::PsionicStorm->isAcquired())
      {
        attackPower += 3;   // Psi Strom
        hitPointPower += hitPointFactor * (unitType.maxHitPoints() + unitType.maxShields());
      }
    } else {
      attackPower += 3;   // Psi Strom
      hitPointPower += hitPointFactor * (unitType.maxHitPoints() + unitType.maxShields());
    }
  } else if (unitType == BWAPI::UnitTypes::Protoss_Scarab)
  {
    // no power
  } else if (unitType == BWAPI::UnitTypes::Protoss_Interceptor)
  {
    // no power
  } else if (unitType == BWAPI::UnitTypes::Terran_SCV)
  {
    // no power
  } else if (unitType == BWAPI::UnitTypes::Terran_Vulture_Spider_Mine)
  {
    // TODO
  } else if (unitType == BWAPI::UnitTypes::Zerg_Broodling)
  {
    // no power
  } else if (unitType == BWAPI::UnitTypes::Zerg_Drone)
  {
    // no power
  } else if (unitType == BWAPI::UnitTypes::Zerg_Queen)
  {
    // TODO
  } else 
  {
    // Special case
    if (unitType == BWAPI::UnitTypes::Protoss_Dark_Archon)
    {
      unitType = BWAPI::UnitTypes::Protoss_Archon;
    } else if (unitType == BWAPI::UnitTypes::Terran_Medic)
    {
      hitPointPower += 150; // heal
    } else if (unitType == BWAPI::UnitTypes::Terran_Siege_Tank_Tank_Mode)
    {
      unitType = BWAPI::UnitTypes::Terran_Siege_Tank_Siege_Mode;
    } else if(unitType == BWAPI::UnitTypes::Terran_Bunker)
    {
      // 4 marines
      damageFactor *= 4;
      // 4 marines + bunker
      hitPointFactor *= 4
        * (unitType.maxHitPoints() + BWAPI::UnitTypes::Terran_Marine.maxHitPoints() + BWAPI::UnitTypes::Terran_Marine.maxShields())
        / (1.0 * (BWAPI::UnitTypes::Terran_Marine.maxHitPoints() + BWAPI::UnitTypes::Terran_Marine.maxShields()));
      unitType = BWAPI::UnitTypes::Terran_Marine;
    } else if (unitType == BWAPI::UnitTypes::Protoss_Carrier)
    {
      // TODO: increase range
      // 8 interceptor. Interceptor cooldown = 1, correct it to 20
      damageFactor *= 8.0 / 20.0;
      hitPointFactor *= 
        (unitType.maxHitPoints() /*+ BWAPI::UnitTypes::Protoss_Interceptor.maxHitPoints() + BWAPI::UnitTypes::Protoss_Interceptor.maxShields()*/)
        / (1.0 * (BWAPI::UnitTypes::Protoss_Interceptor.maxHitPoints() + BWAPI::UnitTypes::Protoss_Interceptor.maxShields()));
      unitType = BWAPI::UnitTypes::Protoss_Interceptor;
    } else if (unitType == BWAPI::UnitTypes::Terran_Science_Vessel)
    {
      attackPower += 2; // Irradiate and EMP Shockwave
      hitPointPower += 250; // Defense matrix
    } else if (unitType == BWAPI::UnitTypes::Zerg_Defiler)
    {
      attackPower += 1; // plague
      hitPointPower += 50; // dark swarm
    }

    BWAPI::WeaponType weapon = unitType.groundWeapon();
    if (unitType == BWAPI::UnitTypes::Protoss_Reaver)
    {
      // TODO: increase range
      weapon = BWAPI::WeaponTypes::Scarab;

      attackPower += damageFactor * (
          weapon.damageAmount() 
          * 1 //unitType.maxGroundHits() 
          * (1 + rangeFactor * 256 /*weapon.maxRange()*/ / maxRange)
          * (1 + splashFactor * weapon.outerSplashRadius() / maxSplashRadius)
        ) / (1.0 * 60 /*weapon.damageCooldown()*/);

      hitPointPower += hitPointFactor * (unitType.maxHitPoints() + unitType.maxShields());
    } else if(weapon != BWAPI::WeaponTypes::None && weapon.damageCooldown() > 0)
    {
      attackPower = damageFactor * (
          weapon.damageAmount() 
          * unitType.maxGroundHits() 
          * (1 + rangeFactor * weapon.maxRange() / maxRange)
          * (1 + splashFactor * weapon.outerSplashRadius() / maxSplashRadius)
        ) / (1.0 * weapon.damageCooldown());

      hitPointPower = hitPointFactor * (unitType.maxHitPoints() + unitType.maxShields());

      if(unitType.hasPermanentCloak() || unitType.cloakingTech() != BWAPI::TechTypes::None)
      {
        hitPointPower *= cloakFactor;
      }

      if(unitType.isFlyer())
      {
        hitPointPower *= flyerFactor;
      }
    } else {
      // no power
    }
  }

  return UnitPower(attackPower, hitPointPower);
}

void ManageArmyProcess::executeScout(BWAPI::Unit* unit)
{
  struct ContainsSameUnitType
  {
    ContainsSameUnitType(BWAPI::Unit* unit)
      : m_unit(unit), m_contains(false)
    {}
    SafeListBase::ConstIterationStep operator()(const Scout* scout)
    {
      m_contains |= containsUnitType(scout->getGroup(), m_unit->getType());

      return SafeListBase::IterationStep();
    }
    bool getResult() const
    {
      return m_contains;
    }
  private:
    bool m_contains;
    BWAPI::Unit* const m_unit;
  };

  Scout* scout = NULL;
  Map* map = Spar->getPerceptualState()->getMap();
  BaseLocation* main = getEnemyMainBaseLocation();
  if (main != NULL && (unit == NULL || !m_enemyBaseScouts.forEachProcess(ContainsSameUnitType(unit)).getResult()))
  {
    scout = instantiateNewScout(unit, main->getParent());
    m_enemyBaseScouts.add(scout);
  }
  else
  {
    const std::set<BaseLocation*>& startBaseLocations = map->getEnemyPossibleStartBaseLocations();
    std::set<BaseLocation*> unexplored;
    for (std::set<BaseLocation*>::const_iterator it = startBaseLocations.begin(); it != startBaseLocations.end(); ++it)
    {
      if ((*it)->getLastVisible() == Visibility::neverVisible())
      {
        unexplored.insert(*it);
      }
    }
    if (!unexplored.empty())
    {
      StaticLocation* location = getRandomElementFromSequence(unexplored.begin(), unexplored.size());
      scout = instantiateNewScout(unit, location);
      m_enemyBaseScouts.add(scout);
      scout->getScoutArrivedEvent().subscribe(&m_scoutArrivedEventHandler, NULL);
    }
    else
    {
      // TODO: use influence map or at least some kind of reasoning!

      StaticLocation* location = getRandomElementFromSequence(map->getBaseLocations().first, map->getNbBaseLocations());
      scout = instantiateNewScout(unit, location);
      m_randomScouts.add(scout);
      scout->getScoutArrivedEvent().subscribe(&m_scoutArrivedEventHandler, NULL);
    }
  }

  scout->start();
}

void ManageArmyProcess::onScoutArrived(void* data, Scout* action, StaticLocation* arrivedAtLocation, BWAPI::Unit*)
{
  unused(arrivedAtLocation);
  unused(data);

  if (action->getGroup()->empty() || (*action->getGroup()->begin())->getType() == BWAPI::UnitTypes::Protoss_Observer)
  {
    // The termination should trigger onUnitRelease() which should call addUnit() (and the Observer may be relocated elsewhere)
    action->externalTerminate();
  }
  else
  {
    BWAPI::Unit* unit = *action->getGroup()->begin();
    action->externalTerminate();
    executeScout(unit);
  }
}

Scout* ManageArmyProcess::instantiateNewScout(BWAPI::Unit* scout, StaticLocation* location)
{
  PersistentUnitGroup temp("Temporary-scout");
  if (scout != NULL)
  {
    temp.insert(scout);
  }
  return Layer3Script::getInstance().getLayer2().instantiateScout(
    this,
    PRIORITY_INITIAL_SCOUT,
    &temp,
    location);
}