/**
 * \file /output/tidd/tidddataoutput.cpp
 * \date 30/9/2022
 * \author Olivier Langella
 * \brief compute TIDD PSM probabilities
 * (https://pubmed.ncbi.nlm.nih.gov/35354356/)
 */

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


#include "tiddcomputing.h"
#include <pappsomspp/core/pappsoexception.h>
#include <odsstream/tsvdirectorywriter.h>
#include <odsstream/tsvreader.h>
#include <QSettings>
#include "../../output/tidd/tidddataoutput.h"
#include "../../input/tidd/tiddresultreader.h"

TiddComputing::TiddComputing()
{

  mp_rProcess = new McqrQProcess();
  mp_rProcess->startRprocess();

  connect(mp_rProcess,
          &QProcess::readyReadStandardOutput,
          this,
          &TiddComputing::updateRscriptTiddOutput);
  connect(mp_rProcess,
          &QProcess::readyReadStandardError,
          this,
          &TiddComputing::handleRscriptTiddMessages);


  m_parameters.m_ms2precision =
    pappso::PrecisionFactory::getDaltonInstance(0.02);
  m_parameters.m_minimumMz = 150;


  mpa_temporaryTiddDir = new QTemporaryDir(QDir::tempPath() + "/tidd");
  mpa_temporaryTiddDir->setAutoRemove(true);

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

  m_tiddFeatureTsvFile.setFile(QString("%1/%2")
                                 .arg(mpa_temporaryTiddDir->filePath("tidd.d"))
                                 .arg("tidd_features.tsv"));


  mpa_gtEngine = new Cutelee::Engine(this);
  auto loader  = std::shared_ptr<Cutelee::FileSystemTemplateLoader>(
    new Cutelee::FileSystemTemplateLoader());
  loader.get()->setTemplateDirs({":/templates/resources/templates/r_scripts"});
  mpa_gtEngine->addTemplateLoader(loader);
}

TiddComputing::~TiddComputing()
{
  mp_rProcess->stopRprocess();
  mp_rProcess->deleteLater();
  // delete mp_rProcess;
  delete mpa_temporaryTiddDir;
}

void
TiddComputing::run(pappso::UiMonitorInterface &monitor, Project *project)
{
  mp_monitor = &monitor;
  try
    {

      if(m_peptideEvidenceList.size() == 0)
        {
          rInstallTiddDependencies();

          computeFeatures(project);
        }
      // R process


      QFileInfo tidd_results_tsv_file(
        mpa_temporaryTiddDir->filePath("tidd_results.tsv"));
      monitor.setStatus(QObject::tr("R process"));
      rProcess(m_tiddFeatureTsvFile.absoluteFilePath(),
               tidd_results_tsv_file.absoluteFilePath());


      // rProcess("/tmp/tidd.HibwYe/tidd.d/tidd_features.tsv",
      //           "/tmp/tidd.HibwYe/tidd_results.tsv");
      // read results and injects new SVM probabilities


      TiddResultReader handler(mp_monitor);
      TsvReader tsv_reader(handler);
      handler.setPeptideEvidenceList(m_peptideEvidenceList);
      // tsv_reader.setSeparator(TsvSeparator::comma);
      QFile tsv_file(tidd_results_tsv_file.absoluteFilePath());
      tsv_reader.parse(tsv_file);
    }
  catch(pappso::PappsoException &error)
    {
      QString err = QObject::tr("Error running TIDD :\n%1").arg(error.qwhat());
      throw pappso::PappsoException(err);
    }
}

void
TiddComputing::computeFeatures(Project *project)
{

  TsvDirectoryWriter *p_writer = nullptr;
  mp_monitor->setStatus(QObject::tr("generating TIDD data files, please wait"));
  QSettings settings;


  p_writer =
    new TsvDirectoryWriter(QDir(mpa_temporaryTiddDir->filePath("tidd.d")));
  p_writer->writeSheet("tidd_features");
  TiddDataOutput tidd_data_output(mp_monitor,
                                  p_writer,
                                  project,
                                  m_parameters.m_ms2precision,
                                  m_parameters.m_minimumMz);

  p_writer->close();

  if(!m_tiddFeatureTsvFile.exists())
    {
      throw pappso::PappsoException(
        QObject::tr("problem generating tidd features in file %1\n")
          .arg(m_tiddFeatureTsvFile.absoluteFilePath()));
    }

  m_peptideEvidenceList = tidd_data_output.getPeptideEvidenceList();
}


