#pragma once
#include "../UnitRecord/UnitRecord.h"
#include <hash_map>

//
// IMPORTANT: If two high templars morph into an archon in the fog of war, one of the templars will never be seen
//            again (its destruction won't ever be confirmed), so the templar pointer will stay in here!
//            Hence this structure only gives a WORST CASE idea of the enemy army composition discovered thus far.
//

class EnemyArmyComposition : public Component
{
protected:
  class UnitWrapper
  {
  public:
    UnitWrapper(UnitRecord* unitRecord)
      : m_enemyUnitRecord(unitRecord)
    {}
    UnitRecord* getUnit() { return m_enemyUnitRecord; }
    const UnitRecord* getUnit() const { return m_enemyUnitRecord; }

    UnitRecord* const m_enemyUnitRecord;
    stdext::hash_multimap<BWAPI::UnitType, UnitWrapper>::const_iterator m_posInContainer;
    Event<boost::tuple<>>::SubscriberID m_unitRecordDestroyID;
    Event<boost::tuple<BWAPI::UnitType>>::SubscriberID m_unitRecordMorphID;
    Event<boost::tuple<BWAPI::Player*>>::SubscriberID m_unitRecordRenegadeID;

  private:
    //const member prevent the use of the assignment operator
    UnitWrapper& operator=(const UnitWrapper&);
  };
  struct extract_unit : std::unary_function<UnitWrapper, UnitRecord*>
  {
    UnitRecord* operator()(UnitWrapper& unitWrapper) const
    {
      return unitWrapper.getUnit();
    }
  };
  struct extract_unit_const : std::unary_function<UnitWrapper, const UnitRecord*>
  {
    extract_unit_const() {}
    extract_unit_const(const extract_unit&) {}

    const UnitRecord* operator()(const UnitWrapper& unitWrapper) const
    {
      return unitWrapper.getUnit();
    }
  };

public:
  class UnitCompositionIterator : public std::iterator<std::forward_iterator_tag, 
                                                       std::pair<BWAPI::UnitType, size_t>>
  {
  public:
    UnitCompositionIterator(const stdext::hash_multimap<BWAPI::UnitType, UnitWrapper>& units,
                            stdext::hash_multimap<BWAPI::UnitType, UnitWrapper>::const_iterator it)
      : m_enemyUnits(units)
      , m_it(it)
    {}
    value_type operator*() const
    {
      return std::make_pair(m_it->first, m_enemyUnits.count(m_it->first));
    }
    UnitCompositionIterator& operator++()
    {
      m_it = m_enemyUnits.upper_bound(m_it->first);
      return *this;
    }
    UnitCompositionIterator operator++(int)
    {
      UnitCompositionIterator copy(*this);
      ++(*this);
      return copy;
    }
    bool operator== (const UnitCompositionIterator& other) const
    {
      assert(&m_enemyUnits == &other.m_enemyUnits);
      return m_it == other.m_it;
    }
    bool operator!= (const UnitCompositionIterator& other) const
    {
      assert(&m_enemyUnits == &other.m_enemyUnits);
      return m_it != other.m_it;
    }
    UnitCompositionIterator& operator=(const UnitCompositionIterator& other)
    {
      assert(&m_enemyUnits == &other.m_enemyUnits);
      m_it = other.m_it;
      return *this;
    }
  private:
    const stdext::hash_multimap<BWAPI::UnitType, UnitWrapper>& m_enemyUnits;
    stdext::hash_multimap<BWAPI::UnitType, UnitWrapper>::const_iterator m_it;
  };

  EnemyArmyComposition();

  ~EnemyArmyComposition();

  /**
   * @param type The type of unit.
   * @return The estimated number of enemy units of this type, either accessible or in the fog of war.
   */
  size_t getNbEnemyUnits(BWAPI::UnitType type) const
  {
    return m_enemyUnits.count(type);
  }

  std::pair<UnitCompositionIterator, UnitCompositionIterator>
    getEstimatedEnemyArmyComposition() const
  {
    return std::make_pair(UnitCompositionIterator(m_enemyUnits, m_enemyUnits.begin()),
                          UnitCompositionIterator(m_enemyUnits, m_enemyUnits.end()));
  }

protected:
  virtual void _output(std::ostream& out) const
  {
    out << "Enemy army composition";
  }

  void onUnitRecordCreate(void*, UnitRecord* unitRecord);
  void onUnitRecordRenegade(void*, UnitRecord* unitRecord, BWAPI::Player* previousPlayer);
  void onEnemyUnitRecordDestroy(void*);
  void onEnemyUnitRecordMorph(void*, BWAPI::UnitType previousUnitType);
  void onEnemyUnitRecordRenegade(void*, BWAPI::Player* previousPlayer);

  void addUnitInternal(UnitRecord* unitRecord)
  {
    stdext::hash_multimap<BWAPI::UnitType, UnitWrapper>::iterator it = 
      m_enemyUnits.insert(std::make_pair(unitRecord->getType().getID(), UnitWrapper(unitRecord)));
    UnitWrapper* unitWrapper = &it->second;
    unitWrapper->m_posInContainer = it;

    unitWrapper->m_unitRecordDestroyID = unitRecord->getIndividualUnitRecordDestroyEvent().subscribe(&m_enemyUnitRecordDestroyEventHandler, unitWrapper);
    unitWrapper->m_unitRecordMorphID = unitRecord->getIndividualUnitRecordMorphEvent().subscribe(&m_enemyUnitRecordMorphEventHandler, unitWrapper);
    unitWrapper->m_unitRecordRenegadeID = unitRecord->getIndividualUnitRecordRenegadeEvent().subscribe(&m_enemyUnitRecordRenegadeEventHandler, unitWrapper);

    unitRecord->acquire(this);
  }

  void removeUnitInternal(UnitWrapper* unitWrapper)
  {
    UnitRecord* unitRecord = unitWrapper->getUnit();

    unitRecord->getIndividualUnitRecordDestroyEvent().unsubscribe(unitWrapper->m_unitRecordDestroyID);
    unitRecord->getIndividualUnitRecordMorphEvent().unsubscribe(unitWrapper->m_unitRecordMorphID);
    unitRecord->getIndividualUnitRecordRenegadeEvent().unsubscribe(unitWrapper->m_unitRecordRenegadeID);

    m_enemyUnits.erase(unitWrapper->m_posInContainer);

    unitRecord->release(this);
  }

  EVENT_HANDLER1(EnemyArmyComposition, onUnitRecordCreate, UnitRecord*) m_unitRecordCreateEventHandler;
  Event<boost::tuple<UnitRecord*>>::SubscriberID m_unitRecordCreateID;
  EVENT_HANDLER2(EnemyArmyComposition, onUnitRecordRenegade, UnitRecord*, BWAPI::Player*) m_unitRecordRenegadeEventHandler;
  Event<boost::tuple<UnitRecord*, BWAPI::Player*>>::SubscriberID m_unitRecordRenegadeID;
  EVENT_HANDLER(EnemyArmyComposition, onEnemyUnitRecordDestroy) m_enemyUnitRecordDestroyEventHandler;
  EVENT_HANDLER1(EnemyArmyComposition, onEnemyUnitRecordMorph, BWAPI::UnitType) m_enemyUnitRecordMorphEventHandler;
  EVENT_HANDLER1(EnemyArmyComposition, onEnemyUnitRecordRenegade, BWAPI::Player*) m_enemyUnitRecordRenegadeEventHandler;

  stdext::hash_multimap<BWAPI::UnitType, UnitWrapper> m_enemyUnits;
};
