#include <QSettings>
#include <QTimer>
#include <QMap>
#include <QDir>
#include <QFileInfo>

#include "sampcheck.h"
#include "qmc2main.h"
#include "procmgr.h"
#include "toolexec.h"
#include "macros.h"

// external global variables
extern MainWindow *qmc2MainWindow;
extern ProcessManager *qmc2ProcessManager;
extern QSettings *qmc2Config;
extern bool qmc2CleaningUp;
extern bool qmc2EarlyStartup;
extern QString verifyLastLine;
extern bool qmc2ReloadActive;
extern bool qmc2ImageCheckActive;
extern bool qmc2SampleCheckActive;
extern bool qmc2EarlyReloadActive;
extern bool qmc2VerifyActive;
extern bool qmc2FilterActive;
extern bool qmc2StopParser;
extern QMap<QString, QTreeWidgetItem *> qmc2GamelistItemMap;
extern QMap<QString, QTreeWidgetItem *> qmc2HierarchyItemMap;

SampleChecker::SampleChecker(QWidget *parent)
  : QDialog(parent, Qt::Dialog | Qt::SubWindow)
{
#ifdef QMC2_DEBUG
  qmc2MainWindow->log(QMC2_LOG_FRONTEND, QString("DEBUG: SampleChecker::SampleChecker(QWidget *parent = %1").arg((qulonglong)parent));
#endif

  setupUi(this);

  ignoreResizeAndMove = TRUE;
  verifyProc = NULL;
}

SampleChecker::~SampleChecker()
{
#ifdef QMC2_DEBUG
  qmc2MainWindow->log(QMC2_LOG_FRONTEND, "DEBUG: SampleChecker::~SampleChecker()");
#endif

  if ( verifyProc )
    verifyProc->terminate();
}

void SampleChecker::restoreLayout()
{
#ifdef QMC2_DEBUG
  qmc2MainWindow->log(QMC2_LOG_FRONTEND, "DEBUG: SampleChecker::restoreLayout()");
#endif

  // restore settings
  checkBoxSamplesSelectGame->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + "SampleChecker/SelectGame", TRUE).toBool());
  if ( qmc2Config->value(QMC2_FRONTEND_PREFIX + "GUI/RestoreLayout").toBool() ) {
    move(qmc2Config->value(QMC2_FRONTEND_PREFIX + "Layout/SampleChecker/Position", pos()).toPoint());
    resize(qmc2Config->value(QMC2_FRONTEND_PREFIX + "Layout/SampleChecker/Size", size()).toSize());
  }

  ignoreResizeAndMove = FALSE;
}

void SampleChecker::closeEvent(QCloseEvent *e)
{
#ifdef QMC2_DEBUG
  qmc2MainWindow->log(QMC2_LOG_FRONTEND, QString("DEBUG: SampleChecker::closeEvent(QCloseEvent *e = %1").arg((qulonglong)e));
#endif

  // save settings
  qmc2Config->setValue(QMC2_FRONTEND_PREFIX + "SampleChecker/SelectGame", checkBoxSamplesSelectGame->isChecked());
  if ( qmc2Config->value(QMC2_FRONTEND_PREFIX + "GUI/SaveLayout").toBool() && !qmc2CleaningUp )
    qmc2Config->setValue(QMC2_FRONTEND_PREFIX + "Layout/SampleChecker/Visible", FALSE);

  if ( e )
    e->accept();
}

void SampleChecker::hideEvent(QHideEvent *e)
{
#ifdef QMC2_DEBUG
  qmc2MainWindow->log(QMC2_LOG_FRONTEND, QString("DEBUG: SampleChecker::hideEvent(QHideEvent *e = %1").arg((qulonglong)e));
#endif

  closeEvent(NULL);
  e->accept();
}

