/**
 * \file /core/tandem_run/tandembatchprocess.cpp
 * \date 5/9/2017
 * \author Olivier Langella
 * \brief handles execution of a bunch of X!Tandem process
 */

/*******************************************************************************
 * Copyright (c) 2017 Olivier Langella <olivier.langella@u-psud.fr>.
 *
 * This file is part of XTPcpp.
 *
 *     XTPcpp 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.
 *
 *     XTPcpp 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 XTPcpp.  If not, see <http://www.gnu.org/licenses/>.
 *
 ******************************************************************************/

#include "tandembatchprocess.h"
#include <QProcess>
#include <pappsomspp/pappsoexception.h>
#include <pappsomspp/exception/exceptionnotfound.h>
#include <QFileInfo>
#include <QDebug>
#include <QTemporaryDir>
#include "../../files/tandemparametersfile.h"
#include <pappsomspp/processing/tandemwrapper/tandemwrapperrun.h>
#include <pappsomspp/processing/filters/filterchargedeconvolution.h>

TandemBatchProcess::TandemBatchProcess(MainWindow *p_main_window,
                                       WorkMonitorInterface *p_monitor,
                                       const TandemRunBatch &tandem_run_batch)
{
  _p_main_window    = p_main_window;
  _tandem_run_batch = tandem_run_batch;
  _p_monitor        = p_monitor;
  _tmp_database_file.setAutoRemove(false);

  // check that output directory is writeable :
  QFileInfo fi_in_dir(_tandem_run_batch._output_directory);
  if(fi_in_dir.isDir() && fi_in_dir.isWritable())
    {
    }
  else
    {
      throw pappso::PappsoException(
        QObject::tr(
          "output directory is not writable. Please check permissions on %1")
          .arg(fi_in_dir.absoluteFilePath()));
    }
}

TandemBatchProcess::~TandemBatchProcess()
{
}

bool
TandemBatchProcess::shouldIstop()
{
  if(_p_main_window->stopWorkerThread())
    {
      return true;
    }
  return false;
}
void
TandemBatchProcess::writeXmlDatabaseFile(QXmlStreamWriter *p_out)
{
  p_out->setAutoFormatting(true);
  p_out->writeStartDocument("1.0");

  //<?xml version="1.0" encoding="UTF-8"?>
  //<bioml label="x! taxon-to-file matching list">
  p_out->writeStartElement("bioml");
  p_out->writeAttribute("label", "x! taxon-to-file matching list");
  //<taxon label="usedefined">
  p_out->writeStartElement("taxon");
  p_out->writeAttribute("label", "usedefined");
  //<file format="peptide"
  // URL="/gorgone/pappso/jouy/users/Didier/fasta/20170608_Delastours/contaminants_standards.fasta"></file>
  //<file format="peptide"
  // URL="/gorgone/pappso/jouy/users/Didier/fasta/20170608_Delastours/Escherichia
  // coli 27J42_WGS_ECOLI_1.fasta"></file>
  for(QString fasta_file : _tandem_run_batch._fasta_file_list)
    {

      if(!QFileInfo(fasta_file).isWritable())
        {
          throw pappso::PappsoException(
            QObject::tr(
              "error : Fasta file %1 is not writable.\n\"tandem.exe\" requires "
              "write permission on fasta files to run correctly.")
              .arg(QFileInfo(fasta_file).absoluteFilePath()));
        }
      p_out->writeStartElement("file");
      p_out->writeAttribute("format", "peptide");
      p_out->writeAttribute("URL", fasta_file);
      p_out->writeEndElement();
    }
  //</taxon>
  p_out->writeEndElement();
  //</bioml>
  p_out->writeEndElement();
  p_out->writeEndDocument();
}

void
TandemBatchProcess::prepareXmlDatabaseFile()
{

  if(_tmp_database_file.open())
    {
      _xml_database_file =
        QFileInfo(_tmp_database_file.fileName()).absoluteFilePath();
      QXmlStreamWriter *p_out = new QXmlStreamWriter();
      p_out->setDevice(&_tmp_database_file);
      writeXmlDatabaseFile(p_out);
      _tmp_database_file.close();
      delete p_out;
    }
  else
    {
      throw pappso::PappsoException(
        QObject::tr("error : cannot open the XML database file : %1\n")
          .arg(_tmp_database_file.fileName()));
    }
}


