#include "stdafx.h"
#include "L2Build.h"
#include "../Layer2Managers.h"
#include "../../../../PerceptualState/World/CurrentWorld.h"

// TODO: burrowed units

L2Build::L2Build(const Process* parent, unsigned int priority, AutoPopulatedLocation* location, BWAPI::UnitType buildingType)
  : Build(parent, priority, location, buildingType)
  , L2GroupTacticalAction(parent, priority)
  , GroupTacticalAction(parent, priority)
  , TacticalAction(parent, priority)
  , Process(parent)
  , m_unitTypeCreatableChangedEventHandler(*this)
  , m_resourceAllocatedEventHandler(*this)
  , m_unitBehaviorTerminatedEventHandler(*this)
  , m_unitCompletedEventHandler(*this)
  , m_buildBehavior(*this)
  , m_resourcesRequest(m_resourceAllocatedEventHandler)
  , m_unitDestroyedEventHandler(*this)
  , m_frameEvent(*this, Scheduler::SCHEDULING_MEDIUM_PRIORITY, 1, 0)
  , m_building(NULL)
  , m_worker(NULL)
{
}

void L2Build::onUnitAddedToGroup(void*, BWAPI::Unit* unit)
{
  unused(unit);
  // Should only happen when reserveUnit() is called
}

void L2Build::onUnitRemovedFromGroup(void*, BWAPI::Unit* unit)
{
  unused(unit);
  assert(m_worker == NULL || m_worker == unit);

  if(m_buildBehavior!=NULL)
  {
    m_buildBehavior->externalTerminate();
  }

  if(m_building == NULL)
  {
    //must start the building with another worker
    initiateBuildBehavior();
  }
}

void L2Build::onStartImplementation()
{
  L2GroupTacticalAction::onStartImplementation();

  if (!m_buildingTypeObject->isCreatable())
  {
    terminate(TerminatedTechUnavailable);
  }
  else
  {
    m_unitTypeCreatableChangedSubscriberId = CurrentWorld::getUnitTypeCreatableChangedEvent().subscribe(&m_unitTypeCreatableChangedEventHandler, NULL);
    Layer2Managers::getInstance().requestResources(
      m_priority,
      Resources(m_buildingType),
      m_resourcesRequest);
  }
}

void L2Build::onCancelImplementation()
{
  L2GroupTacticalAction::onCancelImplementation();
  if(m_building!=NULL && !m_building->isCompleted())
  {
    m_building->cancelConstruction();
  }
  terminate(Cancelled);
}

void L2Build::onEndImplementation()
{
  L2GroupTacticalAction::onEndImplementation();

  Layer2Managers::getInstance().terminateResourcesRequest(m_resourcesRequest);

  if(m_building!=NULL)
  {
    BWAPI::getIndividualUnitDestroyEvent(m_building).unsubscribe(m_unitDestroyedSubscriberId);
  }
  CurrentWorld::getUnitTypeCreatableChangedEvent().unsubscribe(m_unitTypeCreatableChangedSubscriberId);
  Spar->getPerceptualState()->getUnitCompletedEvent().unsubscribe(m_unitCompletedSubscriberId);
  m_frameEvent.deactivate();
}

BWAPI::Unit* L2Build::findBuildProbe(BWAPI::TilePosition tile) const
{
  BWAPI::Unit* worker = NULL;
  double minDistance = boost::numeric::bounds<double>::highest();
  const std::set<BWAPI::Unit*>& unitSet = BWAPI::Broodwar->self()->getUnits();
  for(std::set<BWAPI::Unit*>::const_iterator it = unitSet.begin() ; (it!=unitSet.end()) ; ++it)
  {
    if((*it)->getType() == BWAPI::UnitTypes::Protoss_Probe && (*it)->isCompleted() && BWTA::isConnected((*it)->getTilePosition(), tile))
    {
      const GroupTacticalAction* action = dynamic_cast<Layer2UnitData&>(BWAPI::getUnitData(*it)).getCurrentAction();
      const unsigned int priority = (action == NULL) ? 0 : action->getPriority();
      if (priority < m_priority)
      {
        const int distance = (*it)->getDistance(BWAPI::Position(tile.x()*TILE_SIZE,tile.y()*TILE_SIZE));
        if(worker == NULL || 
           distance + (((*it)->isCarryingMinerals() || (*it)->isCarryingGas()) ? 20 * TILE_SIZE : 0) < 
             minDistance + ((worker->isCarryingMinerals() || worker->isCarryingGas()) ? 20 * TILE_SIZE : 0)) // 20 tiles handicap for probes carrying stuff
        {
          worker = (*it);
          minDistance = distance;
        }
      }
    }
  }
  return worker;
}

void L2Build::onUnitTypeCreatableChanged(void*, const UnitTypeObject* unitTypeObject)
{
  unused(unitTypeObject);
  if (!m_buildingTypeObject->isCreatable())
  {
    terminate(TerminatedTechUnavailable);
  }
}

