#include <BWAPI/Unitset.h>
#include <BWAPI/UnitCommand.h>
#include <BWAPI/Position.h>
#include <BWAPI/Unit.h>
#include <BWAPI/Game.h>
#include <BWAPI/TechType.h>

#include <utility>

namespace BWAPI
{
  // initialize no-set
  const Unitset Unitset::none;

  ////////////////////////////////////////////////////////// Position
  Position Unitset::getPosition() const
  {
    // Declare the local position
    Position retPosition(0,0);
    int validPosCount = 0;

    // Add up the positions for all units in the set
    for ( auto &u : *this )
    {
      Position pos = u->getPosition();
      if ( pos.isValid() )
      {
        retPosition += pos;
        ++validPosCount;
      }
    }

    if (validPosCount > 0)
      retPosition /= validPosCount;
    return retPosition;
  }
  ////////////////////////////////////////////////////////// sets
  Unitset Unitset::getLoadedUnits() const
  {
    Unitset retSet;
    for (auto &u : *this)
    {
      auto units = u->getLoadedUnits();
      retSet.insert(units.begin(), units.end());
    }
    return retSet;
  }
  Unitset Unitset::getInterceptors() const
  {
    Unitset retSet;
    for (auto &u : *this)
    {
      auto units = u->getInterceptors();
      retSet.insert(units.begin(), units.end());
    }
    return retSet;
  }
  Unitset Unitset::getLarva() const
  {
    Unitset retSet;
    for (auto &u : *this)
    {
      auto units = u->getLarva();
      retSet.insert(units.begin(), units.end());
    }
    return retSet;
  }
  ////////////////////////////////////////////////////////// Misc
  void Unitset::setClientInfo(void *clientInfo, int index) const
  {
    if ( index < 0 || index > 255 )
      return;

    // Assign the client info to all units in the set
    for (auto &u : *this)
    {
      u->setClientInfo(clientInfo, index);
    }
  }
  void Unitset::setClientInfo(int clientInfo, int index) const
  {
    this->setClientInfo((void*)(uintptr_t)clientInfo, index);
  }

  Unitset Unitset::getUnitsInRadius(int radius, const UnitFilter &pred) const
  {
    // Return if this unit does not exist
    if ( this->empty() )
      return Unitset::none;

    return Broodwar->getUnitsInRadius(this->getPosition(), radius, pred);
  }
  Unit Unitset::getClosestUnit(const UnitFilter &pred, int radius) const
  {
    // Return if this unit does not exist
    if ( this->empty() )
      return nullptr;

    return Broodwar->getClosestUnit(this->getPosition(), pred, radius);
  }

