#include "stdafx.h"
#include "ProgressTechProcess.h"
#include "../Layer3Script.h"
#include "../../../../PerceptualState/PerceptualState.h"

std::set<SparObject*> ProgressTechProcess::getRootTechsToAcquire(SparObject* target)
{
  std::set<SparObject*> rootTechs;
  if (!target->isActivated())
  {
    std::set<SparObject*> visited;
    getRootTechsToAcquireImpl(target, visited, rootTechs);
  }
  return rootTechs;
}

void ProgressTechProcess::getRootTechsToAcquireImpl(SparObject* target, std::set<SparObject*>& visited, std::set<SparObject*>& rootTechs)
{
  assert(target != NULL && !target->isActivated());

  std::pair<std::set<SparObject*>::iterator, bool> result = visited.insert(target);
  if (!result.second)
  {
    // Cycle! it's impossible to get the tech (e.g. trying to build a Probe when all Probes/Nexuses are destroyed)
  }
  else
  {
    const std::set<SparObject*>& dependencies = target->getDependencies();
    bool allDependenciesActivated = true;
    for (std::set<SparObject*>::const_iterator it = dependencies.begin(); it != dependencies.end(); ++it)
    {
      SparObject* dependency = *it;
      if (!dependency->isActivated())
      {
        allDependenciesActivated = false;
        getRootTechsToAcquireImpl(dependency, visited, rootTechs);
      }
    }
    if (allDependenciesActivated)
    {
      rootTechs.insert(target);
    }
    //visited.erase(result.first); // Visiting the same tech twice will yield the same result
  }
}

void ProgressTechProcess::onTechActionTerminate(void* data)
{
  TechActionWrapper* wrapper = reinterpret_cast<TechActionWrapper*>(data);
  m_currentTechActions.erase(wrapper->m_posInContainer);
}

void ProgressTechProcess::run()
{
  // TODO: which techs to start should be dependent on resources available / resources acquisition rate
  // TODO: both priority sorting and element sorting (to get high priority techs first)
  // TODO: build more forges to get upgrades faster

  const ResourcesSaturation saturation = Spar->getPerceptualState()->getResourceManager()->calculateProbesSaturationRatio();

  // Get all root techs necessary to research the target techs
  const TechSet& currentTechTargets = Layer3Script::getInstance().getTargetTechs();
  std::set<SparObject*> allRootTechs;
  for (TechSet::const_iterator it = currentTechTargets.begin(); it != currentTechTargets.end(); ++it)
  {
    SparObject* techTarget = *it;
    std::set<SparObject*> rootTechs = getRootTechsToAcquire(techTarget);
    allRootTechs.insert(rootTechs.begin(), rootTechs.end());
  }

  // Copy all current tech actions
  std::map<SparObject*, std::map<SparObject*, TechActionWrapper>::iterator> currentTechActions;
  for (std::map<SparObject*, TechActionWrapper>::iterator it = m_currentTechActions.begin(); it != m_currentTechActions.end(); ++it)
  {
    currentTechActions.insert(std::make_pair(it->first, it));
  }
  for (std::set<SparObject*>::const_iterator it = allRootTechs.begin(); it != allRootTechs.end(); ++it)
  {
    SparObject* rootTech = *it;
    std::map<SparObject*, std::map<SparObject*, TechActionWrapper>::iterator>::iterator existingActionForRootTech = currentTechActions.find(rootTech);
    if (existingActionForRootTech != currentTechActions.end())
    {
      currentTechActions.erase(existingActionForRootTech);
    }
    else
    {
      UpgradeTypeObject* upgrade = dynamic_cast<UpgradeTypeObject*>(rootTech);
      if (upgrade != NULL && 
          upgrade->getUpgrade().whatUpgrades() == BWAPI::UnitTypes::Protoss_Forge &&
          Layer3Script::getInstance().getLayer2().getNbAvailableBuildings(BWAPI::UnitTypes::Protoss_Forge, Spar->getPerceptualState()->getMap()) == 0)
      {
        if (m_currentTechActions.find(CurrentWorld::Forge) == m_currentTechActions.end())
          rootTech = CurrentWorld::Forge;
        else
          rootTech = NULL;
      }
      if (rootTech != NULL && shouldResearchTech(rootTech, saturation))
      {
        TacticalAction* action = NULL;
        if (dynamic_cast<TechTypeObject*>(rootTech) != NULL)
          action = Layer3Script::getInstance().getLayer2().instantiateTechResearch(this, PRIORITY_PROGRESS_TECH, dynamic_cast<TechTypeObject*>(rootTech));
        else if (dynamic_cast<UpgradeTypeObject*>(rootTech) != NULL)
          action = Layer3Script::getInstance().getLayer2().instantiateUpgradeResearch(this, PRIORITY_PROGRESS_TECH, dynamic_cast<UpgradeTypeObject*>(rootTech));
        else if (dynamic_cast<BuildingTypeObject*>(rootTech) != NULL)
          action = Layer3Script::getInstance().getLayer2().instantiateBuild(this, PRIORITY_PROGRESS_TECH, Spar->getPerceptualState()->getMap(), dynamic_cast<BuildingTypeObject*>(rootTech)->getBuildingType());
        else if (dynamic_cast<UnitTypeObject*>(rootTech) != NULL)
          action = Layer3Script::getInstance().getLayer2().instantiateRecruit(this, PRIORITY_PROGRESS_TECH, Spar->getPerceptualState()->getMap(), dynamic_cast<UnitTypeObject*>(rootTech)->getUnitType(), false);

        action->zombifyOnTerminate();
        std::pair<std::map<SparObject*, TechActionWrapper>::iterator, bool> result = m_currentTechActions.insert(std::make_pair(rootTech, TechActionWrapper(action)));
        assert(result.second);
        TechActionWrapper& wrapper = result.first->second;
        wrapper.m_posInContainer = result.first;
        action->getTerminateEvent().subscribe(&m_techActionTerminateEventHandler, &wrapper);

        action->start();
      }
    }
  }

  // The actions left in currentTechActions are not reflected in the current target techs so they can be cancelled
  //for (std::map<SparObject*, std::map<SparObject*, TechActionWrapper>::iterator>::iterator it = currentTechActions.begin(); it != currentTechActions.end(); ++it)
  //{
  //  std::map<SparObject*, TechActionWrapper>::iterator toCancel = it->second;
  //  toCancel->second.m_techAction->cancel();
  //}
}

