#pragma once
#include "CompositeLocation.h"
#include "Tile.h"
#include <boost/iterator/transform_iterator.hpp>
#include <list>
#include <hash_map>

class PerceptualState;
class UnitRecord;
class AddUnitToTile;
class AirLocation;
class Region;
class BaseLocation;
class Chokepoint;
class LocationFactory;

/**
 * @brief The map stores a decomposition of the terrain into tiles and regions.
 */
class Map : public CompositeLocation
{
public:
  typedef boost::transform_iterator<extract_pair_second<stdext::hash_map<const BWTA::Chokepoint*, Chokepoint*>::iterator::value_type>, 
                                    stdext::hash_map<const BWTA::Chokepoint*, Chokepoint*>::iterator> 
          chokepoint_iterator;
  typedef boost::transform_iterator<extract_pair_second_const<stdext::hash_map<const BWTA::Chokepoint*, Chokepoint*>::const_iterator::value_type>, 
                                    stdext::hash_map<const BWTA::Chokepoint*, Chokepoint*>::const_iterator> 
          chokepoint_const_iterator;  
  typedef boost::transform_iterator<extract_pair_second<stdext::hash_map<const BWTA::Region*, Region*>::iterator::value_type>, 
                                    stdext::hash_map<const BWTA::Region*, Region*>::iterator> 
          region_iterator;
  typedef boost::transform_iterator<extract_pair_second_const<stdext::hash_map<const BWTA::Region*, Region*>::const_iterator::value_type>, 
                                    stdext::hash_map<const BWTA::Region*, Region*>::const_iterator> 
          region_const_iterator;  
  typedef boost::transform_iterator<extract_pair_second<stdext::hash_map<const BWTA::BaseLocation*, BaseLocation*>::iterator::value_type>, 
                                    stdext::hash_map<const BWTA::BaseLocation*, BaseLocation*>::iterator> 
          base_location_iterator;
  typedef boost::transform_iterator<extract_pair_second_const<stdext::hash_map<const BWTA::BaseLocation*, BaseLocation*>::const_iterator::value_type>, 
                                    stdext::hash_map<const BWTA::BaseLocation*, BaseLocation*>::const_iterator> 
          base_location_const_iterator;

  Map();

  ~Map();

  bool isInLocation(BWAPI::Position position) const
  {
    unused(position);

    return true;
  }

  virtual bool isInLocation(BWAPI::TilePosition tile) const
  {
    unused(tile);

    return true;
  }

  virtual BWAPI::Position getCurrentCenter() const
  {
    int y = BWAPI::Broodwar->mapHeight() * TILE_SIZE;
    int x = BWAPI::Broodwar->mapWidth() * TILE_SIZE;
    return BWAPI::Position(x/2,y/2);
  }

  virtual std::pair<BWAPI::TilePosition, int> getOldestVisibleTile() const;

  virtual BWAPI::TilePosition getRandomTile() const;

  const Tile* getTile(int x, int y) const
  {
    return &m_tiles[x][y];
  }
  const Tile* getTile(BWAPI::TilePosition tile) const
  {
    return getTile(tile.x(), tile.y());
  }
  const Tile* getTile(BWAPI::Position position) const
  {
    return getTile(BWAPI::TilePosition(position));
  }
  Tile* getTile(int x, int y)
  {
    return &m_tiles[x][y];
  }
  Tile* getTile(BWAPI::TilePosition tile)
  {
    return getTile(tile.x(), tile.y());
  }
  Tile* getTile(BWAPI::Position position)
  {
    return getTile(BWAPI::TilePosition(position));
  }

  Chokepoint* getChokepoint(const BWTA::Chokepoint* chokepoint)
  {
    return m_chokepoints.find(chokepoint)->second;
  }

  Region* getRegion(BWAPI::Position position) const
  {
    return getRegion(BWAPI::TilePosition(position));
  }

  Region* getRegion(BWAPI::TilePosition tile) const;

  Region* getRegion(const BWTA::Region* region) const
  {
    return m_regions.find(region)->second;
  }

  BaseLocation* getBaseLocation(const BWTA::BaseLocation* baseLocation) const
  {
    return m_baseLocations.find(baseLocation)->second;
  }

  const std::set<BaseLocation*>& getEnemyPossibleStartBaseLocations() const
  {
    return m_startBaseLocationsCache;
  }

  BaseLocation* getOwnStartBaseLocation() const
  {
    return m_ownStartBaseLocation;
  }

  std::pair<chokepoint_iterator, chokepoint_iterator> getChokepoints()
  {
    return std::make_pair(chokepoint_iterator(m_chokepoints.begin()), chokepoint_iterator(m_chokepoints.end()));
  }
  std::pair<chokepoint_const_iterator, chokepoint_const_iterator> getChokepoints() const
  {
    return std::make_pair(chokepoint_const_iterator(m_chokepoints.begin()), chokepoint_const_iterator(m_chokepoints.end()));
  }
  size_t getNbChokepoints() const { return m_chokepoints.size(); }

  std::pair<region_iterator, region_iterator> getRegions()
  {
    return std::make_pair(region_iterator(m_regions.begin()), region_iterator(m_regions.end()));
  }
  std::pair<region_const_iterator, region_const_iterator> getRegions() const
  {
    return std::make_pair(region_const_iterator(m_regions.begin()), region_const_iterator(m_regions.end()));
  }
  size_t getNbRegions() const { return m_regions.size(); }
  
