/**
 * \file src/input/mzidentml/mzidentmlsaxhandler.cpp
 * \date 14/11/2020
 * \author Olivier Langella
 * \brief parse mzIdentML result file
 */


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

#include "mzidentmlsaxhandler.h"


#include <pappsomspp/exception/exceptionnotfound.h>
#include <cmath>
#include "../../core/peptideevidence.h"
#include "../../utils/peptidestore.h"
#include "../../utils/proteinstore.h"
#include "../../utils/utils.h"
#include "datafilenotsupportedexception.h"


PepXmlSaxHandler::PepXmlSaxHandler(
  Project *p_project,
  IdentificationGroup *p_identification_group,
  IdentificationDataSource *p_identification_data_source)
  : _p_project(p_project)
{
  qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__;
  _p_identification_group = p_identification_group;

  _p_identification_data_source = p_identification_data_source;
  _sp_msrun                     = p_identification_data_source->getMsRunSp();
}

PepXmlSaxHandler::~PepXmlSaxHandler()
{
}

bool
PepXmlSaxHandler::startElement(const QString &namespaceURI [[maybe_unused]],
                               const QString &localName [[maybe_unused]],
                               const QString &qName,
                               const QXmlAttributes &attributes)
{
  // qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__;
  _tag_stack.push_back(qName);
  bool is_ok = true;

  try
    {
      // startElement_group
      if(_tag_stack.size() == 1)
        {
          if(qName != "msms_pipeline_analysis")
            {
              throw DataFileNotSupportedException(
                QObject::tr("ERROR this file is not a pep xml file %1")
                  .arg(qName));
            }
        }
      else if(qName == "msms_pipeline_analysis")
        {
          is_ok = startElement_msms_pipeline_analysis(attributes);
        }
      else if(qName == "msms_run_summary")
        {
          is_ok = startElement_msms_run_summary(attributes);
        }
      else if(qName == "search_database")
        {
          is_ok = startElement_search_database(attributes);
        }
      else if(qName == "search_summary")
        {
          is_ok = startElement_search_summary(attributes);
        }
      else if(qName == "spectrum_query")
        {
          is_ok = startElement_spectrum_query(attributes);
        }
      else if(qName == "search_hit")
        {
          is_ok = startElement_search_hit(attributes);
        }
      else if(qName == "alternative_protein")
        {
          is_ok = startElement_alternative_protein(attributes);
        }

      else if(qName == "peptideprophet_result")
        {
          is_ok = startElement_peptideprophet_result(attributes);
        }
      else if(qName == "interprophet_result")
        {
          is_ok = startElement_interprophet_result(attributes);
        }
      //<sample value="P6_08_10"/>
      else if(qName == "search_score")
        {
          is_ok = startElement_search_score(attributes);
        }
      else if(qName == "mod_aminoacid_mass")
        {
          is_ok = startElement_mod_aminoacid_mass(attributes);
        }
      else if(qName == "modification_info")
        {
          is_ok = startElement_modification_info(attributes);
        }
      _current_text.clear();
    }
  catch(const pappso::PappsoException &exception_pappso)
    {
      _errorStr = QObject::tr(
                    "ERROR in PepXmlSaxHandler::startElement tag "
                    "%1, PAPPSO exception:\n%2")
                    .arg(qName)
                    .arg(exception_pappso.qwhat());
      return false;
    }
  catch(const std::exception &exception_std)
    {
      _errorStr =
        QObject::tr(
          "ERROR in PepXmlSaxHandler::startElement tag %1, std exception:\n%2")
          .arg(qName)
          .arg(exception_std.what());
      return false;
    }
  return is_ok;
}

