/***************************************************************************
**
**  This file is part of GeopsyLand.
**
**  GeopsyLand 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.
**
**  GeopsyLand 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 Foobar.  If not, see <http://www.gnu.org/licenses/>
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2018-12-06
**  Copyright: 2018-2019
**    Marc Wathelet (ISTerre, Grenoble, France)
**
***************************************************************************/

#include "TerminalProcess.h"
#include "MiniBash.h"

/*!
  \class TerminalProcess TerminalProcess.h
  \brief Brief description of class still missing

  Full description of class still missing
*/

/*!
  Description of constructor still missing
*/
TerminalProcess::TerminalProcess(MiniBash * shell)
{
  TRACE;
  _shell=shell;
  _inputTerminalProcess=nullptr;
  _outputTerminalProcess=nullptr;
  _outputType=Standard;
  _type=File;
  _external=nullptr;
  _detached=false;
}

/*!
  Description of destructor still missing
*/
TerminalProcess::~TerminalProcess()
{
  TRACE;
  if(isRunning()) {
    kill();
  }
  delete _external;
}

void TerminalProcess::addArgument(const QString& a)
{
  TRACE;
  if(!a.isEmpty()) {
    if(_name.isEmpty()) {
      _name=a;
    } else if(_outputType==FileNew || _outputType==FileAppend){
      QFileInfo fi(a);
      _outputFile=fi.absoluteFilePath();
    } else {
      _arguments.append(a);
    }
  }
}

bool TerminalProcess::resolvePath(const QStringList& paths, MiniBash * shell)
{
  _type=typeFromName();
  if(_type==File) {
    for(QStringList::const_iterator it=paths.begin(); it!=paths.end(); it++) {
      QDir d(*it);
      if(!_name.isEmpty() && (d.exists(_name)
#ifdef Q_OS_WIN
         || d.exists(_name+".exe")
#endif
         )) {
#ifdef Q_OS_WIN
        if(!d.exists(_name)) {
          _name+=".exe";
        }
#endif
        _name=d.absoluteFilePath(_name);
        QFileInfo fi(_name);
        if(fi.isExecutable()) {
          delete _external;
          _external=new QProcess;
          _external->setProgram(_name);
          _external->setArguments(_arguments);
          connect(_external, SIGNAL(readyReadStandardOutput()), this, SLOT(processStandardOutput()));
          connect(_external, SIGNAL(readyReadStandardError()), this, SLOT(processStandardError()));
          connect(_external, SIGNAL(finished(int, QProcess::ExitStatus)), this, SIGNAL(finished()));
          return true;
        } else {
          shell->putData(tr("%1: Permission denied\n").arg(_name));
          return false;
        }
      }
    }
    shell->putData(tr("%1: command not found\n").arg(_name));
    return false;
  } else {
    connect(this, SIGNAL(sendStandardOutput(const QString&)), this, SLOT(standardOutput(const QString&)), Qt::QueuedConnection);
    connect(this, SIGNAL(sendStandardOutput(const QStringList&)), this, SLOT(standardOutput(const QStringList&)), Qt::QueuedConnection);
    connect(this, SIGNAL(sendStandardError(const QString&)), this, SLOT(standardError(const QString&)), Qt::QueuedConnection);
    return true;
  }
}

void TerminalProcess::start()
{
  switch(_type) {
  case File:
    if(_detached) {
#if(QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
      _external->startDetached();
#else
      emit sendStandardError(tr("startDetached not supported by Qt<5.10"));
      _external->start();
#endif
    } else {
      _external->start();
    }
    _external->waitForStarted();
    break;
  default:
    if(_outputType==FileNew) {
      QFile::remove(_outputFile);
    }
    Thread::start();
    break;
  }
}

