#pragma once
#include "../../Layer1Reactive/Layer1FSM/Layer1FSM.h"
#include "../Layer2Actions.h"
#include "ActionsImplementation/L2Attack.h"
#include "ActionsImplementation/L2AttackMove.h"
#include "ActionsImplementation/L2Build.h"
#include "ActionsImplementation/L2Collect.h"
#include "ActionsImplementation/L2Defend.h"
#include "ActionsImplementation/L2Expand.h"
#include "ActionsImplementation/L2Harass.h"
#include "ActionsImplementation/L2Move.h"
#include "ActionsImplementation/L2OffensiveBuild.h"
#include "ActionsImplementation/L2Recruit.h"
#include "ActionsImplementation/L2Research.h"
#include "ActionsImplementation/L2Scout.h"
#include "ActionsImplementation/L2BuildingPlacer.h"
#include "Layer2ManagersBase.h"
#include "Layer2ManagersUnitData.h"
#include <boost/tuple/tuple.hpp>

class Layer2Managers : public Layer2ManagersBase, 
                       public Layer2Actions
{
public:
  static bool includedIn(Resources r1, Resources r2);

  Layer2Managers(Layer2Cueing& layer2Cueing, Layer1FSM& layer1FSM);
  ~Layer2Managers();

  //void onActionExecuted(L2TacticalAction* action);
  //void onActionEnded(void* data);

  /////////////
  // Actions //
  /////////////
  virtual AttackMove* instantiateAttackMove(const Process* parent, unsigned int priority, const PersistentUnitGroup* initialGroup, AutoPopulatedLocation* location)
  {
    return new L2AttackMove(parent, priority, initialGroup, location);
  }
  virtual Build* instantiateBuild(const Process* parent, unsigned int priority, AutoPopulatedLocation* location, BWAPI::UnitType buildingType)
  {
    return new L2Build(parent, priority, location, buildingType);
  }
  virtual CollectMinerals* instantiateCollectMinerals(const Process* parent, unsigned int priority, const PersistentUnitGroup* initialGroup, BaseLocation* baseLocation)
  {
    return new L2CollectMinerals(parent, priority, initialGroup, baseLocation);
  }
  virtual CollectGas* instantiateCollectGas(const Process* parent, unsigned int priority, const PersistentUnitGroup* initialGroup, BaseLocation* baseLocation)
  {
    return new L2CollectGas(parent, priority, initialGroup, baseLocation);
  }
  virtual Defend* instantiateDefend(const Process* parent, unsigned int priority, const PersistentUnitGroup* initialGroup, Location* location)
  {
    return new L2Defend(parent, priority, initialGroup, location);
  }
  virtual Expand* instantiateExpand(const Process* parent, unsigned int priority, BaseLocation* baseLocation)
  {
    return new L2Expand(parent, priority, baseLocation);
  }
  virtual Harass* instantiateHarass(const Process* parent, unsigned int priority, const PersistentUnitGroup* initialGroup, Location* location)
  {
    return new L2Harass(parent, priority, initialGroup, location);
  }
  //virtual Move* instantiateMove(const Process* parent, unsigned int priority, const PersistentUnitGroup* initialGroup, Location* destination)
  //{
  //  return new L2Move(parent, priority, initialGroup, destination);
  //}
  virtual Recruit* instantiateRecruit(const Process* parent, unsigned int priority, AutoPopulatedLocation* location, const std::map<BWAPI::UnitType, size_t>& unitTypes, bool isInfinite)
  {
    return new L2Recruit(parent, priority, location, unitTypes, isInfinite);
  }
  virtual Recruit* instantiateRecruit(const Process* parent, unsigned int priority, AutoPopulatedLocation* location, BWAPI::UnitType unitType, bool isInfinite)
  {
    return new L2Recruit(parent, priority, location, unitType, isInfinite);
  }
  virtual UpgradeResearch* instantiateUpgradeResearch(const Process* parent, unsigned int priority, UpgradeTypeObject* upgrade)
  {
    return new L2UpgradeResearch(parent, priority, upgrade);
  }
  virtual TechResearch* instantiateTechResearch(const Process* parent, unsigned int priority, TechTypeObject* tech)
  {
    return new L2TechResearch(parent, priority, tech);
  }
  virtual Scout* instantiateScout(const Process* parent, unsigned int priority, StaticLocation* location)
  {
    return new L2Scout(parent, priority, location);
  }
  virtual Scout* instantiateScout(const Process* parent, unsigned int priority, const PersistentUnitGroup* initialGroup, StaticLocation* location)
  {
    return new L2Scout(parent, priority, initialGroup, location);
  }

  //////////////
  // Requests //
  //////////////
  virtual Resources getAvailableResources(unsigned int priority) const;
  virtual Resources getLowestPriorityAvailableResources() const;
  virtual Resources getHighestPriorityAvailableResources() const;
  virtual size_t getNbAvailableBuildings(const BWAPI::UnitType buildingType, const AutoPopulatedLocation* location) const;
  virtual bool needsAdditionalPylons() const;

  L2BuildingPlacer& getBuildingPlacer() { return m_buildingPlacer; }

  void requestResources(unsigned int priority, ActionResources resources, ResourcesRequest& request);
  void changeResourcesRequestPriority(ResourcesRequest& request, unsigned int newPriority);
  void terminateResourcesRequest(ResourcesRequest& request);

  DECLARE_EVENT(OnResourcesAllocated);

  static Layer2Managers& getInstance()
  {
    return static_cast<Layer2Managers&>(Layer2Actions::getInstance());
  }

  typedef Layer2ManagersUnitData L2UnitData;

  void onFrame();

  /////////
  // GUI //
  /////////
  Resources getReservedResources() const
  {
    return m_reservedResources;
  }
  typedef ConstSafeMultisetAdaptor<ResourcesRequest*, SortByPriorityThenID>::const_iterator ResourcesRequestIterator;
  std::pair<ResourcesRequestIterator, ResourcesRequestIterator> getPendingResourcesRequests() const
  {
    ConstSafeMultisetAdaptor<ResourcesRequest*, SortByPriorityThenID> adaptor(m_resourcesRequests);
    return std::make_pair(ResourcesRequestIterator(adaptor.unsafe_begin()), ResourcesRequestIterator(adaptor.unsafe_end()));
  }

protected:
  virtual void onStart();
  virtual void onEnd();

  void reserveResources(Resources resources);
  void unreserveResources(Resources resources);

  L2BuildingPlacer m_buildingPlacer;

  /**
   * @brief Resources reserved by the tactical actions
   */
  Resources m_reservedResources;

  /**
   * @brief Resources requested by the tactical actions
   */
  ResourcesRequestsType m_resourcesRequests;

  /**
   * @brief Number of request received. (Used to give id to the requests)
   */
  unsigned int m_requestCounter;

  struct UseResources
  {
    UseResources(ResourcesRequestsType& resourcesRequests, Resources remainingResource, Layer2Managers* layer2) 
      : m_resourcesRequests(resourcesRequests)
      , m_remainingResources(remainingResource)
      , m_layer2(layer2)
    {}
    ResourcesRequestsType::IterationStep operator()(ResourcesRequest* request);

  private:
    ResourcesRequestsType& m_resourcesRequests;
    Resources m_remainingResources;
    Layer2Managers* const m_layer2;

    //reference members prevent the use of assignement operator
    UseResources& operator=(const UseResources&);
  };

  struct AvailableResourcesFunctor
  {
    AvailableResourcesFunctor(Resources baseResources, unsigned int priority) 
      : m_resources(baseResources)
      , m_priority(priority)
    {}
    ResourcesRequestsType::ConstIterationStep AvailableResourcesFunctor::operator()(const ResourcesRequest* request)
    {
      ResourcesRequestsType::ConstIterationStep result = ResourcesRequestsType::ConstIterationStep();
      if(m_priority > request->priority)
        result.breakIteration = true;
      else
        m_resources -= request->actionResources.getResources();

      return result;
    }
    Resources getResources() const
    {
      return m_resources;
    }
  private:
    const unsigned int m_priority;
    Resources m_resources;

    AvailableResourcesFunctor& operator=(const AvailableResourcesFunctor&);
  };

  struct ClearResourcesRequestsFunctor
  {
    ResourcesRequestsType::IterationStep ClearResourcesRequestsFunctor::operator()(const ResourcesRequest* request)
    {
      unused(request);
      return ResourcesRequestsType::IterationStep(true,false);
    }
  };

};
