#pragma once
#include <BWAPI.h>
#include <functional>
#include <iostream>
#include "Utils/Component.h"
#include "Utils/Event.h"
#include "Utils/Logger.h"
#include "Utils/Utils.h"
#include "UnitManager.h"
#include "UnitData.h"

// "Add-on" stuff to BWAPI, i.e. elements that can be calculated from BWAPI alone (and could thus be present directly in BWAPI)
// without depending on SPAR.

namespace BWAPI
{
  /**
   * @brief The maximum number of supply, which is 400 since BWAPI counts one supply for each zergling.
   */
  static const int MAX_SUPPLY = 200 * 2;
  /**
   * @brief The number of unit types.
   */
  static const int NB_UNITS_TYPE = 245;

  /**
   * @brief The OnUnitDiscover event is raised when a unit comes into our field of view.
   */
  DECLARE_EVENT1(OnUnitDiscover, BWAPI::Unit*);
  /**
   * @brief The OnUnitEvade event is raised when a unit evades from our field of view.
   *        Call BWAPI::isDestroyed() to check whether the unit is destroyed (from a previous onUnitDestroy event).
   */
  DECLARE_EVENT1(OnUnitEvade, BWAPI::Unit*);
  /**
   * @brief The OnUnitShow event is raised when a unit becomes visible.
   */
  DECLARE_EVENT1(OnUnitShow, BWAPI::Unit*);
  /**
   * @brief The OnUnitHide event is raised when a unit becomes invisible.
   */
  DECLARE_EVENT1(OnUnitHide, BWAPI::Unit*);
  /**
   * @brief The OnUnitCreate event is raised when a unit is created, while that unit is in our field of view.
   */
  DECLARE_EVENT1(OnUnitCreate, BWAPI::Unit*);
  /**
   * @brief The OnUnitDestroy event is raised when a unit is destroyed, just before it evades.
   */
  DECLARE_EVENT1(OnUnitDestroy, BWAPI::Unit*);
  /**
   * @brief The OnUnitMorph event is raised when a unit changes type.
   */
  DECLARE_EVENT2(OnUnitMorph, BWAPI::Unit*, BWAPI::UnitType);
  /**
   * @brief The OnUnitRenegade event is raised when a unit renegades to another player.
   */
  DECLARE_EVENT2(OnUnitRenegade, BWAPI::Unit*, BWAPI::Player*);

  namespace detail
  {
    struct AdditionalUnitTypeData
    {
      std::set<BWAPI::UnitType> morphTypes;
      std::set<BWAPI::UnitType> allDerivedMorphTypes;
      std::set<BWAPI::UnitType> allDerivedMobileMorphTypes;

      void addUnitType(BWAPI::UnitType unitType);
    };

    class Singleton : public Component
    {
    public:
      Singleton();

      ::UnitData& getUnitData(BWAPI::Unit* unit)
      {
        ::UnitData* unitData = unitManager.getUnitData(unit);
#ifdef USE_SPAR_LOG
        if (unitData == NULL)
        {
          SPAR_LOG(LogTypes.GENERAL_ERROR, "Error accessing the unit data of a destroyed unit \"%s\"",
            toString(unit).c_str());
        }
#endif
        assert(unitData != NULL);
        return *unitData;
      }

      BWAPI::UnitType updateUnitType(BWAPI::Unit* unit)
      {
        ::UnitData& unitData = getUnitData(unit);
        BWAPI::UnitType previousType = unitData.m_lastType;
        unitData.m_lastType = unit->getType();
        return previousType;
      }

      BWAPI::Player* updatePlayer(BWAPI::Unit* unit)
      {
        ::UnitData& unitData = getUnitData(unit);
        BWAPI::Player* previousPlayer = unitData.m_lastPlayer;
        unitData.m_lastPlayer = unit->getPlayer();
        return previousPlayer;
      }

      void setDestroyed(BWAPI::Unit* unit)
      {
        ::UnitData& unitData = getUnitData(unit);
        unitData.m_isDestroyed = true;
      }

      std::vector<BWAPI::Unit*> evadedUnits;

      AdditionalUnitTypeData additionalUnitTypeData[NB_UNITS_TYPE];

