#pragma once
#include "ui_mainwindow.h"
#include "model/Process.h"
#include "model/Location.h"
#include "model/Tech.h"
#include "model/Task.h"
#include <QtCore/QEvent>
#include <SparAIModule.h>
#include <DecisionMaking/Layer2Actions/Layer2Managers/Layer2Managers.h>
#include <DecisionMaking/Layer3Tactical/Layer3Script/Layer3Script.h>
#include <boost/thread/mutex.hpp>
#include <sstream>

namespace Ui {
  class MainWindow;
}

class MainWindow : public QMainWindow
{
  Q_OBJECT

public:
  explicit MainWindow(UnitsToSelect& units, boost::mutex& unitsMutex);
  ~MainWindow();

  static void initialize();

  bool displayActionsOnly() const
  {
    return ui->chkActionsOnly->isChecked();
  }

  bool displayProcessesUnits() const
  {
    return ui->chkProcessesUnits->isChecked();
  }

  bool takeSchedulerSnapshot() const
  {
    return m_takeSchedulerSnapshot;
  }

  void setTakeSchedulerSnapshot(bool value)
  {
    m_takeSchedulerSnapshot = value;
  }

  virtual bool event(QEvent* ev);

  static int UpdateCurrentProcessesEventType;
  static int UpdatePerceptualStateEventType;
  static int UpdateLayer2EventType;
  static int UpdateLayer3EventType;
  static int SchedulerSnapshotEventType;

public slots:
  void chkMapAnalysisValueChanged(int value) { Config::getInstance().setDisplayMap(bool(value)); }
  void chkUnitsInFogOfWarValueChanged(int value) { Config::getInstance().setDisplayUnitsInFogOfWar(bool(value)); }
  void chkEnemyInfluenceValueChanged(int value) { Config::getInstance().setDisplayEnemyInfluenceMap(bool(value)); }
  void chkSelfInfluenceValueChanged(int value) { Config::getInstance().setDisplaySelfInfluenceMap(bool(value)); }
  void chkVisibilityInfluenceValueChanged(int value) { Config::getInstance().setDisplayVisibilityInfluenceMap(bool(value)); }
  void chkVulnerabilityInfluenceValueChanged(int value) { Config::getInstance().setDisplayVulnerabilityInfluenceMap(bool(value)); }
  void chkStructureInfluenceValueChanged(int value) { Config::getInstance().setDisplayStructureInfluenceMap(bool(value)); }
  void chkDetectionInfluenceValueChanged(int value) { Config::getInstance().setDisplayDetectionInfluenceMap(bool(value)); }
  void chkOwnUnitsActionsValueChanged(int value) { Config::getInstance().setDisplayOwnUnitsActions(bool(value)); }
  void rdbMsToggled(bool checked);
  void txtMsPerFrameTextChanged();
  void btnSchedulerClicked();

private:
  void updateCurrentProcessesUnits();

  UnitsToSelect& m_units;
  boost::mutex& m_unitsMutex;
  bool m_takeSchedulerSnapshot;

  Ui::MainWindow *ui;

  static bool s_initialized;
};

class UpdateCurrentProcessesEvent : public QEvent
{
public:
  UpdateCurrentProcessesEvent(const Process* root)
    : QEvent((QEvent::Type)MainWindow::UpdateCurrentProcessesEventType)
    , m_root(ProcessWrapper::createWrapper(root))
  {
  }
  ~UpdateCurrentProcessesEvent()
  {
    delete m_root;
  }
  ProcessWrapper* getRoot() { return m_root; }
private:
  ProcessWrapper* const m_root;
};

class UpdatePerceptualStateEvent : public QEvent
{
public:
  UpdatePerceptualStateEvent(const PerceptualState& perceptualState)
    : QEvent((QEvent::Type)MainWindow::UpdatePerceptualStateEventType)
  {
    const Map* map = perceptualState.getMap();
    m_root = LocationWrapper::createWrapper(map);

    std::pair<CurrentWorld::tech_const_iterator, CurrentWorld::tech_const_iterator> techs = CurrentWorld::getAllTechs();
    for (; techs.first != techs.second; ++techs.first)
    {
      const TechObject* tech = *techs.first;
      m_techs.push_back(TechWrapper(tech));
    }

    std::pair<EnemyArmyComposition::UnitCompositionIterator, EnemyArmyComposition::UnitCompositionIterator> enemyUnits = 
      perceptualState.getEnemyArmyComposition()->getEstimatedEnemyArmyComposition();
    for (; enemyUnits.first != enemyUnits.second; ++enemyUnits.first)
    {
      m_enemyArmyComposition.insert(*enemyUnits.first);
    }
  }
  ~UpdatePerceptualStateEvent()
  {
    delete m_root;
  }
  LocationWrapper* getRoot() { return m_root; }
  const std::vector<TechWrapper>& getTechs() const { return m_techs; }
  const std::map<BWAPI::UnitType, size_t>& getEnemyArmyComposition() const { return m_enemyArmyComposition; }
private:
  LocationWrapper* m_root;
  std::vector<TechWrapper> m_techs;
  std::map<BWAPI::UnitType, size_t> m_enemyArmyComposition;
};

