#pragma once
#include "../../Utils/Component.h"
#include "../../Utils/Event.h"
#include "../../Utils/Logger.h"
#include "../../Utils/Utils.h"
#include "UpgradeTypeObject.h"
#include "TechTypeObject.h"
#include "BuildingTypeObject.h"
#include "UnitTypeObject.h"
#include <Exp/Formula/Described/AtomicFormula.h>
#include <Exp/Formula/Described/NumericFluent.h>
#include <Exp/Term/Type/PrimitiveType.h>
#include <World/IClosedWorld.h>
#include <list>
#include <sstream>
#include <hash_map>
#include <boost/iterator/transform_iterator.hpp>

template <class S1, class S2, class S3, class S4, class D1, class D2, class D3, class D4> class SparAIModule;

class CurrentWorld : public Component, public virtual planning::IClosedWorld
{
private:
  CurrentWorld();

  struct UpgradeCompare
  {
    bool operator()(const std::pair<BWAPI::UpgradeType, int>& upgrade1, const std::pair<BWAPI::UpgradeType, int>& upgrade2) const
    {
      if (upgrade1.first < upgrade2.first)
        return true;
      else if (upgrade1.first > upgrade2.first)
        return false;
      else
        return upgrade1.second < upgrade2.second;
    }
  };
  struct UpgradeHashCompare : public stdext::hash_compare<std::pair<BWAPI::UpgradeType, int>, UpgradeCompare>
  {
    size_t operator()(const std::pair<BWAPI::UpgradeType, int>& upgrade) const
    {
      return 17 * upgrade.first.getID() + 47 * upgrade.second;
    }
    bool operator()(const std::pair<BWAPI::UpgradeType, int>& upgrade1, const std::pair<BWAPI::UpgradeType, int>& upgrade2) const
    {
      return UpgradeCompare()(upgrade1, upgrade2);
    }
  };
  class CurrentWorldAtomicFormula : public planning::AtomicFormula
  {
  public:
    template <class Iterator>
    CurrentWorldAtomicFormula(const std::string& name, Iterator argumentsBegin, Iterator argumentsEnd, 
                              planning::AtomicFormula::Attributes attributes)
      : planning::AtomicFormula(name, argumentsBegin, argumentsEnd, attributes)
    {}
    CurrentWorldAtomicFormula(const std::string& name, planning::ObjectParameterVariable* argument, planning::AtomicFormula::Attributes attributes)
      : planning::AtomicFormula(name, argument, attributes)
    {}
    virtual bool evaluate(const planning::AtomicFormulaApplication* formula) const = 0;
  };
  class CurrentWorldNumericFluent : public planning::NumericFluent
  {
  public:
    template <class Iterator>
    CurrentWorldNumericFluent(const std::string& name, Iterator argumentsBegin, Iterator argumentsEnd, 
                              planning::NumericFluent::Attributes attributes)
      : planning::NumericFluent(name, argumentsBegin, argumentsEnd, attributes)
    {}
    CurrentWorldNumericFluent(const std::string& name, planning::ObjectParameterVariable* argument, planning::NumericFluent::Attributes attributes)
      : NumericFluent(name, argument, attributes)
    {}
    virtual double evaluate(const planning::NumericFluentApplication* fluent) const = 0;
  };

  class TechAcquiredAtomicFormula : public CurrentWorldAtomicFormula
  {
  public:
    TechAcquiredAtomicFormula();
    virtual bool evaluate(const planning::AtomicFormulaApplication* formula) const;
  };
  class NrUnitCompletedNumericFluent : public CurrentWorldNumericFluent
  {
  public:
    NrUnitCompletedNumericFluent();
    virtual double evaluate(const planning::NumericFluentApplication* fluent) const;
  };

  static void initialize();
  static void terminate();
public:
  static TechAcquiredAtomicFormula* TechAcquired;
  static NrUnitCompletedNumericFluent* NrUnitCompleted;

  static planning::PrimitiveType* UnitPrimitiveType;
  static planning::PrimitiveType* BuildingPrimitiveType;
  static planning::PrimitiveType* TechPrimitiveType;
  static planning::PrimitiveType* TechTypePrimitiveType;
  static planning::PrimitiveType* UpgradeTypePrimitiveType;

