#pragma once
#include "SparAIContainer.h"
#include "PerceptualState/PerceptualState.h"
#include "PerceptualState/World/CurrentWorld.h"
#include "PerceptualState/Locations/LocationFactory.h"
#include "Scheduler/Scheduler.h"
#include "Scheduler/MP/MPScheduler.h"
#include "Scheduler/Timer.h"
#include "SparObserver.h"
#include "UnitManagerImpl.h"
#include "Utils/Logger.h"
#include "Scheduler/Timer.h"
#include "DecisionMaking/Layer3Tactical/Layer3Script/L3BaseLocation.h"

/**
 * @brief SPAR main class. Handles the BWAPI notifications.
 *
 * @tparam S1 Type of sensing layer 1.
 * @tparam S2 Type of sensing layer 2.
 * @tparam S3 Type of sensing layer 3.
 * @tparam S4 Type of sensing layer4.
 * @tparam S1 Type of decision making layer 1.
 * @tparam S2 Type of decision making layer 2.
 * @tparam S3 Type of decision making layer 3.
 * @tparam S4 Type of decision making layer 4.
 */
template <class S1, class S2, class S3, class S4, class D1, class D2, class D3, class D4>
class SparAIModule : public SparAIContainer
{
public:
  SparAIModule(const Process* parent);
  ~SparAIModule();

  S1& getS1() { return *m_layer1SituationAnalysis; }
  S2& getS2() { return *m_layer2SituationAnalysis; }
  S3& getS3() { return *m_layer3SituationAnalysis; }
  S4& getS4() { return *m_layer4SituationAnalysis; }

  D1& getD1() { return m_layer1DecisionMaking; }
  D2& getD2() { return m_layer2DecisionMaking; }
  D3& getD3() { return m_layer3DecisionMaking; }
  D4& getD4() { return m_layer4DecisionMaking; }

  virtual void onStartAIModule();
  virtual void onEndAIModule(bool isWinner);
  virtual void onStartProcess();
  virtual void onEndProcess();
  virtual void onFrame();
  virtual void onSendText(std::string text);

  virtual void onUnitDiscover(BWAPI::Unit* unit);
  virtual void onUnitEvade(BWAPI::Unit* unit);
  virtual void onUnitShow(BWAPI::Unit* unit);
  virtual void onUnitHide(BWAPI::Unit* unit);
  virtual void onUnitCreate(BWAPI::Unit* unit);
  virtual void onUnitDestroy(BWAPI::Unit* unit);
  virtual void onUnitMorph(BWAPI::Unit* unit);
  virtual void onUnitRenegade(BWAPI::Unit* unit);
  virtual void onUnitComplete(BWAPI::Unit* unit);

  virtual void onNukeDetect(BWAPI::Position target);

protected:

  unsigned int getNbMillisecondsAvailable() const
  {
    //
    // AIIDE 2012 (http://webdocs.cs.ualberta.ca/~cdavid/starcraftaicomp/rules.shtml)
    //
    //
    // Make sure that each onframe call does not run longer than 42ms. Entries that slow down games by repeatedly 
    // exceeding this time limit will lose games on time. In particular:
    //
    // In a match a bot LOSES (immediately) on time iff
    //
    // >= 2 frames exceed 10 seconds, or
    // >= 10 frames exceed 1 second, or
    // >= 200 frames exceed 55ms 
    //

    return 42;

    //static const unsigned int MILLISECOND_PER_FRAME_FASTEST = 40; // ~ 1000ms / 24 frames
    //return MILLISECOND_PER_FRAME_FASTEST;
  }

  class SparUnitData : public D1::L1UnitData, 
                       public D2::L2UnitData,
                       public D3::L3UnitData,
                       public PerceptualState::PerceptualStateUnitData,
                       public virtual UnitData
  {
  public:
    SparUnitData(BWAPI::Unit* unit)
      : D1::L1UnitData(unit)
      , D2::L2UnitData(unit)
      , D3::L3UnitData(unit)
      , PerceptualState::PerceptualStateUnitData(unit)
      , UnitData(unit)
    {}

    static UnitData* instantiate(BWAPI::Unit* unit)
    {
      UnitData* unitData = new SparUnitData(unit);
      assert(dynamic_cast<D1::L1UnitData*>(unitData) != NULL);
      assert(dynamic_cast<D2::L2UnitData*>(unitData) != NULL);
      assert(dynamic_cast<D3::L3UnitData*>(unitData) != NULL);
      assert(dynamic_cast<PerceptualState::PerceptualStateUnitData*>(unitData) != NULL);
      
      return unitData;
    }
  };

