/**
 * \file mcql/masschroqlightcli.cpp
 * \date 23/09/2024
 * \author Olivier Langella
 * \brief simple MassChroQ XIC extraction
 */

/*******************************************************************************
 * Copyright (c) 2024 Olivier Langella
 *<Olivier.Langella@universite-paris-saclay.fr>.
 *
 * This file is part of MassChroQ.
 *
 *     MassChroQ 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.
 *
 *     MassChroQ 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 MassChroQ.  If not, see <http://www.gnu.org/licenses/>.
 *
 ******************************************************************************/


#include "masschroqlightcli.h"
#include <pappsomspp/core/pappsoexception.h>
#include <QDateTime>
#include <QCommandLineParser>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <pappsomspp/core/processing/uimonitor/uimonitortextpercent.h>
#include "../config.h"
#include <pappsomspp/core/masschroq/input/jsoninput.h>
#include "input/masschroqml2jsonparser.h"
#include <QThreadPool>
#include <QJsonDocument>
#include <pappsomspp/core/masschroq/utils.h>
#include <pappsomspp/core/masschroq/output/cboroutputstream.h>


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

void
MassChroqLightCli::aboutToQuitApp()
{
}
void
MassChroqLightCli::quit()
{
  // you can do some cleanup here
  // then do emit finished to signal CoreApplication to quit
  emit finished();
}
void
MassChroqLightCli::run()
{

  QTextStream errorStream(stderr, QIODevice::WriteOnly);
  try
    {


      const QDateTime dt_begin = QDateTime::currentDateTime();
      const QString masschroq_dir_path(QCoreApplication::applicationDirPath());

      QCommandLineParser parser;
      parser.setApplicationDescription(
        "Perform Mass Chromatogram Quantification as indicated in the JSON "
        "input FILE or from STDIN. The output is written in a CBOR binary "
        "stream : stdout by default or using the given CBOR output file."
        "For additional information, see the MassChroQ Homepage : "
        "http://pappso.inra.fr/bioinfo/masschroq/");
      parser.addHelpOption();
      parser.addVersionOption();
      parser.addPositionalArgument("MassChroqJson",
                                   tr("MassChroQ JSON input file to execute or "
                                      "MassChroQml file to convert"));

      //    out << "  -c, --cpus integer\t" << endl;
      QCommandLineOption cpusOption(
        QStringList() << "c"
                      << "cpus",
        tr("sets the number of cpus you want to use"),
        tr("number"));
      parser.addOption(cpusOption);

      QCommandLineOption tmpOption(
        QStringList() << "t"
                      << "tmpdir",
        tr("use DIRECTORY as temporary working direcory for masschroq : "
           "temporary "
           "files generated by masschroq during execution time (one file or "
           "several slices at a time per xml data file being analysed) will go "
           "there. By default DIRECTORY is the current working directory of "
           "masschroq. Use this option if you want to set another one."),
        tr("directory"));
      parser.addOption(tmpOption);

      /*
            QCommandLineOption mcqmlFile(QStringList() << "m"
                                                       << "mcqml",
                                         tr("MassChroqML input file"),
                                         tr("filename"));
            parser.addOption(mcqmlFile);
      */

      QCommandLineOption mcqml2JsonFile(
        QStringList() << "mcqml2json",
        tr("conversion from MassChroqML input file to Json"),
        tr("json filename"));
      parser.addOption(mcqml2JsonFile);


      QCommandLineOption cborOutput(QStringList() << "cbor",
                                    tr("CBOR output file"),
                                    tr("CBOR output filename"));
      parser.addOption(cborOutput);


      QCommandLineOption shapeOutput(
        QStringList() << "shape",
        tr("store peak shape in CBOR output"),
        tr("margin in seconds before and after peak boundaries"));
      parser.addOption(shapeOutput);


      QCommandLineOption traceOutput(
        QStringList() << "trace",
        tr("store entire XIC in CBOR output (warning, possibly huge output "
           "file)"));
      parser.addOption(traceOutput);

      parser.process(*m_mcqLightApp);


      QStringList files = parser.positionalArguments();
      if(files.isEmpty())
        files << "-";

      QString input_file = files.at(0);


      pappso::UiMonitorTextPercent ui_monitor(errorStream);
      ui_monitor.setTitle(QObject::tr("MassChroQ version %1")
                            .arg(pappso::masschroq::Utils::getVersion()));


      if(parser.isSet(shapeOutput))
        {
          m_isPeakShapeOutput        = true;
          m_peakShapeMarginInSeconds = parser.value(shapeOutput).toDouble();
        }
      if(parser.isSet(traceOutput))
        {
          m_isTraceOutput = true;
        }


      if(parser.isSet(mcqml2JsonFile))
        {
          // mcqml translation
          QString json_file = parser.value(mcqml2JsonFile);
          if(json_file.isEmpty())
            json_file = "-";
          readMassChroqMl2JsonFile(ui_monitor, input_file, json_file);
        }
      else
        {

          QString cbor_file  = parser.value(cborOutput);
          QString tmpDirName = parser.value(tmpOption);
          if(tmpDirName.isEmpty())
            {
              /// by default, temporary files go in the current working
              /// directory
              tmpDirName = QDir::currentPath();
            }
          else
            {
            }

          m_tmpDirName = tmpDirName;

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

          readJsonStdinOrFile(json_document, input_file);


          readJsonDocument(ui_monitor, json_document, cbor_file);
        }
    }
  catch(pappso::PappsoException &error)
    {
      errorStream << "MassChroQ encountered an error:" << Qt::endl;
      errorStream << error.qwhat() << Qt::endl;

      exit(1);
      m_mcqLightApp->exit(1);
    }
  qDebug() << "before final quit()";

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


void
MassChroqLightCli::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);
    }
  m_cpuNumber = cpu_number;
  ui_monitor.setStatus(QObject::tr("%1 CPUs used").arg(cpu_number));
}