  /////////////
  // Protoss //
  /////////////
  static BuildingTypeObject* Nexus;
  static BuildingTypeObject* Pylon;
  static BuildingTypeObject* Assimilator;
  static BuildingTypeObject* Gateway;
  static BuildingTypeObject* Forge;
  static BuildingTypeObject* CyberneticsCore;
  static BuildingTypeObject* RoboticsFacility;
  static BuildingTypeObject* RoboticsSupportBay;
  static BuildingTypeObject* Observatory;
  static BuildingTypeObject* Stargate;
  static BuildingTypeObject* FleetBeacon;
  static BuildingTypeObject* ArbiterTribunal;
  static BuildingTypeObject* CitadelOfAdun;
  static BuildingTypeObject* TemplarArchives;
  static BuildingTypeObject* ShieldBattery;
  static BuildingTypeObject* PhotonCannon;

  static UnitTypeObject* Probe;
  static UnitTypeObject* Zealot;
  static UnitTypeObject* Dragoon;
  static UnitTypeObject* HighTemplar;
  static UnitTypeObject* DarkTemplar;
  static UnitTypeObject* Archon;
  static UnitTypeObject* DarkArchon;
  static UnitTypeObject* Reaver;
  static UnitTypeObject* Shuttle;
  static UnitTypeObject* Observer;
  static UnitTypeObject* Corsair;
  static UnitTypeObject* Scout;
  static UnitTypeObject* Carrier;
  static UnitTypeObject* Arbiter;

  static TechTypeObject* DisruptionWeb;
  static TechTypeObject* Feedback;
  static TechTypeObject* Hallucination;
  static TechTypeObject* Maelstrom;
  static TechTypeObject* MindControl;
  static TechTypeObject* PsionicStorm;
  static TechTypeObject* Recall;
  static TechTypeObject* StasisField;
  static UpgradeTypeObject* GroundWeapons[3];    // upgrade ground weapons level 1-2-3
  static UpgradeTypeObject* GroundArmor[3];      // upgrade ground armor level 1-2-3
  static UpgradeTypeObject* AirWeapons[3];       // upgrade air weapons level 1-2-3
  static UpgradeTypeObject* AirArmor[3];         // upgrade air armor level 1-2-3
  static UpgradeTypeObject* PlasmaShields[3];    // upgrade plasma shields 1-2-3
  static UpgradeTypeObject* SingularityCharge;   // increase Dragoon attack range
  static UpgradeTypeObject* LegEnhancements;     // faster Zealot movement
  static UpgradeTypeObject* GraviticDrive;       // faster Shuttle movement
  static UpgradeTypeObject* CarrierCapacity;     // max 8 interceptors (instead of 4)
  static UpgradeTypeObject* ReaverCapacity;      // max 10 Scarabs (instead of 5)
  static UpgradeTypeObject* ScarabDamage;        // upgrade Scarab damage (125 instead of 100)
  static UpgradeTypeObject* GraviticBoosters;    // faster Observer movement
  static UpgradeTypeObject* SensorArray;         // increase Observer sight range
  static UpgradeTypeObject* ApialSensors;        // increase Scout sight range
  static UpgradeTypeObject* GraviticThrusters;   // faster Scout movement
  static UpgradeTypeObject* ArgusJewel;          // +50 Corsair energy
  static UpgradeTypeObject* ArgusTalisman;       // +50 Dark Archon energy
  static UpgradeTypeObject* KhaydarinAmulet;     // +50 Templar energy
  static UpgradeTypeObject* KhaydarinCore;       // +50 Arbiter energy