  std::pair<base_location_iterator, base_location_iterator> getBaseLocations()
  {
    return std::make_pair(base_location_iterator(m_baseLocations.begin()), base_location_iterator(m_baseLocations.end()));
  }
  std::pair<base_location_const_iterator, base_location_const_iterator> getBaseLocations() const
  {
    return std::make_pair(base_location_const_iterator(m_baseLocations.begin()), base_location_const_iterator(m_baseLocations.end()));
  }
  size_t getNbBaseLocations() const { return m_baseLocations.size(); }

  void updateLocation(UnitRecord* unitRecord);

  void update(UnitRecord* unitRecord);

  void updateOwnUnits()
  {
    std::pair<Map::unit_iterator, Map::unit_iterator> ownUnits = getOwnUnits();
    updateUnits(ownUnits.first, ownUnits.second);
  }

  void updateEnemyUnits()
  {
    std::pair<Map::unit_iterator, Map::unit_iterator> enemyUnits = getEnemyUnits();
    updateUnits(enemyUnits.first, enemyUnits.second);
  }

  void updateNeutralUnits()
  {
    std::pair<Map::unit_iterator, Map::unit_iterator> neutralUnits = getNeutralUnits();
    updateUnits(neutralUnits.first, neutralUnits.second);
  }

protected:
  friend class PerceptualState;
  friend class AddUnitToTile;
  friend class UnitRecord;

  class AddUnitToTile
  {
  public:
    AddUnitToTile(Map* map, UnitRecord* unitRecord, RaiseEventTaskList& deferredEvents)
      : m_map(map)
      , m_unitRecord(unitRecord)
      , m_deferredEvents(deferredEvents)
    {}
    void operator()(int x, int y) const
    {
      Tile* tile = m_map->getTile(x, y);
      tile->onUnitAddedToLocation(m_unitRecord, tile, m_deferredEvents);
  }
  private:
    Map* const m_map;
    UnitRecord* const m_unitRecord;
    RaiseEventTaskList& m_deferredEvents;

    AddUnitToTile& operator=(const AddUnitToTile&);
  };

  virtual void _output(std::ostream& out) const
  {
    out << "Map";
  }
  
  void initialize(LocationFactory& locationFactory);

  template <class UnitIterator>
  void updateUnits(UnitIterator unitBegin, UnitIterator unitEnd);

  void onUnitDiscover(void*, BWAPI::Unit* unit);
  void onUnitEvade(void*, BWAPI::Unit* unit);
  void onUnitRecordDestroy(void*, UnitRecord* unitRecord);
  void onUnitRecordMorph(void*, UnitRecord* unitRecord, BWAPI::UnitType previousType);
  void onUnitRecordRenegade(void*, UnitRecord* unitRecord, BWAPI::Player* previousPlayer);

  std::vector<Tile*> getTiles(const UnitRecord* unitRecord);
  void addUnit(UnitRecord* unitRecord);
  void removeUnit(UnitRecord* unitRecord);

  EVENT_HANDLER1(Map, onUnitDiscover, BWAPI::Unit*) m_unitDiscoverEventHandler;
  Event<boost::tuple<BWAPI::Unit*>>::SubscriberID m_unitDiscoverID;
  EVENT_HANDLER1(Map, onUnitEvade, BWAPI::Unit*) m_unitEvadeEventHandler;
  Event<boost::tuple<BWAPI::Unit*>>::SubscriberID m_unitEvadeID;
  EVENT_HANDLER1(Map, onUnitRecordDestroy, UnitRecord*) m_unitRecordDestroyEventHandler;
  Event<boost::tuple<UnitRecord*>>::SubscriberID m_unitRecordDestroyID;
  EVENT_HANDLER2(Map, onUnitRecordMorph, UnitRecord*, BWAPI::UnitType) m_unitRecordMorphEventHandler;
  Event<boost::tuple<UnitRecord*, BWAPI::UnitType>>::SubscriberID m_unitRecordMorphID;
  EVENT_HANDLER2(Map, onUnitRecordRenegade, UnitRecord*, BWAPI::Player*) m_unitRecordRenegadeEventHandler;
  Event<boost::tuple<UnitRecord*, BWAPI::Player*>>::SubscriberID m_unitRecordRenegadeID;

  BWTA::RectangleArray<Tile> m_tiles;
  AirLocation* const m_airLocation;
  stdext::hash_map<const BWTA::Chokepoint*, Chokepoint*> m_chokepoints;
  stdext::hash_map<const BWTA::Region*, Region*> m_regions;
  stdext::hash_map<const BWTA::BaseLocation*, BaseLocation*> m_baseLocations;
  std::set<BaseLocation*> m_startBaseLocationsCache;
  BaseLocation* m_ownStartBaseLocation;
};

template <class UnitIterator>
void Map::updateUnits(UnitIterator unitBegin, UnitIterator unitEnd)
{
  while (unitBegin != unitEnd)
  {
    UnitIterator next = ++(UnitIterator(unitBegin));

    update(*unitBegin);

    unitBegin = next;
  }
}
