
/*******************************************************************************
 * Copyright (c) 2017 Olivier Langella <olivier.langella@u-psud.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 "utils.h"
#include <pappsomspp/core/exception/exceptionnotfound.h>
#include <pappsomspp/core/mzrange.h>
#include <cmath>
#include <QProcess>
#include <QDebug>
#include <QFileInfo>
#include <QSettings>

const QUrl
Utils::getOlsUrl(QString psimod_accession)
{

  QString iri(QString("http://purl.obolibrary.org/obo/%1")
                .arg(psimod_accession.replace(":", "_")));
  QUrl url(
    QString("http://www.ebi.ac.uk/ols/ontologies/mod/terms?iri=%1").arg(iri));
  return url;
}

const QString
Utils::getXmlDouble(pappso::pappso_double number)
{
  return QString::number(number, 'g', 6);
}

const QString
Utils::getIdentificationEngineName(IdentificationEngine engine)
{
  QString engine_name;
  switch(engine)
    {
      case IdentificationEngine::XTandem:
        engine_name = "X!Tandem";
        break;
      case IdentificationEngine::mascot:
        engine_name = "Mascot";
        break;

      case IdentificationEngine::peptider:
        engine_name = "Peptider";
        break;
      case IdentificationEngine::OMSSA:
        engine_name = "OMSSA";
        break;
      case IdentificationEngine::SEQUEST:
        engine_name = "Sequest";
        break;
      case IdentificationEngine::Comet:
        engine_name = "Comet";
        break;
      case IdentificationEngine::Morpheus:
        engine_name = "Morpheus";
        break;
      case IdentificationEngine::MSGFplus:
        engine_name = "MS-GF+";
        break;
      case IdentificationEngine::unknown:
        engine_name = "unknown";
        break;
      case IdentificationEngine::SpecOMS:
        engine_name = "SpecOMS";
        break;
      case IdentificationEngine::sage:
        engine_name = "Sage";
        break;
      case IdentificationEngine::PEAKS_Studio:
        engine_name = "PEAKS Studio";
        break;
    }
  return engine_name;
}

const QString
Utils::getDatabaseName(ExternalDatabase database)
{
  QString database_name;
  switch(database)
    {
      case ExternalDatabase::AGI_LocusCode:
        database_name = "AGI_LocusCode";
        break;
      case ExternalDatabase::NCBI_gi:
        database_name = "NCBI_gi";
        break;

      case ExternalDatabase::SwissProt:
        database_name = "Swiss-Prot";
        break;
      case ExternalDatabase::TrEMBL:
        database_name = "TrEMBL";
        break;
      case ExternalDatabase::ref:
        database_name = "ref";
        break;
      case ExternalDatabase::OboPsiMod:
        database_name = "PSI::MOD";
        break;
    }
  return database_name;
}


std::vector<std::pair<pappso::pappso_double, size_t>>
Utils::getHistogram(std::vector<pappso::pappso_double> data_values,
                    unsigned int number_of_class)
{
  std::vector<std::pair<pappso::pappso_double, size_t>> histogram(
    number_of_class + 1);
  try
    {
      qDebug() << "Utils::getHistogram begin";

      std::sort(data_values.begin(), data_values.end());
      pappso::pappso_double min   = *data_values.begin();
      pappso::pappso_double max   = *(data_values.end() - 1);
      pappso::pappso_double total = std::abs(max - min);
      pappso::pappso_double offset =
        (total / (pappso::pappso_double)number_of_class);
      // qDebug() << "Utils::getHistogram number_of_class offset=" << offset;
      for(unsigned int i = 0; i < histogram.size(); i++)
        {
          histogram[i] = std::pair<pappso::pappso_double, size_t>{
            (min + (offset * i) + (offset / 2)), 0};
          // qDebug() << "Utils::getHistogram x=" << histogram[i].first;
        }
      // qDebug() << "Utils::getHistogram data_values";
      for(pappso::pappso_double value : data_values)
        {
          // qDebug() << "Utils::getHistogram value=" << value;
          unsigned int i = std::abs((value - min) / offset);
          // qDebug() << "Utils::getHistogram i=" << i << " size=" <<
          // histogram.size();
          histogram.at(i).second++;
        }
    }
  catch(std::exception &exception_std)
    {
      throw pappso::PappsoException(
        QObject::tr("Utils::getHistogram error %1").arg(exception_std.what()));
    }
  qDebug() << "Utils::getHistogram end";
  return histogram;
}

const QString
Utils::checkXtandemVersion(const QString &tandem_bin_path)
{
  qDebug();
  // check tandem path
  QFileInfo tandem_exe(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 X!Tandem software found at %1 "
                    "(file not readable).")
          .arg(tandem_exe.absoluteFilePath()));
    }
  if(!tandem_exe.isExecutable())
    {
      // dir.path() returns the unique directory path
      throw pappso::PappsoException(
        QObject::tr("Please check permissions on X!Tandem software found at %1 "
                    "(file not executable).")
          .arg(tandem_exe.absoluteFilePath()));
    }


  QString version_return;
  QStringList arguments;

  arguments << "-v";

  QProcess *xt_process = new QProcess();
  // hk_process->setWorkingDirectory(QFileInfo(_hardklor_exe).absolutePath());

  xt_process->start(tandem_bin_path, arguments);

  if(!xt_process->waitForStarted())
    {
      QString err = QObject::tr(
                      "Could not start X!Tandem process "
                      "'%1' with arguments '%2': %3")
                      .arg(tandem_bin_path)
                      .arg(arguments.join(QStringLiteral(" ")))
                      .arg(xt_process->errorString());
      throw pappso::PappsoException(err);
    }

  while(xt_process->waitForReadyRead(30000))
    {
    }
  /*
  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();


  qDebug() << result.constData();

  // X! TANDEM Jackhammer TPP (2013.06.15.1 - LabKey, Insilicos, ISB)


  QRegularExpressionMatch parse_version =
    QRegularExpression("TANDEM ([A-Z,a-z, ]+) \\(([^ ,^\\)]*)")
      .match(result.constData());
  qDebug() << parse_version;
  // Pattern patt = Pattern.compile("X! TANDEM [A-Z]+ \\((.*)\\)",
  //			Pattern.CASE_INSENSITIVE);

  if(parse_version.hasMatch())
    {
      version_return = QString("X!Tandem %1 %2")
                         .arg(parse_version.capturedTexts()[1])
                         .arg(parse_version.capturedTexts()[2]); //.join(" ");
    }
  else
    {
      throw pappso::PappsoException(
        QObject::tr("This executable %1 may not be a valid X!Tandem software. "
                    "Please check your X!Tandem installation.\n%2")
          .arg(tandem_bin_path)
          .arg(result.toStdString().c_str()));
    }

  QProcess::ExitStatus Status = xt_process->exitStatus();
  delete xt_process;
  if(Status != 0)
    {
      // != QProcess::NormalExit
      throw pappso::PappsoException(
        QObject::tr("error executing X!Tandem Status != 0 : %1 %2\n%3")
          .arg(tandem_bin_path)
          .arg(arguments.join(" ").arg(result.data())));
    }
  qDebug();
  return version_return;
}

const QString
Utils::checkSageVersion(const QString &sage_bin_path)
{
  qDebug();
  // check tandem path
  QFileInfo sage_exe(sage_bin_path);
  if(!sage_exe.exists())
    {
      // dir.path() returns the unique directory path
      throw pappso::PappsoException(
        QObject::tr("Sage software not found at %1.\nPlease check the Sage "
                    "installation on your computer and set sage.exe path.")
          .arg(sage_exe.absoluteFilePath()));
    }
  if(!sage_exe.isReadable())
    {
      // dir.path() returns the unique directory path
      throw pappso::PappsoException(
        QObject::tr("Please check permissions on Sage software found at %1 "
                    "(file not readable).")
          .arg(sage_exe.absoluteFilePath()));
    }
  if(!sage_exe.isExecutable())
    {
      // dir.path() returns the unique directory path
      throw pappso::PappsoException(
        QObject::tr("Please check permissions on Sage software found at %1 "
                    "(file not executable).")
          .arg(sage_exe.absoluteFilePath()));
    }


  QString version_return;
  QStringList arguments;

  arguments << "--version";

  QProcess *xt_process = new QProcess();
  // hk_process->setWorkingDirectory(QFileInfo(_hardklor_exe).absolutePath());

  xt_process->start(sage_bin_path, arguments);

  if(!xt_process->waitForStarted())
    {
      QString err = QObject::tr(
                      "Could not start Sage process "
                      "'%1' with arguments '%2': %3")
                      .arg(sage_bin_path)
                      .arg(arguments.join(QStringLiteral(" ")))
                      .arg(xt_process->errorString());
      throw pappso::PappsoException(err);
    }

  while(xt_process->waitForReadyRead(30000))
    {
    }
  /*
  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();


  qDebug() << result.constData();

  // X! TANDEM Jackhammer TPP (2013.06.15.1 - LabKey, Insilicos, ISB)


  QRegularExpressionMatch parse_version =
    QRegularExpression("^sage (.*)").match(result.constData());
  qDebug() << parse_version;
  // Pattern patt = Pattern.compile("X! TANDEM [A-Z]+ \\((.*)\\)",
  //			Pattern.CASE_INSENSITIVE);

  if(parse_version.hasMatch())
    {
      version_return =
        QString("sage %1").arg(parse_version.capturedTexts()[1]); //.join(" ");
    }
  else
    {
      throw pappso::PappsoException(
        QObject::tr("This executable %1 may not be a valid Sage software. "
                    "Please check your Sage installation.\n%2")
          .arg(sage_bin_path)
          .arg(result.toStdString().c_str()));
    }

  QProcess::ExitStatus Status = xt_process->exitStatus();
  delete xt_process;
  if(Status != 0)
    {
      // != QProcess::NormalExit
      throw pappso::PappsoException(
        QObject::tr("error executing Sage Status != 0 : %1 %2\n%3")
          .arg(sage_bin_path)
          .arg(arguments.join(" ").arg(result.data())));
    }
  qDebug();
  return version_return;
}
const QString
Utils::checkMassChroQVersion(const QString &masschroq_bin_path)
{
  // check masschroq path
  QFileInfo masschroq_exe(masschroq_bin_path);
  if(!masschroq_exe.exists())
    {
      // dir.path() returns the unique directory path
      throw pappso::PappsoException(
        QObject::tr(
          "MassChroQ software not found at \"%1\".\nPlease check the MassChroQ "
          "installation on your computer and set masschroq.exe path.")
          .arg(masschroq_exe.absoluteFilePath()));
    }
  if(!masschroq_exe.isReadable())
    {
      // dir.path() returns the unique directory path
      throw pappso::PappsoException(
        QObject::tr(
          "Please check permissions on MassChroQ software found at %1 "
          "(file not readable).")
          .arg(masschroq_exe.absoluteFilePath()));
    }
  if(!masschroq_exe.isExecutable())
    {
      // dir.path() returns the unique directory path
      throw pappso::PappsoException(
        QObject::tr(
          "Please check permissions on MassChroQ software found at %1 "
          "(file not executable).")
          .arg(masschroq_exe.absoluteFilePath()));
    }

  QString version_return;
  QStringList arguments;

  arguments << "-v";

  QProcess *mcq_process = new QProcess();

  mcq_process->start(masschroq_bin_path, arguments);

  if(!mcq_process->waitForStarted())
    {
      QString err = QObject::tr(
                      "Could not start MassChroQ process "
                      "'%1' with arguments '%2': %3")
                      .arg(masschroq_bin_path)
                      .arg(arguments.join(QStringLiteral(" ")))
                      .arg(mcq_process->errorString());
      throw pappso::PappsoException(err);
    }

  while(mcq_process->waitForReadyRead(1000))
    {
    }
  QByteArray result = mcq_process->readAll();


  qDebug() << result.constData();
  //  /home/trenne/developpement/git/masschroq/build/src/masschroq
  QRegularExpressionMatch parse_version =
    QRegularExpression("(MassChroQ [0-9]{1,2}\\.[0-9]{1,2}\\.[0-9]{1,3})")
      .match(QString(result.constData()).simplified());
  qDebug() << parse_version;

  if(parse_version.hasMatch())
    {
      version_return = result.constData();
    }
  else
    {
      throw pappso::PappsoException(
        QObject::tr("This executable %1 may not be a valid MassChroQ software. "
                    "Please check your MassChroQ installation.")
          .arg(masschroq_bin_path));
    }

  QProcess::ExitStatus Status = mcq_process->exitStatus();
  delete mcq_process;
  if(Status != 0)
    {
      // != QProcess::NormalExit
      mcq_process->kill();
      delete mcq_process;
      throw pappso::PappsoException(
        QObject::tr("error executing MassChroQ Status != 0 : %1 %2\n%3")
          .arg(masschroq_bin_path)
          .arg(arguments.join(" ").arg(result.data())));
    }
  qDebug();
  return version_return;
}

const QString
Utils::checkRVersion(const QString r_version)
{
  QString version_return;
  QStringList arguments;

  arguments << "--version";

  QSettings settings;
  QString r_binary_path = settings.value("path/r_binary", "R").toString();
  QProcess *r_process   = new QProcess();

  r_process->start(r_binary_path, arguments);

  if(!r_process->waitForStarted())
    {
      QString err = QObject::tr(
                      "Could not start R process "
                      "'%1' with arguments '%2': %3")
                      .arg(r_binary_path)
                      .arg(arguments.join(QStringLiteral(" ")))
                      .arg(r_process->errorString());
      throw pappso::PappsoException(err);
    }

  while(r_process->waitForReadyRead(1000))
    {
    }
  QByteArray result = r_process->readAll();


  qDebug() << result.constData();
  QString r_version_regex = r_version;
  r_version_regex.replace(".", "\\.");
  QRegularExpressionMatch parse_version =
    QRegularExpression("^(R version " + r_version_regex + "\\.[0-9]{1,2}).*")
      .match(QString(result.constData()).simplified());
  qDebug() << parse_version;

  if(parse_version.hasMatch())
    {
      version_return = parse_version.capturedTexts().front();
    }
  else
    {
      throw pappso::PappsoException(
        QObject::tr(
          "The R installed version isn't correct please install version %1")
          .arg(r_version));
    }

  QProcess::ExitStatus Status = r_process->exitStatus();
  delete r_process;
  if(Status != 0)
    {
      // != QProcess::NormalExit
      r_process->kill();
      delete r_process;
      throw pappso::PappsoException(
        QObject::tr("error executing R Status != 0 : R %1\n%2")
          .arg(arguments.join(" ").arg(result.data())));
    }
  qDebug();
  return version_return;
}


double
Utils::computeFdr(std::size_t count_decoy, std::size_t count_target)
{
  // 10.1016/j.jprot.2010.08.009
  /*
   *In the area of MS/MS-based proteomics, the methods for computing FDR can be
   *broadly grouped into two categories. The discussion here starts with a
   *simple approach based on the use of the target-decoy database search
   *strategy [174]. The target-decoy approach requires minimal distributional
   *assumptions and is easy to implement, which makes it easily applicable in a
   *variety of situations. The strategy requires that experimental MS/MS spectra
   *are searched against a target database of protein sequences appended with
   *the reversed (or randomized, or shuffled) sequences of the same size (see
   *Figure 6 for illustration). Alternatively, the searches against the target
   *and decoy sequences can be performed separately (the differences between
   *these two approaches are discussed below). A similar approach can be used
   *for spectral library searching [175]. The basic assumption is that matches
   *to decoy peptide sequences (decoy PSMs) and false matches to sequences from
   *the target database follow the same distribution. The plausibility of these
   *assumption was discussed in [174]. In the second step, PSMs are filtered
   *using various score cut-offs (e.g. a certain X! Tandem E-value or MASCOT Ion
   *Score cut-off), and the corresponding FDR for each cut-off is estimated as
   *Nd/Nt, where Nt is the number of target PSMs with scores above the cut-off,
   *and Nd is the number of decoy PSMs among them. The underlying assumption is
   *that, given the equal size of the target and decoy database, the number of
   *incorrect target PSMs, Ninc, can be estimated as the number of decoy PSMs.
   *Alternatively, the FDR is sometime estimated as 2Nd/(Nt+Nd).
   */

  // https://doi.org/10.1186/s12953-021-00179-7

  /*
   *False discovery rate and correction factor
   * In general, the target-decoy strategy uses the following equation to
   *estimate the FDR [13]: FDR = #decoy / #target
   */


  return ((double)count_decoy / (double)count_target);
}

