#pragma once
#include "../../Utils/Event.h"
#include "../../../Utils/ReferenceCounted.h"
#include "UnitRecordType/UnitRecordType.h"
#include <boost/circular_buffer.hpp>
#include <deque>
#include <vector>

class BaseLocation;
class Region;
class Map;
class Tracking;
class Reasoning;
class PerceptualState;
class UnitRecordManager;
class UnitRecordTypeFactory;
class AutoPopulatedLocation;
class EnemyArmyComposition;
class TransportUnitRecordType;

/**
 * @brief A unit "record" (barring a better name) is the trace of a certain unit which already appeared somewhere
 *        and was never explicitely deleted.
 *
 * A unit "record" (barring a better name) is the trace of a certain unit which already appeared somewhere
 * and was never deleted (by the destroy() function). Units are known to be destroyed when the onUnitDestroy event is
 * actually received, or some form inference can be performed to deduce that a unit has been destroyed.
 * For example when scouting empty land instead of a previously-scouted building which cannot lift off, we deduce that this
 * building has been destroyed.
 *
 * A "feature" (or bug) of BWAPI is that a given enemy unit going in and out of the fog of war keeps the same associated
 * BWAPI::Unit pointer when it becomes accessible again. This greatly simplifies the tracking process.
 *
 * In particular, that allows us to associate a BWAPI::Unit instance to *exactly one* UnitRecord, instead of having to perform
 * tracking by aggregating different traces of the same unit type.
 *
 * Each UnitRecord instance is associated to a BWAPI::Unit instance which existed at some point. Note that the aforementioned
 * BWAPI::Unit instance may now be destroyed. In certain cases, the unit was destroyed but it may never be possible to infer it
 * directly, e.g. a templar disappearing after morphing into an archon in the fog of war.
 */
class UnitRecord : public ReferenceCounted, public virtual Component
{
protected:
  typedef UnitRecordType::SavedState SavedState;

public: 
  /**
   * @brief Constructs a new unit record based on an existing BWAPI::Unit instance.
   */
  UnitRecord(BWAPI::Unit* unit);

  /**
   * @return The unique ID associated to this unit record.
   */
  size_t getID() const
  {
    return m_id;
  }

  Region* getRegion() const;

  /**
   * @return Whether this unit is accessible, i.e. whether it is in our field of view.
   */
  bool isAccessible() const
  {
    // Not yet evaded
    return m_lastUnitEvadedFrame == -1;
  }

  /**
   * @return Whether the unit has been flagged as destroyed (either explicitely or by inference).
   */
  bool isDestroyed() const
  {
    return m_isDestroyed;
  }

  /**
   * @return The last frame at which the unit left our field of view, or -1 if it is visible.
   */
  int getLastEvadedFrame() const
  {
    return m_lastUnitEvadedFrame;
  }

  /**
   * @return Information about the current type of the unit. The UnitRecordType pointer changes when the unit morphes
   *         into something else.
   */
  UnitRecordType* getCurrentUnitRecordType() const
  {
    return m_currentUnitRecordType;
  }

  typedef boost::circular_buffer<std::pair<int, BWAPI::Position>>::const_iterator position_const_iterator;
  std::pair<position_const_iterator, position_const_iterator> getLastPositions() const
  {
    return std::make_pair(m_lastPositions.begin(), m_lastPositions.end());
  }

  /**
   * @brief Adds the current unit's position to the list of saved positions.
   */
  void recordCurrentPosition()
  {
    m_lastPositions.push_back(std::make_pair(BWAPI::Broodwar->getFrameCount(), getPosition()));
  }

#pragma region Path prediction

  class PredictedPath
  {
  public:
    PredictedPath(const UnitRecord* unitRecord)
      : m_unitRecord(unitRecord)
    {}

    BWAPI::Position getPositionAtFrame(int frameNumber) const;

    /**
     * @return The last frame for which information about this path is available. After this frame, the function
     *         getPositionAtFrame() will always return the position at this last frame.
     */
    int getLastPredictionFrame() const
    {
      int lastFrame = (m_positions.empty() ? BWAPI::Broodwar->getFrameCount() :
                                             m_positions.back().first);
      return lastFrame;
    }

    typedef std::vector<std::pair<int, BWAPI::Position>>::const_iterator const_iterator;

    std::vector<std::pair<int, BWAPI::Position>>& getPositions() { return m_positions; }
    const std::vector<std::pair<int, BWAPI::Position>>& getPositions() const { return m_positions; }

  private:
    const UnitRecord* m_unitRecord;

    /** @brief List of [frame number / position] pairs. */
    std::vector<std::pair<int, BWAPI::Position>> m_positions;
  };

