/**********************************************************************************************
    Copyright (C) 2017 Oliver Eichler <oliver.eichler@gmx.de>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

**********************************************************************************************/

#include "shell/CShell.h"

#include <QtWidgets>

#include "theme/CUiTheme.h"

CShell* CShell::pSelf = nullptr;

CShell::CShell(QWidget* parent, bool isSingleton) : QTextBrowser(parent) {
  if (isSingleton) {
    pSelf = this;
  }

  connect(&cmd, &QProcess::readyReadStandardError, this, &CShell::slotStderr);
  connect(&cmd, &QProcess::readyReadStandardOutput, this, &CShell::slotStdout);

  connect(&cmd, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this,
          &CShell::slotFinished);
  connect(&cmd, &QProcess::errorOccurred, this, &CShell::slotError);
}

CShell::~CShell() {
  if (pSelf == this) {
    pSelf = nullptr;
  }
}

void CShell::slotError(QProcess::ProcessError error) {
  setTextColor(CUiTheme::foreground(CUiTheme::Role::eError));
  insertPlainText(QString(tr("Execution of external program `%1` failed: ")).arg(cmd.program()));
  switch (error) {
    case QProcess::FailedToStart:
      insertPlainText(QString(tr("Process cannot be started.\n")));
      insertPlainText(QString(tr("Make sure the required packages are installed, `%1` exists and is executable.\n"))
                          .arg(cmd.program()));
      // Qt does not emit finished() for FailedToStart — signal completion explicitly so
      // callers waiting on sigFinishedJob (e.g. CVrtAdvisoryDialog) are not left stuck.
      jobSucceeded_ = false;
      emit sigFinishedJob(jobId);
      break;

    case QProcess::Crashed:
      insertPlainText(QString(tr("External process crashed.\n")));
      break;

    default:
      insertPlainText(QString(tr("An unknown error occurred.\n")));
      break;
  }
}

void CShell::slotStderr() {
  QString str;
  setTextColor(CUiTheme::foreground(CUiTheme::Role::eError));
  str = cmd.readAllStandardError();

  if (!str.isEmpty() && str[0] == '\r') {
#ifdef Q_OS_WIN64
    if (str.contains("\n")) {
      insertPlainText("\n");
    } else
#endif  // Q_OS_WIN64
    {
      moveCursor(QTextCursor::End, QTextCursor::MoveAnchor);
      moveCursor(QTextCursor::StartOfLine, QTextCursor::MoveAnchor);
      moveCursor(QTextCursor::End, QTextCursor::KeepAnchor);
      textCursor().removeSelectedText();
    }

#ifdef Q_OS_WIN64
    str = str.split("\r").last().remove("\r").remove("\n");
#else
    str = str.split("\r").last();
#endif
  }

  insertPlainText(str);
  verticalScrollBar()->setValue(verticalScrollBar()->maximum());
}

void CShell::slotStdout() {
  QString str;
  setTextColor(CUiTheme::foreground(CUiTheme::Role::eInfo));
  str = cmd.readAllStandardOutput();

  if (!str.isEmpty() && str[0] == '\r') {
#ifdef Q_OS_WIN64
    if (str.contains("\n")) {
      insertPlainText("\n");
    } else
#endif  // Q_OS_WIN64
    {
      moveCursor(QTextCursor::End, QTextCursor::MoveAnchor);
      moveCursor(QTextCursor::StartOfLine, QTextCursor::MoveAnchor);
      moveCursor(QTextCursor::End, QTextCursor::KeepAnchor);
      textCursor().removeSelectedText();
    }

#ifdef Q_OS_WIN64
    str = str.split("\r").last().remove("\r").remove("\n");
#else
    str = str.split("\r").last();
#endif
  }

  insertPlainText(str);
  verticalScrollBar()->setValue(verticalScrollBar()->maximum());
}

void CShell::stdOut(const QString& str) {
  setTextColor(palette().color(QPalette::Text));
  append(str);
}

void CShell::stdErr(const QString& str) {
  setTextColor(CUiTheme::foreground(CUiTheme::Role::eError));
  append(str);
}

void CShell::slotFinished(int exitCode, QProcess::ExitStatus status) {
  if (exitCode || status) {
    jobSucceeded_ = false;
    emit sigFinishedJob(jobId);
    setTextColor(CUiTheme::foreground(CUiTheme::Role::eError));
    append(tr("!!! failed !!!\n"));
    return;
  }

  ++idxCommand;
  nextCommand();
}

void CShell::slotCancel() {
  if (cmd.state() == QProcess::NotRunning) {
    return;
  }

  stdOut(tr("\nCanceled by user's request.\n"));
  cmd.kill();
}

int CShell::execute(QList<CShellCmd> cmds) {
  if (cmd.state() != QProcess::NotRunning) {
    return -1;
  }

  clear();

  idxCommand = 0;
  commands = cmds;

  nextCommand();
  return ++jobId;
}

void CShell::nextCommand() {
  if (idxCommand >= commands.size()) {
    jobSucceeded_ = true;
    emit sigFinishedJob(jobId);
    setTextColor(CUiTheme::foreground(CUiTheme::Role::eOk));
    append(tr("!!! done !!!\n"));
    return;
  }

  const CShellCmd& command = commands[idxCommand];
  stdOut(command.getCmd() + " " + command.getArgs().join(" ") + "\n");
  cmd.start(command.getCmd(), command.getArgs());
}
