#pragma once
#include "Component.h"
#include "EventHandler.h"
#include "Event.h"
#include "GroupBase.h"
#include "../Utils/Utils.h"
#include "../PerceptualState/PerceptualStateUnitData.h"
#include <BWAPI.h>
#include <boost/iterator/transform_iterator.hpp>
#include <utility>
#include <hash_set>

/**
 * @brief A persistent unit group keeps track of its units by removing them when they are destroyed.
 *        This is particularly important when we don't want dangling destroyed unit pointers everywhere.
 */
class PersistentUnitGroup : public GroupBase<PersistentUnitGroup>
{
protected:
  class UnitWrapper;
  /**
   * @brief The type of container used to store the units.
   */
  typedef stdext::hash_set<UnitWrapper, UnitWrapperBaseHashCompare> UnitWrapperSet;
  //typedef std::set<UnitWrapper> UnitWrapperSet;
  /**
   * @brief Stores additional info associated to units.
   */
  class UnitWrapper : public UnitWrapperBase
  {
  public:
    UnitWrapper(BWAPI::Unit* unit)
      : UnitWrapperBase(unit) {}
  protected:
    friend class PersistentUnitGroup;
    /**
     * @brief Subscription ID for UnitManager's OnIndividualUnitDestroy event.
     */
    mutable PerceptualStateUnitData::OnIndividualUnitDestroy::SubscriberID m_unitDestroyID;
    /**
     * @brief Position of the unit wrapper in the set of unit wrappers.
     */
    UnitWrapperSet::const_iterator m_unitPosInSet;
  };
public:
  /**
   * @brief The type of const iterator used to iterate on the group's units.
   */
  typedef UnitWrapperSet::const_iterator const_iterator;
  /**
   * @brief The type of iterator used to iterate on the group's units.
   */
  typedef UnitWrapperSet::iterator iterator;
  
  /**
   * @brief Constructs an empty persistent unit group.
   * @param name The debug name of this group.
   */
  PersistentUnitGroup(const std::string& name)
    : m_name(name)
    , m_unitDestroyEventHandler(*this)
    , m_emptyEvent(*this)
    , m_unitAddedEvent(*this)
    , m_unitRemovedEvent(*this)
    , m_groupDestroyedEvent(*this)
  {
  }

  /**
   * @brief Constructs a persistent unit group with the specified sequence of units.
   * @tparam iterator The type of iterator over an existing sequence of units.
   * @param name The debug name of this group.
   * @param begin The beginning of the sequence of units.
   * @param end past Past the end of the sequence of units.
   */
  template <class iterator>
  PersistentUnitGroup(const std::string& name, iterator begin, iterator end)
    : m_name(name)
    , m_unitDestroyEventHandler(*this)
    , m_emptyEvent(*this)
    , m_unitAddedEvent(*this)
    , m_unitRemovedEvent(*this)
    , m_groupDestroyedEvent(*this)
  {
    for (; begin != end; ++begin)
      insert(*begin);
  }

  /**
   * @brief The persistent unit group's destructor removes the association with its units.
   */
  ~PersistentUnitGroup();

  /**
   * @return The name of the persistent unit group, useful for debugging (I guess).
   */
  const std::string& getName() const
  { 
    return m_name;
  }

  /**
   * @brief Adds a new unit to this persistent unit group. Subscribes to the new unit's onUnitDestroy event
   *        so that it can be removed from the group upon its deletion.
   * @param unit The unit to add to the group.
   * @return True if the unit wasn't already in the group.
   */
  bool insert(BWAPI::Unit* unit);

  bool insert(const_iterator hint, BWAPI::Unit* unit);

  /**
   * @brief Adds a sequence of units to this persistent unit group. Subscribes to the new units onUnitDestroy events
   *        so that they can be removed from the group upon their deletion.
   * @param begin The beginning of the sequence of units.
   * @param end Past the end of the sequence of units.
   * @return The number of units added (not counting duplicates).
   */
  template <class iterator>
  size_t insert(iterator begin, iterator end)
  {
    size_t nb = 0; 
    for (; begin != end; ++begin)
    {
      if(insert(*begin))
        ++nb;
    }
    return nb;
  }

  /**
   * @brief Removes a unit from this persistent unit group.
   * @param unit The unit to remove from the group.
   * @return True if the unit was present in the group.
   */
  bool remove(BWAPI::Unit* unit)
  {
    const_iterator it = m_group.find(UnitWrapper(unit));
    const bool exists = (it != m_group.end());
    if (exists)
    {
      remove(it);
    }
    return exists;
  }