bool
PepXmlSaxHandler::endElement(const QString &namespaceURI [[maybe_unused]],
                             const QString &localName [[maybe_unused]],
                             const QString &qName)
{

  // qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__;
  bool is_ok = true;
  // endElement_peptide_list
  try
    {
      if(qName == "search_hit")
        {
          is_ok = endElement_search_hit();
        }
      else if(qName == "modification_info")
        {
          is_ok = endElement_modification_info();
        }

      // end of detection_moulon
      // else if ((_tag_stack.size() > 1) &&
      //         (_tag_stack[_tag_stack.size() - 2] == "detection_moulon"))
    }
  catch(pappso::PappsoException &exception_pappso)
    {
      _errorStr = QObject::tr(
                    "ERROR in PepXmlSaxHandler::endElement tag %1, "
                    "PAPPSO exception:\n%2")
                    .arg(qName)
                    .arg(exception_pappso.qwhat());
      return false;
    }
  catch(std::exception &exception_std)
    {
      _errorStr =
        QObject::tr(
          "ERROR in PepXmlSaxHandler::endElement tag %1, std exception:\n%2")
          .arg(qName)
          .arg(exception_std.what());
      return false;
    }

  _current_text.clear();
  _tag_stack.pop_back();

  return is_ok;
}

// <msms_pipeline_analysis date="2015-01-19T13:28:41"
// xmlns="http://regis-web.systemsbiology.net/pepXML"
// xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
// xsi:schemaLocation="http://sashimi.sourceforge.net/schema_revision/pepXML/pepXML_v117.xsd"
// summary_xml="/gorgone/pappso/abrf_2015/mzXML/JD_06232014_sample1-A.pep.xml">

bool
PepXmlSaxHandler::startElement_msms_pipeline_analysis(QXmlAttributes attributes)
{
  bool is_ok                = true;
  QString original_filename = attributes.value("summary_xml");
  return is_ok;
}

// <msms_run_summary
// base_name="/gorgone/pappso/abrf_2015/mzXML/JD_06232014_sample1-A"
// msManufacturer="Thermo Scientific" msModel="Q Exactive"
// raw_data_type="raw" raw_data=".mzXML">
bool
PepXmlSaxHandler::startElement_msms_run_summary(QXmlAttributes attributes)
{
  bool is_ok = true;
  QString old_file;
  if(!_current_complete_msrun_file_path.isEmpty())
    {
      old_file = _current_complete_msrun_file_path;
    }
  _current_complete_msrun_file_path = QString("%1%2")
                                        .arg(attributes.value("base_name"))
                                        .arg(attributes.value("raw_data"));
  if((!old_file.isEmpty()) &&
     (QFileInfo(_current_complete_msrun_file_path).baseName() !=
      QFileInfo(old_file).baseName()))
    {
      throw pappso::PappsoException(
        QObject::tr("ERROR reading pepxml file :\nX!TandemPipeline does not "
                    "support identification source files containing results "
                    "from multiple MS runs (%1 != %2)")
          .arg(old_file)
          .arg(_current_complete_msrun_file_path));
    }
  _sp_msrun.get()->setFileName(attributes.value("base_name"));
  _sp_msrun.get()->setSampleName(
    QFileInfo(_current_complete_msrun_file_path).baseName());
  return is_ok;
}

// <search_database
// local_path="/gorgone/pappso/abrf_2015/fasta/iPRG2015.fasta" type="AA"/>
//<search_database
// local_path="/gorgone/pappso/abrf_2015/fasta/iPRG2015.TargDecoy.fasta"
// database_name="SearchDB_1" size_in_db_entries="13256" type="NA"/>

bool
PepXmlSaxHandler::startElement_search_database(QXmlAttributes attributes)
{
  bool is_ok = true;
  FastaFile fasta_file(attributes.value("local_path"));
  if(!attributes.value("database_name").isEmpty())
    {
      fasta_file.setXmlId(attributes.value("database_name"));
    }
  _p_identification_data_source->addFastaFile(
    _p_project->getFastaFileStore().getInstance(fasta_file));
  return is_ok;
}

// <search_summary
// base_name="/gorgone/pappso/moulon/users/Olivier/Delphine_Pecqueur/mzXML/330_01_01_C3G3_150122_01"
// search_engine="X! Tandem (k-score)" precursor_mass_type="monoisotopic"
// fragment_mass_type="monoisotopic" search_id="1">
bool
PepXmlSaxHandler::startElement_search_summary(QXmlAttributes attributes)
{
  bool is_ok             = true;
  _current_search_engine = attributes.value("search_engine");
  return is_ok;
}

