#pragma once
#include "../Utils/Event.h"
#include "../Utils/Functors.h"
#include "../Utils/Vector2.h"
#include "Tracking/Tracking.h"
#include "Locations/Dynamic/GroupRectangularLocation.h"
#include "Locations/Static/AutoPopulated/Map.h"
#include "Locations/Dynamic/UnitCircularLocation.h"
#include "InfluenceMap/InfluenceMap.h" 
#include "InfluenceMap/StructuralImportance.h"
#include "InfluenceMap/Threat.h"
#include "InfluenceMap/Visibility.h"
#include "InfluenceMap/Detection.h"
#include "InfluenceMap/Vulnerability.h"
#include "Processes/MonitoringProcess/MonitoringProcess.h"
#include "Processes/UpdateInfluenceMapProcess.h"
#include "Processes/UpdateUnitRecordsProcess.h"
#include "Reasoning/EnemyArmyComposition.h"
#include "Reasoning/Reasoning.h"
#include "Reasoning/PathPrediction/SimplePathPrediction.h"
#include "Resources/ResourceManager.h"
#include "PerceptualStateUnitData.h"
#include <algorithm>

class TechMonitoringProcess;
class TechTypeObject;
class UpgradeMonitoringProcess;
class UpgradeTypeObject;

/**
 * @brief This class provides data and events analyzed from the game state. It is (and should keep on being)
 *        independent from the decision making modules, as in, a human could play the game and the sensing
 *        algorithms would work regardless.
 * @note The one exception to the independence principle is the onResearchStarted()/onUpgradeStarted() methods,
 *       which are called by decision making processes. The alternative would be to poll the isResearching()/isUpgrading()
 *       flags of the appropriate buildings every frame...
 */
class PerceptualState : public Process
{
public:
  PerceptualState(LocationFactory& locationFactory);

  ~PerceptualState();

  UnitRecord* getUnitRecord(BWAPI::Unit* unit) const
  {
    return dynamic_cast<PerceptualStateUnitData&>(BWAPI::getUnitData(unit)).getUnitRecord();
  }

  void onFrame()
  {
#ifdef NON_FINAL_VERSION
    onFrameDebug();
#endif
  }

  TechMonitoringProcess* onResearchStarted(TechTypeObject* m_tech, BWAPI::Unit* m_building);
  UpgradeMonitoringProcess* onUpgradeStarted(UpgradeTypeObject* m_upgrade, BWAPI::Unit* m_building);

  // TODO: all this getUnits() stuff should return enemy units in fog of war as well (UnitRecords)

  template <class Predicate>
  std::set<BWAPI::Unit*> getUnits(const CircularLocation& location, Predicate predicate) const
  {
    std::set<BWAPI::Unit*> units = BWAPI::Broodwar->getUnitsInRadius(location.getCurrentCenter(), location.getRadius());
    std::set<BWAPI::Unit*>::iterator it = units.begin();
    while (it != units.end())
    {
      std::set<BWAPI::Unit*>::iterator next = ++(std::set<BWAPI::Unit*>::iterator(it));
      if (!predicate(*it))
        units.erase(it);
      it = next;
    }
    return units;
  }

  std::set<BWAPI::Unit*> getUnits(const CircularLocation& location) const
  {
    return BWAPI::Broodwar->getUnitsInRadius(location.getCurrentCenter(), location.getRadius());
  }

  template <class Location>
  std::set<BWAPI::Unit*> getUnits(const Location& location)
  {
    return getUnits(location, True());
  }

