
/**
 * \file ptmzxmlconverter.cpp
 * \date 25/11/2019
 * \author Olivier Langella
 * \brief converts any mz file in basic mzXML (enough to use X!Tandem)
 */

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

#include "mzxmlconverter.h"
#include <QCommandLineParser>
#include <QDateTime>
#include <QTimer>
#include <QFile>
#include <pappsomspp/core/pappsoexception.h>
#include <pappsomspp/core/msfile/msfileaccessor.h>
#include <pappsomspp/core/msrun/output/mzxmloutput.h>
#include <pappsomspp/core/msrun/private/timsmsrunreaderms2.h>
#include <pappsomspp/core/processing/filters/filtersuitestring.h>
#include <pappsomspp/core/processing/filters/filterchargedeconvolution.h>
#include <pappsomspp/core/processing/uimonitor/uimonitorvoid.h>
#include <pappsomspp/core/processing/uimonitor/uimonitortextpercent.h>
#include <pappsomspp/core/msfile/mzformatenumstr.h>
#include <QFileInfo>
#include <QThreadPool>


PtMzXmlConverter::PtMzXmlConverter(QObject *parent) : QObject(parent)
{
  // get the instance of the main application
  app = QCoreApplication::instance();
  // setup everything here
  // create any global objects
  // setup debug and warning mode
}

// 10ms after the application starts this method will run
// all QT messaging is running at this point so threads, signals and slots
// will all work as expected.
void
PtMzXmlConverter::run()
{
  qSetMessagePattern(QString("%{file}@%{line}, %{function}(): %{message}"));
  /* ./src/pt-mzxmlconverter -m --ms2filters "chargeDeconvolution|0.02dalton" -i
   /gorgone/pappso/fichiers_fabricants/Bruker/tims_doc/tdf-sdk/example_data/200ngHeLaPASEF_2min_compressed.d/analysis.tdf
   -o
   /gorgone/pappso/versions_logiciels_pappso/i2masschroq/bruker/200ngHeLaPASEF_2min.mzXML
  */

  //./src/pt-mzxmlconverter -i
  /// gorgone/pappso/fichiers_fabricants/Bruker/tims_doc/tdf-sdk/example_data/200ngHeLaPASEF_2min_compressed.d/analysis.tdf
  //-o /tmp/test.xml


  QTextStream errorStream(stderr, QIODevice::WriteOnly);

  try
    {
      // qDebug();
      QCommandLineParser parser;

      // throw pappso::PappsoException("test");
      QString description(SOFTWARE_NAME);

      description += " mzXML converter\n\n";

      description +=
        "mzxmlconverter converts mass spectrometry data files from"
        " any format understood by ProteoWizard or from Bruker's timsTOF "
        "format to"
        " an mzXML format that is understood by X!Tandem.";

      parser.setApplicationDescription(description);

      parser.addHelpOption();
      parser.addVersionOption();
      QCommandLineOption inputOption(
        QStringList() << "i",
        QCoreApplication::translate("main", "m/z data file <input>."),
        QCoreApplication::translate("main", "input"));


      QCommandLineOption outputOption(
        QStringList() << "o"
                      << "output",
        QCoreApplication::translate("main",
                                    "Write mzXML output file <output>."),
        QCoreApplication::translate("main", "output"));


      QCommandLineOption cpusOption(
        QStringList() << "c"
                      << "cpus",
        QCoreApplication::translate("main", "number of CPUs to use <cpus>."),
        QCoreApplication::translate("main", "cpus"));


      QCommandLineOption timstofMs2Daltonprecision(
        QStringList() << "d"
                      << "dalton",
        QCoreApplication::translate(
          "main",
          "timsTOF MS2 measurement precision in dalton (+-0.02 by default)."),
        QCoreApplication::translate("main", "dalton"));

      parser.addOption(outputOption);
      parser.addOption(inputOption);
      parser.addOption(cpusOption);
      parser.addOption(timstofMs2Daltonprecision);

      // qDebug();

      // Process the actual command line arguments given by the user
      parser.process(*app);

      // QCoreApplication * app(this);
      // Add your main code here
      // qDebug();

      QTextStream outputStream(stdout, QIODevice::WriteOnly);
      pappso::UiMonitorText monitor(outputStream);

      QElapsedTimer timer;
      timer.start();
      const QStringList args = parser.positionalArguments();


      uint cpu_number = 100;
      QString cpusStr = parser.value(cpusOption);
      if(!cpusStr.isEmpty())
        {
          cpu_number = cpusStr.toUInt();
        }


      double dalton_precision =
        parser.value(timstofMs2Daltonprecision).toDouble();
      if(parser.value(timstofMs2Daltonprecision).isEmpty())
        dalton_precision = 0.02;
      QString filters_str =
        QString("chargeDeconvolution|%1dalton mzExclusion|%2dalton")
          .arg(dalton_precision)
          .arg(dalton_precision / 2);
      uint ideal_number_of_thread = (uint)QThread::idealThreadCount();
      // QThreadPool::globalInstance()->setMaxThreadCount(1);
      if(cpu_number > ideal_number_of_thread)
        {
          cpu_number = ideal_number_of_thread;
        }
      else
        {
          QThreadPool::globalInstance()->setMaxThreadCount(cpu_number);
        }


      QString mzFileStr = parser.value(inputOption);
      if(mzFileStr.isEmpty())
        {
          throw pappso::PappsoException(tr(
            "No input file name provided, please provide a complete file path "
            "to the m/z data file with the -i option"));
        }
      qDebug() << "QDir::currentPath()=" << QDir::currentPath();

      pappso::MsFileAccessor file_access(mzFileStr, "runa1");
      file_access.setPreferredFileReaderType(pappso::Enums::MsDataFormat::brukerTims,
                                             pappso::Enums::FileReaderType::tims_ms2);
      file_access.getMsRunIds();

      qDebug();
      pappso::MsRunReaderSPtr p_reader;
      p_reader = file_access.msRunReaderSPtr(file_access.getMsRunIds().front());
      pappso::TimsMsRunReaderMs2 *tims2_reader =
        dynamic_cast<pappso::TimsMsRunReaderMs2 *>(p_reader.get());
      if(tims2_reader != nullptr)
        {
          qDebug();
          tims2_reader->setMs2BuiltinCentroid(true);

          std::shared_ptr<pappso::FilterSuiteString> ms2filter;
          if(filters_str.isEmpty())
            {
              filters_str =
                "chargeDeconvolution|0.02dalton mzExclusion|0.01dalton";
            }

          ms2filter = std::make_shared<pappso::FilterSuiteString>(filters_str);

          monitor.setStatus(tr("timsTOF MS2 filters : %1").arg(filters_str));

          tims2_reader->setMs2FilterCstSPtr(ms2filter);
          qDebug();
        }


      qDebug();
      pappso::MzxmlOutput *p_mzxml_output;
      QString mzxmlFileStr = parser.value(outputOption);
      if(mzxmlFileStr.isEmpty())
        {
          qDebug();
          pappso::UiMonitorVoid monitor_null;

          QTextStream outputStream(stdout, QIODevice::WriteOnly);
          p_mzxml_output =
            new pappso::MzxmlOutput(monitor_null, outputStream.device());
          p_mzxml_output->maskMs1(true);

          if(cpu_number > 1)
            {
              p_mzxml_output->setReadAhead(true);
            }
          p_mzxml_output->write(p_reader.get());

          p_mzxml_output->close();
        }
      else
        {
          qDebug();

          QFile output_file(mzxmlFileStr);

          monitor.setTitle(tr("converting %1 input file from %2 format to "
                              "mzXML file %3 ")
                             .arg(mzFileStr)
                             .arg(pappso::MsDataFormatEnumStr::toString(
                               file_access.getFileFormat()))
                             .arg(mzxmlFileStr));
          // qDebug() << " TsvDirectoryWriter::writeSheet " <<
          // QFileInfo(*_p_ofile).absoluteFilePath();
          if(output_file.open(QIODevice::WriteOnly))
            {

              pappso::UiMonitorVoid monitor_null;
              p_mzxml_output = new pappso::MzxmlOutput(
                monitor_null, QTextStream(&output_file).device());
              p_mzxml_output->maskMs1(true);


              if(cpu_number > 1)
                {
                  p_mzxml_output->setReadAhead(true);
                }

              p_mzxml_output->write(p_reader.get());

              p_mzxml_output->close();
            }
          else
            {

              throw pappso::PappsoException(
                tr("unable to write into %1 mzXML output file")
                  .arg(QFileInfo(mzxmlFileStr).absoluteFilePath()));
            }

          qint64 conversionTime = timer.elapsed();

          monitor.setStatus(QObject::tr("Conversion finished in %1 seconds")
                              .arg(conversionTime / 1000));
        }

      qDebug();
    }
  catch(pappso::PappsoException &error)
    {
      errorStream << QString("Oops! an error occurred in %1. Don't Panic :\n%2")
                       .arg(QCoreApplication::applicationName())
                       .arg(error.qwhat());

      errorStream.flush();
      exit(1);
      app->exit(1);
    }

  catch(std::exception &error)
    {
      errorStream << QString("Oops! an error occurred in %1. Don't Panic :\n%2")
                       .arg(QCoreApplication::applicationName())
                       .arg(error.what());
      errorStream.flush();
      exit(1);
      app->exit(1);
    }


  // you must call quit when complete or the program will stay in the
  // messaging loop
  quit();
}