  static BuildingTypeObject* getBuildingTypeObject(BWAPI::UnitType buildingType)
  {
    stdext::hash_map<BWAPI::UnitType, BuildingTypeObject*>::const_iterator it = s_currentWorld.m_buildingTypeObjects.find(buildingType);
    //if (it == s_currentWorld.m_buildingTypeObjects.end())
    //{
    //  return NULL;
    //}
    //else
    //{
      return it->second;
    //}
  }
  static UnitTypeObject* getUnitTypeObject(BWAPI::UnitType unitType)
  {
    stdext::hash_map<BWAPI::UnitType, UnitTypeObject*>::const_iterator it = s_currentWorld.m_unitTypeObjects.find(unitType);
    if (it == s_currentWorld.m_unitTypeObjects.end())
    {
      return NULL;
    }
    else
    {
      return it->second;
    }
  }
  static TechTypeObject* getTechTypeObject(BWAPI::TechType techType)
  {
    stdext::hash_map<BWAPI::TechType, TechTypeObject*>::const_iterator it = s_currentWorld.m_techTypeObjects.find(techType);
    //if (it == s_currentWorld.m_techTypeObjects.end())
    //{
    //  return NULL;
    //}
    //else
    //{
      return it->second;
    //}
  }
  static UpgradeTypeObject* getUpgradeTypeObject(BWAPI::UpgradeType upgradeType, int level = 1)
  {
    stdext::hash_map<std::pair<BWAPI::UpgradeType, int>, UpgradeTypeObject*>::const_iterator it = s_currentWorld.m_upgradeTypeObjects.find(std::make_pair(upgradeType, level));
    //if (it == s_currentWorld.m_upgradeTypeObjects.end())
    //{
    //  return NULL;
    //}
    //else
    //{
      return it->second;
    //}
  }

  typedef boost::transform_iterator<extract_pair_second_const<stdext::hash_map<BWAPI::TechType, TechTypeObject*>::const_iterator::value_type>, 
                                    stdext::hash_map<BWAPI::TechType, TechTypeObject*>::const_iterator> 
          tech_type_object_const_iterator;
  static std::pair<tech_type_object_const_iterator, tech_type_object_const_iterator> getTechTypeObjects()
  {
    return std::make_pair(tech_type_object_const_iterator(s_currentWorld.m_techTypeObjects.begin()), tech_type_object_const_iterator(s_currentWorld.m_techTypeObjects.end()));
  }

  typedef boost::transform_iterator<extract_pair_second_const<stdext::hash_map<std::pair<BWAPI::UpgradeType, int>, UpgradeTypeObject*, UpgradeHashCompare>::const_iterator::value_type>, 
                                    stdext::hash_map<std::pair<BWAPI::UpgradeType, int>, UpgradeTypeObject*, UpgradeHashCompare>::const_iterator> 
          upgrade_type_object_const_iterator;
  static std::pair<upgrade_type_object_const_iterator, upgrade_type_object_const_iterator> getUpgradeTypeObjects()
  {
    return std::make_pair(upgrade_type_object_const_iterator(s_currentWorld.m_upgradeTypeObjects.begin()), upgrade_type_object_const_iterator(s_currentWorld.m_upgradeTypeObjects.end()));
  }

  typedef concatenate_iterators<boost::tuple<//std::pair<unit_type_tech_const_iterator, unit_type_tech_const_iterator>, 
                                             std::pair<tech_type_object_const_iterator, tech_type_object_const_iterator>,
                                             std::pair<upgrade_type_object_const_iterator, upgrade_type_object_const_iterator>>, 
                                const TechObject*> 
    tech_const_iterator;
  static std::pair<tech_const_iterator, tech_const_iterator> getAllTechs()
  {
    return std::make_pair(tech_const_iterator(boost::make_tuple(//std::make_pair(unit_type_tech_const_iterator(UnitTypeTechs.begin()), unit_type_tech_const_iterator(UnitTypeTechs.end())),
                                                                std::make_pair(tech_type_object_const_iterator(s_currentWorld.m_techTypeObjects.begin()), tech_type_object_const_iterator(s_currentWorld.m_techTypeObjects.end())),
                                                                std::make_pair(upgrade_type_object_const_iterator(s_currentWorld.m_upgradeTypeObjects.begin()), upgrade_type_object_const_iterator(s_currentWorld.m_upgradeTypeObjects.end())))),
                          tech_const_iterator(boost::make_tuple(//std::make_pair(unit_type_tech_const_iterator(UnitTypeTechs.end()), unit_type_tech_const_iterator(UnitTypeTechs.end())),
                                                                std::make_pair(tech_type_object_const_iterator(s_currentWorld.m_techTypeObjects.end()), tech_type_object_const_iterator(s_currentWorld.m_techTypeObjects.end())),
                                                                std::make_pair(upgrade_type_object_const_iterator(s_currentWorld.m_upgradeTypeObjects.end()), upgrade_type_object_const_iterator(s_currentWorld.m_upgradeTypeObjects.end())))));
  }