  template <class Predicate>
  std::set<BWAPI::Unit*> getUnits(const RectangularLocation& location, Predicate predicate) const
  {
    // Could use this
    //std::set<BWAPI::Unit*> allUnits(BWAPI::Broodwar->getUnitsInRectangle(location.getTopLeftCorner().makeValid(), location.getBottomRightCorner().makeValid()));
    //std::set<BWAPI::Unit*>::iterator it = allUnits.begin();
    //while (it != allUnits.end())
    //{
    //  std::set<BWAPI::Unit*>::iterator next = ++(std::set<BWAPI::Unit*>::iterator(it));
    //  if (!predicate(*it))
    //    allUnits.erase(it);
    //  it = next;
    //}

    const BWAPI::TilePosition topLeft(location.getTopLeftCorner().makeValid());
    int tileLeft = topLeft.x();
    int tileTop = topLeft.y();
    const BWAPI::TilePosition bottomRight(location.getBottomRightCorner().makeValid());
    int tileRight = bottomRight.x();
    int tileBottom = bottomRight.y();

    std::set<BWAPI::Unit*> allUnits;
    for (int x = tileLeft; x <= tileRight; ++x)
    {
      for (int y = tileTop; y <= tileBottom; ++y)
      {
        const std::set<BWAPI::Unit*>& units = BWAPI::Broodwar->getUnitsOnTile(x, y);
        for (std::set<BWAPI::Unit*>::const_iterator it = units.begin(); it != units.end(); ++it)
        {
          BWAPI::Unit* unit= *it;
          BWAPI::Position pos = unit->getPosition();
          if (is_between<int, true>(location.getTopLeftCorner().x(), location.getBottomRightCorner().x())(pos.x()) &&
              is_between<int, true>(location.getTopLeftCorner().y(), location.getBottomRightCorner().y())(pos.y()) &&
              predicate(unit))
            allUnits.insert(unit);
        }
      }
    }
#ifdef _DEBUG
    for (std::set<BWAPI::Unit*>::const_iterator it = allUnits.begin(); it != allUnits.end(); ++it)
    {
      BWAPI::Position pos = (*it)->getPosition();
      bool isBetweenX = is_between<int, true>(location.getTopLeftCorner().x(), location.getBottomRightCorner().x())(pos.x());
      bool isBetweenY = is_between<int, true>(location.getTopLeftCorner().y(), location.getBottomRightCorner().y())(pos.y());
      assert(isBetweenX && isBetweenY);
    }
#endif
    return allUnits;
  }
  template <class Predicate>
  std::set<BWAPI::Unit*> getUnits(const UnitCircularLocation& location, Predicate predicate) const
  {
    return getUnits(location.getCircularLocation(), predicate);
  }
  template <class Predicate>
  std::set<BWAPI::Unit*> getUnits(const GroupRectangularLocation& location, Predicate predicate) const
  {
    if (location.getGroup()->empty())
      return std::set<BWAPI::Unit*>();
    else
      return getUnits(location.getExpandedRectangularLocation(), predicate);
  }

  template <class LocationType>
  std::set<BWAPI::Unit*> getOwnUnits(const LocationType& location) const
  {
    return getUnits(location, IsOwnUnit<BWAPI::Unit>());
  }

  template <class LocationType>
  std::set<BWAPI::Unit*> getEnemyUnits(const LocationType& location) const
  {
    return getUnits(location, IsEnemyUnit<BWAPI::Unit>());
  }

  std::set<BWAPI::Unit*> getEnemiesNearUnit(BWAPI::Unit* unit, int range) const
  {
    return getEnemyUnits(CircularLocation(unit->getPosition(), range));
  }

  /**
   * @brief Checks whether the destination is reachable by a ground unit.
   *        It checks if there is a static obstacle and if the position itself is obstructed (either by other units or by static obstacles).
   * @param source Source position.
   * @param destination Destination position.
   * @return Whether the destination is reachable from the source position.
   */
  bool isPositionReachable(BWAPI::Position source, BWAPI::Position destination) const;

  /**
   * @brief Computes the set of units currently targeting the specified unit.
   * @param unit Any unit.
   * @return The set of units currently targeting the specified unit.
   */
  std::set<BWAPI::Unit*> PerceptualState::getTargetingUnits(BWAPI::Unit* unit) const;

  /**
   * @brief Returns whether a given unit is targeted.
   * @param unit Any unit.
   * @return Whether the specified unit is targeted.
   */
  bool isUnitTargeted(BWAPI::Unit* unit) const;