// <spectrum_query spectrum="JD_06232014_sample1-A.00005.00005.2"
// start_scan="5" end_scan="5" precursor_neutral_mass="819.347822"
// assumed_charge="2" index="1" retention_time_sec="1.4">
// <spectrum_query spectrum="JD_06232014_sample1-A.00006.00006.2"
// start_scan="6" end_scan="6" precursor_neutral_mass="870.405029296875"
// assumed_charge="2" index="1">
bool
PepXmlSaxHandler::startElement_spectrum_query(QXmlAttributes attributes)
{
  bool is_ok           = true;
  QString spectrum_ref = attributes.value("spectrum");
  if(_current_complete_msrun_file_path.isEmpty())
    {
      _sp_msrun.get()->setFileName(QFileInfo(spectrum_ref).baseName());
    }
  unsigned int start_scan = attributes.value("start_scan").toUInt();
  unsigned int end_scan   = attributes.value("end_scan").toUInt();
  if(start_scan != end_scan)
    {
      throw pappso::PappsoException(
        QObject::tr("ERROR reading pepxml file :\nunable to read search "
                    "results from '%1' as start %2 and end %3 scans are "
                    "different")
          .arg(_current_search_engine)
          .arg(start_scan)
          .arg(end_scan));
    }
  _scan           = start_scan;
  _current_charge = attributes.value("assumed_charge").toUInt();
  if(attributes.value("retention_time_sec").isEmpty())
    {
      QString message =
        QObject::tr(
          "ERROR reading pepxml file :\n"
          "unable to read search results from '%1' as retention time "
          "is not given in spectrum_query elements")
          .arg(_current_search_engine);
      qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__ << " "
               << message;
      // throw new MSMSException(message);

      _current_retention_time = 0;
    }
  else
    {
      _current_retention_time =
        attributes.value("retention_time_sec").toDouble();
    }
  _current_precursor_neutral_mass =
    attributes.value("precursor_neutral_mass").toDouble();
  return is_ok;
}

//<alternative_protein protein="sp|P46784|RS10B_YEAST" protein_descr="40S
//       ribosomal protein S10-B OS=Saccharomyces cerevisiae (strain ATCC 204508
//
// S288c) GN=RPS10B PE=1 SV=1" num_tol_term="2" peptide_prev_aa="K"
// peptide_next_aa="N"/>
bool
PepXmlSaxHandler::startElement_alternative_protein(QXmlAttributes attributes)
{
  bool is_ok                  = true;
  ProteinXtpSp sp_xtp_protein = ProteinXtp().makeProteinXtpSp();
  sp_xtp_protein.get()->setAccession(attributes.value("protein"));
  if(attributes.value("protein_descr").isEmpty())
    {
      sp_xtp_protein.get()->setCompleteDescription(attributes.value("protein"));
    }
  else
    {
      sp_xtp_protein.get()->setDescription(attributes.value("protein_descr"));
    }
  sp_xtp_protein.get()->setFastaFileP(
    _p_identification_data_source->getFastaFileList()[0].get());

  sp_xtp_protein = _p_project->getProteinStore().getInstance(sp_xtp_protein);

  _p_protein_match_list.push_back(
    _p_identification_group->getProteinMatchInstance(
      sp_xtp_protein.get()->getAccession()));

  _p_protein_match_list.back()->setProteinXtpSp(sp_xtp_protein);

  _p_protein_match_list.back()->setChecked(true);
  return is_ok;
}

