text: #include "ProductionManager.h"
  #include "Util.h"
  #include "CCBot.h"
  
  ProductionManager::ProductionManager(CCBot & bot)
      : m_bot             (bot)
      , m_buildingManager (bot)
      , m_queue           (bot)
  {
  
  }
  
  void ProductionManager::setBuildOrder(const BuildOrder & buildOrder)
  {
      m_queue.clearAll();
  
      for (size_t i(0); i<buildOrder.size(); ++i)
      {
          m_queue.queueAsLowestPriority(buildOrder[i], true);
      }
  }
  
  
  void ProductionManager::onStart()
  {
      m_buildingManager.onStart();
      setBuildOrder(m_bot.Strategy().getOpeningBookBuildOrder());
  }
  
  void ProductionManager::onFrame()
  {
      fixBuildOrderDeadlock();
      manageBuildOrderQueue();
  
      // TODO: if nothing is currently building, get a new goal from the strategy manager
      // TODO: detect if there's a build order deadlock once per second
      // TODO: triggers for game things like cloaked units etc
  
      m_buildingManager.onFrame();
      drawProductionInformation();
  }
  
  // on unit destroy
  void ProductionManager::onUnitDestroy(const Unit & unit)
  {
      // TODO: might have to re-do build order if a vital unit died
  }
  
  void ProductionManager::manageBuildOrderQueue()
  {
      // if there is nothing in the queue, oh well
      if (m_queue.isEmpty())
      {
          return;
      }
  
      // the current item to be used
      BuildOrderItem & currentItem = m_queue.getHighestPriorityItem();
  
      // while there is still something left in the queue
      while (!m_queue.isEmpty())
      {
          // this is the unit which can produce the currentItem
          Unit producer = getProducer(currentItem.type);
  
          // check to see if we can make it right now
          bool canMake = canMakeNow(producer, currentItem.type);
  
          // TODO: if it's a building and we can't make it yet, predict the worker movement to the location
  
          // if we can make the current item
          if (producer.isValid() && canMake)
          {
              // create it and remove it from the _queue
              create(producer, currentItem);
              m_queue.removeCurrentHighestPriorityItem();
  
              // don't actually loop around in here
              break;
          }
          // otherwise, if we can skip the current item
          else if (m_queue.canSkipItem())
          {
              // skip it
              m_queue.skipItem();
  
              // and get the next one
              currentItem = m_queue.getNextHighestPriorityItem();
          }
          else
          {
              // so break out
              break;
          }
      }
  }
  
  void ProductionManager::fixBuildOrderDeadlock()
  {
      if (m_queue.isEmpty()) { return; }
      BuildOrderItem & currentItem = m_queue.getHighestPriorityItem();
  
      // check to see if we have the prerequisites for the topmost item
      bool hasRequired = m_bot.Data(currentItem.type).requiredUnits.empty();
      for (auto & required : m_bot.Data(currentItem.type).requiredUnits)
      {
          if (m_bot.UnitInfo().getUnitTypeCount(Players::Self, required, false) > 0 || m_buildingManager.isBeingBuilt(required))
          {
              hasRequired = true;
              break;
          }
      }
  
      if (!hasRequired)
      {
          std::cout << currentItem.type.getName() << " needs " << m_bot.Data(currentItem.type).requiredUnits[0].getName() << "\n";
          m_queue.queueAsHighestPriority(MetaType(m_bot.Data(currentItem.type).requiredUnits[0], m_bot), true);
          fixBuildOrderDeadlock();
          return;
      }
  
      // build the producer of the unit if we don't have one
      bool hasProducer = m_bot.Data(currentItem.type).whatBuilds.empty();
      for (auto & producer : m_bot.Data(currentItem.type).whatBuilds)
      {
          if (m_bot.UnitInfo().getUnitTypeCount(Players::Self, producer, false) > 0 || m_buildingManager.isBeingBuilt(producer))
          {
              hasProducer = true;
              break;
          }
      }
  
      if (!hasProducer)
      {
          m_queue.queueAsHighestPriority(MetaType(m_bot.Data(currentItem.type).whatBuilds[0], m_bot), true);
          fixBuildOrderDeadlock();
      }
  
      // build a refinery if we don't have one and the thing costs gas
      auto refinery = Util::GetRefinery(m_bot.GetPlayerRace(Players::Self), m_bot);
      if (m_bot.Data(currentItem.type).gasCost > 0 && m_bot.UnitInfo().getUnitTypeCount(Players::Self, refinery, false) == 0)
      {
          m_queue.queueAsHighestPriority(MetaType(refinery, m_bot), true);
      } 
  
      // build supply if we need some
      auto supplyProvider = Util::GetSupplyProvider(m_bot.GetPlayerRace(Players::Self), m_bot);
      if (m_bot.Data(currentItem.type).supplyCost > (m_bot.GetMaxSupply() - m_bot.GetCurrentSupply()) && !m_buildingManager.isBeingBuilt(supplyProvider))
      {
          m_queue.queueAsHighestPriority(MetaType(supplyProvider, m_bot), true);
      }
  }
  
  Unit ProductionManager::getProducer(const MetaType & type, CCPosition closestTo)
  {
      // get all the types of units that cna build this type
      auto & producerTypes = m_bot.Data(type).whatBuilds;
  
      // make a set of all candidate producers
      std::vector<Unit> candidateProducers;
      for (auto unit : m_bot.UnitInfo().getUnits(Players::Self))
      {
          // reasons a unit can not train the desired type
          if (std::find(producerTypes.begin(), producerTypes.end(), unit.getType()) == producerTypes.end()) { continue; }
          if (!unit.isCompleted()) { continue; }
          if (m_bot.Data(unit).isBuilding && unit.isTraining()) { continue; }
          if (unit.isFlying()) { continue; }
  
          // TODO: if unit is not powered continue
          // TODO: if the type is an addon, some special cases
          // TODO: if the type requires an addon and the producer doesn't have one
  
          // if we haven't cut it, add it to the set of candidates
          candidateProducers.push_back(unit);
      }
  
      return getClosestUnitToPosition(candidateProducers, closestTo);
  }
  
  Unit ProductionManager::getClosestUnitToPosition(const std::vector<Unit> & units, CCPosition closestTo)
  {
      if (units.size() == 0)
      {
          return Unit();
      }
  
      // if we don't care where the unit is return the first one we have
      if (closestTo.x == 0 && closestTo.y == 0)
      {
          return units[0];
      }
  
      Unit closestUnit;
      double minDist = std::numeric_limits<double>::max();
  
      for (auto & unit : units)
      {
          double distance = Util::Dist(unit, closestTo);
          if (!closestUnit.isValid() || distance < minDist)
          {
              closestUnit = unit;
              minDist = distance;
          }
      }
  
      return closestUnit;
  }
  
  // this function will check to see if all preconditions are met and then create a unit
  void ProductionManager::create(const Unit & producer, BuildOrderItem & item)
  {
      if (!producer.isValid())
      {
          return;
      }
  
      // if we're dealing with a building
      if (item.type.isBuilding())
      {
          if (item.type.getUnitType().isMorphedBuilding())
          {
              producer.morph(item.type.getUnitType());
          }
          else
          {
              m_buildingManager.addBuildingTask(item.type.getUnitType(), Util::GetTilePosition(m_bot.GetStartLocation()));
          }
      }
      // if we're dealing with a non-building unit
      else if (item.type.isUnit())
      {
          producer.train(item.type.getUnitType());
      }
      else if (item.type.isUpgrade())
      {
          // TODO: UPGRADES
          //Micro::SmartAbility(producer, m_bot.Data(item.type.getUpgradeID()).buildAbility, m_bot);
      }
  }
  
  bool ProductionManager::canMakeNow(const Unit & producer, const MetaType & type)
  {
      if (!producer.isValid() || !meetsReservedResources(type))
      {
          return false;
      }
  
  #ifdef SC2API
      sc2::AvailableAbilities available_abilities = m_bot.Query()->GetAbilitiesForUnit(producer.getUnitPtr());
  
      // quick check if the unit can't do anything it certainly can't build the thing we want
      if (available_abilities.abilities.empty())
      {
          return false;
      }
      else
      {
          // check to see if one of the unit's available abilities matches the build ability type
          sc2::AbilityID MetaTypeAbility = m_bot.Data(type).buildAbility;
          for (const sc2::AvailableAbility & available_ability : available_abilities.abilities)
          {
              if (available_ability.ability_id == MetaTypeAbility)
              {
                  return true;
              }
          }
      }
  
      return false;
  #else
      bool canMake = meetsReservedResources(type);
  	if (canMake)
  	{
  		if (type.isUnit())
  		{
  			canMake = BWAPI::Broodwar->canMake(type.getUnitType().getAPIUnitType(), producer.getUnitPtr());
  		}
  		else if (type.isTech())
  		{
  			canMake = BWAPI::Broodwar->canResearch(type.getTechType(), producer.getUnitPtr());
  		}
  		else if (type.isUpgrade())
  		{
  			canMake = BWAPI::Broodwar->canUpgrade(type.getUpgrade(), producer.getUnitPtr());
  		}
  		else
  		{	
  			BOT_ASSERT(false, "Unknown type");
  		}
  	}
  
  	return canMake;
  #endif
  }
  
  bool ProductionManager::detectBuildOrderDeadlock()
  {
      // TODO: detect build order deadlocks here
      return false;
  }
  
  int ProductionManager::getFreeMinerals()
  {
      return m_bot.GetMinerals() - m_buildingManager.getReservedMinerals();
  }
  
  int ProductionManager::getFreeGas()
  {
      return m_bot.GetGas() - m_buildingManager.getReservedGas();
  }
  
  // return whether or not we meet resources, including building reserves
  bool ProductionManager::meetsReservedResources(const MetaType & type)
  {
      // return whether or not we meet the resources
      int minerals = m_bot.Data(type).mineralCost;
      int gas = m_bot.Data(type).gasCost;
  
      return (m_bot.Data(type).mineralCost <= getFreeMinerals()) && (m_bot.Data(type).gasCost <= getFreeGas());
  }
  
  void ProductionManager::drawProductionInformation()
  {
      if (!m_bot.Config().DrawProductionInfo)
      {
          return;
      }
  
      std::stringstream ss;
      ss << "Production Information\n\n";
  
      for (auto & unit : m_bot.UnitInfo().getUnits(Players::Self))
      {
          if (unit.isBeingConstructed())
          {
              //ss << sc2::UnitTypeToName(unit.unit_type) << " " << unit.build_progress << "\n";
          }
      }
  
      ss << m_queue.getQueueInformation();
  
      m_bot.Map().drawTextScreen(0.01f, 0.01f, ss.str(), CCColor(255, 255, 0));
  }
  