  /**
   * @brief Computes the farthest position a ground unit can travel to in a given direction.
   * @param position The position from which to compute the destination.
   * @param angle The direction in which the unit will travel.
   * @return The position the unit should travel to.
   */
  BWAPI::Position getGroundFrontier(BWAPI::Position position, const double angle) const;

  /**
   * @brief Searches for an arbitrary position on the border of the enemy influence. Can be used for example to outrange the enemy using Reavers.
   * @param position The initial position to look from.
   * @return Arbitrary position on the border of enemy influence.
   */
  BWAPI::Position getEnemyInfluenceBorder(BWAPI::Position position) const;

  /**
   * @brief Searches for a safe (as in, devoid of enemy influence) position for the given unit to retreat to.
   * @param unit The unit that is to retreat.
   * @return The position to retreat to.
   */
  BWAPI::Position getSafeRetreatPosition(BWAPI::Unit* unit) const;

  int getMapWidth() const { return m_mapWidth; }
  int getMapHeight() const { return m_mapHeight; }

  const InfluenceMapRef<Detection>::map_ptr getDetection() const
  {
    return m_detectionInfluenceMap.get();
  }
  const InfluenceMapRef<Vulnerability>::map_ptr getVulnerability() const
  {
    return m_vulnerabilityInfluenceMap.get();
  }
  const InfluenceMapRef<Threat>::map_ptr getSelfThreat() const
  {
    return m_selfThreatInfluenceMap.get();
  }
  const InfluenceMapRef<Threat>::map_ptr getEnemyThreat() const
  {
    return m_enemyThreatInfluenceMap.get();
  }
  const InfluenceMapRef<Visibility>::map_ptr getVisibility() const
  {
    return m_visibilityInfluenceMap.get();
  }

  /**
   * @return The map storing a decomposition of the terrain into tiles and regions.
   */
  Map* getMap() { return &m_map; }
  /**
   * @return The map storing a decomposition of the terrain into tiles and regions.
   */
  const Map* getMap() const { return &m_map; }
  /**
   * @return Component used to track the enemy army composition.
   */
  const EnemyArmyComposition* getEnemyArmyComposition() const { return &m_enemyArmyComposition; }
  /**
   * @return Component used to perform reasoning about the game state.
   */
  const Reasoning* getReasoning() const { return &m_reasoning; }
  /**
   * @return Component used to model resources acquisition rate.
   */
  const ResourceManager* getResourceManager() const { return &m_resourceManager; }

  //
  // Dynamically creating static locations will require events if we allow them to be subsequently deleted
  //

  DECLARE_EVENT1(OnUnitCompleted, BWAPI::Unit*);
  OnUnitCompleted& getUnitCompletedEvent() const { return m_unitCompletedEvent; }

  DECLARE_EVENT1(OnUnitRecordCreate, UnitRecord*);
  OnUnitRecordCreate& getUnitRecordCreateEvent() const { return m_unitRecordCreateEvent; }
  DECLARE_EVENT1(OnUnitRecordDestroy, UnitRecord*);
  OnUnitRecordDestroy& getUnitRecordDestroyEvent() const { return m_unitRecordDestroyEvent; }
  DECLARE_EVENT2(OnUnitRecordMorph, UnitRecord*, BWAPI::UnitType);
  OnUnitRecordMorph& getUnitRecordMorphEvent() const { return m_unitRecordMorphEvent; }
  DECLARE_EVENT2(OnUnitRecordRenegade, UnitRecord*, BWAPI::Player*);
  OnUnitRecordRenegade& getUnitRecordRenegadeEvent() const { return m_unitRecordRenegadeEvent; }

  typedef PerceptualStateUnitData PerceptualStateUnitData;

  /////////
  // GUI //
  /////////
  const UnitRecord* getUnitRecord(size_t unitRecordID)
  {
    std::map<size_t, UnitRecordWrapper>::iterator it = m_unitRecords.find(unitRecordID);
    if (it == m_unitRecords.end())
      return NULL;
    else
      return it->second.m_unitRecord;
  }

protected:
  friend PerceptualStateUnitData;

  void onFrameDebug();