      OnUnitDiscover unitDiscoverEvent;
      OnUnitEvade unitEvadeEvent;
      OnUnitShow unitShowEvent;
      OnUnitHide unitHideEvent;
      OnUnitCreate unitCreateEvent;
      OnUnitDestroy unitDestroyEvent;
      OnUnitMorph unitMorphEvent;
      OnUnitRenegade unitRenegadeEvent;

      UnitManager unitManager;

    protected:
      virtual void _output(std::ostream& out) const
      {
        out << "BWAPI Singletion";
      }
    };

    extern Singleton singleton;
    extern double mapDiagonalLength;
    extern DWORD mainThreadId;
  }

  void Addon_Init();

  inline bool isOnMainThread()
  {
    const DWORD currentThreadId = ::GetCurrentThreadId();

    return detail::mainThreadId == currentThreadId;
  }

  inline DWORD getMainThreadId()
  {
    return detail::mainThreadId;
  }

  inline void setUnitDataFactory(UnitDataFactory* unitDataFactory)
  {
    detail::singleton.unitManager.setUnitDataFactory(unitDataFactory);

    //
    // Initialize unit data for the existing units
    //
    const std::set<BWAPI::Unit*>& units = BWAPI::Broodwar->getAllUnits();
    for (std::set<BWAPI::Unit*>::const_iterator it = units.begin(); it != units.end(); ++it)
    {
      detail::singleton.unitManager.addUnitData(*it);
    }
  }

  inline ::UnitData& getUnitData(BWAPI::Unit* unit)
  {
    return detail::singleton.getUnitData(unit);
  }

  //
  // Native events
  //
  inline OnUnitDiscover& getUnitDiscoverEvent() { return detail::singleton.unitDiscoverEvent; }
  inline OnUnitEvade& getUnitEvadeEvent() { return detail::singleton.unitEvadeEvent; }
  inline OnUnitShow& getUnitShowEvent() { return detail::singleton.unitShowEvent; }
  inline OnUnitHide& getUnitHideEvent() { return detail::singleton.unitHideEvent; }
  inline OnUnitCreate& getUnitCreateEvent() { return detail::singleton.unitCreateEvent; }
  inline OnUnitDestroy& getUnitDestroyEvent() { return detail::singleton.unitDestroyEvent; }
  inline OnUnitMorph& getUnitMorphEvent() { return detail::singleton.unitMorphEvent; }
  inline OnUnitRenegade& getUnitRenegadeEvent() { return detail::singleton.unitRenegadeEvent; }

  /**
   * @brief The OnIndividualUnitDestroy event is raised when a particular unit is destroyed (just before it evades).
   */
  typedef ::UnitData::OnIndividualUnitDestroy OnIndividualUnitDestroy;
  inline OnIndividualUnitDestroy& getIndividualUnitDestroyEvent(BWAPI::Unit* unit)
  {
    return getUnitData(unit).getIndividualUnitDestroyEvent();
  }
  /**
   * @brief The OnIndividualUnitEvade event is raised when a particular unit evades from our field of view.
   *        Call BWAPI::isDestroyed() to check whether the unit is destroyed (from a previous onUnitDestroy event).
   */
  typedef ::UnitData::OnIndividualUnitEvade OnIndividualUnitEvade;
  inline OnIndividualUnitEvade& getIndividualUnitEvadeEvent(BWAPI::Unit* unit)
  {
    return getUnitData(unit).getIndividualUnitEvadeEvent();
  }
  //typedef UnitData::OnIndividualUnitMorph OnIndividualUnitMorph;
  //inline OnIndividualUnitMorph& getOnIndividualUnitMorphEvent(BWAPI::Unit* unit)
  //{
  //  return getUnitData(unit).getIndividualUnitMorphEvent();
  //}
  //typedef UnitData::OnIndividualUnitRenegade OnIndividualUnitRenegade;
  //inline OnIndividualUnitRenegade& getOnIndividualUnitRenegadeEvent(BWAPI::Unit* unit)
  //{
  //  return getUnitData(unit).getIndividualUnitRenegadeEvent();
  //}