  class SparUnitDataFactory : public UnitDataFactory
  {
  public:
    SparUnitData* instantiateUnitData(BWAPI::Unit* unit)
    {
      return new SparUnitData(unit);
    }
  };

  class SparLocationFactory : public LocationFactory
  {
  protected:
    typedef typename D3::L3BaseLocation SparBaseLocation;
  public:
    SparBaseLocation* instantiateBaseLocation(BWTA::BaseLocation* baseLocation, Region* parent)
    {
      return new SparBaseLocation(baseLocation, parent);
    }
  };

  SparLocationFactory m_sparLocationFactory;
  SparUnitDataFactory m_sparUnitDataFactory;

  Timer m_timer;
  // Keep m_scheduler BEFORE m_perceptualState (else deleting locations will fail on Component::_verifyPendingTasks())
  Scheduler m_scheduler;
  MP::MPScheduler m_mpScheduler;
  PerceptualState m_perceptualState;

  // Situation analysis
  S1 m_layer1SituationAnalysis;
  S2 m_layer2SituationAnalysis;
  S3 m_layer3SituationAnalysis;
  S4 m_layer4SituationAnalysis;

  // Decision making
  D1 m_layer1DecisionMaking;
  D2 m_layer2DecisionMaking;
  D3 m_layer3DecisionMaking;
  D4 m_layer4DecisionMaking;
};

template <class S1, class S2, class S3, class S4, class D1, class D2, class D3, class D4>
SparAIModule<S1, S2, S3, S4, D1, D2, D3, D4>::SparAIModule(const Process* parent)
  : SparAIContainer(parent, &m_scheduler, &m_mpScheduler, &m_perceptualState, &m_timer)
  , m_mpScheduler(&m_scheduler)
  , m_perceptualState(m_sparLocationFactory)
  , m_layer1DecisionMaking(m_layer1SituationAnalysis)
  , m_layer2DecisionMaking(m_layer2SituationAnalysis, m_layer1DecisionMaking)
  , m_layer3DecisionMaking(m_layer3SituationAnalysis, m_layer2DecisionMaking)
  , m_layer4DecisionMaking(m_layer4SituationAnalysis, m_layer3DecisionMaking)
  , m_layer1SituationAnalysis()
  , m_layer2SituationAnalysis(m_layer1SituationAnalysis)
  , m_layer3SituationAnalysis(m_layer2SituationAnalysis)
  , m_layer4SituationAnalysis(m_layer3SituationAnalysis)
{
  BWAPI::setUnitDataFactory(&m_sparUnitDataFactory);

  SPAR_LOG(LogTypes.GENERAL, "SPAR AI created");
}

template <class S1, class S2, class S3, class S4, class D1, class D2, class D3, class D4>
SparAIModule<S1, S2, S3, S4, D1, D2, D3, D4>::~SparAIModule()
{
  SPAR_LOG(LogTypes.GENERAL, "SPAR AI destroyed");
}

template <class S1, class S2, class S3, class S4, class D1, class D2, class D3, class D4>
void SparAIModule<S1, S2, S3, S4, D1, D2, D3, D4>::onStartAIModule()
{
  SPAR_LOG(LogTypes.GENERAL, "Game started");

  SparAIContainer::onStartAIModule();
}

template <class S1, class S2, class S3, class S4, class D1, class D2, class D3, class D4>
void SparAIModule<S1, S2, S3, S4, D1, D2, D3, D4>::onEndAIModule(bool isWinner)
{
  if(isWinner)
    SPAR_LOG(LogTypes.GENERAL, "Game won");
  else
    SPAR_LOG(LogTypes.GENERAL, "Game lost");

  SparAIContainer::onEndAIModule(isWinner);
}

template <class S1, class S2, class S3, class S4, class D1, class D2, class D3, class D4>
void SparAIModule<S1, S2, S3, S4, D1, D2, D3, D4>::onStartProcess()
{
  SparAIContainer::onStartProcess();

  // Enable some cheat flags
  BWAPI::Broodwar->enableFlag(BWAPI::Flag::UserInput);
  //BWAPI::Broodwar->setGUI(false);
  //BWAPI::Broodwar->enableFlag(Flag::CompleteMapInformation);

  m_perceptualState.start();

  m_layer1DecisionMaking.start();
  m_layer2DecisionMaking.start();
  m_layer3DecisionMaking.start();
  m_layer4DecisionMaking.start();

  //m_layer1SituationAnalysis.start();
  //m_layer2SituationAnalysis.start();
  //m_layer3SituationAnalysis.start();
  //m_layer4SituationAnalysis.start();
}