// <search_hit hit_rank="5" peptide="MKDFSTK" peptide_prev_aa="K"
// peptide_next_aa="R" protein="sp|P34218|SAS3_YEAST" num_tot_proteins="1"
// num_matched_ions="2" tot_num_ions="12" calc_neutral_pep_mass="871.410939"
// massdiff="-1.006264" num_tol_term="2" num_missed_cleavages="1"
// num_matched_peptides="190">
bool
PepXmlSaxHandler::startElement_search_hit(QXmlAttributes attributes)
{
  bool is_ok = true;
  _p_protein_match_list.clear();
  //_current_protein.setAccession(attributes.value("protein"));
  ProteinXtpSp sp_xtp_protein = ProteinXtp().makeProteinXtpSp();
  sp_xtp_protein.get()->setAccession(attributes.value("protein"));
  if(attributes.value("protein_descr").isEmpty())
    {
      sp_xtp_protein.get()->setCompleteDescription(attributes.value("protein"));
    }
  else
    {
      sp_xtp_protein.get()->setDescription(attributes.value("protein_descr"));
    }
  sp_xtp_protein.get()->setFastaFileP(
    _p_identification_data_source->getFastaFileList()[0].get());

  sp_xtp_protein = _p_project->getProteinStore().getInstance(sp_xtp_protein);

  _p_protein_match_list.push_back(
    _p_identification_group->getProteinMatchInstance(
      sp_xtp_protein.get()->getAccession()));

  _p_protein_match_list[0]->setProteinXtpSp(sp_xtp_protein);

  _p_protein_match_list[0]->setChecked(true);

  _current_peptide_sp =
    PeptideXtp(attributes.value("peptide").simplified()).makePeptideXtpSp();


  _p_peptide_evidence = new PeptideEvidence(_sp_msrun.get(), _scan);

  _p_peptide_evidence->setRetentionTime(_current_retention_time);
  _p_peptide_evidence->setCharge(_current_charge);

  /* pappso::pappso_double xtandem_mhtheoretical =
     attributes.value("mh").simplified().toDouble();
   pappso::pappso_double xtandem_delta =
     attributes.value("delta").simplified().toDouble();
 */
  // delta – the spectrum mh minus the calculated mh

  // exp mass computed from X!Tandem mh :

  /*pappso::pappso_double _mass_obser =
    attributes.value("calc_neutral_pep_mass").toDouble() +
    attributes.value("massdiff").toDouble();
*/

  //_p_peptide_evidence->setExperimentalMass(_mass_obser);
  _p_peptide_evidence->setExperimentalMass(_current_precursor_neutral_mass);

  _p_peptide_evidence->setIdentificationDataSource(
    _p_identification_data_source);

  IdentificationEngine search_engine = IdentificationEngine::unknown;
  if(_current_search_engine == "X! Tandem (k-score)")
    {
      // search_engine="X! Tandem (k-score)"
      search_engine = IdentificationEngine::XTandem;
    }

  else if(_current_search_engine == "X! Tandem")
    {
      // files coming from msfragger
      search_engine = IdentificationEngine::XTandem;
    }

  else if(_current_search_engine == "OMSSA")
    {
      search_engine = IdentificationEngine::OMSSA;
    }
  else if(_current_search_engine == "Comet")
    {
      search_engine = IdentificationEngine::Comet;
    }
  else if(_current_search_engine == "MS-GF+")
    {
      search_engine = IdentificationEngine::MSGFplus;
    }

  if(search_engine == IdentificationEngine::unknown)
    {
      throw pappso::PappsoException(
        QObject::tr(
          "ERROR reading pepxml file :\nsearch engine %1 is not known.")
          .arg(_current_search_engine));
    }

  _p_peptide_evidence->setIdentificationEngine(search_engine);
  _p_peptide_evidence->setChecked(true);

  return is_ok;
}

bool
PepXmlSaxHandler::endElement_search_hit()
{

  bool is_ok = true;
  _current_peptide_sp =
    _p_project->getPeptideStore().getInstance(_current_peptide_sp);

  _p_peptide_evidence->setPeptideXtpSp(_current_peptide_sp);

  _current_peptide_match.setPeptideEvidenceSp(
    _p_peptide_evidence->getIdentificationDataSource()
      ->getPeptideEvidenceStore()
      .getInstance(_p_peptide_evidence));
  if(_p_protein_match_list.size() == 0)
    {
      throw pappso::PappsoException(
        QObject::tr("ERROR "
                    "PepXmlSaxHandler::endElement_search_hit:\n_p_protein_"
                    "match_list.size() == 0"));
    }

  for(auto &protein_match : _p_protein_match_list)
    {
      protein_match->addPeptideMatch(_current_peptide_match);
    }

  delete _p_peptide_evidence;
  return is_ok;
}