  DECLARE_EVENT1(OnTechAcquired, const TechObject*);
  static OnTechAcquired& getTechAcquiredEvent() { return s_currentWorld.m_techAcquiredEvent; }

  DECLARE_EVENT1(OnUnitTypeCompletedChanged, const UnitTypeObject*);
  static OnUnitTypeCompletedChanged& getBuildingCompletedChangedEvent() { return s_currentWorld.m_unitTypeCompletedChangedEvent; }

  DECLARE_EVENT1(OnUnitTypeCreatableChanged, const UnitTypeObject*);
  static OnUnitTypeCreatableChanged& getUnitTypeCreatableChangedEvent() { return s_currentWorld.m_unitTypeCreatableChangedEvent; }


#pragma region IClosedWorld implementation

  // Current world is read-only at the moment

  virtual bool isSet(const planning::AtomicFormulaApplication* formula) const;

  virtual void set(const planning::AtomicFormulaApplication* formula)
  {
    unused(formula);
    throw std::runtime_error("Not implemented");
  }

  virtual void unset(const planning::AtomicFormulaApplication* formula)
  {
    unused(formula);
    throw std::runtime_error("Not implemented");
  }

  virtual double getNumericFluent(const planning::NumericFluentApplication* fluent) const;

  virtual const planning::Object* getObjectFluent(const planning::ObjectFluentApplication* fluent) const
  {
    unused(fluent);
    throw std::runtime_error("Not implemented");
  }

  virtual void setNumericFluent(const planning::NumericFluentApplication* fluent, double value)
  {
    unused(fluent);
    unused(value);
    throw std::runtime_error("Not implemented");
  }

  virtual void setNumericFluent(const planning::NumericFluentApplication* fluent, 
                                const planning::ArithmeticFunction& function, 
                                double argument)
  {
    unused(fluent);
    unused(function);
    unused(argument);
    throw std::runtime_error("Not implemented");
  }

  virtual void setObjectFluent(const planning::ObjectFluentApplication* fluent, const planning::Object* value)
  {
    unused(fluent);
    unused(value);
    throw std::runtime_error("Not implemented");
  }

  virtual void undefineObjectFluent(const planning::ObjectFluentApplication* fluent)
  {
    unused(fluent);
    throw std::runtime_error("Not implemented");
  }

  ///////////////////////////////////////////////

  virtual IClosedWorld* copy() const
  {
    throw std::runtime_error("Not implemented");
  }

  virtual size_t getHashCode() const
  {
    throw std::runtime_error("Not implemented");
  }

  virtual int compareTo(const planning::IClosedWorld& other) const
  {
    unused(other);
    throw std::runtime_error("Not implemented");
  }

#pragma endregion

  static const CurrentWorld* getInstance()
  {
    return &s_currentWorld;
  }

protected:
  friend class PerceptualState;
  friend class TechObject;
  friend class TechTypeObject;
  friend class UpgradeTypeObject;
  friend class BuildingTypeObject;
  friend class UnitTypeObject;

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

private:
  static void raiseUnitTypeCompletedChangedEvent(const UnitTypeObject* unitType)
  {
    SPAR_LOG(LogTypes.PERCEPTUAL_STATE, "Unit type instance %s: %s", 
      unitType->isCompleted() ? "completed" : "lost",
      toString(unitType).c_str());
    s_currentWorld.m_unitTypeCompletedChangedEvent.raise(unitType);
  }

  static void raiseUnitTypeCreatableChangedEvent(const UnitTypeObject* unitType)
  {
    SPAR_LOG(LogTypes.PERCEPTUAL_STATE, "Unit type %s: %s", 
      unitType->isCreatable() ? "creatable" : "not creatable",
      toString(unitType).c_str());
    s_currentWorld.m_unitTypeCreatableChangedEvent.raise(unitType);
  }

  static void raiseTechAcquired(const TechObject* techObject)
  {
    SPAR_LOG(LogTypes.PERCEPTUAL_STATE, "Tech acquired: %s", toString(techObject).c_str());
    s_currentWorld.m_techAcquiredEvent.raise(techObject);
  }