  virtual void onStart();

  virtual void onEnd();

  virtual void _output(std::ostream& out) const
  {
    out << "Perceptual state";
  }

  void onUnitProcessTerminate(void* data)
  {
    const MonitoringProcess* unitProcess = reinterpret_cast<const MonitoringProcess*>(data);
    m_unitProcesses.erase(unitProcess->m_posInContainer);
  }

  void onUnitRecordCreate(UnitRecord* unitRecord);

  void onUnitRecordDestroy(void* data);

  void onUnitRecordMorph(void* data, BWAPI::UnitType previousUnitType)
  {
    UnitRecord* unitRecord = reinterpret_cast<UnitRecord*>(data);
    m_unitRecordMorphEvent.raise(unitRecord, previousUnitType);
  }

  void onUnitRecordRenegade(void* data, BWAPI::Player* previousPlayer)
  {
    UnitRecord* unitRecord = reinterpret_cast<UnitRecord*>(data);
    m_unitRecordRenegadeEvent.raise(unitRecord, previousPlayer);
  }

  void onUnitDiscover(void*, BWAPI::Unit* unit);
  void onUnitEvade(void*, BWAPI::Unit* unit);
  void onUnitDestroy(void*, BWAPI::Unit* unit);
  void onUnitMorph(void*, BWAPI::Unit* unit, BWAPI::UnitType previousType);
  void onUnitRenegade(void*, BWAPI::Unit* unit, BWAPI::Player* previousPlayer);

  void monitorUnitCompletion(BWAPI::Unit* unit, const char* description);

  void displayMap();
  void displayUnitsInFogOfWar();

  struct UnitRecordWrapper
  {
    UnitRecordWrapper(UnitRecord* unitRecord)
      : m_unitRecord(unitRecord)
    {}
    UnitRecord* m_unitRecord;
    std::map<size_t, UnitRecordWrapper>::const_iterator m_posInList;
    Event<boost::tuple<>>::SubscriberID m_individualUnitRecordDestroyID;
    Event<boost::tuple<BWAPI::UnitType>>::SubscriberID m_individualUnitRecordMorphID;
    Event<boost::tuple<BWAPI::Player*>>::SubscriberID m_individualUnitRecordRenegadeID;
  };

  /////////////////////////////////////////////////////////////////////////////

private:
  void addUnitWrapper(UnitRecord* unitRecord)
  {
    std::pair<std::map<size_t, UnitRecordWrapper>::iterator, bool> result = 
      m_unitRecords.insert(std::make_pair(unitRecord->getID(), UnitRecordWrapper(unitRecord)));
    assert(result.second);
    UnitRecordWrapper& wrapper = result.first->second;
    wrapper.m_posInList = result.first;
    wrapper.m_individualUnitRecordDestroyID = unitRecord->getIndividualUnitRecordDestroyEvent().subscribe(&m_unitRecordDestroyEventHandler, &wrapper);
    wrapper.m_individualUnitRecordMorphID = unitRecord->getIndividualUnitRecordMorphEvent().subscribe(&m_unitRecordMorphEventHandler, unitRecord);
    wrapper.m_individualUnitRecordRenegadeID = unitRecord->getIndividualUnitRecordRenegadeEvent().subscribe(&m_unitRecordRenegadeEventHandler, unitRecord);

    unitRecord->acquire(this);
  }

  void removeUnitWrapper(UnitRecordWrapper* unitWrapper)
  {
    UnitRecord* unitRecord = unitWrapper->m_unitRecord;
    unitRecord->getIndividualUnitRecordDestroyEvent().unsubscribe(unitWrapper->m_individualUnitRecordDestroyID);
    unitRecord->getIndividualUnitRecordMorphEvent().unsubscribe(unitWrapper->m_individualUnitRecordMorphID);
    unitRecord->getIndividualUnitRecordRenegadeEvent().unsubscribe(unitWrapper->m_individualUnitRecordRenegadeID);
    m_unitRecords.erase(unitWrapper->m_posInList);

    unitRecord->release(this);
  }