// <peptideprophet_result probability="0.0245"
// all_ntt_prob="(0.0000,0.0093,0.0245)">
bool
PepXmlSaxHandler::startElement_peptideprophet_result(QXmlAttributes attributes)
{
  bool is_ok = true;
  _p_peptide_evidence->setParam(
    PeptideEvidenceParam::peptide_prophet_probability,
    attributes.value("probability").toDouble());
  return is_ok;
}

// <interprophet_result probability="0.00886064"
// all_ntt_prob="(0,0.0033303,0.00886064)">
bool
PepXmlSaxHandler::startElement_interprophet_result(QXmlAttributes attributes)
{
  bool is_ok = true;
  _p_peptide_evidence->setParam(
    PeptideEvidenceParam::peptide_inter_prophet_probability,
    attributes.value("probability").toDouble());
  return is_ok;
}

/*
 * <search_score name="xcorr" value="0.528"/> <search_score name="deltacn"
 * value="0.059"/> <search_score name="deltacnstar" value="0.000"/>
 * <search_score name="spscore" value="29.4"/> <search_score name="sprank"
 * value="2"/> <search_score name="expect" value="1.00E+00"/>
 */
bool
PepXmlSaxHandler::startElement_search_score(QXmlAttributes attributes)
{
  bool is_ok       = true;
  QString name     = attributes.value("name");
  QString valueStr = attributes.value("value");
  if(!valueStr.isEmpty())
    {
      if(name == "expect")
        {
          _p_peptide_evidence->setEvalue(valueStr.simplified().toDouble());
        }
      else if(name == "EValue")
        {
          _p_peptide_evidence->setEvalue(valueStr.simplified().toDouble());
        }

      IdentificationEngine identification_engine =
        _p_peptide_evidence->getIdentificationEngine();
      if(identification_engine == IdentificationEngine::OMSSA)
        {
          if(name == "pvalue")
            {
              _p_peptide_evidence->setParam(PeptideEvidenceParam::omssa_pvalue,
                                            valueStr.simplified().toDouble());
            }
          else if(name == "expect")
            {
              _p_peptide_evidence->setParam(PeptideEvidenceParam::omssa_evalue,
                                            valueStr.simplified().toDouble());
            }
        }
      else if(identification_engine == IdentificationEngine::XTandem)
        {
          if(name == "hyperscore")
            {
              _p_peptide_evidence->setParam(
                PeptideEvidenceParam::tandem_hyperscore,
                QVariant(valueStr.simplified().toDouble()));
            }
        }
      else if(identification_engine == IdentificationEngine::Comet)
        {
          if(name == "xcorr")
            {
              _p_peptide_evidence->setParam(
                PeptideEvidenceParam::comet_xcorr,
                QVariant(valueStr.simplified().toDouble()));
            }
          else if(name == "deltacn")
            {
              _p_peptide_evidence->setParam(
                PeptideEvidenceParam::comet_deltacn,
                QVariant(valueStr.simplified().toDouble()));
            }
          else if(name == "deltacnstar")
            {
              _p_peptide_evidence->setParam(
                PeptideEvidenceParam::comet_deltacnstar,
                QVariant(valueStr.simplified().toDouble()));
            }
          else if(name == "spscore")
            {
              _p_peptide_evidence->setParam(
                PeptideEvidenceParam::comet_spscore,
                QVariant(valueStr.simplified().toDouble()));
            }
          else if(name == "sprank")
            {
              _p_peptide_evidence->setParam(
                PeptideEvidenceParam::comet_sprank,
                QVariant(valueStr.simplified().toInt()));
            }
          else if(name == "expect")
            {
              _p_peptide_evidence->setParam(
                PeptideEvidenceParam::comet_expectation_value,
                QVariant(valueStr.simplified().toDouble()));
              _p_peptide_evidence->setEvalue(valueStr.simplified().toDouble());
            }
        }
      else if(identification_engine == IdentificationEngine::MSGFplus)
        {
          if(name == "raw")
            {
              _p_peptide_evidence->setParam(
                PeptideEvidenceParam::msgfplus_raw,
                QVariant(valueStr.simplified().toDouble()));
            }
          else if(name == "SpecEValue")
            {
              _p_peptide_evidence->setParam(
                PeptideEvidenceParam::msgfplus_SpecEValue,
                QVariant(valueStr.simplified().toDouble()));
            }
          else if(name == "EValue")
            {
              _p_peptide_evidence->setParam(
                PeptideEvidenceParam::msgfplus_EValue,
                QVariant(valueStr.simplified().toDouble()));
              _p_peptide_evidence->setEvalue(valueStr.simplified().toDouble());
            }

          else if(name == "denovo")
            {
              _p_peptide_evidence->setParam(
                PeptideEvidenceParam::msgfplus_denovo,
                QVariant(valueStr.simplified().toDouble()));
            }
          else if(name == "IsotopeError")
            {
              _p_peptide_evidence->setParam(
                PeptideEvidenceParam::msgfplus_isotope_error,
                QVariant(valueStr.simplified().toInt()));
            }
        }
    }
  return is_ok;
}