void TerminalProcess::run()
{
  switch(_type) {
  case File:
    break;
  case Cat:
    if(!_arguments.isEmpty()) {
      QFile f(MiniBash::toNativePath(_arguments.first()));
      if(f.open(QIODevice::ReadOnly)) {
        QTextStream s(&f);
        emit sendStandardOutput(s.readAll());
      } else {
        emit sendStandardError(tr("cat: %1: No such a file\n").arg(_arguments.first()));
      }
    } else {
      emit sendStandardError(tr("Missing file name\n"));
    }
    break;
  case Ls: {
      QDir d;
      if(!_arguments.isEmpty()) {
        d.setPath(MiniBash::toNativePath(_arguments.first()));
      }
      QStringList dirs=d.entryList(QDir::Dirs, QDir::Name);
      addDirMark(dirs);
      QStringList list=d.entryList(QDir::Files, QDir::Name);
      list.append(dirs);
      list.sort(Qt::CaseInsensitive);
      emit sendStandardOutput(list);
    }
    break;
  case Echo:
    if(_arguments.isEmpty()) {
      emit sendStandardOutput(QString("\n"));
    } else {
      emit sendStandardOutput(_arguments.join(" ")+"\n");
    }
    break;
  case Cd:
    if(_arguments.isEmpty()) {
      QDir::setCurrent(QDir::homePath());
    } else {
      if(!QDir::setCurrent(MiniBash::toNativePath(_arguments.first()))) {
        emit sendStandardError(tr("cd: %1: Not a directory\n").arg(_arguments.first()));
      }
    }
    break;
  case Cp:
  case Mv:
  case Rm:
    break;
  case Mkdir:
    if(_arguments.isEmpty()) {
      emit sendStandardError(tr("Missing directory name\n"));
    } else {
      QDir d;
      if(!d.mkdir(MiniBash::toNativePath(_arguments.first()))) {
        emit sendStandardError(tr("mkdir: %1: cannot create directory\n").arg(_arguments.first()));
      }
    }
    break;
  case Head:
  case Tail:
    break;
  case Pwd: {
      QDir d;
      emit sendStandardOutput(MiniBash::fromNativePath(d.absolutePath())+"\n");
    }
    break;
  case Exit:
    emit exit();
    break;
  case Help:
    emit sendStandardOutput("cat       : print the contents of a text file.\n"
                            "cd DIR    : change current directory to DIR.\n"
                            "echo MSG  : print any message MSG.\n"
                            "help      : print this help message.\n"
                            "ls        : list the directories and the files of the current directory.\n"
                            "mkdir DIR : create directory DIR.\n"
                            "pwd       : print the current directory.\n"
                            "\n"
                            "Text completion available by pressing TAB once or twice.\n"
                            "Copy and paste text with the mouse central button.\n"
                            "\n");
    break;
  }
  emit finished();
}

void TerminalProcess::addDirMark(QStringList& list)
{
  for(QStringList::iterator it=list.begin(); it!=list.end(); it++) {
    *it+="/";
  }
}

TerminalProcess::TerminalProcessType TerminalProcess::typeFromName() const
{
  if(_name.isEmpty()) {
    return File;
  }
  switch(_name.at(0).unicode()) {
  case 'c':
    if(_name=="cat") {
      return Cat;
    } else if(_name=="cp") {
      return Cp;
    } else if(_name=="cd") {
      return Cd;
    }
    break;
  case 'e':
    if(_name=="echo") {
      return Echo;
    } else if(_name=="exit") {
      return Exit;
    }
    break;
  case 'h':
    if(_name=="head") {
      return Head;
    } else if(_name=="help") {
      return Help;
    }
    break;
  case 'l':
    if(_name=="ls") {
      return Ls;
    }
    break;
  case 'm':
    if(_name=="mv") {
      return Mv;
    } else if(_name=="mkdir") {
      return Mkdir;
    }
    break;
  case 'p':
    if(_name=="pwd") {
      return Pwd;
    }
    break;
  case 'r':
    if(_name=="rm") {
      return Rm;
    }
    break;
  case 't':
    if(_name=="tail") {
      return Tail;
    }
    break;
  default:
    break;
  }
  return File;
}

void TerminalProcess::processStandardOutput()
{
  standardOutput(_external->readAllStandardOutput());
}

void TerminalProcess::processStandardError()
{
  standardError(_external->readAllStandardError());
}

void TerminalProcess::standardOutput(const QString& data)
{
  standardOutput(data.toLocal8Bit());
}

void TerminalProcess::standardOutput(const QStringList& data)
{
  _shell->putData(data);
}

void TerminalProcess::standardError(const QString& data)
{
  standardError(data.toLocal8Bit());
}

void TerminalProcess::standardOutput(const QByteArray& data)
{
  switch(_outputType) {
  case Standard:
    _shell->putData(data);
    break;
  case Pipe:
    _outputTerminalProcess->standardInput(data);
    break;
  case FileNew:
  case FileAppend:
    QFile f(_outputFile);
    if(f.open(QIODevice::Append)) {
      f.write(data);
    } else {
      _shell->putData(tr("%1: Cannot write to file\n").arg(_outputFile));
    }
    break;
  }
}

void TerminalProcess::closeStandardOutput()
{
  if(_outputType==Pipe) {
    if(_outputTerminalProcess->_type==File) {
      _outputTerminalProcess->_external->closeWriteChannel();
    } else {
      // TODO
    }
  }
}

void TerminalProcess::standardError(const QByteArray& data)
{
  _shell->putData(data);
}

void TerminalProcess::standardInput(const QByteArray& data)
{
  switch(_type) {
  case File:
    if(_external) {
      _external->write(data);
    }
    break;
  default:
    break;
  }
}

bool TerminalProcess::isRunning()
{
  switch(_type) {
  case File:
    if(_external) {
      return _external->state()==QProcess::Running;
    }
    break;
  default:
    break;
  }
  return Thread::isRunning();
}

void TerminalProcess::kill()
{
  switch(_type) {
  case File:
    if(_external) {
      _external->kill();
    }
    break;
  default:
    terminate();
    break;
  }
}