// call this routine to quit the application
void
PtMzXmlConverter::quit()
{
  // you can do some cleanup here
  // then do emit finished to signal CoreApplication to quit
  emit finished();
}

// shortly after quit is called the CoreApplication will signal this routine
// this is a good place to delete any objects that were created in the
// constructor and/or to stop any threads
void
PtMzXmlConverter::aboutToQuitApp()
{
  // stop threads
  // sleep(1);   // wait for threads to stop.
  // delete any objects
}


int
main(int argc, char **argv)
{
  // QTextStream consoleErr(stderr);
  // QTextStream consoleOut(stdout, QIODevice::WriteOnly);
  // ConsoleOut::setCout(new QTextStream(stdout, QIODevice::WriteOnly));
  // ConsoleOut::setCerr(new QTextStream(stderr, QIODevice::WriteOnly));
  qDebug();
  QCoreApplication app(argc, argv);
  qDebug();
  QCoreApplication::setApplicationName("mzxmlconverter");
  QCoreApplication::setApplicationVersion(i2MassChroQ_VERSION);
  QLocale::setDefault(QLocale::system());

  // create the main class
  PtMzXmlConverter myMain;
  // connect up the signals
  QObject::connect(&myMain, SIGNAL(finished()), &app, SLOT(quit()));
  QObject::connect(
    &app, SIGNAL(aboutToQuit()), &myMain, SLOT(aboutToQuitApp()));
  qDebug();


  // This code will start the messaging engine in QT and in
  // 10ms it will start the execution in the MainClass.run routine;
  QTimer::singleShot(10, &myMain, SLOT(run()));
  return app.exec();
}