void SampleChecker::showEvent(QShowEvent *e)
{
#ifdef QMC2_DEBUG
  qmc2MainWindow->log(QMC2_LOG_FRONTEND, QString("DEBUG: SampleChecker::showEvent(QShowEvent *e = %1").arg((qulonglong)e));
#endif

  ignoreResizeAndMove = TRUE;

  if ( qmc2Config->value(QMC2_FRONTEND_PREFIX + "GUI/SaveLayout").toBool() )
    qmc2Config->setValue(QMC2_FRONTEND_PREFIX + "Layout/SampleChecker/Visible", TRUE);

  QTimer::singleShot(0, this, SLOT(restoreLayout()));

  if ( e )
    e->accept();
}

void SampleChecker::moveEvent(QMoveEvent *e)
{
#ifdef QMC2_DEBUG
  qmc2MainWindow->log(QMC2_LOG_FRONTEND, QString("DEBUG: SampleChecker::moveEvent(QMoveEvent *e = %1").arg((qulonglong)e));
#endif

  if ( !ignoreResizeAndMove )
    if ( !qmc2CleaningUp && !qmc2EarlyStartup )
      if ( qmc2Config->value(QMC2_FRONTEND_PREFIX + "GUI/SaveLayout").toBool() )
        qmc2Config->setValue(QMC2_FRONTEND_PREFIX + "Layout/SampleChecker/Position", pos());

  e->accept();
}

void SampleChecker::resizeEvent(QResizeEvent *e)
{
#ifdef QMC2_DEBUG
  qmc2MainWindow->log(QMC2_LOG_FRONTEND, QString("DEBUG: SampleChecker::resizeEvent(QResizeEvent *e = %1").arg((qulonglong)e));
#endif

  if ( !ignoreResizeAndMove )
    if ( !qmc2CleaningUp && !qmc2EarlyStartup )
      if ( qmc2Config->value(QMC2_FRONTEND_PREFIX + "GUI/SaveLayout").toBool() )
        qmc2Config->setValue(QMC2_FRONTEND_PREFIX + "Layout/SampleChecker/Size", size());

  e->accept();
}

void SampleChecker::verify()
{
#ifdef QMC2_DEBUG
  qmc2MainWindow->log(QMC2_LOG_FRONTEND, QString("DEBUG: SampleChecker::verify()"));
#endif

#if defined(QMC2_SDLMAME)
  qmc2SampleCheckActive = TRUE;
  qmc2StopParser = FALSE;

  sampleSets.clear();
  verifyLastLine = "";
  verifyTimer.start();
  qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("verifying samples"));
  listWidgetSamplesGood->clear();
  labelSamplesGood->setText(tr("Good: 0"));
  listWidgetSamplesBad->clear();
  labelSamplesBad->setText(tr("Bad: 0"));
  listWidgetSamplesObsolete->clear();
  labelSamplesObsolete->setText(tr("Obsolete: 0"));
  pushButtonSamplesRemoveObsolete->setEnabled(FALSE);
  qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("check pass 1: sample status"));
  
  QStringList args;
  QString command = qmc2Config->value("MAME/FilesAndDirectories/ExecutableFile").toString();
  args << "-samplepath" << qmc2Config->value("MAME/Configuration/Global/samplepath").toString();
  args << "-verifysamples";

  verifyProc = qmc2ProcessManager->process(qmc2ProcessManager->start(command, args, FALSE));
  connect(verifyProc, SIGNAL(error(QProcess::ProcessError)), this, SLOT(verifyError(QProcess::ProcessError)));
  connect(verifyProc, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(verifyFinished(int, QProcess::ExitStatus)));
  connect(verifyProc, SIGNAL(readyReadStandardOutput()), this, SLOT(verifyReadyReadStandardOutput()));
  connect(verifyProc, SIGNAL(readyReadStandardError()), this, SLOT(verifyReadyReadStandardError()));
  connect(verifyProc, SIGNAL(started()), this, SLOT(verifyStarted()));
  connect(verifyProc, SIGNAL(stateChanged(QProcess::ProcessState)), this, SLOT(verifyStateChanged(QProcess::ProcessState)));
#endif
}