void
TiddComputing::rInstallTiddDependencies()
{
  try
    {
      qWarning() << "rInstallTiddDependencies";

      mp_monitor->setStatus("Check R installation and TIDD dependencies");
      Cutelee::Template rscript_template_sp =
        mpa_gtEngine->loadByName("tiddlibinstall.R");
      Cutelee::Context tidd_install_context;

      QString rcode = rscript_template_sp->render(&tidd_install_context);
      if(rcode.isEmpty())
        {
          QString err = QObject::tr("Rcode is empty");
          throw pappso::PappsoException(err);
        }
      qWarning() << "rcode= " << rcode;
      m_tiddFinished = false;
      mp_rProcess->executeMcqrStep("tidd_dependencies_install", rcode);

      qWarning() << "rInstallTiddDependencies "
                 << mp_rProcess->getRscriptCode();

      while(m_tiddFinished == false)
        {
          qWarning() << "R installation state=" << m_tiddFinished
                     << " mp_rProcess->waitForFinished(1000)";
          mp_rProcess->waitForFinished(1000);
        };
    }
  catch(pappso::PappsoException &error)
    {
      QString err =
        QObject::tr("Error while running TIDD dependencies R script :\n%1")
          .arg(error.qwhat());
      throw pappso::PappsoException(err);
    }
}


void
TiddComputing::rProcess(const QString &feature_file, const QString &result_file)
{

  try
    {
      Cutelee::Template rscript_template_sp =
        mpa_gtEngine->loadByName("tidd.R");
      Cutelee::Context tidd_context;

      tidd_context.insert("feature_file", feature_file);
      tidd_context.insert("result_file", result_file);
      tidd_context.insert("train_size",
                          QString("%1").arg(m_parameters.m_trainSize));
      tidd_context.insert("iteration",
                          QString("%1").arg(m_parameters.m_iteration));
      tidd_context.insert("k_cross", QString("%1").arg(m_parameters.m_kCross));

      mp_monitor->setStatus("Computing SVM probabilities");

      m_tiddFinished    = false;
      QString tidd_code = rscript_template_sp->render(&tidd_context);
      qWarning() << "tidd code =" << tidd_code;

      if(tidd_code.isEmpty())
        {
          QString err = QObject::tr("Rcode is empty");
          throw pappso::PappsoException(err);
        }
      mp_rProcess->executeMcqrStep("tidd", tidd_code);

      qWarning() << mp_rProcess->getRscriptCode();

      // mp_rProcess->executeOffTheRecord(QString("quit( save = \"no\")"));

      while(m_tiddFinished == false)
        {
          qWarning() << "state=" << m_tiddFinished
                     << " mp_rProcess->waitForFinished(1000)";
          mp_rProcess->waitForFinished(1000);
        };
    }
  catch(pappso::PappsoException &error)
    {
      QString err = QObject::tr("Error while running TIDD R script :\n%1")
                      .arg(error.qwhat());
      throw pappso::PappsoException(err);
    }
}


void
TiddComputing::updateRscriptTiddOutput()
{
  QString mcqr_process_results = mp_rProcess->readAllStandardOutput();
  mcqr_process_results         = mcqr_process_results.trimmed();

  qWarning() << "out " << mcqr_process_results;

  if(mcqr_process_results.startsWith("Results_FDR="))
    {
      qWarning() << "Results_FDR=" << mcqr_process_results;
      m_results.fdr = mcqr_process_results.split("=")[1].toDouble();
    }
  if(mcqr_process_results.startsWith("score_threshold="))
    {
      qWarning() << "score_threshold=" << mcqr_process_results;
      m_results.svmProbThreshold =
        mcqr_process_results.split("=")[1].toDouble();
    }
}


void
TiddComputing::handleRscriptTiddMessages()
{
  QRegularExpression line_separator(
    QString("(\n|%1)").arg(QChar::LineSeparator));
  QString mcqr_message = mp_rProcess->readAllStandardError();
  mcqr_message         = mcqr_message.trimmed();
  qWarning() << "err " << mcqr_message;
  QStringList mcqr_messages = mcqr_message.split(line_separator);
  for(QString message : mcqr_messages)
    {
      message = message.trimmed();

      qWarning() << "line " << message;
      QRegularExpression mcqr_info_regex("^MCQRInfo: (.*)");
      QRegularExpression mcqr_begin_regex("^MCQRBegin: (.*)");
      QRegularExpression mcqr_end_regex("^MCQREnd: (.*)");

      // Error in doTryCatch(return(expr), name, parentenv, handler):
      QRegularExpression mcqr_error_regex("^Err(eu|o)r (in|:) (.*):(.*)$");

      QRegularExpressionMatch mcqr_error_regex_match =
        mcqr_error_regex.match(message, 0);

      if(mcqr_error_regex_match.hasMatch())
        {
          throw pappso::PappsoException(mcqr_error_regex_match.captured(4));
        }
      else // Other messages
        {
          if(message.startsWith("Results_FDR="))
            {
              qWarning() << "err Results_FDR=" << message;
              m_results.fdr = message.split("=")[1].toDouble();
            }
          else if(message.startsWith("score_threshold="))
            {
              qWarning() << "err score_threshold=" << message;
              m_results.svmProbThreshold = message.split("=")[1].toDouble();
            }
          else if(message.startsWith("MCQREnd: tidd_dependencies_install"))
            {
              m_tiddFinished = true;
            }
          else if(message.startsWith("MCQREnd: tidd"))
            {
              m_tiddFinished = true;
            }
        }
    }
}

void
TiddComputing::setTiddParameters(const TiddParameters &parameters)
{
  m_parameters = parameters;
}

const TiddResults &
TiddComputing::getTiddResults() const
{
  return m_results;
}