  static void addBuildingTypeObject(BuildingTypeObject* buildingType)
  {
    std::pair<stdext::hash_map<BWAPI::UnitType, BuildingTypeObject*>::iterator, bool> result =
      s_currentWorld.m_buildingTypeObjects.insert(std::make_pair(buildingType->getBuildingType(), buildingType));
    if (!result.second)
    {
      const std::string name = buildingType->getBuildingType().getName();
      throw std::runtime_error(std::string("Building type object duplicates \"") + name + "\"");
    }
  }

  static void addUnitTypeObject(UnitTypeObject* unitType)
  {
    std::pair<stdext::hash_map<BWAPI::UnitType, UnitTypeObject*>::iterator, bool> result =
      s_currentWorld.m_unitTypeObjects.insert(std::make_pair(unitType->getUnitType(), unitType));
    if (!result.second)
    {
      const std::string name = unitType->getUnitType().getName();
      throw std::runtime_error(std::string("Unit type object duplicates \"") + name + "\"");
    }
  }

  static void addTechTypeTech(TechTypeObject* tech)
  {
    std::pair<stdext::hash_map<BWAPI::TechType, TechTypeObject*>::iterator, bool> result =
      s_currentWorld.m_techTypeObjects.insert(std::make_pair(tech->getTech(), tech));
    if (!result.second)
    {
      const std::string name = tech->getTech().getName();
      throw std::runtime_error(std::string("Tech type duplicates \"") + name + "\"");
    }
  }

  static void addUpgradeTypeTech(UpgradeTypeObject* tech)
  {
    std::pair<stdext::hash_map<std::pair<BWAPI::UpgradeType, int>, UpgradeTypeObject*>::iterator, bool> result =
      s_currentWorld.m_upgradeTypeObjects.insert(std::make_pair(std::make_pair(tech->getUpgrade(), tech->getLevel()), tech));
    if (!result.second)
    {
      const std::string name = tech->getUpgrade().getName();
      std::stringstream ss;
      ss << "Upgrade type tech duplicates \"" << name << " level " << tech->getLevel() << "\"";
      throw std::runtime_error(ss.str());
    }
  }

  static void onUnitCompleted(void*, BWAPI::Unit* unit);
  static void onUnitDestroy(void*, BWAPI::Unit* unit);
  static void onUnitMorph(void*, BWAPI::Unit* unit, BWAPI::UnitType previousType);
  static void onUnitRenegade(void*, BWAPI::Unit* unit, BWAPI::Player* previousPlayer);

  static CurrentWorld s_currentWorld;

  stdext::hash_map<BWAPI::UnitType, BuildingTypeObject*> m_buildingTypeObjects;
  stdext::hash_map<BWAPI::UnitType, UnitTypeObject*> m_unitTypeObjects;
  stdext::hash_map<BWAPI::TechType, TechTypeObject*> m_techTypeObjects;
  stdext::hash_map<std::pair<BWAPI::UpgradeType, int>, UpgradeTypeObject*, UpgradeHashCompare> m_upgradeTypeObjects;

  EVENT(OnTechAcquired) m_techAcquiredEvent;
  EVENT(OnUnitTypeCompletedChanged) m_unitTypeCompletedChangedEvent;
  EVENT(OnUnitTypeCreatableChanged) m_unitTypeCreatableChangedEvent;

  EVENT_HANDLER1(CurrentWorld, onUnitCompleted, BWAPI::Unit*) m_onUnitCompletedEventHandler;
  Event<boost::tuples::tuple<BWAPI::Unit*>>::SubscriberID m_onUnitCompletedID;
  EVENT_HANDLER1(CurrentWorld, onUnitDestroy, BWAPI::Unit*) m_onUnitDestroyEventHandler;
  Event<boost::tuples::tuple<BWAPI::Unit*>>::SubscriberID m_onUnitDestroyID;
  EVENT_HANDLER2(CurrentWorld, onUnitMorph, BWAPI::Unit*, BWAPI::UnitType) m_onUnitMorphEventHandler;
  Event<boost::tuples::tuple<BWAPI::Unit*, BWAPI::UnitType>>::SubscriberID m_onUnitMorphID;
  EVENT_HANDLER2(CurrentWorld, onUnitRenegade, BWAPI::Unit*, BWAPI::Player*) m_onUnitRenegadeEventHandler;
  Event<boost::tuples::tuple<BWAPI::Unit*, BWAPI::Player*>>::SubscriberID m_onUnitRenegadeID;
};