#include "stdafx.h"
#include "mainwindow.h"
#include <sstream>
#include <hash_map>

template <class T>
bool convert(const QString& str, T& t)
{
  std::stringstream ss(str.toStdString());
  return !(ss >> t).fail();
}

bool MainWindow::s_initialized(false);
int MainWindow::UpdateCurrentProcessesEventType;
int MainWindow::UpdatePerceptualStateEventType;
int MainWindow::UpdateLayer2EventType;
int MainWindow::UpdateLayer3EventType;
int MainWindow::SchedulerSnapshotEventType;

void MainWindow::initialize()
{
  assert(!s_initialized);
  s_initialized = true;

  UpdateCurrentProcessesEventType = QEvent::registerEventType();
  UpdatePerceptualStateEventType = QEvent::registerEventType();
  UpdateLayer2EventType = QEvent::registerEventType();
  UpdateLayer3EventType = QEvent::registerEventType();
  SchedulerSnapshotEventType = QEvent::registerEventType();
}

MainWindow::MainWindow(UnitsToSelect& units, boost::mutex& unitsMutex)
  : ui(new Ui::MainWindow)
  , m_units(units)
  , m_unitsMutex(unitsMutex)
  , m_takeSchedulerSnapshot(false)
{
  assert(s_initialized);

  ui->setupUi(this);

  ui->chkMapAnalysis->setChecked(Config::getInstance().displayMap());
  ui->chkUnitsInFogOfWar->setChecked(Config::getInstance().displayUnitsInFogOfWar());
  ui->chkEnemyInfluence->setChecked(Config::getInstance().displayEnemyInfluenceMap());
  ui->chkSelfInfluence->setChecked(Config::getInstance().displaySelfInfluenceMap());
  ui->chkVisibilityInfluence->setChecked(Config::getInstance().displayVisibilityInfluenceMap());
  ui->chkVulnebilityInfluence->setChecked(Config::getInstance().displayVulnerabilityInfluenceMap());
  ui->chkStructureInfluence->setChecked(Config::getInstance().displayStructureInfluenceMap());
  ui->chkOwnUnitsActions->setChecked(Config::getInstance().displayOwnUnitsActions());
  ui->chkActionsOnly->setChecked(Config::getInstance().displayActionsOnlyInGUI());
  ui->chkProcessesUnits->setChecked(Config::getInstance().displayProcessesUnitsInGUI());
  ui->chkDetectionInfluence->setChecked(Config::getInstance().displayDetectionInfluenceMap());

  QObject::connect(ui->chkMapAnalysis, SIGNAL(stateChanged(int)),
                   this, SLOT(chkMapAnalysisValueChanged(int)));
  QObject::connect(ui->chkUnitsInFogOfWar, SIGNAL(stateChanged(int)),
                   this, SLOT(chkUnitsInFogOfWarValueChanged(int)));
  QObject::connect(ui->chkEnemyInfluence, SIGNAL(stateChanged(int)),
                   this, SLOT(chkEnemyInfluenceValueChanged(int)));
  QObject::connect(ui->chkSelfInfluence, SIGNAL(stateChanged(int)),
                   this, SLOT(chkSelfInfluenceValueChanged(int)));
  QObject::connect(ui->chkVisibilityInfluence, SIGNAL(stateChanged(int)),
                   this, SLOT(chkVisibilityInfluenceValueChanged(int)));  
  QObject::connect(ui->chkStructureInfluence, SIGNAL(stateChanged(int)),
                   this, SLOT(chkStructureInfluenceValueChanged(int)));
  QObject::connect(ui->chkDetectionInfluence, SIGNAL(stateChanged(int)),
                   this, SLOT(chkDetectionInfluenceValueChanged(int)));
  QObject::connect(ui->chkOwnUnitsActions, SIGNAL(stateChanged(int)),
                   this, SLOT(chkOwnUnitsActionsValueChanged(int)));
  QObject::connect(ui->chkVulnebilityInfluence, SIGNAL(stateChanged(int)),
                   this, SLOT(chkVulnerabilityInfluenceValueChanged(int)));


  QObject::connect(ui->rdbMsLimit, SIGNAL(toggled(bool)),
                   this, SLOT(rdbMsToggled(bool)));
  QObject::connect(ui->rdbMsNoLimit, SIGNAL(toggled(bool)),
                   this, SLOT(rdbMsToggled(bool)));
  QObject::connect(ui->rdbMsScheduler, SIGNAL(toggled(bool)),
                   this, SLOT(rdbMsToggled(bool)));
  QObject::connect(ui->txtMsPerFrame, SIGNAL(textChanged()),
                   this, SLOT(txtMsPerFrameTextChanged()));

  QObject::connect(ui->btnScheduler, SIGNAL(clicked(bool)),
                   this, SLOT(btnSchedulerClicked()));

  const std::set<BWAPI::UnitType>& unitTypes = BWAPI::UnitTypes::allUnitTypes();
  for (std::set<BWAPI::UnitType>::const_iterator it = unitTypes.begin(); it != unitTypes.end(); ++it)
  {
    BWAPI::UnitType type = *it;
    std::stringstream ss;
    ss << type.getID() << ". " << type.getName();
    ui->lstUnitTypes->addItem(QString::fromStdString(ss.str()));
  }

  const std::set<BWAPI::UpgradeType>& upgradeTypes = BWAPI::UpgradeTypes::allUpgradeTypes();
  for (std::set<BWAPI::UpgradeType>::const_iterator it = upgradeTypes.begin(); it != upgradeTypes.end(); ++it)
  {
    BWAPI::UpgradeType type = *it;
    std::stringstream ss;
    ss << type.getID() << ". " << type.getName();
    ui->lstUpgradeTypes->addItem(QString::fromStdString(ss.str()));
  }

  const std::set<BWAPI::TechType>& techTypes = BWAPI::TechTypes::allTechTypes();
  for (std::set<BWAPI::TechType>::const_iterator it = techTypes.begin(); it != techTypes.end(); ++it)
  {
    BWAPI::TechType type = *it;
    std::stringstream ss;
    ss << type.getID() << ". " << type.getName();
    ui->lstTechTypes->addItem(QString::fromStdString(ss.str()));
  }

  const unsigned int nbMillisecondsPerFrame = Config::getInstance().getAvailableMillisecondsPerFrame();
  QRadioButton* button;
  if (nbMillisecondsPerFrame == Config::MS_SCHEDULER)
  {
    button = ui->rdbMsScheduler;
  }
  else if (nbMillisecondsPerFrame == Config::MS_NO_LIMIT)
  {
    button = ui->rdbMsNoLimit;
  }
  else
  {
    button = ui->rdbMsLimit;
    ui->txtMsPerFrame->setPlainText(QString(nbMillisecondsPerFrame));
  }
  button->setChecked(true);
}