template <class S1, class S2, class S3, class S4, class D1, class D2, class D3, class D4>
void SparAIModule<S1, S2, S3, S4, D1, D2, D3, D4>::onEndProcess()
{
  SparAIContainer::onEndProcess();
}

template <class S1, class S2, class S3, class S4, class D1, class D2, class D3, class D4>
void SparAIModule<S1, S2, S3, S4, D1, D2, D3, D4>::onFrame()
{
#ifdef NON_FINAL_VERSION
  BWAPI::Broodwar->drawTextScreen(10, 10, "AVG %0.4f\nFPS %d\nAPM %d\nPos (%d,%d)\nTile (%d,%d)", 
    BWAPI::Broodwar->getAverageFPS(), 
    BWAPI::Broodwar->getFPS(),
    int(BWAPI::Broodwar->getAPM()),
    (BWAPI::Broodwar->getScreenPosition() + BWAPI::Broodwar->getMousePosition()).x(), (BWAPI::Broodwar->getScreenPosition() + BWAPI::Broodwar->getMousePosition()).y(),
    BWAPI::TilePosition(BWAPI::Broodwar->getScreenPosition() + BWAPI::Broodwar->getMousePosition()).x(), BWAPI::TilePosition(BWAPI::Broodwar->getScreenPosition() + BWAPI::Broodwar->getMousePosition()).y());
#endif

  Timer::value_type endTime;
#ifdef NON_FINAL_VERSION
  unsigned int nbMilliseconds = Config::getInstance().getAvailableMillisecondsPerFrame();
  if (nbMilliseconds == Config::MS_NO_LIMIT)
  {
    endTime = boost::numeric::bounds<Timer::value_type>::highest();
  }
  else
  {
    if (nbMilliseconds == Config::MS_SCHEDULER)
    {
      nbMilliseconds = getNbMillisecondsAvailable();
    }
    endTime = m_timer.currentTime() + (nbMilliseconds * m_timer.timeFrequency() / 1000);
  }
#else
  const unsigned int nbMilliseconds = getNbMillisecondsAvailable();
  endTime = m_timer.currentTime() + (nbMilliseconds * m_timer.timeFrequency() / 1000);
#endif

  //Timer::value_type t0 = Spar->getTimer()->currentTime();
  SparAIContainer::onFrame();
  //Timer::value_type t1 = Spar->getTimer()->currentTime();

  BWAPI::onFrame();

  m_perceptualState.onFrame();
  //Timer::value_type t2 = Spar->getTimer()->currentTime();

  m_layer4SituationAnalysis.onFrame();
  //Timer::value_type t3 = Spar->getTimer()->currentTime();
  m_layer3SituationAnalysis.onFrame();
  //Timer::value_type t4 = Spar->getTimer()->currentTime();
  m_layer2SituationAnalysis.onFrame();
  //Timer::value_type t5 = Spar->getTimer()->currentTime();
  m_layer1SituationAnalysis.onFrame();
  //Timer::value_type t6 = Spar->getTimer()->currentTime();

  m_layer4DecisionMaking.onFrame();
  //Timer::value_type t7 = Spar->getTimer()->currentTime();
  m_layer3DecisionMaking.onFrame();
  //Timer::value_type t8 = Spar->getTimer()->currentTime();
  m_layer2DecisionMaking.onFrame();
  //Timer::value_type t9 = Spar->getTimer()->currentTime();
  m_layer1DecisionMaking.onFrame();
  //Timer::value_type t10 = Spar->getTimer()->currentTime();

  //Timer::value_type t11 = Spar->getTimer()->currentTime();

  //Timer::value_type t12 = Spar->getTimer()->currentTime();

  Spar->getScheduler()->onFrame(endTime);
  //Timer::value_type t13 = Spar->getTimer()->currentTime();

  //SPAR_LOG(LogTypes.GENERAL_ERROR, "AIC %I64d PS %I64d 4A %I64d 3A %I64d 2A %I64d 1A %I64d 4D %I64d 3D %I64d 2D %I64d 1D %I64d UM %I64d GUI %I64d S %I64d"
  //  , t1 - t0, t2 - t1, t3 - t2, t4 - t3, t5 - t4, t6 - t5, t7 - t6, t8 - t7, t9 - t8, t10 - t9, t11 - t10, t12 - t11, t13 - t12);

  // We may need to move the GUI before the scheduler, but in this case some stuff may produce errors
  // (techs not acquired, for example).
#ifdef USE_SPAR_GUI
  std::list<SparObserver*>::const_iterator it = m_observers.begin();
  while (it != m_observers.end())
  {
    std::list<SparObserver*>::const_iterator next = ++(std::list<SparObserver*>::const_iterator(it));
    (*it)->onFrame();
    it = next;
  }
#endif
}