bool ProgressTechProcess::shouldResearchTech(SparObject* tech, const ResourcesSaturation& saturation) const
{
  static const int PSI_TO_TRIGGER_TECHS = 20 * 2;

  //static const int PSI_TO_TRIGGER_FORGE = 40 * 2;
  //static const int PSI_TO_TRIGGER_OBSERVERS = 60 * 2;
  //static const int PSI_ZERG_TO_TRIGGER_TEMPLARS = 70 * 2;

  // Allow one building tech + one other tech if there's no building tech at the moment
  // TODO: should depend on resources acquisition rate

  unused(tech);

  if (BWAPI::Broodwar->self()->supplyUsed() < PSI_TO_TRIGGER_TECHS)
    return false;

  // Calculate nb techs
  size_t nbBuildingTechs = 0;
  size_t nbOtherTechs = 0;
  for (std::map<SparObject*, TechActionWrapper>::const_iterator it = m_currentTechActions.begin(); it != m_currentTechActions.end(); ++it)
  {
    BuildingTypeObject* buildingTech = dynamic_cast<BuildingTypeObject*>(it->first);
    if (buildingTech != NULL)
    {
      assert(buildingTech->getBuildingType().isBuilding());
      ++nbBuildingTechs;
    }
    else
    {
      ++nbOtherTechs;
    }
  }

  // Set max nb techs
  const int maxNbBuildingTechs = std::min(std::max(1, static_cast<int>(saturation.getTotalNbProbes() / 20)), 3);
  const int maxNbOtherTechs = maxNbBuildingTechs - nbBuildingTechs;

  if (dynamic_cast<BuildingTypeObject*>(tech) != NULL)
  {
    return nbBuildingTechs < maxNbBuildingTechs;
  }
  else
  {
    return nbOtherTechs < maxNbOtherTechs;
  }


  //return nbBuildingTechs == 0 &&
  //       (dynamic_cast<BuildingTypeObject*>(tech) != NULL || nbOtherTechs < NB_MAX_OTHER_TECHS);
}