  inline void onUnitDiscover(BWAPI::Unit* unit)
  {
    SPAR_LOG(LogTypes.ON_DISCOVER, "onUnitDiscover(0x%x): %s %s", unit,
      (unit->getPlayer() == BWAPI::Broodwar->self() ? "Own" : (unit->getPlayer()->isNeutral() ? "Neutral" : "Enemy")),
      toString(unit).c_str());

    detail::singleton.unitManager.addUnitData(unit);

    detail::singleton.unitDiscoverEvent.raise(unit);
  }
  inline void onUnitEvade(BWAPI::Unit* unit)
  {
    // Raise onUnitDestroy event before onUnitEvade
    detail::singleton.evadedUnits.push_back(unit);
  }
  inline void onUnitEvadeImpl(BWAPI::Unit* unit)
  {
    SPAR_LOG(LogTypes.ON_EVADE, "onUnitEvade(0x%x): %s %s", unit,
      (unit->getPlayer() == BWAPI::Broodwar->self() ? "Own" : (unit->getPlayer()->isNeutral() ? "Neutral" : "Enemy")),
      toString(unit).c_str());

    ::UnitData& unitData = getUnitData(unit);
    unitData.getIndividualUnitEvadeEvent().raise();
    detail::singleton.unitEvadeEvent.raise(unit);

    if (unitData.isDestroyed())
    {
      detail::singleton.unitManager.removeUnitData(unit);
    }
#ifdef _DEBUG_COMPONENT
    else
    {
      unitData.getIndividualUnitDestroyEvent()._verifyPendingSubscriptions("ERROR BWAPI::onEvade(): Make sure to unsubscribe event handler \"%s\" from event \"%s\" when the unit evades in the fog of war.");
      unitData.getIndividualUnitEvadeEvent()._verifyPendingSubscriptions("ERROR BWAPI::onEvade(): Make sure to unsubscribe event handler \"%s\" from event \"%s\" when the unit evades in the fog of war.");
    }
#endif
  }
  inline void onUnitShow(BWAPI::Unit* unit)
  {
    SPAR_LOG(LogTypes.ON_SHOW, "onUnitShow(0x%x): %s %s", unit,
      (unit->getPlayer() == BWAPI::Broodwar->self() ? "Own" : (unit->getPlayer()->isNeutral() ? "Neutral" : "Enemy")),
      toString(unit).c_str());

    detail::singleton.unitShowEvent.raise(unit);
  }
  inline void onUnitHide(BWAPI::Unit* unit)
  {
    SPAR_LOG(LogTypes.ON_HIDE, "onUnitHide(0x%x): %s %s", unit,
      (unit->getPlayer() == BWAPI::Broodwar->self() ? "Own" : (unit->getPlayer()->isNeutral() ? "Neutral" : "Enemy")),
      toString(unit).c_str());

    detail::singleton.unitHideEvent.raise(unit);
  }
  inline void onUnitCreate(BWAPI::Unit* unit)
  {
    SPAR_LOG(LogTypes.ON_CREATE, "onUnitCreate(0x%x): %s %s", unit,
      (unit->getPlayer() == BWAPI::Broodwar->self() ? "Own" : (unit->getPlayer()->isNeutral() ? "Neutral" : "Enemy")),
      toString(unit).c_str());

    detail::singleton.unitManager.addUnitData(unit);

    detail::singleton.unitCreateEvent.raise(unit);
  }
  inline void onUnitDestroy(BWAPI::Unit* unit)
  {
    SPAR_LOG(LogTypes.ON_DESTROY, "onUnitDestroy(0x%x): %s %s", unit,
      (unit->getPlayer() == BWAPI::Broodwar->self() ? "Own" : (unit->getPlayer()->isNeutral() ? "Neutral" : "Enemy")),
      toString(unit).c_str());

    detail::singleton.setDestroyed(unit);

    getUnitData(unit).getIndividualUnitDestroyEvent().raise();
    detail::singleton.unitDestroyEvent.raise(unit);
  }
  inline void onUnitMorph(BWAPI::Unit* unit)
  {
    SPAR_LOG(LogTypes.ON_MORPH, "onUnitMorph(0x%x): %s %s", unit,
      (unit->getPlayer() == BWAPI::Broodwar->self() ? "Own" : (unit->getPlayer()->isNeutral() ? "Neutral" : "Enemy")),
      toString(unit).c_str());

    BWAPI::UnitType previousUnitType = detail::singleton.updateUnitType(unit);

    if (previousUnitType != unit->getType())
      detail::singleton.unitMorphEvent.raise(unit, previousUnitType);
  }
  inline void onUnitRenegade(BWAPI::Unit* unit)
  {
    SPAR_LOG(LogTypes.ON_RENEGADE, "onUnitRenegade(0x%x): %s %s", unit,
      (unit->getPlayer() == BWAPI::Broodwar->self() ? "Own" : (unit->getPlayer()->isNeutral() ? "Neutral" : "Enemy")),
      toString(unit).c_str());

    BWAPI::Player* previousPlayer = detail::singleton.updatePlayer(unit);

    if (previousPlayer != unit->getPlayer())
      detail::singleton.unitRenegadeEvent.raise(unit, previousPlayer);
  }
  inline void onUnitComplete(BWAPI::Unit* unit)
  {
    SPAR_LOG(LogTypes.ON_COMPLETE, "onUnitComplete(0x%x): %s %s", unit,
      (unit->getPlayer() == BWAPI::Broodwar->self() ? "Own" : (unit->getPlayer()->isNeutral() ? "Neutral" : "Enemy")),
      toString(unit).c_str());
    // TODO
  }
  inline void onNukeDetect(BWAPI::Position target)
  {
    SPAR_LOG(LogTypes.ON_NUKE_DETECT, "onNukeDetect%s", toString(target).c_str());
    // TODO
  }
  inline void onFrame()
  {
    SPAR_LOG(LogTypes.ON_FRAME, "onFrame()");

    for (std::vector<BWAPI::Unit*>::const_iterator it = detail::singleton.evadedUnits.begin();
         it != detail::singleton.evadedUnits.end();
         ++it)
    {
      BWAPI::Unit* unit = *it;

      // We could decide to remove the onUnitEvade event for destroyed units
      onUnitEvadeImpl(unit);
    }
    detail::singleton.evadedUnits.clear();
  }