class UpdateLayer2Event : public QEvent
{
public:
  template <class Layer2>
  UpdateLayer2Event(const Layer2& layer2)
    : QEvent((QEvent::Type)MainWindow::UpdateLayer2EventType)
  {
    initialize(layer2);
  }
  Resources getReservedResources() const { return m_reservedResources; }
  const std::vector<QString>& getResourcesRequests() const { return m_resourcesRequests; }
private:
  template <class Layer2>
  void initializeBase(const Layer2& layer2)
  {
    unused(layer2);
  }
  template <class Layer2>
  void initialize(const Layer2& layer2)
  {
    initializeBase(layer2);
  }
  template <>
  void initialize<Layer2Managers>(const Layer2Managers& layer2)
  {
    initializeBase(layer2);
    m_reservedResources = layer2.getReservedResources();
    std::pair<Layer2Managers::ResourcesRequestIterator, Layer2Managers::ResourcesRequestIterator> requests = layer2.getPendingResourcesRequests();
    for (; requests.first != requests.second; ++requests.first)
    {
      m_resourcesRequests.push_back(QString::fromStdString(toString(**requests.first)));
    }
  }

  Resources m_reservedResources;
  std::vector<QString> m_resourcesRequests;
};

class UpdateLayer3Event : public QEvent
{
public:
  template <class Layer3>
  UpdateLayer3Event(const Layer3& layer3)
    : QEvent((QEvent::Type)MainWindow::UpdateLayer3EventType)
  {
    initialize(layer3);
  }
  const std::vector<QString>& getTargetArmyComposition() const { return m_targetArmyComposition; }
  const std::vector<QString>& getTargetTechs() const { return m_targetTechs; }
  const std::vector<QString>& getCurrentTechActions() const { return m_currentTechActions; }
private:
  template <class Layer3>
  void initializeBase(const Layer3& layer3)
  {
    const ArmyComposition& army = layer3.getTargetArmyComposition();
    const std::map<BWAPI::UnitType, size_t>& unlimitedComposition = army.getUnlimitedComposition();
    for (std::map<BWAPI::UnitType, size_t>::const_iterator it = unlimitedComposition.begin(); it != unlimitedComposition.end(); ++it)
    {
      const std::pair<BWAPI::UnitType, size_t>& unit = *it;
      std::stringstream ss;
      ss << unit.second << " " << unit.first;
      m_targetArmyComposition.push_back(QString::fromStdString(ss.str()));
    }

    const TechSet& techs = layer3.getTargetTechs();
    for (TechSet::const_iterator it = techs.begin(); it != techs.end(); ++it)
    {
      m_targetTechs.push_back(QString::fromStdString(toString(*it)));
    }
  }
  template <class Layer3>
  void initialize(const Layer3& layer3)
  {
    initializeBase(layer3);
  }
  template <>
  void initialize<Layer3Script>(const Layer3Script& layer3)
  {
    initializeBase(layer3);
    const ProgressTechProcess* progressTech = layer3.getProgressTechProcess();
    std::pair<ProgressTechProcess::TechActionIterator, ProgressTechProcess::TechActionIterator> techActions = progressTech->getCurrentTechActions();
    for (; techActions.first != techActions.second; ++techActions.first)
    {
      std::pair<const SparObject*, const TacticalAction*> tech = *techActions.first;
      std::stringstream ss;
      ss << *tech.first << ": " << *tech.second;
      m_currentTechActions.push_back(QString::fromStdString(ss.str()));
    }
  }

  std::vector<QString> m_targetArmyComposition;
  std::vector<QString> m_targetTechs;
  std::vector<QString> m_currentTechActions;
};

class SchedulerSnapshotEvent : public QEvent
{
public:
  SchedulerSnapshotEvent(const Scheduler* scheduler)
    : QEvent((QEvent::Type)MainWindow::SchedulerSnapshotEventType)
    , m_frame(static_cast<size_t>(BWAPI::Broodwar->getFrameCount()))
  {
    std::pair<Scheduler::TaskIterator, Scheduler::TaskIterator> tasks = scheduler->getCurrentTasks();
    for (; tasks.first != tasks.second; ++tasks.first)
    {
      m_tasks.insert(m_tasks.end(), TaskWrapper(*tasks.first));
    }
  }
  const std::list<TaskWrapper>& getCurrentTasks() const { return m_tasks; }
  size_t getSnapshotFrame() const { return m_frame; }
private:
  std::list<TaskWrapper> m_tasks;
  const size_t m_frame;
};