  // Events
  EVENT(OnUnitCompleted) m_unitCompletedEvent;
  //
  EVENT(OnUnitRecordCreate) m_unitRecordCreateEvent;
  EVENT(OnUnitRecordDestroy)m_unitRecordDestroyEvent;
  EVENT(OnUnitRecordMorph) m_unitRecordMorphEvent;
  EVENT(OnUnitRecordRenegade) m_unitRecordRenegadeEvent;

  // Influence maps
  InfluenceMapRef<Threat> m_enemyThreatInfluenceMap;
  InfluenceMapRef<Threat> m_selfThreatInfluenceMap;
  InfluenceMapRef<Visibility> m_visibilityInfluenceMap;
  InfluenceMapRef<Detection> m_detectionInfluenceMap;
  //SC 2012/01/28: not used at the moment
  //InfluenceMapRef<StructuralImportance> m_structureInfluenceMap;
  InfluenceMapRef<Vulnerability> m_vulnerabilityInfluenceMap;

  const int m_mapWidth;
  const int m_mapHeight;

  // Misc
  std::map<size_t, UnitRecordWrapper> m_unitRecords;
  stdext::hash_map<BWAPI::Unit*, MonitoringProcess*> m_unitProcesses;
  EVENT_HANDLER(PerceptualState, onUnitProcessTerminate) m_unitProcessTerminateEventHandler;
  EVENT_HANDLER(PerceptualState, onUnitRecordDestroy) m_unitRecordDestroyEventHandler;
  EVENT_HANDLER1(PerceptualState, onUnitRecordMorph, BWAPI::UnitType) m_unitRecordMorphEventHandler;
  EVENT_HANDLER1(PerceptualState, onUnitRecordRenegade, BWAPI::Player*) m_unitRecordRenegadeEventHandler;
  EVENT_HANDLER1(PerceptualState, onUnitDiscover, BWAPI::Unit*) m_unitDiscoverEventHandler;
  Event<boost::tuple<BWAPI::Unit*>>::SubscriberID m_unitDiscoverID;
  EVENT_HANDLER1(PerceptualState, onUnitEvade, BWAPI::Unit*) m_unitEvadeEventHandler;
  Event<boost::tuple<BWAPI::Unit*>>::SubscriberID m_unitEvadeID;
  EVENT_HANDLER2(PerceptualState, onUnitMorph, BWAPI::Unit*, BWAPI::UnitType) m_unitMorphEventHandler;
  Event<boost::tuple<BWAPI::Unit*, BWAPI::UnitType>>::SubscriberID m_unitMorphID;
  EVENT_HANDLER2(PerceptualState, onUnitRenegade, BWAPI::Unit*, BWAPI::Player*) m_unitRenegadeEventHandler;
  Event<boost::tuple<BWAPI::Unit*, BWAPI::Player*>>::SubscriberID m_unitRenegadeID;
  EVENT_HANDLER1(PerceptualState, onUnitDestroy, BWAPI::Unit*) m_unitDestroyEventHandler;
  Event<boost::tuple<BWAPI::Unit*>>::SubscriberID m_unitDestroyID;

  LocationFactory& m_locationFactory;
  Tracking m_tracking;
  ResourceManager m_resourceManager;
  Map m_map;
  Reasoning m_reasoning;
  EnemyArmyComposition m_enemyArmyComposition;
  SimplePathPrediction m_pathPrediction;

  // Processes
  UpdateInfluenceMapProcess<Threat> m_updateEnemyThreatInfluenceMap;
  UpdateInfluenceMapProcess<Threat> m_updateSelfThreatInfluenceMap;
  UpdateInfluenceMapProcess<Visibility> m_updateVisibilityInfluenceMap;
  UpdateInfluenceMapProcess<Detection> m_updateDetectionInfluenceMap;
  //UpdateInfluenceMapProcess<StructuralImportance> m_updateStructuralImportanceInfluenceMap;
  UpdateInfluenceMapProcess<Vulnerability> m_updateVulnerabilityInfluenceMap;
  UpdateUnitRecordsProcess m_updateUnits;
};
