/**
 * \file spomscli.cpps
 * \date 20/02/2026
 * \author Olivier Langella
 * \brief CLI tool for PeptidOMS project
 */


/*
 * PeptidOMS, Spectra to protein alignment tool
 * Copyright (C) 2025  Olivier Langella
 * <olivier.langella@universite-paris-saclay.fr>
 *
 * 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 "config.h"
#include "spomscli.h"
#include "core/runningspoms.h"
#include <sys/stat.h>
#include <QJsonDocument>
#include <QCommandLineParser>
#include <QDateTime>
#include <QTimer>
#include <QFile>
#include <QFileInfo>
#include <QDebug>
#include <QThreadPool>
#include <pappsomspp/core/pappsoexception.h>
#include <pappsomspp/core/processing/uimonitor/uimonitortext.h>
#include <pappsomspp/core/msfile/msfileaccessor.h>
#include <pappsomspp/core/msrun/private/timsmsrunreaderms2.h>
#include <pappsomspp/core/processing/filters/filtersuitestring.h>
#include <pappsomspp/core/processing/cbor/psm/evalscan/psmspecpeptidoms.h>


SpomsCli::SpomsCli(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
}


SpomsCli::~SpomsCli()
{
}


// call this routine to quit the application
void
SpomsCli::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
SpomsCli::aboutToQuitApp()
{
  // stop threads
  // sleep(1);   // wait for threads to stop.
  // delete any objects
}


QJsonObject
SpomsCli::readJsonFile(const QString &filename) const
{
  QJsonDocument json_document;
  qDebug() << filename;
  QJsonParseError parseError;
  QFile json_in(filename);

  if(!json_in.open(QIODevice::ReadOnly))
    {
      throw pappso::PappsoException(
        QObject::tr("error reading json from file %1").arg(filename));
    }


  QByteArray jsonData = json_in.readAll();
  // ConsoleOut::mcq_cerr() << jsonData;
  json_document = QJsonDocument::fromJson(jsonData, &parseError);

  if(parseError.error != QJsonParseError::ParseError::NoError)
    {
      throw pappso::PappsoException(
        QObject::tr("error parsing json from stdin, at byte %2:\n%1")
          .arg(parseError.errorString())
          .arg(parseError.offset));
    }
  // ConsoleOut::mcq_cerr() << json_document.toJson();

  return json_document.object();
}

void
SpomsCli::setCpuNumber(pappso::UiMonitorInterface &ui_monitor, uint cpu_number)
{

  qDebug() << " Quantificator::setCpuNumber" << cpu_number;
  uint ideal_number_of_thread = (uint)QThread::idealThreadCount();
  if(cpu_number > ideal_number_of_thread)
    {
      cpu_number = ideal_number_of_thread;
    }
  else
    {
      QThreadPool::globalInstance()->setMaxThreadCount(cpu_number);
    }
  ui_monitor.setStatus(QObject::tr("%1 CPUs used").arg(cpu_number));
}


// 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
SpomsCli::run()
{
  qSetMessagePattern(QString("%{file}@%{line}, %{function}(): %{message}"));

  //./src/peptidoms
  // -m
  // /gorgone/pappso/data_extraction_pappso/mzML/20120906_balliau_extract_1_A01_urnb-1.mzML
  // -f /gorgone/pappso/data_extraction_pappso/database/Genome_Z_mays_5a.fasta
  // -p params.json
  // -c 10


  QTextStream errorStream(stderr, QIODevice::WriteOnly);
  QTextStream outputStream(stderr, QIODevice::WriteOnly);

  try
    {
      qDebug();
      QCommandLineParser parser;
      pappso::UiMonitorText monitor(errorStream);

      // throw pappso::PappsoException("test");
      parser.setApplicationDescription(
        QString("%1 ")
          .arg(QCoreApplication::applicationName())
          .append("peptidoms")
          .append(" (")
          .append(VERSION)
          .append(")")
          .append(" -- spectra to protein alignment tool"));

      parser.addHelpOption();
      parser.addVersionOption();

      QCommandLineOption fastaInputOption(
        QStringList() << "f"
                      << "fasta",
        QCoreApplication::translate("FASTA file",
                                    "path to the FASTA input file <fasta>."),
        QCoreApplication::translate("fasta", "fasta"));

      QCommandLineOption mzInputOption(
        QStringList() << "m"
                      << "mz",
        QCoreApplication::translate("main", "mz file <mz>."),
        QCoreApplication::translate("main", "mz"));


      QCommandLineOption cborInputOption(
        QStringList() << "i",
        QCoreApplication::translate("PSM cbor",
                                    "path to the PSM cbor input file <input>."),
        QCoreApplication::translate("input", "input"));

      QCommandLineOption cborOutputOption(
        QStringList() << "o",
        QCoreApplication::translate(
          "PSM cbor", "path to the PSM cbor output file <output>."),
        QCoreApplication::translate("output", "output"));

      QCommandLineOption parametersOption(
        QStringList() << "p"
                      << "parameters",
        QCoreApplication::translate("parameters", "JSON parameter file"),
        QCoreApplication::translate("parameters", "parameters"));

      QCommandLineOption cpusOption(
        QStringList() << "c"
                      << "cpus",
        tr("sets the number of cpus you want to use"),
        tr("number"));
      parser.addOption(cpusOption);


      parser.addOption(fastaInputOption);
      parser.addOption(mzInputOption);
      parser.addOption(cborInputOption);
      parser.addOption(cborOutputOption);
      parser.addOption(parametersOption);
      qDebug();

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

      // QCoreApplication * app(this);
      // Add your main code here
      // qDebug();
      const QDateTime dt_begin = QDateTime::currentDateTime();
      const QStringList args   = parser.positionalArguments();


      QJsonObject json_parameters;
      if(parser.isSet(parametersOption))
        {
          // mcqml translation
          QString json_file = parser.value(parametersOption);
          json_parameters   = readJsonFile(json_file);
        }
      else
        {
          throw pappso::PappsoException(
            QObject::tr("JSON parameter file is missing"));
        }

      qDebug();


      QString cpusStr = parser.value(cpusOption);
      if(cpusStr.isEmpty())
        {
          // mcqout() << "masschroqml_validation cpusStr.isEmpty()" << endl;
          setCpuNumber(monitor, 1);
          // mcqout() << "masschroqml_validation cpusStr.isEmpty() 2"<<
          // endl;
        }
      else
        {
          setCpuNumber(monitor, cpusStr.toUInt());
        }

      // PsmProcess cbor_psm_process(json_parameters);


      qDebug();
      QFile output_cbor_file;
      if(!parser.isSet(cborOutputOption))
        {
          output_cbor_file.open(stdout, QIODevice::WriteOnly);
        }
      else
        {
          output_cbor_file.setFileName(parser.value(cborOutputOption));
          if(!output_cbor_file.open(QIODevice::WriteOnly))
            {
              throw pappso::PappsoException(
                QObject::tr("Unable to open PSM CBOR output file '%1' :\n%2\n")
                  .arg(QFileInfo(output_cbor_file).absoluteFilePath())
                  .arg(output_cbor_file.errorString()));
            }
        }

      qDebug();
      pappso::cbor::CborStreamWriter cbor_writer(&output_cbor_file);


      QString mzFileStr = parser.value(mzInputOption);

      if(!mzFileStr.isEmpty())
        {
          // build a spectrum collection from mz file

          qDebug();
          monitor.setStatus(
            QObject::tr("getting mz data file %1").arg(mzFileStr));

          pappso::MsFileAccessor accessor(mzFileStr, 0);
          accessor.setPreferredFileReaderType(
            pappso::Enums::MsDataFormat::brukerTims,
            pappso::Enums::FileReaderType::tims_ms2);
          pappso::MsRunReaderSPtr msrun_reader =
            accessor.getMsRunReaderSPtrByRunId("", "msrun");

          if(accessor.getFileFormat() ==
             pappso::Enums::MsDataFormat::brukerTims)
            {
              qDebug();
              pappso::TimsMsRunReaderMs2 *tims2_reader =
                dynamic_cast<pappso::TimsMsRunReaderMs2 *>(msrun_reader.get());
              if(tims2_reader != nullptr)
                {
                  qDebug();

                  tims2_reader->setMs2FilterCstSPtr(
                    std::make_shared<pappso::FilterSuiteString>(
                      "chargeDeconvolution|0.02dalton mzExclusion|0.01dalton"));
                  qDebug();
                }
              qDebug();
            }

          qDebug();


          std::vector<QFileInfo> fasta_fileinfo_list;

          QString fastaStr = parser.value(fastaInputOption);
          if(!fastaStr.isEmpty())
            {
              fasta_fileinfo_list.push_back(QFileInfo(fastaStr));
              // outputStream << fastaStr;
            }

          RunningSpoms spoms_run(monitor, json_parameters);

          spoms_run.run(msrun_reader, fasta_fileinfo_list, &cbor_writer);

          qDebug();
        }
      else
        {
          // take cbor input ?

          QFile input_cbor_file;
          if(!parser.isSet(cborInputOption))
            {
              input_cbor_file.open(stdin, QIODevice::ReadOnly);
            }
          else
            {
              input_cbor_file.setFileName(parser.value(cborInputOption));
              if(!input_cbor_file.open(QIODevice::ReadOnly))
                {
                  throw pappso::PappsoException(
                    QObject::tr("Unable to open PSM CBOR file '%1' :\n%2\n")
                      .arg(QFileInfo(input_cbor_file).absoluteFilePath())
                      .arg(input_cbor_file.errorString()));
                }
            }


          pappso::cbor::psm::PsmSpecPeptidOms psm_specpeptidoms(
            1000, &cbor_writer, json_parameters);

          qDebug();
          psm_specpeptidoms.readCbor(&input_cbor_file, monitor);
          qDebug();
          psm_specpeptidoms.close();
          qDebug();
          // xic_writer.write(xic);
          output_cbor_file.close();
          qDebug();
          input_cbor_file.close();
          qDebug();
        }

      qDebug();
      // xic_writer.write(xic);
      output_cbor_file.close();


      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 << Qt::endl << Qt::endl;

      errorStream.flush();
      app->exit(1);
      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 << Qt::endl << Qt::endl;

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


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


int
main(int argc, char **argv)
{
  umask(0);
  // 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("peptidoms");
  QCoreApplication::setApplicationVersion(VERSION);
  QLocale::setDefault(QLocale::system());

  // create the main class
  SpomsCli 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();
}
