#pragma once
#include "../../../Utils/Utils.h"
#include "../../PerceptualState/Resources/Resources.h"
#include "../../SituationAnalysis/Layer2Cueing/Layer2Cueing.h"
#include "../Layer1Reactive/Layer1Reactive.h"
#include "Interface/Layer2UnitData.h"

class AttackMove;
class Build;
class CollectMinerals;
class CollectGas;
class Defend;
class Harass;
class Recruit;
class UpgradeResearch;
class TechResearch;
class Scout;
class Expand;
class TechTypeObject;
class UpgradeTypeObject;
class GroupTacticalAction;
class Location;
class StaticLocation;
class AutoPopulatedLocation;
class BaseLocation;

class Layer2Actions : public Process
{
public:
  Layer2Actions(Layer2Cueing& layer2Cueing, Layer1Reactive& layer1Reactive)
    : Process(Spar)
    , m_layer2Cueing(layer2Cueing)
    , m_layer1Reactive(layer1Reactive)
    , m_unitIdleEvent(*this)
  {
    s_instance = this;
  }

  ///////////////////////
  // Level 3 interface //
  ///////////////////////
  virtual AttackMove* instantiateAttackMove(const Process* parent, unsigned int priority, const PersistentUnitGroup* initialGroup, AutoPopulatedLocation* location) = 0;
  virtual Build* instantiateBuild(const Process* parent, unsigned int priority, AutoPopulatedLocation* location, BWAPI::UnitType buildingType) = 0;
  virtual CollectMinerals* instantiateCollectMinerals(const Process* parent, unsigned int priority, const PersistentUnitGroup* initialGroup, BaseLocation* baseLocation) = 0;
  virtual CollectGas* instantiateCollectGas(const Process* parent, unsigned int priority, const PersistentUnitGroup* initialGroup, BaseLocation* baseLocation) = 0;
  virtual Defend* instantiateDefend(const Process* parent, unsigned int priority, const PersistentUnitGroup* initialGroup, Location* location) = 0;
  virtual Expand* instantiateExpand(const Process* parent, unsigned int priority, BaseLocation* baseLocation) = 0;
  virtual Harass* instantiateHarass(const Process* parent, unsigned int priority, const PersistentUnitGroup* initialGroup, Location* location) = 0;
  //virtual Move* instantiateMove(const Process* parent, unsigned int priority, const PersistentUnitGroup* initialGroup, Location* destination) = 0;
  virtual Recruit* instantiateRecruit(const Process* parent, unsigned int priority, AutoPopulatedLocation* location, const std::map<BWAPI::UnitType, size_t>& unitTypes, bool isInfinite) = 0;
  virtual Recruit* instantiateRecruit(const Process* parent, unsigned int priority, AutoPopulatedLocation* location, BWAPI::UnitType unitType, bool isInfinite) = 0;
  virtual UpgradeResearch* instantiateUpgradeResearch(const Process* parent, unsigned int priority, UpgradeTypeObject* upgrade) = 0;
  virtual TechResearch* instantiateTechResearch(const Process* parent, unsigned int priority, TechTypeObject* tech) = 0;
  virtual Scout* instantiateScout(const Process* parent, unsigned int priority, StaticLocation* location) = 0;
  virtual Scout* instantiateScout(const Process* parent, unsigned int priority, const PersistentUnitGroup* initialGroup, StaticLocation* location) = 0;

  virtual Resources getAvailableResources(unsigned int priority) const = 0;
  virtual Resources getLowestPriorityAvailableResources() const = 0;
  virtual Resources getHighestPriorityAvailableResources() const = 0;
  virtual size_t getNbAvailableBuildings(const BWAPI::UnitType buildingType, const AutoPopulatedLocation* location) const = 0;
  virtual bool needsAdditionalPylons() const = 0; // Should be handled in layer3 actually. Problem?

  DECLARE_EVENT2(OnUnitIdle, BWAPI::Unit*, const GroupTacticalAction*); // The unit released and the previous action
  OnUnitIdle& getUnitIdleEvent() const { return m_unitIdleEvent; }

  static Layer2Actions& getInstance()
  {
    return *s_instance;
  }

  typedef Layer2UnitData L2UnitData;

  void onFrame() {}
protected:
  virtual void onStart() {}
  virtual void onEnd() {}

  virtual void _output(std::ostream& out) const
  {
    out << "Layer 2 Decision making - Actions implementation";
  }

  static Layer2Actions* s_instance;

  Layer2Cueing& m_layer2Cueing;
  Layer1Reactive& m_layer1Reactive;

  EVENT(OnUnitIdle) m_unitIdleEvent;
};