void
TandemBatchProcess::run()
{
  qDebug() << _tandem_run_batch._preset_file;


  // check tandem path
  QFileInfo tandem_exe(_tandem_run_batch._tandem_bin_path);
  if(!tandem_exe.exists())
    {
      // dir.path() returns the unique directory path
      throw pappso::PappsoException(
        QObject::tr(
          "X!Tandem software not found at %1.\nPlease check the X!Tandem "
          "installation on your computer and set tandem.exe path.")
          .arg(tandem_exe.absoluteFilePath()));
    }
  if(!tandem_exe.isReadable())
    {
      // dir.path() returns the unique directory path
      throw pappso::PappsoException(
        QObject::tr("Please check permissions on !Tandem software found at %1.")
          .arg(tandem_exe.absoluteFilePath()));
    }


  QTemporaryDir tmp_dir;
  if(!tmp_dir.isValid())
    {
      // dir.path() returns the unique directory path
      throw pappso::PappsoException(
        QObject::tr("problem creating temporary directory in %1\n")
          .arg(tmp_dir.path()));
    }

  TandemParametersFile orig_parameter_file(_tandem_run_batch._preset_file);
  _preset_file =
    QString("%1/%2").arg(tmp_dir.path()).arg(orig_parameter_file.getFilename());

  TandemParametersFile new_parameter_file(_preset_file);
  TandemParameters new_param = orig_parameter_file.getTandemParameters();
  //  <note type="input" label="spectrum, threads">1</note>
  new_param.setParamLabelValue(
    "spectrum, threads",
    QString("%1").arg(_tandem_run_batch._number_of_threads));
  new_parameter_file.setTandemParameters(new_param);

  try
    {
      // m_centroid_timstof_option =
      //   new_param.getValue("spectrum, timstof MS2 centroid parameters");
    }
  catch(pappso::ExceptionNotFound &error)
    {
      // if centroid parameters are not in preset :
      // no error has to be sent, this is a normal preset
      // m_centroid_timstof_option = "";
    }

  prepareXmlDatabaseFile();
  qDebug();
  int i = 0;
  _p_monitor->setProgressMaximumValue(_tandem_run_batch._mz_file_list.size());
  for(QString mz_file : _tandem_run_batch._mz_file_list)
    {
      if(shouldIstop())
        {
          throw pappso::PappsoException(
            QObject::tr("X!Tandem stopped by the user processing %1 sample")
              .arg(mz_file));
        }
      _p_monitor->message(QObject::tr("running X!Tandem on %1\n").arg(mz_file),
                          i);
      runOne(mz_file);
      i++;
    }

  _p_monitor->finished(QObject::tr("%1 X!Tandem job(s) finished")
                         .arg(_tandem_run_batch._mz_file_list.size()));
  qDebug();
}

void
TandemBatchProcess::writeXmlInputFile(QXmlStreamWriter *p_out,
                                      const QString &mz_file)
{

  QFileInfo mz_file_info(mz_file);
  //<?xml version="1.0" encoding="UTF-8"?>

  p_out->setAutoFormatting(true);
  p_out->writeStartDocument("1.0");

  //<bioml label="20170405_Delastour_11.xml">
  p_out->writeStartElement("bioml");
  p_out->writeAttribute("label", mz_file_info.fileName());

  //<note type="heading">Paths</note>
  p_out->writeStartElement("note");
  p_out->writeAttribute("type", "heading");
  p_out->writeCharacters("Paths");
  p_out->writeEndElement();

  //<note type="input" label="list path, default
  // parameters">/gorgone/pappso/tmp/temp_condor_job93294001891239208719639434471283743/QExactive_IAM_4CPU_Classic_ClassicAvecReversed..xml</note>
  p_out->writeStartElement("note");
  p_out->writeAttribute("type", "input");
  p_out->writeAttribute("label", "list path, default parameters");
  p_out->writeCharacters(_preset_file);
  p_out->writeEndElement();

  //<note type="input" label="list path, taxonomy
  // information">/gorgone/pappso/tmp/temp_condor_job93294001891239208719639434471283743/database.xml</note>
  p_out->writeStartElement("note");
  p_out->writeAttribute("type", "input");
  p_out->writeAttribute("label", "list path, taxonomy information");
  p_out->writeCharacters(_xml_database_file);
  p_out->writeEndElement();


  //<note type="input" label="spectrum,
  // path">/gorgone/pappso/jouy/raw/2017_Qex/20170405_Delastours/20170405_Delastour_11.mzXML</note>

  p_out->writeStartElement("note");
  p_out->writeAttribute("type", "input");
  p_out->writeAttribute("label", "spectrum, path");
  if(mz_file_info.isDir())
    {
      p_out->writeCharacters(mz_file + "/analysis.tdf");
    }
  else
    {
      p_out->writeCharacters(mz_file);
    }
  p_out->writeEndElement();

  //<note type="heading">Protein general</note>
  p_out->writeStartElement("note");
  p_out->writeAttribute("type", "heading");
  p_out->writeCharacters("Protein general");
  p_out->writeEndElement();

  //<note type="input" label="protein, taxon">usedefined</note>
  p_out->writeStartElement("note");
  p_out->writeAttribute("type", "input");
  p_out->writeAttribute("label", "protein, taxon");
  p_out->writeCharacters("usedefined");
  p_out->writeEndElement();
  //<note type="heading">Output</note>
  p_out->writeStartElement("note");
  p_out->writeAttribute("type", "heading");
  p_out->writeCharacters("Output");
  p_out->writeEndElement();

  //<note type="input" label="output,
  // path">/gorgone/pappso/jouy/users/Didier/Xcal2017/2017_05_04_Delastours/Interrogation_souches_sequencees/Patient27/J42/20170405_Delastour_11.xml</note>

  p_out->writeStartElement("note");
  p_out->writeAttribute("type", "input");
  p_out->writeAttribute("label", "output, path");
  if(mz_file_info.completeSuffix() == "tdf")
    {

      QString output_name =
        QFileInfo(mz_file_info.absoluteDir().dirName()).baseName();
      qDebug() << output_name;
      p_out->writeCharacters(QString("%1/%2.xml")
                               .arg(_tandem_run_batch._output_directory)
                               .arg(output_name));
    }
  else if(mz_file_info.isDir())
    {
      QString output_name = QFileInfo(mz_file_info).baseName();
      qDebug() << output_name;
      p_out->writeCharacters(QString("%1/%2.xml")
                               .arg(_tandem_run_batch._output_directory)
                               .arg(output_name));
    }
  else
    {
      p_out->writeCharacters(QString("%1/%2.xml")
                               .arg(_tandem_run_batch._output_directory)
                               .arg(mz_file_info.baseName()));
    }
  p_out->writeEndElement();

  //</bioml>
  p_out->writeEndElement();


  p_out->writeEndDocument();
}