void
MassChroqLightCli::readMassChroqMl2JsonFile(pappso::UiMonitorInterface &monitor,
                                            const QString &xml_filename,
                                            const QString &json_file)
{


  mcql::MassChroqMl2JsonParser parser;

  QFile file(QFileInfo(xml_filename).absoluteFilePath());
  if(xml_filename == "-")
    {
      file.open(stdin, QIODevice::ReadOnly);
    }
  else
    {
      if(!file.open(QIODevice::ReadOnly))
        {
          throw pappso::PappsoException(
            QObject::tr("Unable to open xml masschroqML file '%1' :\n%2\n")
              .arg(QFileInfo(xml_filename).absoluteFilePath())
              .arg(file.errorString()));
        }
      file.open(QIODevice::ReadOnly);


      monitor.setStatus(QObject::tr("Parsing XML input file '%1'")
                          .arg(QFileInfo(xml_filename).absoluteFilePath()));
    }


  if(parser.read(&file))
    {
      file.close();
      QJsonDocument doc;

      parser.populateJsonDocument(doc);


      QFile jsonf(json_file);
      if(json_file == "-" ? jsonf.open(stdout, QIODevice::WriteOnly)
                          : jsonf.open(QIODevice::WriteOnly))
        {
        }
      jsonf.write(doc.toJson());
      jsonf.close();
    }
  else
    {
      file.close();
      throw pappso::PappsoException(
        QObject::tr("error reading masschroqML input file '%1' :\n")
          .arg(QFileInfo(xml_filename).absoluteFilePath())
          .append(parser.errorString()));
    }

  monitor.setStatus(QObject::tr("MassChroQ : DONE on file '%1'")
                      .arg(QFileInfo(xml_filename).absoluteFilePath()));
}

void
MassChroqLightCli::readJsonDocument(pappso::UiMonitorInterface &monitor,
                                    const QJsonDocument &json_document,
                                    QString &cbor_filename)
{

  // ConsoleOut::mcq_cout() << json_document.toJson();
  pappso::masschroq::JsonInput parser(monitor, m_tmpDirName, json_document);

  QFile cbor_file(cbor_filename);

  if(cbor_filename.isEmpty())
    {
      if(!cbor_file.open(stdout, QIODevice::WriteOnly))
        {
          throw pappso::PappsoException(QObject::tr("error opening stdout"));
        }
    }
  else
    {
      if(!cbor_file.open(QIODevice::WriteOnly))
        {
          throw pappso::PappsoException(
            QObject::tr("error opening file %1").arg(cbor_filename));
        }
    }
  pappso::masschroq::CborOutputStream cbor_output(&cbor_file);
  cbor_output.setIsPeakShapeOutput(m_isPeakShapeOutput,
                                   m_peakShapeMarginInSeconds);
  cbor_output.setIsTraceOutput(m_isTraceOutput);
  parser.action(cbor_output);
  cbor_output.close();
  cbor_file.close();
}

void
MassChroqLightCli::readJsonStdinOrFile(QJsonDocument &json_document,
                                       QString &filename)
{

  qDebug() << filename;
  QJsonParseError parseError;
  QFile json_in(filename);
  if(filename == "-")
    {
      if(!json_in.open(stdin, QIODevice::ReadOnly))
        {

          throw pappso::PappsoException(
            QObject::tr("error reading json from stdin"));
        }
    }
  else
    {

      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();
}
