#pragma once
#include "../../PerceptualState.h"
#include "MonitoringProcess.h"

/**
 * @brief Process that monitors something taking place inside a building.
 */
class InBuildingMonitoringProcess : public virtual MonitoringProcess
{
public:
  InBuildingMonitoringProcess(const Process* parent, BWAPI::Unit* building)
    : MonitoringProcess(parent)
    , m_building(building)
  {
    assert(m_building->getType().isBuilding());
  }
  /////////
  // GUI //
  /////////
  virtual UnitRecordGroup _getUnitsInvolved() const
  {
    return UnitRecordGroup(&m_building, &m_building + 1);
  }
  static const TerminationMode BuildingDestroyed;
protected:
  BWAPI::Unit* const m_building;
};

/**
 * @brief Process used to monitor the completion of units.
 */
template <class DerivedMonitoringProcess>
class InBuildingMonitoringProcessT : public MonitoringProcessT<DerivedMonitoringProcess>, public virtual InBuildingMonitoringProcess
{
public:
  InBuildingMonitoringProcessT(const Process* parent, BWAPI::Unit* building)
    : MonitoringProcessT(parent)
    , InBuildingMonitoringProcess(parent, building)
    , MonitoringProcess(parent)
    , m_buildingDestroyedEventHandler(*this)
    , m_unitCompletedEventHandler(*this)
  {
  }
protected:
  virtual void onStart()
  {
    m_buildingDestroyedSubscriberId = BWAPI::getIndividualUnitDestroyEvent(m_building).subscribe(&m_buildingDestroyedEventHandler, NULL);
    MonitoringProcessT::onStart();
  }
  virtual void onEnd()
  {
    MonitoringProcessT::onEnd();
    BWAPI::getIndividualUnitDestroyEvent(m_building).unsubscribe(m_buildingDestroyedSubscriberId);
    Spar->getPerceptualState()->getUnitCompletedEvent().unsubscribe(m_unitCompletedSubscriberId);
  }
  void onTimeoutImpl()
  {
    if (!static_cast<DerivedMonitoringProcess*>(this)->testCompletion())
    {
      if (m_building->isUnpowered())
      {
        // Let's wait for some pylon to complete, else we may end up periodically checking for completion
        // (every frame in the worst case) for an unpowered building.
        m_unitCompletedSubscriberId = Spar->getPerceptualState()->getUnitCompletedEvent().subscribe(&m_unitCompletedEventHandler, NULL);
      }
      else
      {
        // Here it could very well be that the building was unpowered for a time (but is powered on now), which
        // explains why the unit/tech isn't completed yet.
        static_cast<DerivedMonitoringProcess*>(this)->setNewTimeout(false/*true*/);
        m_timeoutEvent.activate();
      }
    }
  }
  void onBuildingDestroyed(void*)
  {
    if (!static_cast<DerivedMonitoringProcess*>(this)->testCompletion())
    {
      terminate(BuildingDestroyed);
    }
  }
  void onUnitCompleted(void*, BWAPI::Unit* unit)
  {
    if (unit->getType() == BWAPI::UnitTypes::Protoss_Pylon)
    {
      if (!static_cast<DerivedMonitoringProcess*>(this)->testCompletion())
      {
        //
        // Here we could test (one frame later) whether the building is actually re-powered before running
        // the countdown again. However there could be problems if two different pylons get completed one frame
        // apart from each other, thus potentially needing two different "one frame later" events.
        //
        //if (!m_building->isUnpowered())
        //{
          Spar->getPerceptualState()->getUnitCompletedEvent().unsubscribe(m_unitCompletedSubscriberId);
          static_cast<DerivedMonitoringProcess*>(this)->setNewTimeout(false);
          // One additional frame for pylons to power stuff
          m_timeoutEvent.setWaitingTimeBeforeExecution(m_timeoutEvent.getWaitingTimeBeforeExecution() + 1);
          m_timeoutEvent.activate();
        //}
      }
    }
  }

  Event<boost::tuples::tuple<>>::SubscriberID m_buildingDestroyedSubscriberId;
  EVENT_HANDLER(InBuildingMonitoringProcessT, onBuildingDestroyed) m_buildingDestroyedEventHandler;
  Event<boost::tuples::tuple<BWAPI::Unit*>>::SubscriberID m_unitCompletedSubscriberId;
  EVENT_HANDLER1(InBuildingMonitoringProcessT, onUnitCompleted, BWAPI::Unit*) m_unitCompletedEventHandler;
};