void
TandemBatchProcess::runOne(const QString &mz_file)
{
  qDebug();
  QTemporaryFile xml_input_file;
  xml_input_file.setAutoRemove(true);
  if(xml_input_file.open())
    {
      QXmlStreamWriter *p_out = new QXmlStreamWriter();
      p_out->setDevice(&xml_input_file);

      writeXmlInputFile(p_out, mz_file);

      xml_input_file.close();
      delete p_out;
    }
  else
    {
      throw pappso::PappsoException(
        QObject::tr("error : cannot open the XML X!Tandem input file : %1\n")
          .arg(xml_input_file.fileName()));
    }


  QStringList arguments;
  QString tandem_path;
  QSettings settings;

  // @todo : do not rely on filenames to decide which process to use
  if(QFileInfo(mz_file).completeSuffix() != "tdf")
    {
      arguments << xml_input_file.fileName();
      tandem_path = _tandem_run_batch._tandem_bin_path;

      QProcess *xt_process = new QProcess();
      xt_process->start(tandem_path, arguments);

      if(!xt_process->waitForStarted())
        {
          throw pappso::PappsoException(
            QObject::tr("X!Tandem process failed to start"));
        }

      while(xt_process->waitForReadyRead(_max_xt_time_ms))
        {
          _p_monitor->appendText(xt_process->readAll().data());
          // data.append(xt_process->readAll());
          if(shouldIstop())
            {
              xt_process->kill();
              delete xt_process;
              throw pappso::PappsoException(
                QObject::tr("X!Tandem stopped by the user processing %1 sample")
                  .arg(mz_file));
            }
        }
      /*
      if (!xt_process->waitForFinished(_max_xt_time_ms)) {
          throw pappso::PappsoException(QObject::tr("can't wait for X!Tandem
      process to finish : timeout at %1").arg(_max_xt_time_ms));
      }
      */
      QByteArray result = xt_process->readAll();

      QProcess::ExitStatus Status = xt_process->exitStatus();

      delete xt_process;
      if(Status != 0)
        {
          xt_process->kill();
          delete xt_process;
          throw pappso::PappsoException(
            QObject::tr("error executing X!Tandem Status != 0 : %1 %2\n%3")
              .arg(_tandem_run_batch._tandem_bin_path)
              .arg(arguments.join(" ").arg(result.data())));
        }
    }
  else
    {
      QTextStream errorStream(stderr, QIODevice::WriteOnly);
      QTextStream outputStream(stdout, QIODevice::WriteOnly);
      try
        {

          // QProcess::ExitStatus Status = xt_process->exitStatus();
          qDebug();
          const QDateTime dt_begin = QDateTime::currentDateTime();
          QSettings settings;
          QString tmpDirOption =
            settings.value("timstof/tmp_dir_path", QDir::tempPath()).toString();
          if(!QDir(tmpDirOption).exists())
            {
              QDir().mkdir(tmpDirOption);
            }

          pappso::TandemWrapperRun tandem_run(
            _tandem_run_batch._tandem_bin_path, tmpDirOption);

          tandem_run.run(xml_input_file.fileName(), outputStream, errorStream);


          qDebug();
        }
      catch(pappso::PappsoException &error)
        {
          errorStream
            << "Oops! an error occurred in PAPPSO MS tools. Don't panic :"
            << endl;
          errorStream << error.qwhat() << endl;

          throw pappso::PappsoException(
            QObject::tr("error executing pappso::TandemWrapperRun :\n%1")
              .arg(error.qwhat()));
        }

      catch(std::exception &error)
        {
          errorStream
            << "Oops! an std error occurred in PAPPSO MS tools. Don't panic :"
            << endl;
          errorStream << error.what() << endl;
          throw pappso::PappsoException(
            QObject::tr("error executing pappso::TandemWrapperRun :\n%1")
              .arg(error.what()));
        }
    }
  qDebug();
}