  /**
   * @param unit A particular unit.
   * @return Whether the unit was previously flagged as destroyed by a onUnitDestroy event.
   */
  inline bool isDestroyed(BWAPI::Unit* unit)
  {
    return getUnitData(unit).isDestroyed();
  }
  /**
   * @param unitType A particular unit type.
   * @return All the unit types the unit may morph in.
   */
  inline const std::set<BWAPI::UnitType>& getImmediateMorphTypes(BWAPI::UnitType unitType)
  {
    return detail::singleton.additionalUnitTypeData[unitType].morphTypes;
  }
  /**
   * @param unitType A particular unit type.
   * @return All the unit types the unit may eventually morph in.
   */
  inline const std::set<BWAPI::UnitType>& getAllDerivedMorphTypes(BWAPI::UnitType unitType)
  {
    return detail::singleton.additionalUnitTypeData[unitType].allDerivedMorphTypes;
  }
  /**
   * @param unitType A particular unit type.
   * @param newUnitType A new unit type.
   * @return Whether a unit of a particular type can morph to the new type.
   */
  inline bool canMorphTo(BWAPI::UnitType unitType, BWAPI::UnitType newUnitType)
  {
    const std::set<BWAPI::UnitType>& derivedMorphTypes = getAllDerivedMorphTypes(unitType);
    return derivedMorphTypes.find(newUnitType) != derivedMorphTypes.end();
  }
  /**
   * @param unitType A particular unit type.
   * @return All the mobile unit types the unit may eventually morph in.
   */
  inline const std::set<BWAPI::UnitType>& getAllDerivedMobileMorphTypes(BWAPI::UnitType unitType)
  {
    return detail::singleton.additionalUnitTypeData[unitType].allDerivedMobileMorphTypes;
  }
  /**
   * @param unitType A particular unit type.
   * @return Whether the unit type may move (not necessarily when commanded to do so, like larvas).
   */
  inline bool isMobile(BWAPI::UnitType unitType)
  {
    return unitType.canMove() || unitType.isFlyingBuilding();
  }
  /**
   * @param unitType A particular unit type.
   * @return Whether the unit type may be loaded in a transport.
   */
  inline bool isLoadable(BWAPI::UnitType unitType)
  {
    static const int SPACE_REQUIRED_FOR_UNLOADABLE_UNITS = 255;
    return unitType.spaceRequired() != SPACE_REQUIRED_FOR_UNLOADABLE_UNITS;
  }
  /**
   * @param transportType The type of transport unit.
   * @param unitType A particular unit type.
   * @return Whether a unit of the particular type may be loaded in a transport of the particular type.
   */
  inline bool canLoadUnit(BWAPI::UnitType transportType, BWAPI::UnitType unitType)
  {
    assert(transportType.spaceProvided() != 0);
    return isLoadable(unitType) &&
           (transportType != BWAPI::UnitTypes::Terran_Bunker || unitType.isOrganic());
  }
  /**
   * @param transportType The type of transport unit.
   * @return Whether troops inside the transport unit are destroyed when the transport unit is itself destroyed.
   */
  inline bool troopsDestroyedInTransport(BWAPI::UnitType transportType)
  {
    assert(transportType.spaceProvided() != 0);
    return transportType != BWAPI::UnitTypes::Terran_Bunker;
  }
  /**
   * @brief Applies a function for each tile under the specified building.
   * @tparam TileFunction Type of function to apply.
   * @param tile The tile position of the building.
   * @param unitType The type of building.
   * @param function The function to apply on each tile the building is under.
   * @return A copy of the function applied to all tiles.
   */
  template <class TileFunction>
  inline TileFunction forEachTileUnderBuilding(BWAPI::TilePosition tile, BWAPI::UnitType unitType, TileFunction function)
  {
    assert(unitType.isBuilding());

    // Works for buildings since they have a tile-wise resolution
    const int tileWidth = unitType.tileWidth();
    const int tileHeight = unitType.tileHeight();
    for (int x = 0; x < tileWidth; ++x)
      for (int y = 0; y < tileHeight; ++y)
        function(tile.x() + x, tile.y() + y);
  }
  /**
   * @brief Applies a function for each tile under the specified unit (which may be a building).
   * @tparam TileFunction Type of function to apply.
   * @param tile The tile position of the unit (serves as optimization in the case the unit is a building).
   * @param position The position of the unit.
   * @param unitType The type of unit.
   * @param function The function to apply on each tile the unit is under.
   * @param additionalTiles The number of additional tiles around the unit to consider.
   * @return A copy of the function applied to all tiles.
   */
  template <class TileFunction>
  inline TileFunction forEachTileUnderUnit(BWAPI::TilePosition tile, BWAPI::Position position, BWAPI::UnitType unitType, TileFunction function, int additionalTiles = 0)
  {
    //
    //see BWAPI::GameUnits.cpp line 351
    //

    if (unitType.isBuilding())
    {
      unused(position);

      // Works for buildings since they have a tile-wise resolution

      const int startX = std::max(0, tile.x() - additionalTiles);
      const int endX   = std::min(BWAPI::Broodwar->mapWidth(), tile.x() + unitType.tileWidth() + additionalTiles);
      const int startY = std::max(0, tile.y() - additionalTiles);
      const int endY   = std::min(BWAPI::Broodwar->mapHeight(), tile.y() + unitType.tileHeight() + additionalTiles);

      assert(startX >= 0 && startX < BWAPI::Broodwar->mapWidth());
      assert(endX >= 0 && endX <= BWAPI::Broodwar->mapWidth());
      assert(startY >= 0 && startY < BWAPI::Broodwar->mapHeight());
      assert(endY >= 0 && endY <= BWAPI::Broodwar->mapHeight());

      for (int x = startX; x < endX; ++x)
        for (int y = startY; y < endY; ++y)
          function(x, y);
    }
    else
    {
      unused(tile);

      //int startX = i->getLeft() / TILE_SIZE;
      //int endX   = (i->getRight() + TILE_SIZE - 1) / TILE_SIZE; // Division - round up
      //int startY = i->getTop() / TILE_SIZE;
      //int endY   = (i->getBottom() + TILE_SIZE - 1) / TILE_SIZE;
      //for (int x = startX; x < endX && x < mapSize.cx; ++x)
      //  for (int y = startY; y < endY && y < mapSize.cy; ++y)
      //    unitsOnTileData[x][y].insert(i);

      const int startX = std::max(0, (position.x() - unitType.dimensionLeft()) / TILE_SIZE - additionalTiles);
      const int endX   = std::min(BWAPI::Broodwar->mapWidth(), (position.x() + unitType.dimensionRight() + TILE_SIZE - 1) / TILE_SIZE + additionalTiles); // Division - round up
      const int startY = std::max(0, (position.y() - unitType.dimensionUp()) / TILE_SIZE - additionalTiles);
      const int endY   = std::min(BWAPI::Broodwar->mapHeight(), (position.y() + unitType.dimensionUp() + TILE_SIZE - 1) / TILE_SIZE + additionalTiles);

      assert(startX >= 0 && startX < BWAPI::Broodwar->mapWidth());
      assert(endX >= 0 && endX <= BWAPI::Broodwar->mapWidth());
      assert(startY >= 0 && startY < BWAPI::Broodwar->mapHeight());
      assert(endY >= 0 && endY <= BWAPI::Broodwar->mapHeight());

      for (int x = startX; x < endX; ++x)
        for (int y = startY; y < endY; ++y)
          function(x, y);
    }
    return function;
  }
  /**
   * @brief Checks whether the current unit is in our field of view.
   * @param tile The tile position of the unit (serves as optimization in the case the unit is a building).
   * @param position The position of the unit.
   * @param unitType The type of unit.
   * @param additionalTiles The number of additional tiles to check around the unit.
   * @return Whether the unit is in our field of view.
   */
  inline bool isInFieldOfView(BWAPI::TilePosition tile, BWAPI::Position position, BWAPI::UnitType unitType, int additionalTiles = 0)
  {
    struct AtLeastOneTileVisible
    {
    public:
      AtLeastOneTileVisible()
        : m_visible(false)
      {}
      void operator()(int x, int y)
      {
        m_visible = m_visible || BWAPI::Broodwar->isVisible(x, y);
      }
      bool getResult() const
      {
        return m_visible;
      }
    private:
      bool m_visible;
    };

    return BWAPI::forEachTileUnderUnit(tile, position, unitType, AtLeastOneTileVisible(), additionalTiles).getResult();
  }