MainWindow::~MainWindow()
{
  delete ui;
}

void MainWindow::rdbMsToggled(bool checked)
{
  if (checked)
  {
    if (ui->rdbMsNoLimit->isChecked())
    {
      ui->txtMsPerFrame->setEnabled(false);
      Config::getInstance().setAvailableMillisecondsPerFrame(Config::MS_NO_LIMIT);
    }
    else if (ui->rdbMsScheduler->isChecked())
    {
      ui->txtMsPerFrame->setEnabled(false);
      Config::getInstance().setAvailableMillisecondsPerFrame(Config::MS_SCHEDULER);
    }
    else if (ui->rdbMsLimit->isChecked())
    {
      ui->txtMsPerFrame->setEnabled(true);
      unsigned int ms;
      if (convert<unsigned int>(ui->txtMsPerFrame->toPlainText(), ms))
        Config::getInstance().setAvailableMillisecondsPerFrame(ms);
    }
  }
}

void MainWindow::txtMsPerFrameTextChanged()
{
  if (ui->rdbMsLimit->isChecked())
  {
    unsigned int ms;
    if (convert<unsigned int>(ui->txtMsPerFrame->toPlainText(), ms))
      Config::getInstance().setAvailableMillisecondsPerFrame(ms);
  }
}

void MainWindow::btnSchedulerClicked()
{
  m_takeSchedulerSnapshot = true;
}

void MainWindow::updateCurrentProcessesUnits()
{
  // Takes place on the GUI thread

  //boost::mutex::scoped_lock(m_unitsMutex);
  m_unitsMutex.lock();

  m_units.clear();
  QList<QTreeWidgetItem*> selectedItems = ui->trvProcesses->selectedItems();
  for (QList<QTreeWidgetItem*>::const_iterator it = selectedItems.begin(); it != selectedItems.end(); ++it)
  {
    QTreeWidgetItemKey* item = dynamic_cast<QTreeWidgetItemKey*>(*it);
    if (item != NULL)
    {
      item->addUnits(m_units);
    }
  }

  m_unitsMutex.unlock();
}

