#pragma once
#include "SetContainer.h"
#include <BWAPI/Position.h>
#include <BWAPI/Filters.h>
#include <iterator>

namespace BWAPI
{
  // Forward declarations
  class UnitType;
  class Regionset;
  class UnitCommand;
  class TechType;

  /// <summary>The Unitset is a container for a set of pointers to Unit objects. It is typically
  /// used for groups of units instead of having to manage each Unit individually.</summary>
  ///
  /// @see Unit
  class Unitset : public SetContainer<BWAPI::Unit, std::hash<void*>>
  {
  public:
    /// <summary>A blank Unitset containing no elements.</summary> This is typically used as a
    /// return value for BWAPI interface functions that have encountered an error.
    static const Unitset none;

    Unitset() : SetContainer<BWAPI::Unit, std::hash<void*>>() {}

    Unitset& operator = (const Unitset& other) {
        clear();
        std::copy(other.begin(), other.end(), std::inserter(*this, begin()));
        return *this;
    }

    /// <summary>Calculates the average of all valid Unit positions in this set.</summary>
    ///
    /// @returns Average Position of all units in the set.
    ///
    /// @see UnitInterface::getPosition
    Position getPosition() const;

    /// <summary>Creates a single set containing all units that are loaded into units of this set.</summary>
    ///
    /// @returns The set of all loaded units.
    ///
    /// @see UnitInterface::getLoadedUnits
    Unitset getLoadedUnits() const;

    /// <summary>Creates a single set containing all the @Interceptors of all @Carriers in this set.</summary>
    ///
    /// @returns The set of all @Interceptors .
    /// 
    /// @see UnitInterface::getInterceptors
    Unitset getInterceptors() const;

    /// <summary>Creates a single set containing all the @Larvae of all @Hatcheries, @Lairs, and
    /// @Hives in this set.</summary>
    ///
    /// @returns The set of all @Larvae .
    ///
    /// @see UnitInterface::getLarva
    Unitset getLarva() const;

    /// <summary>Sets the client info for every unit in this set.</summary>
    ///
    /// <param name="clientInfo"> (optional)
    ///   A pointer to client information, managed by the AI module, or nullptr if client
    ///   information is to be cleared.
    /// </param>
    /// <param name="index"> (optional)
    ///   An key value for the client info mapping so that more than one piece of data can be
    ///   mapped to the same unit.
    /// </param>
    ///
    /// @see UnitInterface::setClientInfo
    void setClientInfo(void *clientInfo = nullptr, int index = 0) const;
    /// @overload
    void setClientInfo(int clientInfo = 0, int index = 0) const;

    /// @copydoc UnitInterface::getUnitsInRadius
    Unitset getUnitsInRadius(int radius, const UnitFilter &pred = nullptr) const;

    /// @copydoc UnitInterface::getClosestUnit
    Unit getClosestUnit(const UnitFilter &pred = nullptr, int radius = 999999) const;

    /// @name Unit Commands
    /// @{

    /// @copydoc UnitInterface::issueCommand
    bool issueCommand(UnitCommand command) const;
    
    /// @copydoc UnitInterface::attack
    bool attack(Position target, bool shiftQueueCommand = false) const;

    /// @copydoc UnitInterface::attack
    bool attack(Unit target, bool shiftQueueCommand = false) const;

    /// @copydoc UnitInterface::build
    bool build(UnitType type, TilePosition target = TilePositions::None) const;

    /// @copydoc UnitInterface::buildAddon
    bool buildAddon(UnitType type) const;

    /// @copydoc UnitInterface::train
    bool train(UnitType type) const;

    /// @copydoc UnitInterface::morph
    bool morph(UnitType type) const;

    /// @copydoc UnitInterface::setRallyPoint
    bool setRallyPoint(Unit target) const;

    /// @copydoc UnitInterface::setRallyPoint
    bool setRallyPoint(Position target) const;

    /// @copydoc UnitInterface::move
    bool move(Position target, bool shiftQueueCommand = false) const;

    /// @copydoc UnitInterface::patrol
    bool patrol(Position target, bool shiftQueueCommand = false) const;

    /// @copydoc UnitInterface::holdPosition
    bool holdPosition(bool shiftQueueCommand = false) const;

    /// @copydoc UnitInterface::stop
    bool stop(bool shiftQueueCommand = false) const;

    /// @copydoc UnitInterface::follow
    bool follow(Unit target, bool shiftQueueCommand = false) const;

    /// @copydoc UnitInterface::gather
    bool gather(Unit target, bool shiftQueueCommand = false) const;

    /// @copydoc UnitInterface::returnCargo
    bool returnCargo(bool shiftQueueCommand = false) const;

    /// @copydoc UnitInterface::repair
    bool repair(Unit target, bool shiftQueueCommand = false) const;

    /// @copydoc UnitInterface::burrow
    bool burrow() const;

    /// @copydoc UnitInterface::unburrow
    bool unburrow() const;

    /// @copydoc UnitInterface::cloak
    bool cloak() const;

    /// @copydoc UnitInterface::decloak
    bool decloak() const;

    /// @copydoc UnitInterface::siege
    bool siege() const;

    /// @copydoc UnitInterface::unsiege
    bool unsiege() const;

    /// @copydoc UnitInterface::lift
    bool lift() const;

    /// @copydoc UnitInterface::load
    bool load(Unit target, bool shiftQueueCommand = false) const;

    /// @copydoc UnitInterface::unloadAll(bool)
    bool unloadAll(bool shiftQueueCommand = false) const;

    /// @copydoc UnitInterface::unloadAll(Position,bool)
    bool unloadAll(Position target, bool shiftQueueCommand = false) const;

    /// @copydoc UnitInterface::rightClick
    bool rightClick(Unit target, bool shiftQueueCommand = false) const;

    /// @copydoc UnitInterface::rightClick
    bool rightClick(Position target, bool shiftQueueCommand = false) const;

    /// @copydoc UnitInterface::haltConstruction
    bool haltConstruction() const;

    /// @copydoc UnitInterface::cancelConstruction
    bool cancelConstruction() const;

    /// @copydoc UnitInterface::cancelAddon
    bool cancelAddon() const;

    /// @copydoc UnitInterface::cancelTrain
    bool cancelTrain(int slot = -2) const;

    /// @copydoc UnitInterface::cancelMorph
    bool cancelMorph() const;

    /// @copydoc UnitInterface::cancelResearch
    bool cancelResearch() const;

    /// @copydoc UnitInterface::cancelUpgrade
    bool cancelUpgrade() const;
    
    /// @copydoc UnitInterface::useTech
    bool useTech(TechType tech, Unit target = nullptr) const;

    /// @copydoc UnitInterface::useTech
    bool useTech(TechType tech, Position target) const;

    ///@}
  };
}