  /**
   * @brief Removes a unit from this persistent unit group.
   * @param pos The position of the unit to remove.
   */
  void remove(const_iterator pos);

  /**
   * @brief Remove all the units from this group.
   */
  void clear()
  {
    iterator it = begin();
    iterator nextIt;
    while(it != end())
    {
      nextIt = ++(iterator)it;
      remove(it);
      it = nextIt;
    }
  }

  /**
   * @brief Checks whether the given unit is present in the group.
   * @param unit The unit to check for membership.
   * @return True if the unit is contained in the group.
   */
  bool contains(BWAPI::Unit* unit) const
  {
    return m_group.find(unit) != m_group.end();
  }

  /**
   * @brief Attemps to find the unit in the group.
   * @param unit The unit to find in the group.
   * @return An iterator pointing to the position of the unit sought after in the group, 
   *         or end() if the unit was not found.
   */
  const_iterator find(BWAPI::Unit* unit) const
  {
    return m_group.find(unit);
  }

  /**
   * @return The beginning of the sequence of units.
   */
  const_iterator begin() const
  {
    return m_group.begin();
  }

  /**
   * @return Past the end of the sequence of units.
   */
  const_iterator end() const
  {
    return m_group.end();
  }

  /**
   * @return The beginning of the sequence of units.
   */
  iterator begin()
  {
    //return boost::transform_iterator<extract_unit, UnitWrapperSet::iterator>(m_group.begin());
    return m_group.begin();
  }

  /**
   * @return Past the end of the sequence of units.
   */
  iterator end()
  {
    //return boost::transform_iterator<extract_unit, UnitWrapperSet::iterator>(m_group.end());
    return m_group.end();
  }

  /**
   * @return The number of units contained in the group.
   */
  size_t size() const
  {
	  return m_group.size();
  }

  /**
   * @return Whether the group is empty.
   */
  bool empty() const
  {
    return m_group.empty();
  }

  /**
   * @brief The OnEmpty event is raised when the group becomes empty.
   */
  DECLARE_EVENT(OnEmpty);
  OnEmpty& getEmptyEvent() const { return m_emptyEvent; }

  /**
   * @brief The OnGroupDestroyed event is raised when the group is destroyed.
   */
  DECLARE_EVENT(OnGroupDestroyed);
  OnGroupDestroyed& getGroupDestroyedEvent() const { return m_groupDestroyedEvent; }

  /**
   * @brief The OnUnitAdded event is raised when a unit is added to the group.
   */
  DECLARE_EVENT1(OnUnitAdded, BWAPI::Unit*);
  OnUnitAdded& getUnitAddedEvent() const { return m_unitAddedEvent; }

  /**
   * @brief The OnUnitRemoved event is raised when a unit is removed from the group.
   *        Note that the unit removed may have been destroyed.
   */
  DECLARE_EVENT1(OnUnitRemoved, BWAPI::Unit*);
  OnUnitRemoved& getUnitRemovedEvent() const { return m_unitRemovedEvent; }

  /**
   * @brief The empty group, included here for convenience.
   */
  static const PersistentUnitGroup EmptyGroup;

protected:
  PersistentUnitGroup(const PersistentUnitGroup& other);
  PersistentUnitGroup& operator=(const PersistentUnitGroup& other) const;

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

  /**
   * @brief Notification when a unit is destroyed. The unit is removed from the group.
   * @param data The data specified when subscribing to the unit's destruction, 
   *             corresponding to the position in the unit wrappers' list in this context.
   */
  void onUnitDestroy(void* data)
  {
    UnitWrapperSet::iterator* id = reinterpret_cast<UnitWrapperSet::iterator*>(data);
    remove(*id);
  }

  /**
   * @brief The OnEmpty event.
   */
  EVENT(OnEmpty) m_emptyEvent;
  /**
   * @brief The OnGroupDestroyed event.
   */
  EVENT(OnGroupDestroyed) m_groupDestroyedEvent;
  /**
   * @brief The OnUnitAdded event.
   */
  EVENT(OnUnitAdded) m_unitAddedEvent;
  /**
   * @brief The OnUnitRemoved event.
   */
  EVENT(OnUnitRemoved) m_unitRemovedEvent;
  /**
   * @brief The debug name of this persistent unit group.
   */
  const std::string m_name;
  /**
   * @brief The event handler to be notified when a unit is destroyed.
   */
  EVENT_HANDLER(PersistentUnitGroup, onUnitDestroy) m_unitDestroyEventHandler;
  /**
   * @brief The inner group of units.
   */
  UnitWrapperSet m_group;
};