pappso::Enums::MsDataFormat
Utils::guessDataFileFormatFromFile(const QString &filename)
{
  qDebug() << filename;
  QString extension = QFileInfo(filename).suffix().toLower();
  qDebug() << extension;
  if(extension == "tdf")
    {
      return pappso::Enums::MsDataFormat::brukerTims;
    }
  else if(extension == "mzxml")
    {
      return pappso::Enums::MsDataFormat::mzXML;
    }
  else if(extension == "mzml")
    {
      return pappso::Enums::MsDataFormat::mzML;
    }
  else if(extension == "mgf")
    {
      qDebug() << filename;
      return pappso::Enums::MsDataFormat::MGF;
    }
  else
    {
      qDebug() << filename;
      return pappso::Enums::MsDataFormat::unknown;
    }
}

void
Utils::transformPlainNewLineToHtmlFormat(QString &transform_line)
{
  if(!transform_line.contains("<!-- html table generated"))
    {
      QRegularExpression rx("(\\n+)");
      transform_line.replace(rx, "<br/>").append("<br/>");
    }
  qDebug() << transform_line;
}

std::size_t
Utils::getMegaBytesFromString(const QString &memory_string, bool &is_ok)
{
  qDebug() << memory_string;
  is_ok             = true;
  QString mem       = memory_string.toUpper().trimmed();
  QString unit      = mem.mid(mem.size() - 1, 1);
  double mem_double = mem.mid(0, mem.size() - 1).toDouble(&is_ok);
  qDebug() << " mem=" << mem << " unit=" << unit
           << " mem_double=" << mem_double;
  if(is_ok)
    {
      if(unit == "K")
        {
          mem_double = mem_double / 1000;
          if(mem_double < 0)
            mem_double = 1;
        }
      if(unit == "G")
        {
          mem_double = mem_double * 1000;
        }
      return (std::size_t)mem_double;
    }
  return 0;
}


QJsonArray
Utils::toJson(const std::vector<double> &myVec)
{
  QJsonArray result;
  for(auto i = myVec.begin(); i != myVec.end(); i++)
    {
      result.push_back((*i));
    }
  return result;
}