// <modification_info mod_nterm_mass="43.018389" modified_peptide="SQRDCR">
bool
PepXmlSaxHandler::startElement_modification_info(QXmlAttributes attributes)
{
  bool is_ok = true;

  if(!attributes.value("mod_nterm_mass").isEmpty())
    {
      pappso::AaModificationP modif =
        Utils::guessAaModificationPbyMonoisotopicMassDelta(
          attributes.value("mod_nterm_mass").toDouble());

      _current_peptide_sp.get()->addAaModification(modif, 0);
    }
  return is_ok;
}
// <modification_info modified_peptide="SQRDCR"> <mod_aminoacid_mass
// position="5" mass="160.030649"/> </modification_info>
bool
PepXmlSaxHandler::startElement_mod_aminoacid_mass(QXmlAttributes attributes)
{
  bool is_ok            = true;
  double mass           = attributes.value("mass").toDouble();
  unsigned int position = attributes.value("position").toUInt() - 1;
  const pappso::Aa aa(
    _current_peptide_sp.get()->getSequence()[position].toLatin1());
  double mass_modif = mass - aa.getMass();

  pappso::AaModificationP modif =
    Utils::guessAaModificationPbyMonoisotopicMassDelta(mass_modif);

  _current_peptide_sp.get()->addAaModification(modif, position);
  return is_ok;
}


bool
PepXmlSaxHandler::endElement_modification_info()
{
  bool is_ok = true;
  return is_ok;
}


bool
PepXmlSaxHandler::error(const QXmlParseException &exception)
{
  _errorStr = QObject::tr(
                "Parse error at line %1, column %2 :\n"
                "%3")
                .arg(exception.lineNumber())
                .arg(exception.columnNumber())
                .arg(exception.message());

  return false;
}


bool
PepXmlSaxHandler::fatalError(const QXmlParseException &exception)
{
  _errorStr = QObject::tr(
                "Parse error at line %1, column %2 :\n"
                "%3")
                .arg(exception.lineNumber())
                .arg(exception.columnNumber())
                .arg(exception.message());
  return false;
}

QString
PepXmlSaxHandler::errorString() const
{
  return _errorStr;
}


bool
PepXmlSaxHandler::endDocument()
{
  return true;
}

bool
PepXmlSaxHandler::startDocument()
{
  return true;
}

bool
PepXmlSaxHandler::characters(const QString &str)
{
  _current_text += str;
  return true;
}