void SampleChecker::verifyStarted()
{
#ifdef QMC2_DEBUG
  qmc2MainWindow->log(QMC2_LOG_FRONTEND, "DEBUG: SampleChecker::verifyStarted()");
#endif

}

void SampleChecker::verifyFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
  QProcess *proc = (QProcess *)sender();

#ifdef QMC2_DEBUG
  qmc2MainWindow->log(QMC2_LOG_FRONTEND, "DEBUG: SampleChecker::verifyFinished(int exitCode = " + QString::number(exitCode) + ", QProcess::ExitStatus exitStatus = " + QString::number(exitStatus) + "): proc = 0x" + QString::number((ulong)proc, 16));
#endif

  qmc2ProcessManager->procMap.remove(proc);
  qmc2ProcessManager->procCount--;

  qApp->processEvents();
  QTimer::singleShot(0, this, SLOT(verifyObsolete()));
}

void SampleChecker::verifyObsolete()
{
#ifdef QMC2_DEBUG
  qmc2MainWindow->log(QMC2_LOG_FRONTEND, "DEBUG: SampleChecker::verifyObsolete()");
#endif

  if ( !qmc2StopParser ) {
    qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("check pass 2: obsolete sample sets"));
    QStringList fileList;
    QString samplePath = qmc2Config->value("MAME/Configuration/Global/samplepath").toString();
    if ( !samplePath.endsWith("/") ) samplePath += "/";
    recursiveFileList(samplePath, fileList);
    int i;
#ifdef QMC2_DEBUG
    for (i = 0; i < sampleSets.count(); i++)
      qmc2MainWindow->log(QMC2_LOG_FRONTEND, QString("DEBUG: SampleChecker::verifyObsolete(): sampleSets[%1] = %2").arg(i).arg(sampleSets[i]));
#endif
    for (i = 0; i < fileList.count() && !qmc2StopParser; i++) {
      QString relativeFilePath = fileList[i].remove(samplePath);
      QString gameName = relativeFilePath.toLower().remove(QRegExp("\\.zip$"));
#ifdef QMC2_DEBUG
      qmc2MainWindow->log(QMC2_LOG_FRONTEND, QString("DEBUG: SampleChecker::verifyObsolete(): fileList[i] = %1, relativeFilePath = %2, gameName = %3").arg(fileList[i]).arg(relativeFilePath).arg(gameName));
#endif
      if ( !sampleSets.contains(gameName) ) {
        listWidgetSamplesObsolete->addItem(relativeFilePath);
        labelSamplesObsolete->setText(tr("Obsolete: %1").arg(listWidgetSamplesObsolete->count()));
      }
    }
  }
  listWidgetSamplesGood->sortItems(Qt::AscendingOrder);
  listWidgetSamplesBad->sortItems(Qt::AscendingOrder);
  listWidgetSamplesObsolete->sortItems(Qt::AscendingOrder);
  qApp->processEvents();
  QTime elapsedTime;
  elapsedTime = elapsedTime.addMSecs(verifyTimer.elapsed());
  qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("done (verifying samples, elapsed time = %1)").arg(elapsedTime.toString("mm:ss.zzz")));
  qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("%1 good, %2 bad (or missing), %3 obsolete").arg(listWidgetSamplesGood->count()).arg(listWidgetSamplesBad->count()).arg(listWidgetSamplesObsolete->count()));

  // enable removal button if obsolete samples exist
  pushButtonSamplesRemoveObsolete->setEnabled(listWidgetSamplesObsolete->count() > 0);

  pushButtonSamplesCheck->setText(tr("&Check samples"));
  qmc2SampleCheckActive = FALSE;
}