struct ElementSavedData
{
  ElementSavedData(const QTreeWidgetItem* item)
    : m_isSelected(item->isSelected())
    , m_isExpanded(item->isExpanded())
  {}
  void restore(QTreeWidgetItem* item) const
  {
    item->setSelected(m_isSelected);
    item->setExpanded(m_isExpanded);
  }
private:
  bool m_isSelected;
  bool m_isExpanded;
};

struct SavedData
{
  static stdext::hash_map<const void*, SavedData> save(const QTreeWidget* tree)
  {
    stdext::hash_map<const void*, SavedData> data;
    for (int i = 0; i < tree->topLevelItemCount(); ++i)
    {
      QTreeWidgetItemKey* child = dynamic_cast<QTreeWidgetItemKey*>(tree->topLevelItem(i));
      assert(child != NULL);
      std::pair<stdext::hash_map<const void*, SavedData>::iterator, bool> result = 
        data.insert(std::make_pair(child->getKey(), SavedData(child)));
      result.first->second.saveChildren(child);
    }
    return data;
  }

  SavedData(const QTreeWidgetItemKey* item)
    : m_key(item->getKey())
    , m_elementData(item)
  {
  }
  void saveChildren(const QTreeWidgetItemKey* item)
  {
    for (int i = 0; i < item->childCount(); ++i)
    {
      QTreeWidgetItemKey* child = dynamic_cast<QTreeWidgetItemKey*>(item->child(i));
      assert(child != NULL);
      std::pair<stdext::hash_map<const void*, SavedData>::iterator, bool> result = 
        m_childrenData.insert(std::make_pair(child->getKey(), SavedData(child)));
      result.first->second.saveChildren(child);
    }
  }
  const ElementSavedData& getElementData() const { return m_elementData; }
  const SavedData* getChildrenData(const void* key) const
  {
    stdext::hash_map<const void*, SavedData>::const_iterator it = m_childrenData.find(key);
    return it != m_childrenData.end() ? &it->second : NULL;
  }
private:
  const void* m_key;
  ElementSavedData m_elementData;
  stdext::hash_map<const void*, SavedData> m_childrenData;
};

// This save/restore process wouldn't be needed if we connected a data model directly (event-driven)

stdext::hash_map<const void*, SavedData> save(const QTreeWidget* tree)
{
  return SavedData::save(tree);
}

void restore(QTreeWidgetItemKey* item, const SavedData* data)
{
  if (data != NULL)
  {
    data->getElementData().restore(item);
    for (int i = 0; i < item->childCount(); ++i)
    {
      QTreeWidgetItemKey* child = dynamic_cast<QTreeWidgetItemKey*>(item->child(i));
      restore(child, data->getChildrenData(child->getKey()));
    }
  }
}

void restore(QTreeWidget* tree, const stdext::hash_map<const void*, SavedData>& data)
{
  for (int i = 0; i < tree->topLevelItemCount(); ++i)
  {
    QTreeWidgetItemKey* topLevelChild = dynamic_cast<QTreeWidgetItemKey*>(tree->topLevelItem(i));
    stdext::hash_map<const void*, SavedData>::const_iterator it = data.find(topLevelChild->getKey());
    restore(topLevelChild, it != data.end() ? &it->second : NULL);
  }
}