  bool Unitset::issueCommand(UnitCommand command) const
  {
    return Broodwar->issueCommand(*this, command);
  }
  //--------------------------------------------- ATTACK MOVE ------------------------------------------------
  bool Unitset::attack(Position target, bool shiftQueueCommand) const
  {
    return this->issueCommand(UnitCommand::attack(nullptr, target, shiftQueueCommand));
  }
  bool Unitset::attack(Unit target, bool shiftQueueCommand) const
  {
    return this->issueCommand(UnitCommand::attack(nullptr, target, shiftQueueCommand));
  }
  //--------------------------------------------- BUILD ------------------------------------------------------
  bool Unitset::build(UnitType type, TilePosition target) const
  {
    if ( target == TilePositions::None )
      return this->train(type);
    return this->issueCommand(UnitCommand::build(nullptr, target, type));
  }
  //--------------------------------------------- BUILD ADDON ------------------------------------------------
  bool Unitset::buildAddon(UnitType type) const
  {
    bool result = false;
    for (auto &it : *this)
    {
      result |= it->buildAddon(type);
    }
    return result;
  }
  //--------------------------------------------- TRAIN ------------------------------------------------------
  bool Unitset::train(UnitType type) const
  {
    return this->issueCommand(UnitCommand::train(nullptr,type));
  }
  //--------------------------------------------- MORPH ------------------------------------------------------
  bool Unitset::morph(UnitType type) const
  {
    return this->issueCommand(UnitCommand::morph(nullptr,type));
  }
  //--------------------------------------------- SET RALLY POSITION -----------------------------------------
  bool Unitset::setRallyPoint(Position target) const
  {
    return this->issueCommand(UnitCommand::setRallyPoint(nullptr, target));
  }
  bool Unitset::setRallyPoint(Unit target) const
  {
    return this->issueCommand(UnitCommand::setRallyPoint(nullptr, target));
  }
  //--------------------------------------------- MOVE -------------------------------------------------------
  bool Unitset::move(Position target, bool shiftQueueCommand) const
  {
    return this->issueCommand(UnitCommand::move(nullptr,target, shiftQueueCommand));
  }
  //--------------------------------------------- PATROL -----------------------------------------------------
  bool Unitset::patrol(Position target, bool shiftQueueCommand) const
  {
    return this->issueCommand(UnitCommand::patrol(nullptr,target, shiftQueueCommand));
  }
  //--------------------------------------------- HOLD POSITION ----------------------------------------------
  bool Unitset::holdPosition(bool shiftQueueCommand) const
  {
    return this->issueCommand(UnitCommand::holdPosition(nullptr, shiftQueueCommand));
  }
  //--------------------------------------------- STOP -------------------------------------------------------
  bool Unitset::stop(bool shiftQueueCommand) const
  {
    return this->issueCommand(UnitCommand::stop(nullptr, shiftQueueCommand));
  }
  //--------------------------------------------- FOLLOW -----------------------------------------------------
  bool Unitset::follow(Unit target, bool shiftQueueCommand) const
  {
    return this->issueCommand(UnitCommand::follow(nullptr,target, shiftQueueCommand));
  }
  //--------------------------------------------- GATHER -----------------------------------------------------
  bool Unitset::gather(Unit target, bool shiftQueueCommand) const
  {
    return this->issueCommand(UnitCommand::gather(nullptr,target, shiftQueueCommand));
  }
  //--------------------------------------------- RETURN CARGO -----------------------------------------------
  bool Unitset::returnCargo(bool shiftQueueCommand) const
  {
    return this->issueCommand(UnitCommand::returnCargo(nullptr, shiftQueueCommand));
  }
  //--------------------------------------------- REPAIR -----------------------------------------------------
  bool Unitset::repair(Unit target, bool shiftQueueCommand) const
  {
    return this->issueCommand(UnitCommand::repair(nullptr,target, shiftQueueCommand));
  }
  //--------------------------------------------- BURROW -----------------------------------------------------
  bool Unitset::burrow() const
  {
    return this->issueCommand(UnitCommand::burrow(nullptr));
  }
  //--------------------------------------------- UNBURROW ---------------------------------------------------
  bool Unitset::unburrow() const
  {
    return this->issueCommand(UnitCommand::unburrow(nullptr));
  }
  //--------------------------------------------- CLOAK ------------------------------------------------------
  bool Unitset::cloak() const
  {
    return this->issueCommand(UnitCommand::cloak(nullptr));
  }
  //--------------------------------------------- DECLOAK ----------------------------------------------------
  bool Unitset::decloak() const
  {
    return this->issueCommand(UnitCommand::decloak(nullptr));
  }
  //--------------------------------------------- SIEGE ------------------------------------------------------
  bool Unitset::siege() const
  {
    return this->issueCommand(UnitCommand::siege(nullptr));
  }
  //--------------------------------------------- UNSIEGE ----------------------------------------------------
  bool Unitset::unsiege() const
  {
    return this->issueCommand(UnitCommand::unsiege(nullptr));
  }
  //--------------------------------------------- LIFT -------------------------------------------------------
  bool Unitset::lift() const
  {
    return this->issueCommand(UnitCommand::lift(nullptr));
  }
  //--------------------------------------------- LOAD -------------------------------------------------------
  bool Unitset::load(Unit target, bool shiftQueueCommand) const
  {
    return this->issueCommand(UnitCommand::load(nullptr,target, shiftQueueCommand));
  }
  //--------------------------------------------- UNLOAD ALL -------------------------------------------------
  bool Unitset::unloadAll(bool shiftQueueCommand) const
  {
    return this->issueCommand(UnitCommand::unloadAll(nullptr, shiftQueueCommand));
  }
  //--------------------------------------------- UNLOAD ALL -------------------------------------------------
  bool Unitset::unloadAll(Position target, bool shiftQueueCommand) const
  {
    return this->issueCommand(UnitCommand::unloadAll(nullptr,target, shiftQueueCommand));
  }
  //--------------------------------------------- RIGHT CLICK ------------------------------------------------
  bool Unitset::rightClick(Unit target, bool shiftQueueCommand) const
  {
    return this->issueCommand(UnitCommand::rightClick(nullptr, target, shiftQueueCommand));
  }
  bool Unitset::rightClick(Position target, bool shiftQueueCommand) const
  {
    return this->issueCommand(UnitCommand::rightClick(nullptr, target, shiftQueueCommand));
  }
  //--------------------------------------------- HALT CONSTRUCTION ------------------------------------------
  bool Unitset::haltConstruction() const
  {
    return this->issueCommand(UnitCommand::haltConstruction(nullptr));
  }
  //--------------------------------------------- CANCEL CONSTRUCTION ----------------------------------------
  bool Unitset::cancelConstruction() const
  {
    return this->issueCommand(UnitCommand::cancelConstruction(nullptr));
  }
  //--------------------------------------------- CANCEL ADDON -----------------------------------------------
  bool Unitset::cancelAddon() const
  {
    return this->issueCommand(UnitCommand::cancelAddon(nullptr));
  }
  //--------------------------------------------- CANCEL TRAIN -----------------------------------------------
  bool Unitset::cancelTrain(int slot) const
  {
    return this->issueCommand(UnitCommand::cancelTrain(nullptr, slot));
  }
  //--------------------------------------------- CANCEL MORPH -----------------------------------------------
  bool Unitset::cancelMorph() const
  {
    return this->issueCommand(UnitCommand::cancelMorph(nullptr));
  }
  //--------------------------------------------- CANCEL RESEARCH --------------------------------------------
  bool Unitset::cancelResearch() const
  {
    return this->issueCommand(UnitCommand::cancelResearch(nullptr));
  }
  //--------------------------------------------- CANCEL UPGRADE ---------------------------------------------
  bool Unitset::cancelUpgrade() const
  {
    return this->issueCommand(UnitCommand::cancelUpgrade(nullptr));
  }
  //--------------------------------------------- USE TECH ---------------------------------------------------
  bool Unitset::useTech(TechType tech, Unit target) const
  {
    if (target == nullptr)
      return this->issueCommand(UnitCommand::useTech(nullptr, tech));
    return this->issueCommand(UnitCommand::useTech(nullptr, tech, target));
  }
  bool Unitset::useTech(TechType tech, Position target) const
  {
    return this->issueCommand(UnitCommand::useTech(nullptr, tech, target));
  }
}