void SampleChecker::verifyReadyReadStandardOutput()
{
  QProcess *proc = (QProcess *)sender();

#ifdef QMC2_DEBUG
  qmc2MainWindow->log(QMC2_LOG_FRONTEND, "DEBUG: SampleChecker::verifyReadyReadStandardOutput(): proc = 0x" + QString::number((ulong)proc, 16));
#endif

  // process sample verification output
  int i;
  QString s = verifyLastLine + proc->readAllStandardOutput();
  QStringList lines = s.split("\n");

  if ( s.endsWith("\n") ) {
    verifyLastLine = "";
  } else {
    verifyLastLine = lines.last();
    lines.removeLast();
  }

  QStringList badList;
  for (i = 0; i < lines.count(); i++) {
    if ( lines[i].simplified().isEmpty() )
      continue;
    QStringList words = lines[i].simplified().split(" ");
#if defined(QMC2_SDLMAME)
    if ( lines[i].startsWith("sampleset ") && lines[i].endsWith("good") ) {
      listWidgetSamplesGood->addItem(words[1]);
      labelSamplesGood->setText(tr("Good: %1").arg(listWidgetSamplesGood->count()));
      sampleSets << words[1];
    } else if ( !lines[i].contains("samplesets found,") ) {
      QString sampleName = words[0].remove(":"); 
      if ( !badList.contains(sampleName) ) {
        listWidgetSamplesBad->addItem(sampleName);
        labelSamplesBad->setText(tr("Bad: %1").arg(listWidgetSamplesBad->count()));
        badList << sampleName;
        sampleSets << sampleName;
      }
    }
#endif
  }

  if ( qmc2StopParser && verifyProc )
    verifyProc->terminate();

  qApp->processEvents();
}

void SampleChecker::verifyReadyReadStandardError()
{
  QProcess *proc = (QProcess *)sender();

#ifdef QMC2_DEBUG
  qmc2MainWindow->log(QMC2_LOG_FRONTEND, "DEBUG: SampleChecker::verifyReadyReadStandardError(): proc = 0x" + QString::number((ulong)proc, 16));
#endif

}

void SampleChecker::verifyError(QProcess::ProcessError processError)
{
#ifdef QMC2_DEBUG
  qmc2MainWindow->log(QMC2_LOG_FRONTEND, "DEBUG: SampleChecker::verifyError(QProcess::ProcessError processError = " + QString::number(processError) + ")");
#endif

}

void SampleChecker::verifyStateChanged(QProcess::ProcessState processState)
{
#ifdef QMC2_DEBUG
  qmc2MainWindow->log(QMC2_LOG_FRONTEND, "DEBUG: SampleChecker::verifyStateChanged(QProcess::ProcessState = " + QString::number(processState) + ")");
#endif

}

void SampleChecker::on_pushButtonSamplesCheck_clicked()
{
#ifdef QMC2_DEBUG
  qmc2MainWindow->log(QMC2_LOG_FRONTEND, "DEBUG: SampleChecker::on_pushButtonSamplesCheck_clicked()");
#endif

  if ( qmc2ReloadActive ) {
    qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("please wait for reload to finish and try again"));
    return;
  }

  if ( qmc2FilterActive ) {
    qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("please wait for ROM state filter to finish and try again"));
    return;
  }

  if ( qmc2VerifyActive ) {
    qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("please wait for ROM verification to finish and try again"));
    return;
  }

  if ( qmc2ImageCheckActive ) {
    qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("please wait for image check to finish and try again"));
    return;
  }

  if ( qmc2SampleCheckActive ) {
    qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("stopping sample check upon user request"));
    qmc2StopParser = TRUE;
    return;
  }

  pushButtonSamplesCheck->setText(tr("&Stop check"));
  verify();
}

void SampleChecker::on_pushButtonSamplesRemoveObsolete_clicked()
{
#ifdef QMC2_DEBUG
  qmc2MainWindow->log(QMC2_LOG_FRONTEND, "DEBUG: SampleChecker::on_pushButtonSamplesRemoveObsolete_clicked()");
#endif

  QString command = qmc2Config->value(QMC2_FRONTEND_PREFIX + "Tools/FileRemovalTool").toString();
  QStringList args = qmc2Config->value(QMC2_FRONTEND_PREFIX + "Tools/FileRemovalToolArguments").toString().split(" ");

  int i, j;
  QStringList addArgs;
  for (i = 0; i < args.count(); i++) {
    if ( args[i] == "$FILELIST$" ) {
      QList<QListWidgetItem *> items = listWidgetSamplesObsolete->findItems("*", Qt::MatchWildcard); 
      QString samplePath = qmc2Config->value("MAME/Configuration/Global/samplepath").toString();
      if ( !samplePath.endsWith("/") )
        samplePath += "/";
      for (j = 0; j < items.count(); j++)
        addArgs << samplePath + items[j]->text();
      args.removeAt(i);
      args << addArgs;
    }
  }

  ToolExecutor fileRemovalTool(this, command, args);
  fileRemovalTool.exec();
}