  /** @brief List of [probability, paths] pairs. */
  typedef std::vector<std::pair<double, PredictedPath>> PredictedPaths;

  /**
   * @return The predicted future positions of the unit.
   */
  const PredictedPaths& getPredictedPaths() const
  {
    return m_predictedPaths;
  }

  void setPredictedPaths(const UnitRecord::PredictedPaths& predictedPaths)
  {
    m_predictedPaths = predictedPaths;
  }

#pragma endregion

  BWAPI::UnitType getType() const;

  BWAPI::Player* getPlayer() const { return isAccessible() ? m_unit->getPlayer() : m_savedState->getPlayer(); }

  bool isCompleted() const { return isAccessible() ? m_unit->isCompleted() : m_savedState->isCompleted(); }

  BWAPI::TilePosition getTilePosition() const { return isAccessible() ? m_unit->getTilePosition() : m_savedState->getTilePosition(); }
  BWAPI::Position getPosition() const { return isAccessible() ? m_unit->getPosition() : m_savedState->getPosition(); }

  int getHitPoints() const { return isAccessible() ? m_unit->getHitPoints() : m_savedState->getHitPoints(); }
  int getShields() const { return isAccessible() ? m_unit->getShields() : m_savedState->getShields(); }

  UnitRecord* getTransport() const { return m_transport; }

  /**
   * @brief Returns the unit pointer associated with this unit. The unit pointer CANNOT BE RETRIEVED IF THE UNIT IS NOT ACCESSIBLE.
   * @return The unit pointer associated with this unit.
   */
  BWAPI::Unit* getUnit() const
  {
    // WARNING: IF THE UNIT IS NOT ACCESSIBLE (IN FOG OF WAR) THE UNIT POINTER MAY NOT BE VALID ANY MORE

    // If unit is not accessible, use saved state instead.
    // However this can be tolerated for minerals since they always have the same pointer.
    assert(isAccessible() || m_savedState->getType().isMineralField());

    return m_unit;
  }

  /**
   * @brief Returns the last saved state associated with this unit. The saved state CANNOT BE RETRIEVED IF THE UNIT IS ACCESSIBLE
   *        (as in this case you should unit the unit pointer's data directly).
   * @return The saved state associated with this unit.
   */
  const SavedState& getLastKnownState() const
  {
    return *m_savedState;
  }

  DECLARE_EVENT(OnIndividualUnitRecordDestroy);
  OnIndividualUnitRecordDestroy& getIndividualUnitRecordDestroyEvent() const { return m_individualUnitRecordDestroyEvent; }
  DECLARE_EVENT1(OnIndividualUnitRecordMorph, BWAPI::UnitType);
  OnIndividualUnitRecordMorph& getIndividualUnitRecordMorphEvent() const { return m_individualUnitRecordMorphEvent; }
  DECLARE_EVENT1(OnIndividualUnitRecordRenegade, BWAPI::Player*);
  OnIndividualUnitRecordRenegade& getIndividualUnitRecordRenegadeEvent() const { return m_individualUnitRecordRenegadeEvent; }

protected:
  friend class PerceptualState;
  friend class AutoPopulatedLocation;
  friend class BaseLocation;
  friend class Map;
  friend class UnitRecordManager;
  friend class TransportUnitRecordType;
  friend class Reasoning;
  friend class Tracking;

  virtual void _output(std::ostream& out) const;

  virtual SavedState* saveState();

  /**
   * @brief Signals that the unit pointer is now accessible.
   */
  void onUnitDiscover();

  /**
   * @brief Signals that the unit is transitioning outside the field of view; save data.
   */
  void onUnitEvade();

  /**
   * @brief Signals that the unit is possibly morphing into a different type.
   */
  void onUnitMorph()
  {
    assert(isAccessible());
    if (m_unit->getType() != m_currentUnitRecordType->getType())
      morph(m_unit->getType());
  }

  /**
   * @brief Signals that the unit is possibly renegading to another player.
   */
  void onUnitRenegade()
  {
    assert(isAccessible());
    if (m_unit->getPlayer() != m_player)
      renegade(m_unit->getPlayer());
  }

  /**
   * @brief Signals that the unit's last known position was scouted but the unit is not there anymore.
   */
  void onSavedPositionScouted();

  //
  // Transformations
  //

  void morph(const std::set<BWAPI::UnitType>& possibleNewTypes);

  void morph(BWAPI::UnitType newUnitType);

  void renegade(BWAPI::Player* player);

  void setTransport(UnitRecord* transport);

  void onTransportDestroyed(UnitRecord* transport);

