#include "stdafx.h"
#include "Reasoning.h"
#include "../UnitRecord/UnitRecordType/TransportUnitRecordType.h"
#include "../UnitRecord/UnitRecordManager.h"
#include "../PerceptualState.h"

Reasoning::Reasoning()
  : m_unitEvadeEventHandler(*this)
  , m_unitRemovedEventHandler(*this)
{
  m_unitEvadeID = BWAPI::getUnitEvadeEvent().subscribe(&m_unitEvadeEventHandler, NULL);
  m_unitRemovedID = Spar->getPerceptualState()->getMap()->getUnitRemovedEvent().subscribe(&m_unitRemovedEventHandler, NULL);
}

Reasoning::~Reasoning()
{
  BWAPI::getUnitEvadeEvent().unsubscribe(m_unitEvadeID);
  Spar->getPerceptualState()->getMap()->getUnitRemovedEvent().unsubscribe(m_unitRemovedID);
}

struct FindTransport
{
  FindTransport(BWAPI::Unit* unit)
    : m_unit(unit)
  {}
  void operator()(int x, int y)
  {
    const std::set<BWAPI::Unit*>& units = BWAPI::Broodwar->getUnitsOnTile(x, y);
    for (std::set<BWAPI::Unit*>::const_iterator it = units.begin(); it != units.end(); ++it)
    {
      BWAPI::Unit* possibleTransport = *it;
      if (possibleTransport->getPlayer() == m_unit->getPlayer() &&
          possibleTransport->getType().spaceProvided() != 0 && 
          canLoadUnit(possibleTransport->getType(), m_unit->getType()))
      {
        m_possibleTransports.insert(possibleTransport);
      }
    }
  }
  const std::set<BWAPI::Unit*>& getResult() const
  {
    return m_possibleTransports;
  }
private:
  BWAPI::Unit* const m_unit;
  std::set<BWAPI::Unit*> m_possibleTransports;

  FindTransport & operator=(const FindTransport &);
};

void Reasoning::onUnitEvade(void*, BWAPI::Unit* unit) const
{
  // unit->isLoaded() is never true for enemy units

  if (!BWAPI::isDestroyed(unit) &&
      BWAPI::isLoadable(unit->getType()) &&
      !unit->isBurrowed() &&
      !unit->isCloaked() &&
      BWAPI::isInFieldOfView(unit->getTilePosition(), unit->getPosition(), unit->getType(), 1 /* One additional tile to eliminate false positives */))
  {
    std::set<BWAPI::Unit*> possibleTransports = 
      BWAPI::forEachTileUnderUnit(unit->getTilePosition(), unit->getPosition(), unit->getType(), FindTransport(unit)).getResult();
    if (possibleTransports.size() == 1)
    {
      UnitRecord* unitRecord = Spar->getPerceptualState()->getUnitRecord(unit);
      UnitRecord* transport = Spar->getPerceptualState()->getUnitRecord(*possibleTransports.begin());
      unitRecord->setTransport(transport);
    }
  }
}

void Reasoning::onUnitRecordRemovedFromMap(void*, UnitRecord* unitRecord, BWAPI::UnitType previousType, BWAPI::Player*) const
{
  unused(previousType);
  assert(previousType == unitRecord->getType());

  if (!unitRecord->isDestroyed() && !BWAPI::isMobile(unitRecord->getType()))
  {
    // TODO: the unit may have morphed into something stationary but smaller...!

    // TODO: here we could discriminate on the total time needed to morph (since we know the last frame at which we spotted the unit)
    const std::set<BWAPI::UnitType>& mobileMorphTypes = BWAPI::getAllDerivedMobileMorphTypes(unitRecord->getType());
    if (!mobileMorphTypes.empty())
    {
      unitRecord->morph(mobileMorphTypes);
    }
    else
    {
      // The unit couldn't move, so if it couldn't morph either we conclude it has been destroyed
      unitRecord->destroy("Unit cannot move but was not found at previous position");
    }
  }
}