void SampleChecker::on_listWidgetSamplesGood_itemSelectionChanged()
{
#ifdef QMC2_DEBUG
  qmc2MainWindow->log(QMC2_LOG_FRONTEND, "DEBUG: SampleChecker::on_listWidgetSamplesGood_itemSelectionChanged()");
#endif

  if ( checkBoxSamplesSelectGame->isChecked() ) {
    QList<QListWidgetItem *> items = listWidgetSamplesGood->selectedItems();
    if ( items.count() > 0 )
      selectItem(items[0]->text());
  }
}

void SampleChecker::on_listWidgetSamplesBad_itemSelectionChanged()
{
#ifdef QMC2_DEBUG
  qmc2MainWindow->log(QMC2_LOG_FRONTEND, "DEBUG: SampleChecker::on_listWidgetSamplesBad_itemSelectionChanged()");
#endif

  if ( checkBoxSamplesSelectGame->isChecked() ) {
    QList<QListWidgetItem *> items = listWidgetSamplesBad->selectedItems();
    if ( items.count() > 0 )
      selectItem(items[0]->text());
  }
}

void SampleChecker::selectItem(QString gameName)
{
#ifdef QMC2_DEBUG
  qmc2MainWindow->log(QMC2_LOG_FRONTEND, QString("DEBUG: SampleChecker::selectItem(QString gameName = %1)").arg(gameName));
#endif

  switch ( qmc2MainWindow->stackedWidgetView->currentIndex() ) {
    case QMC2_VIEWGAMELIST_INDEX: {
      QTreeWidgetItem *gameItem = qmc2GamelistItemMap[gameName];
      if ( gameItem ) {
        qmc2MainWindow->treeWidgetGamelist->clearSelection();
        qmc2MainWindow->treeWidgetGamelist->setCurrentItem(gameItem);
        qmc2MainWindow->treeWidgetGamelist->scrollToItem(gameItem, QAbstractItemView::PositionAtTop);
        gameItem->setSelected(TRUE);
      }
      break;
    }
    case QMC2_VIEWHIERARCHY_INDEX: {
      QTreeWidgetItem *hierarchyItem = qmc2HierarchyItemMap[gameName];
      if ( hierarchyItem ) {
        qmc2MainWindow->treeWidgetHierarchy->clearSelection();
        qmc2MainWindow->treeWidgetHierarchy->setCurrentItem(hierarchyItem);
        qmc2MainWindow->treeWidgetHierarchy->scrollToItem(hierarchyItem, QAbstractItemView::PositionAtTop);
        hierarchyItem->setSelected(TRUE);
      }
      break;
    }
  }
}

void SampleChecker::recursiveFileList(const QString &sDir, QStringList &fileNames)
{
#ifdef QMC2_DEBUG
  qmc2MainWindow->log(QMC2_LOG_FRONTEND, QString("DEBUG: SampleChecker::recursiveFileList(const QString& sDir = %1, QStringList &fileNames)").arg(sDir));
#endif

  QDir dir(sDir);
  QFileInfoList list = dir.entryInfoList();
  int i;
  for (i = 0; i < list.count(); i++) {
    QFileInfo info = list[i];
    QString path = info.filePath();
    if ( info.isDir() ) {
      // directory recursion
      if ( info.fileName() != ".." && info.fileName() != "." ) {
        recursiveFileList(path, fileNames);
        qApp->processEvents();
      }
    } else
      fileNames << path;
  }
} 