  /**
   * @brief Considers the unit represent by this unit record as destroyed.
   *        This is triggered either by the unambiguous onUnitDestroy BWAPI event or by some kind of inference.
   * @param reason A string representing the rationale behind the destruction.
   */
  void destroy(const char* reason);

  //
  // Variables
  //

  /** @brief Unique ID, used to provide a unique identifier to the unit record. Necessary for the GUI. */
  const size_t m_id;

  /** @brief Last known unit pointer, possibly pointing to invalid data if the unit is not currently accessible. */
  BWAPI::Unit* const m_unit;

  /** @brief Whether the destroy() function has been called for this unit record. */
  bool m_isDestroyed;

  /** @brief Last frame where the unit was accessible, or -1 if it is currently accessible. */
  int m_lastUnitEvadedFrame;

  /**
   * @brief First frame where the saved unit's position was invalidated (happens when a unit is first spotted,
   *        then fades in the fog of war, and then its position is scouted but the unit isn't there anymore), 
   *        or -1 if unit's previous position has not been scouted yet.
   *        Note that m_firstSavedPositionInvalidatedFrame is only defined if m_lastUnitEvadedFrame != -1.
   */
  int m_firstSavedPositionInvalidatedFrame;

  /** @brief External transport unit that is possibly carrying the unit. */
  UnitRecord* m_transport;

  /** @brief Last known player controlling the unit. */
  BWAPI::Player* m_player;

  /** @brief Data about the current type of the unit. New UnitRecordType instances may be generated when the unit morphes. */
  AutoPtr<UnitRecordType> m_currentUnitRecordType;

  /** @brief Last saved state about the unit, possibly before it evaded in the fog of war. */
  AutoPtr<SavedState> m_savedState;

  /** @brief History of the last known (tracked) positions of the unit. */
  boost::circular_buffer<std::pair<int, BWAPI::Position>> m_lastPositions;

  /** @brief Predictions about the future position of the unit. Sum of probabilities should be <= 1.
   *         If they do not sum to 1, the remaining probability should be considered as arbitrary
   *         movement, e.g. a uniform distribution around the unit given its speed.
   */
  PredictedPaths m_predictedPaths;

  EVENT(OnIndividualUnitRecordDestroy) m_individualUnitRecordDestroyEvent;
  EVENT(OnIndividualUnitRecordMorph) m_individualUnitRecordMorphEvent;
  EVENT(OnIndividualUnitRecordRenegade) m_individualUnitRecordRenegadeEvent;

#pragma region Locations cache data

  struct TilesLocationParameters
  {
    explicit TilesLocationParameters()
      : m_tile(BWAPI::TilePositions::None)
      , m_position(BWAPI::Positions::None)
      , m_unitType(BWAPI::UnitTypes::None)
    {}
    explicit TilesLocationParameters(BWAPI::TilePosition tile, BWAPI::Position position, BWAPI::UnitType unitType)
      : m_tile(tile)
      , m_position(position)
      , m_unitType(unitType)
    {}
    bool operator==(const TilesLocationParameters& other) const
    {
      return m_tile == other.m_tile &&
             m_position == other.m_position &&
             m_unitType == other.m_unitType;
    }
    bool operator!=(const TilesLocationParameters& other) const
    {
      return !(*this == other);
    }
    TilesLocationParameters& operator=(const TilesLocationParameters& other)
    {
      m_tile = other.m_tile;
      m_position = other.m_position;
      m_unitType = other.m_unitType;
      return *this;
    }
  private:
    BWAPI::TilePosition m_tile;
    BWAPI::Position m_position;
    BWAPI::UnitType m_unitType;
  };

  /**
   * @brief The parameters that were used to calculate the tiles this unit was on (see BWAPI::forEachTileUnderUnit()).
   */
  TilesLocationParameters m_tilesLocationParametersCache;

  /** 
   * @brief Per-[UnitRecord / AutoPopulatedLocation] data. Could be stored in the AutoPopulatedLocations instead,
   *        but that would probably slow down the retrieval of the data in question.
   */
  mutable stdext::hash_map<AutoPopulatedLocation*, void*> m_hostLocations;

  typedef stdext::hash_map<AutoPopulatedLocation*, void*>::iterator DataID;
  std::pair<DataID, bool> addData(AutoPopulatedLocation* location, void* data) const
  {
    return m_hostLocations.insert(std::make_pair(location, data));
  }
  DataID getData(AutoPopulatedLocation* location)
  {
    return m_hostLocations.find(location);
  }
  bool isValid(DataID id) const
  {
    return id != m_hostLocations.end();
  }
  void removeData(DataID id)
  {
    m_hostLocations.erase(id);
  }

#pragma endregion

  static size_t s_id;

private:
  UnitRecord(const UnitRecord& other);
};