void L2Build::onResourcesAllocated(void*, BWAPI::Unit*)
{
  initiateBuildBehavior();
}

void L2Build::initiateBuildBehavior()
{
  //bool reserved = reserveUnit(worker);
  //m_buildingPlacer->useWorker(m_worker);

  //get a location
  int minDistance = (m_buildingType == BWAPI::UnitTypes::Protoss_Pylon) ? 
    3 :
    1;
  do
  {
    m_buildTile = Layer2Managers::getInstance().getBuildingPlacer().getBuildingLocation(m_worker, m_buildingType, m_location, minDistance);
  }
  while (m_buildTile == BWAPI::TilePositions::None && --minDistance >= 0);

  if(m_buildTile == BWAPI::TilePositions::None)
  {
    terminate(TerminatedNoSpace);
  }
  else
  {
    //send a probe to construct
    m_worker = findBuildProbe(m_buildTile);
    if(m_worker==NULL)
    {
      terminate(TerminatedNoWorker);
    }
    else
    {
      bool reserved = addUnit(m_worker);
      unused(reserved); // remove when reserved is finally used
      assert(reserved);
      //m_buildingPlacer->useWorker(m_worker);

      
      //bool testTopLeft = m_buildTile.isValid();
      //bool testTopRight = BWAPI::TilePosition(m_buildTile.x()+m_buildingType.tileWidth()-1,m_buildTile.y()).isValid();
      //bool testBottomLeft = BWAPI::TilePosition(m_buildTile.x(),m_buildTile.y()-m_buildingType.tileHeight()+1).isValid();
      //bool testBottomRight = BWAPI::TilePosition(m_buildTile.x()+m_buildingType.tileWidth()-1,m_buildTile.y()-m_buildingType.tileHeight()+1).isValid();
      //bool test2 = BWAPI::Broodwar->isExplored(m_buildTile);
      //bool test3 = worker->build(m_buildTile,m_buildingType);

      Layer2Managers::getInstance().getBuildingPlacer().reservePosition(m_buildTile,m_buildingType);
      m_buildBehavior = new BuildBehavior(this, m_priority,m_worker,m_buildTile,m_buildingType);
      m_buildBehavior->getTerminateEvent().subscribe(&m_unitBehaviorTerminatedEventHandler, NULL);
      m_buildBehavior->start();
    }
  }
}

void L2Build::onUnitBehaviorTerminated(void*)
{
  m_building = m_buildBehavior->getBuilding();
  if (m_building != NULL)
  {
    Layer2Managers::getInstance().terminateResourcesRequest(m_resourcesRequest);

    CurrentWorld::getUnitTypeCreatableChangedEvent().unsubscribe(m_unitTypeCreatableChangedSubscriberId);
    m_unitDestroyedSubscriberId = BWAPI::getIndividualUnitDestroyEvent(m_building).subscribe(&m_unitDestroyedEventHandler, NULL);
    m_unitCompletedSubscriberId = Spar->getPerceptualState()->getUnitCompletedEvent().subscribe(&m_unitCompletedEventHandler, NULL);
    m_frameEvent.activate();

    m_buildingStartedEvent.raise(this, m_building);
  }

  // Should exactly match the reservations
  Layer2Managers::getInstance().getBuildingPlacer().unreservePosition(m_buildTile,m_buildingType);
  if (m_worker!=NULL)
  {
    removeUnit(m_worker); // will trigger onUnitRemovedFromGroup()
  }
}

void L2Build::onFrame()
{
  struct MaxGroundThreat
  {
  public:
    MaxGroundThreat()
      : m_maxGroundThreat(0)
    {}
    void operator()(int x, int y)
    {
      m_maxGroundThreat = std::max(m_maxGroundThreat, Spar->getPerceptualState()->getEnemyThreat()->getData(x, y).futureGround);
    }
    double getResult() const
    {
      return m_maxGroundThreat;
    }
  private:
    double m_maxGroundThreat;
  };

  if (double(m_building->getHitPoints()) / double(m_building->getType().maxHitPoints()) <= 0.075 &&
      m_building->isUnderAttack() && 
      forEachTileUnderUnit(m_building->getTilePosition(), m_building->getPosition(), m_building->getType(), MaxGroundThreat()).getResult() > 0)
  {
    cancel();
  }
}

void L2Build::onUnitCompleted(void*, BWAPI::Unit* unit)
{
  if(m_building == unit)
  {
    Layer2Managers::getInstance().getBuildingPlacer().onBuildingCompleted(unit);
    m_buildingCompletedEvent.raise(this, m_building);
    terminate(Success);
  }
}

void L2Build::onUnitDestroyed(void*)
{
  // Happens when the building in construction is destroyed
  terminate(Cancelled);
}