  inline bool isOnFrontier(TilePosition tile)
  {
    return (tile.x() == 0 || tile.y() == 0 || tile.x() == (Broodwar->mapWidth() - 1)  || tile.y() == (Broodwar->mapHeight() - 1));
  }

  inline BWAPI::Position getCenter(BWAPI::TilePosition tile)
  {
    return BWAPI::Position((tile.x() * TILE_SIZE) + (TILE_SIZE / 2), (tile.y() * TILE_SIZE) + (TILE_SIZE / 2));
  }

  inline double getMapDiagonalLength()
  {
    return detail::mapDiagonalLength;
  }

  inline double getDistance(BWAPI::TilePosition tile, BWAPI::TilePosition goal)
  {
    double dx = std::abs(tile.x() - goal.x());
    double dy = std::abs(tile.y() - goal.y());
    return SQRT_OF_TWO * std::min(dx, dy) + std::abs(dx - dy);
  }

  struct tile_position_hash : std::unary_function<BWAPI::TilePosition, std::size_t>
  {
    std::size_t operator()(BWAPI::TilePosition pos) const
    {
      return static_cast<std::size_t>((pos.y() << 16) | pos.x());
    }
  };

  struct tile_position_hash_ordering : stdext::hash_compare<BWAPI::TilePosition, std::less<BWAPI::TilePosition>>
  {
    std::size_t operator()(BWAPI::TilePosition pos) const
    {
      return static_cast<std::size_t>((pos.y() << 16) | pos.x());
    }
    bool operator()(BWAPI::TilePosition pos1, BWAPI::TilePosition pos2) const
    {
      if (pos1.x() < pos2.x())
        return true;
      else if (pos1.x() > pos2.x())
        return false;
      else
        return pos1.y() < pos2.y();
    }
  };

  inline std::ostream& operator<<(std::ostream& out, const TilePosition& tile)
  {
    out << "Tile(" << tile.x() << ", " << tile.y() << ")";
    return out;
  }
  inline std::ostream& operator<<(std::ostream& out, const Position& position)
  {
    out << "(" << position.x() << ", " << position.y() << ")";
    return out;
  }
  inline std::ostream& operator<<(std::ostream& out, const Unit& unit)
  {
    out << unit.getType().getName() << " " << unit.getID();
    if (unit.getPosition() != Positions::None)
      out << " (" << unit.getPosition().x() << ", " << unit.getPosition().y() << ")";
    return out;
  }
  inline std::ostream& operator<<(std::ostream& out, const UnitType& unitType)
  {
    out << unitType.getName();
    return out;
  }
  inline std::ostream& operator<<(std::ostream& out, const TechType& techType)
  {
    out << techType.getName();
    return out;
  }
  inline std::ostream& operator<<(std::ostream& out, const UpgradeType& upgradeType)
  {
    out << upgradeType.getName();
    return out;
  }
  inline std::ostream& operator<<(std::ostream& out, const Player& player)
  {
    out << player.getName();
    return out;
  }
}