bool MainWindow::event(QEvent* ev)
{
  // At the moment the controls are directly cleared then refreshed... obviously a better solution would be to
  // modify the model instead.

  if (ev->type() == UpdateCurrentProcessesEventType)
  {
    ev->accept();
    UpdateCurrentProcessesEvent* processesEvent = static_cast<UpdateCurrentProcessesEvent*>(ev);

    stdext::hash_map<const void*, SavedData> data = save(ui->trvProcesses);

    ui->trvProcesses->clear();
    processesEvent->getRoot()->display(ui->trvProcesses, displayActionsOnly(), displayProcessesUnits());

    restore(ui->trvProcesses, data);

    updateCurrentProcessesUnits();
  }
  else if (ev->type() == UpdatePerceptualStateEventType)
  {
    ev->accept();
    UpdatePerceptualStateEvent* perceptualStateEvent = static_cast<UpdatePerceptualStateEvent*>(ev);

    stdext::hash_map<const void*, SavedData> data = save(ui->trvLocations);
    ui->trvLocations->clear();
    perceptualStateEvent->getRoot()->display(ui->trvLocations);
    restore(ui->trvLocations, data);

    const std::vector<TechWrapper>& techs = perceptualStateEvent->getTechs();
    ui->lstTechs->clear();
    for (std::vector<TechWrapper>::const_iterator it = techs.begin(); it != techs.end(); ++it)
    {
      const TechWrapper& techWrapper = *it;
      QListWidgetItem* item = new QListWidgetItem();
      techWrapper.displayIn(item);
      ui->lstTechs->addItem(item);
    }

    const std::map<BWAPI::UnitType, size_t>& enemyArmyComposition = perceptualStateEvent->getEnemyArmyComposition();
    ui->lstEnemyArmyComposition->clear();
    for (std::map<BWAPI::UnitType, size_t>::const_iterator it = enemyArmyComposition.begin(); it != enemyArmyComposition.end(); ++it)
    {
      std::stringstream ss;
      ss << it->second << " " << it->first.getName();
      ui->lstEnemyArmyComposition->addItem(QString::fromStdString(ss.str()));
    }
  }
  else if (ev->type() == UpdateLayer2EventType)
  {
    ev->accept();
    UpdateLayer2Event* layer2Event = static_cast<UpdateLayer2Event*>(ev);

    Resources reservedResources = layer2Event->getReservedResources();
    ui->lblReservedMinerals->setText(QString::number(reservedResources.getMinerals()));
    ui->lblReservedGas->setText(QString::number(reservedResources.getGas()));
    ui->lblReservedSupply->setText(QString::number(reservedResources.getSupply()));

    ui->lstResourcesRequests->clear();
    const std::vector<QString>& resourcesRequests = layer2Event->getResourcesRequests();
    for (std::vector<QString>::const_iterator it = resourcesRequests.begin(); it != resourcesRequests.end(); ++it)
    {
      ui->lstResourcesRequests->addItem(*it);
    }
  }
  else if (ev->type() == UpdateLayer3EventType)
  {
    ev->accept();
    UpdateLayer3Event* layer3Event = static_cast<UpdateLayer3Event*>(ev);

    ui->lstTargetArmyComposition->clear();
    const std::vector<QString>& targetArmy = layer3Event->getTargetArmyComposition();
    for (std::vector<QString>::const_iterator it = targetArmy.begin(); it != targetArmy.end(); ++it)
    {
      ui->lstTargetArmyComposition->addItem(*it);
    }

    ui->lstTargetTechs->clear();
    const std::vector<QString>& targetTechs = layer3Event->getTargetTechs();
    for (std::vector<QString>::const_iterator it = targetTechs.begin(); it != targetTechs.end(); ++it)
    {
      ui->lstTargetTechs->addItem(*it);
    }

    ui->lstCurrentTechActions->clear();
    const std::vector<QString>& currentTechActions = layer3Event->getCurrentTechActions();
    for (std::vector<QString>::const_iterator it = currentTechActions.begin(); it != currentTechActions.end(); ++it)
    {
      ui->lstCurrentTechActions->addItem(*it);
    }
  }
  else if (ev->type() == SchedulerSnapshotEventType)
  {
    ev->accept();
    SchedulerSnapshotEvent* schedulerEvent = static_cast<SchedulerSnapshotEvent*>(ev);

    ui->trvScheduler->clear();
    const std::list<TaskWrapper>& currentTasks = schedulerEvent->getCurrentTasks();
    QTreeWidgetItem* currentItem = new QTreeWidgetItem(ui->trvScheduler);
    size_t currentFrame = 0.;
    currentItem->setText(0, "Snapshot taken at the end of frame " + QString::number(schedulerEvent->getSnapshotFrame()));
    currentItem->setExpanded(true);
    
    for (std::list<TaskWrapper>::const_iterator it = currentTasks.begin(); it != currentTasks.end(); ++it)
    {
      const TaskWrapper& task = *it;
      if (currentItem == NULL || currentFrame != task.getExecutionFrame())
      {
        currentFrame = task.getExecutionFrame();
        currentItem = new QTreeWidgetItem(ui->trvScheduler);
        currentItem->setText(0, QString::number(currentFrame));
        currentItem->setExpanded(true);
      }
      task.display(currentItem, schedulerEvent->getSnapshotFrame());
    }
  }
  QMainWindow::event(ev);

  return ev->isAccepted();
}