// Copyright (c) 2012-2017 VideoStitch SAS
// Copyright (c) 2018 stitchEm

#include "batchertask.hpp"

#include <QApplication>
#include <QDir>
#include <QProgressBar>
#include <QStyle>
#include <sstream>

#include "autoelidelabel.hpp"
#include "libvideostitch-gui/mainwindow/vscommandprocess.hpp"
#include "libvideostitch-gui/utils/pluginshelpers.hpp"
#include "libvideostitch-base/logmanager.hpp"
#include "libvideostitch-base/common-config.hpp"
#include "libvideostitch/parse.hpp"
#include "libvideostitch/ptv.hpp"

int BatcherTask::lastID = 0;

BatcherTask::BatcherTask(QObject *parent)
    : QObject(parent),
      ptvName(nullptr),
      taskProgress(nullptr),
      process(nullptr),
      state(Idle),
      id(-1),
      firstFrame(-1),
      lastFrame(-1) {}

bool BatcherTask::operator<(const BatcherTask &toCompare) { return id < toCompare.id; }

bool BatcherTask::operator>(const BatcherTask &toCompare) { return id > toCompare.id; }

void BatcherTask::setPtvName(AutoElideLabel *name) { ptvName = name; }

void BatcherTask::setID(int id) { this->id = id; }

QString BatcherTask::getLog() const { return log; }

QString BatcherTask::getPtvName() const { return ptvName->text(); }

int BatcherTask::getID() const { return id; }

BatcherTask::State BatcherTask::getState() const { return state; }

void BatcherTask::resetState() {
  setState(BatcherTask::Idle);
  taskProgress->setFormat("");
  setErrorStyle(false);
  taskProgress->setMaximum(100);
  taskProgress->setValue(0);
}

bool BatcherTask::isRunning() const { return (process) ? process->processState() == QProcess::Running : false; }

void BatcherTask::setTaskProgress(QProgressBar *taskProgress) { this->taskProgress = taskProgress; }

void BatcherTask::start() {
  if (state != BatcherTask::Idle) {
    emit finished();
    return;
  }

  QStringList deviceStrings;
  for (int device : devices) {
    deviceStrings.append(QString::number(device));
  }
  RetrieveFrameBounds(firstFrame, lastFrame);

  QStringList arguments;
  if (firstFrame != -1 && lastFrame != -1) {
    arguments << "-f" << QString::number(firstFrame);
    arguments << "-l" << QString::number(lastFrame);
  }
  arguments << "-v" << QString::number(3);
  arguments << "-i" << ptvName->text();
  arguments << "-d" << deviceStrings.join(",");
  arguments << "-p" << VideoStitch::Plugin::getCorePluginFolderPath();
  auto path = VideoStitch::Plugin::getGpuCorePluginFolderPath();
  if (!path.isEmpty()) {
    arguments << "-p" << path;
  }

  process = new VSCommandProcess(this);
  connect(process, SIGNAL(signalProgression(int)), this, SLOT(setProgress(int)));
  connect(process, SIGNAL(signalProcessStateChanged(QProcess::ProcessState)), this,
          SLOT(processStateChanged(QProcess::ProcessState)));
  connect(process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(processFinished(int, QProcess::ExitStatus)));
  connect(process, SIGNAL(logMessage(QString)), this, SLOT(logMessage(QString)));
  connect(process, SIGNAL(logMessage(QString)), VideoStitch::Helper::LogManager::getInstance(),
          SLOT(writeToLogFile(QString)));

  VideoStitch::Helper::LogManager::getInstance()->writeToLogFile("Starting to process " + ptvName->text());
  log = QString();
  process->start(arguments);
}

void BatcherTask::kill() {
  setState(Canceled);
  taskProgress->setFormat(tr("Canceled"));
  setErrorStyle(false);
  if (process) {
    process->processStop();
  }
}

void BatcherTask::setDevices(const QList<int> &deviceIds) { devices = deviceIds; }

void BatcherTask::setProgress(int current) {
  if (firstFrame != -1 && lastFrame != -1) {
    taskProgress->setFormat(tr("Processing") + " %p%");
    taskProgress->setMaximum(lastFrame - firstFrame);
    taskProgress->setValue(current - firstFrame);
  } else {
    // We don't have information about the last stitchable frame so we show the current processed frame.
    // TODO FIXME: VSA-6002
    taskProgress->setFormat(tr("Processing frame ") + QString::number(current));
    taskProgress->setMaximum(100);
    taskProgress->setValue(100);
  }
}

void BatcherTask::processStateChanged(QProcess::ProcessState newState) {
  switch (newState) {
    case QProcess::NotRunning:
      setState(Finished);
      break;
    case QProcess::Running:
      setState(Processing);
      break;
    case QProcess::Starting:
      setState(Processing);
      break;
  }
}

void BatcherTask::processFinished(int exitCode, QProcess::ExitStatus status) {
  Q_UNUSED(status)
  QString preffix;
  if (state == Canceled) {
    preffix = tr("Canceled");
    VideoStitch::Helper::LogManager::getInstance()->writeToLogFile("Process canceled: " + ptvName->text());
  } else {
    if (exitCode != 0) {
      setState(Error);
      preffix = tr("Error");
      VideoStitch::Helper::LogManager::getInstance()->writeToLogFile(
          ptvName->text() + " finished on error code: " + QString::number(exitCode));
    } else {
      setState(Finished);
      preffix = tr("Finished");
      VideoStitch::Helper::LogManager::getInstance()->writeToLogFile(ptvName->text() + " was successfully processed");
    }
  }
  setErrorStyle(exitCode != 0);
  emit finished();
  process->deleteLater();
  process = nullptr;
  taskProgress->setFormat(preffix);
  taskProgress->setMaximum(100);
  taskProgress->setValue(100);
}

void BatcherTask::setState(State newState) { state = newState; }

void BatcherTask::logMessage(const QString &logLine) {
  this->log += logLine + "\n";
  emit newLogLine(logLine);
}

QString BatcherTask::getStringFromState() {
  switch (state) {
    case Processing:
      return tr("Processing");
    case Idle:
      return tr("Idle");
    case Error:
      return tr("Error");
    case Finished:
      return tr("Finished");
    case Canceled:
      return tr("Canceled");
    default:
      return tr("Unknown state");
  }
}

void BatcherTask::RetrieveFrameBounds(int &firstFrame, int &lastFrame) const {
  VideoStitch::Potential<VideoStitch::Ptv::Parser> parser(VideoStitch::Ptv::Parser::create());
  if (!parser.ok()) {
    VideoStitch::Helper::LogManager::getInstance()->writeToLogFile("Could not initialize the ptv parser.");
    return;
  }
  if (!parser->parse(ptvName->text().toLocal8Bit().constData())) {
    VideoStitch::Helper::LogManager::getInstance()->writeToLogFile(
        QString("Could not parse the ptv: %0").arg(parser->getErrorMessage().c_str()));
    return;
  }
  const VideoStitch::Ptv::Value &ptv = parser->getRoot();
  firstFrame = ptv.has("first_frame")->asInt();
  lastFrame = ptv.has("last_frame")->asInt();
  if (ptv.has("output")) {
    const VideoStitch::Ptv::Value &output = *ptv.has("output");
    if (!output.has("process_sequence")) {
      firstFrame = -1;
      lastFrame = -1;
    }
  }
}

void BatcherTask::setErrorStyle(bool error) {
  taskProgress->setProperty("error", error);
  taskProgress->style()->unpolish(taskProgress);
  taskProgress->style()->polish(taskProgress);
  taskProgress->update();
}