template <class S1, class S2, class S3, class S4, class D1, class D2, class D3, class D4>
void SparAIModule<S1, S2, S3, S4, D1, D2, D3, D4>::onUnitDiscover(BWAPI::Unit* unit)
{
  BWAPI::onUnitDiscover(unit);
}

template <class S1, class S2, class S3, class S4, class D1, class D2, class D3, class D4>
void SparAIModule<S1, S2, S3, S4, D1, D2, D3, D4>::onUnitEvade(BWAPI::Unit* unit)
{
  BWAPI::onUnitEvade(unit);
}

template <class S1, class S2, class S3, class S4, class D1, class D2, class D3, class D4>
void SparAIModule<S1, S2, S3, S4, D1, D2, D3, D4>::onUnitShow(BWAPI::Unit* unit)
{
  BWAPI::onUnitShow(unit);
}

template <class S1, class S2, class S3, class S4, class D1, class D2, class D3, class D4>
void SparAIModule<S1, S2, S3, S4, D1, D2, D3, D4>::onUnitHide(BWAPI::Unit* unit)
{
  BWAPI::onUnitHide(unit);
}

template <class S1, class S2, class S3, class S4, class D1, class D2, class D3, class D4>
void SparAIModule<S1, S2, S3, S4, D1, D2, D3, D4>::onUnitCreate(BWAPI::Unit* unit)
{
  BWAPI::onUnitCreate(unit);
}

template <class S1, class S2, class S3, class S4, class D1, class D2, class D3, class D4>
void SparAIModule<S1, S2, S3, S4, D1, D2, D3, D4>::onUnitDestroy(BWAPI::Unit* unit)
{
  BWAPI::onUnitDestroy(unit);
}

template <class S1, class S2, class S3, class S4, class D1, class D2, class D3, class D4>
void SparAIModule<S1, S2, S3, S4, D1, D2, D3, D4>::onUnitMorph(BWAPI::Unit* unit)
{
  BWAPI::onUnitMorph(unit);
}

template <class S1, class S2, class S3, class S4, class D1, class D2, class D3, class D4>
void SparAIModule<S1, S2, S3, S4, D1, D2, D3, D4>::onUnitRenegade(BWAPI::Unit* unit)
{
  BWAPI::onUnitRenegade(unit);
}

template <class S1, class S2, class S3, class S4, class D1, class D2, class D3, class D4>
void SparAIModule<S1, S2, S3, S4, D1, D2, D3, D4>::onUnitComplete(BWAPI::Unit* unit)
{
  BWAPI::onUnitComplete(unit);
}

template <class S1, class S2, class S3, class S4, class D1, class D2, class D3, class D4>
void SparAIModule<S1, S2, S3, S4, D1, D2, D3, D4>::onNukeDetect(BWAPI::Position target)
{
  BWAPI::onNukeDetect(target);
}

template <class S1, class S2, class S3, class S4, class D1, class D2, class D3, class D4>
void SparAIModule<S1, S2, S3, S4, D1, D2, D3, D4>::onSendText(std::string text)
{
  BWAPI::Broodwar->sendText("%s",text.c_str());

  if (text == "actions")
  {
    BWAPI::Broodwar->drawTextScreen(5, 0, "Currently %u actions:", getNbChildProcesses());
    struct PrintChildProcess
    {
      PrintChildProcess() : m_line(0)
      {}
      SafeListBase::ConstIterationStep operator()(const Process* process) const
      {
        BWAPI::Broodwar->drawTextScreen(5, 16*m_line, "%d. %s", m_line, toString(process).c_str());
        ++m_line;

        return SafeListBase::ConstIterationStep();
      }
    private:
      mutable int m_line;
    };
    forEachChildProcess(PrintChildProcess());
  }
}
