/**
 * \file src/input/mzidentml/mzidentmlreader.cpp
 * \date 24/11/2022
 * \author Olivier Langella
 * \brief new method to read mzIdentML XML files
 */


/*******************************************************************************
 * 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 "mzidentmlreader.h"
#include <pappsomspp/core/utils.h>
#include <pappsomspp/core/psm/deepprot/deepprotenum.h>


#include <pappsomspp/core/exception/exceptionnotfound.h>
#include <pappsomspp/core/exception/exceptioninterrupted.h>
#include <pappsomspp/core/exception/exceptionnotrecognized.h>
#include "../../utils/utils.h"
#include "msrunlocationreader.h"
#include <QDebug>


QString
MzIdentMlReader::CvParam::toString() const
{
  return QString("%1 %2 %3 %4").arg(cvRef).arg(accession).arg(name).arg(value);
}


QString
MzIdentMlReader::UserParam::toString() const
{
  return QString("%1 %2").arg(name).arg(value);
}


MzIdentMlReader::MzIdentMlReader(pappso::UiMonitorInterface *p_monitor,
                                 Project *p_project,
                                 const QFileInfo &mzident_file)
{

  mp_monitor = p_monitor;
  mp_project = p_project;
  qDebug() << mzident_file.absoluteFilePath() << "'";
  m_identificationEngine = IdentificationEngine::unknown;

  MsRunLocationReader msrun_location_reader;

  if(msrun_location_reader.readFile(mzident_file.absoluteFilePath()))
    {
    }
  else
    {
      qDebug() << msrun_location_reader.errorString();
      if(msrun_location_reader.errorString().endsWith("Not an MzIdentML file"))
        {
          throw pappso::ExceptionNotRecognized(
            QObject::tr("Error reading %1 not mzIdentML file :\n %2")
              .arg(mzident_file.absoluteFilePath())
              .arg(msrun_location_reader.errorString()));
        }
      else
        {
          throw pappso::PappsoException(
            QObject::tr("Error reading %1 mzIdentML file :\n %2")
              .arg(mzident_file.absoluteFilePath())
              .arg(msrun_location_reader.errorString()));
        }
    }

  const std::vector<MsRunLocationReader::SpectraData> &spectra_data_list =
    msrun_location_reader.getSpectraDataList();

  std::vector<IdentificationGroup *> identification_list =
    p_project->getIdentificationGroupList();
  if(p_project->getProjectMode() == ProjectMode::combined)
    {
      IdentificationGroup *identification_group_p = nullptr;
      if(identification_list.size() == 0)
        {
          identification_group_p = p_project->newIdentificationGroup();
        }
      else
        {
          identification_group_p = identification_list[0];
        }

      for(auto &spectra_data : spectra_data_list)
        {
          m_mapSpectraDataId2IdentificationGroupPtr.insert(
            {spectra_data.id, identification_group_p});
        }
    }
  else
    {

      for(auto &spectra_data : spectra_data_list)
        {
          IdentificationGroup *identification_group_p = nullptr;
          for(IdentificationGroup *identification_p_flist : identification_list)
            {
              if(identification_p_flist->containSample(spectra_data.name))
                {
                  identification_group_p = identification_p_flist;
                  break;
                }
            }
          if(identification_group_p == nullptr)
            {
              identification_group_p = p_project->newIdentificationGroup();
            }

          m_mapSpectraDataId2IdentificationGroupPtr.insert(
            {spectra_data.id, identification_group_p});
        }
    }


  std::size_t i = p_project->getIdentificationDataSourceStore()
                    .getIdentificationDataSourceList()
                    .size();
  for(auto &identGroupPair : m_mapSpectraDataId2IdentificationGroupPtr)
    {
      QString spectra_id = identGroupPair.first;
      auto it            = std::find_if(
        spectra_data_list.begin(),
        spectra_data_list.end(),
        [spectra_id](const MsRunLocationReader::SpectraData &spectra_data) {
          return (spectra_data.id == spectra_id);
        });
      if(it == spectra_data_list.end())
        {
          throw pappso::PappsoException(
            QObject::tr(
              "Error reading %1 mzIdentML file :\n spectra id %2 not found")
              .arg(mzident_file.absoluteFilePath())
              .arg(spectra_id));
        }
      MsRunSp msrun_sp = p_project->getMsRunStore().getInstance(it->location);
      auto mzident_identification_source_sp =
        p_project->getIdentificationDataSourceStore()
          .buildIdentificationMzIdentMlFileSp(mzident_file, i, msrun_sp);
      identGroupPair.second->addIdentificationDataSourceP(
        mzident_identification_source_sp.get());
      i += 1;
      m_mapSpectraDataId2IdentificationMzIdentMlFileSp.insert(
        {identGroupPair.first, mzident_identification_source_sp});
    }


  if(!readFile(mzident_file.absoluteFilePath()))
    {

      if(msrun_location_reader.errorString() == "Not an MzIdentML file")
        {
          throw pappso::ExceptionNotRecognized(
            QObject::tr("Error reading %1 not mzIdentML file :\n %2")
              .arg(mzident_file.absoluteFilePath())
              .arg(msrun_location_reader.errorString()));
        }
      else
        {
          throw pappso::PappsoException(
            QObject::tr("Error reading %1 mzIdentML file :\n %2")
              .arg(mzident_file.absoluteFilePath())
              .arg(errorString()));
        }
    }

  for(auto &pair_mzident_source :
      m_mapSpectraDataId2IdentificationMzIdentMlFileSp)
    {
      pair_mzident_source.second.get()->setIdentificationEngine(
        getIdentificationEngine());
      pair_mzident_source.second.get()->setIdentificationEngineVersion(
        getIdentificationEngineVersion());
    }
}

MzIdentMlReader::~MzIdentMlReader()
{
}


IdentificationEngine
MzIdentMlReader::getIdentificationEngine() const
{
  if(m_IdentificationEngineMap.size() == 1)
    {
      return m_IdentificationEngineMap.begin()->second;
    }
  if(m_IdentificationEngineMap.size() == 0)
    {
      return IdentificationEngine::unknown;
    }
  else
    {
      throw pappso::PappsoException(
        "Unable to read MzIdentML file containing multiple identification "
        "engines results");
    }
}


void
MzIdentMlReader::readStream()
{
  // mp_monitor->setStatus("reading X!Tandem result file");
  if(m_qxmlStreamReader.readNextStartElement())
    {
      if(m_qxmlStreamReader.name().toString() == "MzIdentML")
        {
          while(m_qxmlStreamReader.readNextStartElement())
            {
              // cvList
              if(m_qxmlStreamReader.name().toString() == "cvList")
                {
                  m_qxmlStreamReader.skipCurrentElement();
                }
              // AnalysisSoftwareList
              else if(m_qxmlStreamReader.name().toString() ==
                      "AnalysisSoftwareList")
                {
                  while(readAnalysisSoftware())
                    {
                    }
                } // Provider
              else if(m_qxmlStreamReader.name().toString() == "Provider")
                {
                  m_qxmlStreamReader.skipCurrentElement();
                }
              // AuditCollection
              else if(m_qxmlStreamReader.name().toString() == "AuditCollection")
                {
                  m_qxmlStreamReader.skipCurrentElement();
                }
              // SequenceCollection
              else if(m_qxmlStreamReader.name().toString() ==
                      "SequenceCollection")
                {
                  while(readSequenceCollectionItem())
                    {
                    }
                }
              // AnalysisCollection
              else if(m_qxmlStreamReader.name().toString() ==
                      "AnalysisCollection")
                {
                  while(readAnalysisCollectionItem())
                    {
                    }
                }
              // AnalysisProtocolCollection
              else if(m_qxmlStreamReader.name().toString() ==
                      "AnalysisProtocolCollection")
                {
                  m_qxmlStreamReader.skipCurrentElement();
                }

              // DataCollection
              else if(m_qxmlStreamReader.name().toString() == "DataCollection")
                {
                  while(readDataCollectionItem())
                    {
                    }
                }
              else if(m_qxmlStreamReader.name().toString() ==
                      "BibliographicReference")
                {
                  m_qxmlStreamReader.skipCurrentElement();
                }

              else
                {
                  m_qxmlStreamReader.raiseError(
                    QObject::tr("element %1 not implemented")
                      .arg(m_qxmlStreamReader.name()));
                  m_qxmlStreamReader.skipCurrentElement();
                }
            }
        }
      else
        {
          m_qxmlStreamReader.raiseError(
            QObject::tr("Not an MzIdentML input file"));
          m_qxmlStreamReader.skipCurrentElement();
        }
    }
}

bool
MzIdentMlReader::readAnalysisSoftware()
{


  /** @brief stores the current analysis software id
   */
  QString analysisSoftwareId;


  /** @brief tells if the software name has been found and is handled by the
   * parser
   */
  IdentificationEngine analysisSotwareNameFound = IdentificationEngine::unknown;
  if(m_qxmlStreamReader.readNextStartElement())
    {
      if(m_qxmlStreamReader.name().toString() == "AnalysisSoftware")
        {

          // <AnalysisSoftware version="0.0.9" name="DeepProt" id="as1">


          analysisSoftwareId =
            m_qxmlStreamReader.attributes().value("id").toString();
          QString software_name =
            m_qxmlStreamReader.attributes().value("name").toString();


          m_analysisSoftwareVersion =
            m_qxmlStreamReader.attributes().value("version").toString();

          if(software_name == "SpecOMS")
            {
              analysisSotwareNameFound = IdentificationEngine::SpecOMS;

              m_qxmlStreamReader.skipCurrentElement();
            }
          else if(software_name == "DeepProt")
            {
              analysisSotwareNameFound = IdentificationEngine::SpecOMS;

              m_qxmlStreamReader.skipCurrentElement();
            }
          else
            {
              while(m_qxmlStreamReader.readNextStartElement())
                {
                  if(m_qxmlStreamReader.name().toString() == "SoftwareName")
                    {
                      while(m_qxmlStreamReader.readNextStartElement())
                        {
                          if(m_qxmlStreamReader.name().toString() == "cvParam")
                            {
                              CvParam cv_param = readCvParam();
                              if(cv_param.accession == "MS:1001476")
                                {
                                  analysisSotwareNameFound =
                                    IdentificationEngine::XTandem;
                                }
                              else if(cv_param.accession == "MS:1002048")
                                {
                                  analysisSotwareNameFound =
                                    IdentificationEngine::MSGFplus;
                                }
                              else if(cv_param.accession == "MS:1001946")
                                {
                                  analysisSotwareNameFound =
                                    IdentificationEngine::PEAKS_Studio;
                                }
                            }
                        }
                    }
                  else
                    {
                      m_qxmlStreamReader.skipCurrentElement();
                    }
                }
            }


          switch(analysisSotwareNameFound)
            {
              case IdentificationEngine::SpecOMS:
                break;
              case IdentificationEngine::MSGFplus:
                break;
              case IdentificationEngine::XTandem:
                break;
              case IdentificationEngine::PEAKS_Studio:
                break;

              default:
                m_qxmlStreamReader.raiseError(
                  QObject::tr(
                    "identification results from %1 are not supported yet, "
                    "Please contact "
                    "the PAPPSO team.")
                    .arg(software_name));
                return false;
            }


          auto it = m_IdentificationEngineMap.insert(
            std::pair<QString, IdentificationEngine>(analysisSoftwareId,
                                                     analysisSotwareNameFound));

          if(it.second == false)
            {
              it.first->second = analysisSotwareNameFound;
            }
        }
      else
        {
          m_qxmlStreamReader.raiseError(
            QObject::tr("Not an MzIdentML input file"));
          m_qxmlStreamReader.skipCurrentElement();
          return false;
        }
      return true;
    }
  return false;
}

bool
MzIdentMlReader::readSequenceCollectionItem()
{
  if(m_qxmlStreamReader.readNextStartElement())
    {
      if(m_qxmlStreamReader.name().toString() == "DBSequence")
        {
          readDBSequence();
        }
      else if(m_qxmlStreamReader.name().toString() == "Peptide")
        {
          readPeptide();
        }
      // PeptideEvidence
      else if(m_qxmlStreamReader.name().toString() == "PeptideEvidence")
        {
          readPeptideEvidence();
        }
      else
        {
          m_qxmlStreamReader.raiseError(
            QObject::tr("Error in MzIdentML input file, %1 no DBSequence")
              .arg(m_qxmlStreamReader.name()));
          m_qxmlStreamReader.skipCurrentElement();
          return false;
        }
      return true;
    }
  return false;
}

void
MzIdentMlReader::readDBSequence()
{

  // attributes.value("base_name")
  // ProteinXtpSp sp_xtp_protein = _current_protein.makeProteinXtpSp();
  ProteinXtpSp protein = ProteinXtp().makeProteinXtpSp();
  protein.get()->setAccession(
    m_qxmlStreamReader.attributes().value("accession").toString());

  protein = mp_project->getProteinStore().getInstance(protein);


  // we have only an xml id for the database ref, no file location
  //  so register search database for proein later
  auto it_database2protein_list = m_searchDatabase_ref2proteinList.insert(
    std::pair<QString, std::vector<ProteinXtpSp>>(
      m_qxmlStreamReader.attributes().value("searchDatabase_ref").toString(),
      std::vector<ProteinXtpSp>()));
  it_database2protein_list.first->second.push_back(protein);
  /*
  FastaFileSp fastaFile =
    mp_project->getFastaFileStore().getInstance(FastaFile(getFastaLocation(
      m_qxmlStreamReader.attributes().value("searchDatabase_ref").toString())));

  mp_identificationDataSource->addFastaFile(fastaFile);
  */


  m_ProteinXmlIdMap.insert(std::pair<QString, ProteinXtpSp>(
    m_qxmlStreamReader.attributes().value("id").toString(), protein));

  while(m_qxmlStreamReader.readNextStartElement())
    {
      if(m_qxmlStreamReader.name().toString() == "cvParam")
        {
          CvParam cv_param = readCvParam();

          if(cv_param.accession == "MS:1001088")
            {
              // protein description
              protein.get()->setDescription(cv_param.value);
            }
          else if(cv_param.accession == "MS:1001195")
            {
              // PSI-MS MS:1001195 decoy DB type reverse
              protein.get()->setIsDecoy(true);
            }
          else
            {

              m_qxmlStreamReader.raiseError(
                QObject::tr("unknown cvParam for DBSequence tag : %1")
                  .arg(cv_param.toString()));
            }
        }
      else if(m_qxmlStreamReader.name().toString() == "Seq")
        {

          protein.get()->setSequence(m_qxmlStreamReader.readElementText());
        }
      else
        {

          m_qxmlStreamReader.raiseError(
            QObject::tr("Error in MzIdentML/DBSequence unexpected %1 tag")
              .arg(m_qxmlStreamReader.name()));
          m_qxmlStreamReader.skipCurrentElement();
        }
    }

  // searchDatabase_ref="SearchDB_1"

  // protein.get()->setFastaFileP(fastaFile.get());
}

MzIdentMlReader::CvParam
MzIdentMlReader::readCvParam()
{
  CvParam cv_param;
  cv_param.cvRef = m_qxmlStreamReader.attributes().value("cvRef").toString();
  cv_param.accession =
    m_qxmlStreamReader.attributes().value("accession").toString();
  cv_param.name  = m_qxmlStreamReader.attributes().value("name").toString();
  cv_param.value = m_qxmlStreamReader.attributes().value("value").toString();
  cv_param.unitAccession =
    m_qxmlStreamReader.attributes().value("unitAccession").toString();
  cv_param.unitName =
    m_qxmlStreamReader.attributes().value("unitName").toString();
  cv_param.unitCvRef =
    m_qxmlStreamReader.attributes().value("unitCvRef").toString();
  m_qxmlStreamReader.skipCurrentElement();
  return cv_param;
}

MzIdentMlReader::UserParam
MzIdentMlReader::readUserParam()
{
  UserParam user_param;

  user_param.name  = m_qxmlStreamReader.attributes().value("name").toString();
  user_param.value = m_qxmlStreamReader.attributes().value("value").toString();
  m_qxmlStreamReader.skipCurrentElement();

  return user_param;
}


void
MzIdentMlReader::readPeptide()
{
  PeptideXtpSp peptide;

  QString xml_id = m_qxmlStreamReader.attributes().value("id").toString();

  // PeptideSequence
  if(m_qxmlStreamReader.readNextStartElement())
    {
      if(m_qxmlStreamReader.name().toString() == "PeptideSequence")
        {
          peptide =
            PeptideXtp(m_qxmlStreamReader.readElementText().simplified())
              .makePeptideXtpSp();
        }
      else
        {

          m_qxmlStreamReader.raiseError(
            QObject::tr("Error in MzIdentML/Peptide no PeptideSequence"));
        }
    }

  while(m_qxmlStreamReader.readNextStartElement())
    {
      if(m_qxmlStreamReader.name().toString() == "Modification")
        {
          //      <Modification monoisotopicMassDelta="-0.02682025649"
          //      location="1">
          //          <cvParam accession="-0.0268203" cvRef="PSI-MOD" name=""/>
          //      </Modification>

          Modification modification;
          modification.monoisotopicMassDelta = m_qxmlStreamReader.attributes()
                                                 .value("monoisotopicMassDelta")
                                                 .toDouble();
          modification.location =
            m_qxmlStreamReader.attributes().value("location").toUInt();
          bool is_cv_param = false;
          while(m_qxmlStreamReader.readNextStartElement())
            {
              if(m_qxmlStreamReader.name().toString() == "cvParam")
                {
                  is_cv_param          = true;
                  modification.cvParam = readCvParam();


                  // qDebug() << "startElement_aa ";
                  pappso::AaModificationP modif = nullptr;


                  qDebug() << modification.monoisotopicMassDelta;
                  if(modification.cvParam.accession != "")
                    {
                      qDebug() << modification.cvParam.accession;
                      if(modification.cvParam.accession.startsWith("UNIMOD:"))
                        {
                          modif =
                            pappso::Utils::translateAaModificationFromUnimod(
                              modification.cvParam.accession);
                        }
                      else
                        {
                          // hope it is psi mod:
                          if(modification.cvParam.accession.startsWith("MOD:"))
                            {
                              modif = pappso::AaModification::getInstance(
                                modification.cvParam.accession);
                            }
                          else if(modification.cvParam.accession ==
                                  "MS:1001460")
                            {
                              //[Term]
                              // id: MS:1001460
                              // name: unknown modification
                              // def: "This term should be given if the
                              // modification was unknown." [PSI:PI] is_a:
                              // MS:1001471 ! peptide modification details
                              modif = pappso::AaModification::
                                getInstanceCustomizedMod(
                                  modification.monoisotopicMassDelta);
                            }
                          else
                            {
                              qInfo() << "MzIdentMlSaxHandler::endElement_"
                                         "Modification unknown "
                                         "modification "
                                      << modification.cvParam.accession << " "
                                      << modification.cvParam.name;
                            }
                        }
                    }

                  if(modif == nullptr)
                    {
                      m_qxmlStreamReader.raiseError(
                        QObject::tr(
                          "Error in MzIdentML/Peptide/Modification/cvParam "
                          "modification accession %1 not found")
                          .arg(modification.cvParam.accession));
                      return;
                    }

                  if(modification.location == 0)
                    {
                      peptide.get()->addAaModification(modif, 0);
                    }
                  else
                    {
                      peptide.get()->addAaModification(
                        modif, modification.location - 1);
                    }
                }
              else
                {

                  m_qxmlStreamReader.raiseError(
                    QObject::tr("Error in MzIdentML/Peptide/Modification "
                                "unexpected %1 tag")
                      .arg(m_qxmlStreamReader.name()));
                }
            }

          if(is_cv_param == false)
            {
              pappso::AaModificationP modif_without_cvparam = nullptr;
              // no cv param element

              if(modification.location == 0)
                {
                  modif_without_cvparam =
                    pappso::Utils::guessAaModificationPbyMonoisotopicMassDelta(
                      peptide.get()->getAa(0).getAminoAcidChar(),
                      modification.monoisotopicMassDelta);
                  peptide.get()->addAaModification(modif_without_cvparam, 0);
                }
              else
                {
                  modif_without_cvparam =
                    pappso::Utils::guessAaModificationPbyMonoisotopicMassDelta(
                      peptide.get()
                        ->getAa(modification.location - 1)
                        .getAminoAcidChar(),
                      modification.monoisotopicMassDelta);
                  peptide.get()->addAaModification(modif_without_cvparam,
                                                   modification.location - 1);
                }
            }
        }

      else
        {

          m_qxmlStreamReader.raiseError(
            QObject::tr("Error in MzIdentML/Peptide unexpected %1 tag")
              .arg(m_qxmlStreamReader.name()));
        }
    }


  peptide = mp_project->getPeptideStore().getInstance(peptide);


  m_PeptideIdMap.insert(std::pair<QString, PeptideXtpSp>(xml_id, peptide));
}

bool
MzIdentMlReader::readPeptideEvidence()
{
  qDebug();
  MzidPeptideEvidence pe;

  auto itprot = m_ProteinXmlIdMap.find(
    m_qxmlStreamReader.attributes().value("dBSequence_ref").toString());
  if(itprot == m_ProteinXmlIdMap.end())
    {
      m_qxmlStreamReader.raiseError(QObject::tr("dBSequence_ref %1 not defined")
                                      .arg(m_qxmlStreamReader.attributes()
                                             .value("dBSequence_ref")
                                             .toString()));
      return false;
    }
  qDebug();
  pe.protein = itprot->second;


  auto itpep = m_PeptideIdMap.find(
    m_qxmlStreamReader.attributes().value("peptide_ref").toString());
  if(itpep == m_PeptideIdMap.end())
    {
      m_qxmlStreamReader.raiseError(
        QObject::tr("peptide_ref %1 not defined")
          .arg(
            m_qxmlStreamReader.attributes().value("peptide_ref").toString()));
      return false;
    }
  qDebug();
  pe.peptide = itpep->second;

  pe.start   = m_qxmlStreamReader.attributes().value("start").toUInt() - 1;
  pe.end     = m_qxmlStreamReader.attributes().value("end").toUInt() - 1;
  pe.isDecoy = false;
  if(m_qxmlStreamReader.attributes().value("isDecoy").toString() == "true")
    {
      pe.isDecoy = true;
    }
  qDebug();
  m_MzidPeptideEvidenceIdMap.insert(std::pair<QString, MzidPeptideEvidence>(
    m_qxmlStreamReader.attributes().value("id").toString(), pe));
  m_qxmlStreamReader.skipCurrentElement();
  qDebug();
  return true;
}

bool
MzIdentMlReader::readAnalysisCollectionItem()
{

  qDebug();
  if(m_qxmlStreamReader.readNextStartElement())
    {
      if(m_qxmlStreamReader.name().toString() == "SpectrumIdentification")
        {
          readSpectrumIdentification();
          qDebug();
        }
      else if(m_qxmlStreamReader.name().toString() == "ProteinDetection")
        {
          m_qxmlStreamReader.skipCurrentElement();
        }
      else
        {
          m_qxmlStreamReader.raiseError(
            QObject::tr(
              "Error in MzIdentML/AnalysisCollection, unexpected %1 t")
              .arg(m_qxmlStreamReader.name()));
          m_qxmlStreamReader.skipCurrentElement();
          return false;
        }
      return true;
    }
  return false;
}

void
MzIdentMlReader::readSpectrumIdentification()
{

  m_qxmlStreamReader.skipCurrentElement();
}

bool
MzIdentMlReader::readDataCollectionItem()
{

  if(m_qxmlStreamReader.readNextStartElement())
    {
      if(m_qxmlStreamReader.name().toString() == "Inputs")
        {
          readInputs();
        }

      // AnalysisData
      else if(m_qxmlStreamReader.name().toString() == "AnalysisData")
        {
          readAnalysisData();
        }
      else
        {
          m_qxmlStreamReader.raiseError(
            QObject::tr("Error in MzIdentML/DataCollection, unexpected %1 tag")
              .arg(m_qxmlStreamReader.name()));
          m_qxmlStreamReader.skipCurrentElement();
          return false;
        }
      return true;
    }
  return false;
}

void
MzIdentMlReader::readInputs()
{
  while(m_qxmlStreamReader.readNextStartElement())
    {
      if(m_qxmlStreamReader.name().toString() == "SearchDatabase")
        {
          readSearchDatabase();
        }
      else if(m_qxmlStreamReader.name().toString() == "SpectraData")
        {
          readSpectraData();
        }
      else if(m_qxmlStreamReader.name().toString() == "SourceFile")
        {
          m_qxmlStreamReader.skipCurrentElement();
        }

      else
        {
          m_qxmlStreamReader.raiseError(
            QObject::tr(
              "Error in MzIdentML/DataCollection/Inputs, unexpected %1 tag")
              .arg(m_qxmlStreamReader.name()));
          m_qxmlStreamReader.skipCurrentElement();
        }
    }
}


//<SearchDatabase numDatabaseSequences="136828"
// location="/home/thierry/test/MS-GF+/Genome_Z_mays_v5a_conta.fasta"
// id="SearchDB_1">
//      <FileFormat>
//        <cvParam cvRef="PSI-MS" accession="MS:1001348" name="FASTA format"/>
//      </FileFormat>
//      <DatabaseName>
//        <userParam name="Genome_Z_mays_v5a_conta.fasta"/>
//      </DatabaseName>
//    </SearchDatabase>
bool
MzIdentMlReader::readSearchDatabase()
{

  qDebug();
  QString id = m_qxmlStreamReader.attributes().value("id").toString();
  // auto itfasta = m_FastaFileIdMap.find(id);

  QString location =
    m_qxmlStreamReader.attributes().value("location").toString();

  if(location.isEmpty())
    {
      m_qxmlStreamReader.raiseError(
        QObject::tr("SearchDatabase id %1 location is empty").arg(id));
      return false;
    }


  FastaFileSp fastaFile =
    mp_project->getFastaFileStore().getInstance(FastaFile(location));

  for(auto &pair_source : m_mapSpectraDataId2IdentificationMzIdentMlFileSp)
    {
      pair_source.second->addFastaFile(fastaFile);
    }

  m_FastaFileIdMap.insert(std::pair<QString, FastaFileSp>(id, fastaFile));

  // recover each protein to set fasta file origin
  auto it_protein_list = m_searchDatabase_ref2proteinList.find(id);
  if(it_protein_list != m_searchDatabase_ref2proteinList.end())
    {
      for(ProteinXtpSp &protein : it_protein_list->second)
        {
          protein.get()->setFastaFileP(fastaFile.get());
        }
    }
  m_qxmlStreamReader.skipCurrentElement();
  qDebug();
  return true;
}

void
MzIdentMlReader::readSpectraData()
{

  qDebug();
  MsRunSp msrun = mp_project->getMsRunStore().getInstance(
    m_qxmlStreamReader.attributes().value("location").toString());


  // msrun.get()->setXmlId(attributes.value("id"));
  msrun.get()->setSampleName(
    m_qxmlStreamReader.attributes().value("name").toString());


  m_MsRunIdMap.insert(std::pair<QString, MsRunSp>(
    m_qxmlStreamReader.attributes().value("id").toString(), msrun));
  m_qxmlStreamReader.skipCurrentElement();
}

void
MzIdentMlReader::readAnalysisData()
{

  qDebug() << m_qxmlStreamReader.name();
  while(m_qxmlStreamReader.readNextStartElement())
    {
      qDebug() << m_qxmlStreamReader.name();
      if(m_qxmlStreamReader.name().toString() == "SpectrumIdentificationList")
        {
          while(m_qxmlStreamReader.readNextStartElement())
            {
              qDebug() << m_qxmlStreamReader.name();
              if(m_qxmlStreamReader.name().toString() ==
                 "SpectrumIdentificationResult")
                {
                  readSpectrumIdentificationResult();
                }
              else if(m_qxmlStreamReader.name().toString() ==
                      "FragmentationTable")
                {
                  m_qxmlStreamReader.skipCurrentElement();
                }
              else
                {
                  m_qxmlStreamReader.raiseError(
                    QObject::tr("Error in "
                                "MzIdentML/DataCollection/AnalysisData/"
                                "SpectrumIdentificationList, unexpected %1 tag")
                      .arg(m_qxmlStreamReader.name()));
                  m_qxmlStreamReader.skipCurrentElement();
                }
            }
        }
      else if(m_qxmlStreamReader.name().toString() == "ProteinDetectionList")
        {
          // ProteinDetectionList
          m_qxmlStreamReader.skipCurrentElement();
        }
      else
        {
          m_qxmlStreamReader.raiseError(
            QObject::tr("Error in MzIdentML/DataCollection/AnalysisData, "
                        "unexpected %1 tag")
              .arg(m_qxmlStreamReader.name()));
          m_qxmlStreamReader.skipCurrentElement();
        }
    }
}

void
MzIdentMlReader::readSpectrumIdentificationResult()
{
  qDebug() << m_qxmlStreamReader.name();
  SpectrumIdentificationResult spectrum_identification_result;
  spectrum_identification_result.cvParamList.clear();
  spectrum_identification_result.userParamList.clear();
  spectrum_identification_result.spectrumIdentificationItemList.clear();

  spectrum_identification_result.spectrumID =
    m_qxmlStreamReader.attributes().value("spectrumID").toString();
  spectrum_identification_result.id =
    m_qxmlStreamReader.attributes().value("id").toString();

  QString spectra_id =
    m_qxmlStreamReader.attributes().value("spectraData_ref").toString();
  auto itmzidentsource =
    m_mapSpectraDataId2IdentificationMzIdentMlFileSp.find(spectra_id);

  if(itmzidentsource == m_mapSpectraDataId2IdentificationMzIdentMlFileSp.end())
    {
      m_qxmlStreamReader.raiseError(
        QObject::tr("spectraData_ref %1 not defined in "
                    "m_mapSpectraDataId2IdentificationMzIdentMlFileSp")
          .arg(spectra_id));
    }
  spectrum_identification_result.mzident_source_sp = itmzidentsource->second;

  auto itidentgroup =
    m_mapSpectraDataId2IdentificationGroupPtr.find(spectra_id);

  if(itidentgroup == m_mapSpectraDataId2IdentificationGroupPtr.end())
    {
      m_qxmlStreamReader.raiseError(
        QObject::tr("spectraData_ref %1 not defined in "
                    "m_mapSpectraDataId2IdentificationGroupPtr")
          .arg(spectra_id));
    }
  spectrum_identification_result.identification_group_p = itidentgroup->second;

  qDebug() << m_qxmlStreamReader.name();

  while(m_qxmlStreamReader.readNextStartElement())
    {
      qDebug() << m_qxmlStreamReader.name();
      if(m_qxmlStreamReader.name() == QString("SpectrumIdentificationItem"))
        {
          qDebug();
          readSpectrumIdentificationItem(spectrum_identification_result);
        }
      else if(m_qxmlStreamReader.name() == QString("cvParam"))
        {
          CvParam cv_param = readCvParam();
          qDebug() << cv_param.toString();
          spectrum_identification_result.cvParamList.push_back(cv_param);
        }

      else if(m_qxmlStreamReader.name() == QString("userParam"))
        {
          UserParam user_param = readUserParam();
          qDebug() << user_param.toString();
        }
      else
        {
          m_qxmlStreamReader.raiseError(
            QObject::tr("Error in "
                        "MzIdentML/DataCollection/AnalysisData/"
                        "SpectrumIdentificationList/"
                        "SpectrumIdentificationResult, unexpected %1 tag")
              .arg(m_qxmlStreamReader.name()));
          // m_qxmlStreamReader.skipCurrentElement();
        }
    }

  if(m_qxmlStreamReader.hasError())
    return;
  qDebug() << m_qxmlStreamReader.name();

  // find scan number
  spectrum_identification_result.spectrumIndex   = 0;
  spectrum_identification_result.isSpectrumIndex = false;
  spectrum_identification_result.scanNum         = 0;
  spectrum_identification_result.retentionTime   = 0;

  // spectrumID="index=194"
  if(spectrum_identification_result.spectrumID.startsWith("index="))
    {
      bool is_ok = false;
      spectrum_identification_result.spectrumIndex =
        spectrum_identification_result.spectrumID.mid(6).toULongLong(&is_ok);
      spectrum_identification_result.isSpectrumIndex = true;
      if(!is_ok)
        {
          m_qxmlStreamReader.raiseError(
            QObject::tr("reading spectrum index failed in %1"
                        "SpectrumIdentificationResult id %2")
              .arg(spectrum_identification_result.spectrumID)
              .arg(spectrum_identification_result.id));
          return;
        }
    }

  //  <cvParam cvRef="PSI-MS" accession="MS:1001115" name="scan number(s)"
  //        value="16079"/>
  for(auto cvParam : spectrum_identification_result.cvParamList)
    {

      qDebug() << cvParam.toString();
      if(cvParam.accession == "MS:1001115")
        {
          spectrum_identification_result.scanNum = cvParam.value.toUInt();
        }
      else if(cvParam.accession == "MS:1003062")
        {
          spectrum_identification_result.isSpectrumIndex = true;
          spectrum_identification_result.spectrumIndex = cvParam.value.toUInt();
        }
      else if((cvParam.accession == "MS:1000016") ||
              (cvParam.accession == "MS:1000894"))
        {
          //[Term]
          // id: MS:1000894
          // name: retention time
          // def: "A time interval from the start of chromatography when an
          // analyte exits a chromatographic column." [PSI:MS]

          //            [Term]
          // id: MS:1000016
          // name: scan start time
          // def: "The time that an analyzer started a scan, relative to the
          // start of the MS run." [PSI:MS]


          spectrum_identification_result.retentionTime =
            cvParam.value.toDouble();
        }
    }
  if((spectrum_identification_result.scanNum == 0) &&
     (spectrum_identification_result.isSpectrumIndex == false))
    {
      m_qxmlStreamReader.raiseError(
        QObject::tr("scan number or spectrum index not found in "
                    "SpectrumIdentificationResult id %1")
          .arg(spectrum_identification_result.id));
    }


  if(spectrum_identification_result.retentionTime == 0)
    {
      m_qxmlStreamReader.raiseError(
        QObject::tr(
          "retention time not found in SpectrumIdentificationResult id %1")
          .arg(spectrum_identification_result.id));
    }


  for(auto spectrumIdentificationItem :
      spectrum_identification_result.spectrumIdentificationItemList)
    {
      processSpectrumIdentificationItem(spectrum_identification_result,
                                        spectrumIdentificationItem);
    }
}

void
MzIdentMlReader::readSpectrumIdentificationItem(
  MzIdentMlReader::SpectrumIdentificationResult &spectrum_identification_result)
{

  qDebug();
  spectrum_identification_result.spectrumIdentificationItemList.push_back(
    SpectrumIdentificationItem());
  spectrum_identification_result.spectrumIdentificationItemList.back()
    .mzidPeptideEvidenceList.clear();
  spectrum_identification_result.spectrumIdentificationItemList.back()
    .cvParamList.clear();
  spectrum_identification_result.spectrumIdentificationItemList.back()
    .userParamList.clear();

  spectrum_identification_result.spectrumIdentificationItemList.back()
    .chargeState =
    m_qxmlStreamReader.attributes().value("chargeState").toUInt();

  spectrum_identification_result.spectrumIdentificationItemList.back()
    .experimentalMassToCharge = m_qxmlStreamReader.attributes()
                                  .value("experimentalMassToCharge")
                                  .toDouble();


  auto itpeptide = m_PeptideIdMap.find(
    m_qxmlStreamReader.attributes().value("peptide_ref").toString());

  if(itpeptide == m_PeptideIdMap.end())
    {
      m_qxmlStreamReader.raiseError(
        QObject::tr("peptide_ref %1 not defined")
          .arg(m_qxmlStreamReader.attributes().value("peptide_ref")));
    }
  spectrum_identification_result.spectrumIdentificationItemList.back().peptide =
    itpeptide->second;


  while(m_qxmlStreamReader.readNextStartElement())
    {
      qDebug() << m_qxmlStreamReader.name();
      if(m_qxmlStreamReader.name() == QString("PeptideEvidenceRef"))
        {

          auto itpeptideEvidence =
            m_MzidPeptideEvidenceIdMap.find(m_qxmlStreamReader.attributes()
                                              .value("peptideEvidence_ref")
                                              .toString());

          if(itpeptideEvidence == m_MzidPeptideEvidenceIdMap.end())
            {
              m_qxmlStreamReader.raiseError(
                QObject::tr("peptideEvidence_ref %1 not defined")
                  .arg(m_qxmlStreamReader.attributes().value(
                    "peptideEvidence_ref")));
            }

          spectrum_identification_result.spectrumIdentificationItemList.back()
            .mzidPeptideEvidenceList.push_back(itpeptideEvidence->second);
          m_qxmlStreamReader.skipCurrentElement();
        }
      else if(m_qxmlStreamReader.name() == QString("cvParam"))
        {
          spectrum_identification_result.spectrumIdentificationItemList.back()
            .cvParamList.push_back(readCvParam());
          qDebug() << spectrum_identification_result
                        .spectrumIdentificationItemList.back()
                        .cvParamList.back()
                        .toString();
        }
      else if(m_qxmlStreamReader.name() == QString("userParam"))
        {
          UserParam user_param = readUserParam();
          spectrum_identification_result.spectrumIdentificationItemList.back()
            .userParamList.push_back(user_param);
          qDebug() << user_param.toString();
        }
      else
        {
          m_qxmlStreamReader.raiseError(
            QObject::tr("Error in "
                        "MzIdentML/DataCollection/AnalysisData/"
                        "SpectrumIdentificationList/"
                        "SpectrumIdentificationResult/"
                        "SpectrumIdentificationItem, unexpected %1 tag")
              .arg(m_qxmlStreamReader.name()));
        }
    }
  qDebug();
}


void
MzIdentMlReader::processSpectrumIdentificationItem(
  SpectrumIdentificationResult &spectrum_identification_result,
  const SpectrumIdentificationItem &spectrumIdentificationItem)
{


  qDebug();

  if(spectrum_identification_result.isSpectrumIndex)
    {
      spectrum_identification_result.scanNum =
        spectrum_identification_result.spectrumIndex;
    }
  PeptideEvidence peptide_evidence(
    spectrum_identification_result.mzident_source_sp.get()->getMsRunSp().get(),
    spectrum_identification_result.scanNum,
    spectrum_identification_result.isSpectrumIndex);
  peptide_evidence.setRetentionTime(
    spectrum_identification_result.retentionTime);
  peptide_evidence.setCharge(spectrumIdentificationItem.chargeState);
  peptide_evidence.setPeptideXtpSp(spectrumIdentificationItem.peptide);
  qDebug() << peptide_evidence.getPeptideXtpSp().get()->toAbsoluteString();
  peptide_evidence.setChecked(true);
  peptide_evidence.setIdentificationDataSource(
    spectrum_identification_result.mzident_source_sp.get());
  peptide_evidence.setIdentificationEngine(getIdentificationEngine());

  peptide_evidence.setExperimentalMassToCharge(
    spectrumIdentificationItem.experimentalMassToCharge);

  qDebug();

  //          <cvParam cvRef="PSI-MS" accession="MS:1002049"
  //          name="MS-GF:RawScore" value="356"/> <cvParam cvRef="PSI-MS"
  //          accession="MS:1002050" name="MS-GF:DeNovoScore" value="369"/>
  //          <cvParam cvRef="PSI-MS" accession="MS:1002052"
  //          name="MS-GF:SpecEValue" value="9.149361665076834E-40"/> <cvParam
  //          cvRef="PSI-MS" accession="MS:1002053" name="MS-GF:EValue"
  //          value="2.057944235338586E-32"/>
  //         <userParam name="IsotopeError" value="0"/>
  //         <userParam name="AssumedDissociationMethod" value="HCD"/>
  for(auto cvParam : spectrumIdentificationItem.cvParamList)
    {

      //<cvParam accession="MS:1002258" cvRef="PSI-MS" value="7"
      // name="Comet:matched ions"/>
      if(cvParam.accession == "MS:1002049")
        {
          // PSI-MS MS:1002049 MS-GF:RawScore 356
          peptide_evidence.setParam(PeptideEvidenceParam::msgfplus_raw,
                                    QVariant(cvParam.value.toUInt()));
        }
      else if(cvParam.accession == "MS:1002050")
        {
          // msgfplus_denovo     = 9,  ///< MS:1002050  "MS-GF de novo score."
          peptide_evidence.setParam(PeptideEvidenceParam::msgfplus_denovo,
                                    QVariant(cvParam.value.toUInt()));
        }

      else if(cvParam.accession == "MS:1002052")
        {
          peptide_evidence.setParam(PeptideEvidenceParam::msgfplus_SpecEValue,
                                    QVariant(cvParam.value.toDouble()));
        }

      else if(cvParam.accession == "MS:1002053")
        {
          // PSI-MS MS:1002053 MS-GF:EValue
          peptide_evidence.setParam(PeptideEvidenceParam::msgfplus_EValue,
                                    QVariant(cvParam.value.toDouble()));
        }

      else if(cvParam.accession == "MS:1002054")
        {
          // <cvParam cvRef="PSI-MS" accession="MS:1002054" name="MS-GF:QValue"
          // value="0.0"/>
          peptide_evidence.setParam(PeptideEvidenceParam::msgfplus_QValue,
                                    QVariant(cvParam.value.toDouble()));
        }
      else if(cvParam.accession == "MS:1002055")
        {
          //  <cvParam cvRef="PSI-MS" accession="MS:1002055"
          //  name="MS-GF:PepQValue" value="0.0"/>
          peptide_evidence.setParam(PeptideEvidenceParam::msgfplus_PepQValue,
                                    QVariant(cvParam.value.toDouble()));
        }
      else if(cvParam.accession == "MS:1001331")
        {
          // PSI-MS MS:1001331 tandem hyperscore
          peptide_evidence.setParam(PeptideEvidenceParam::tandem_hyperscore,
                                    QVariant(cvParam.value.toDouble()));
        }
      else if(cvParam.accession == "MS:1001330")
        {
          // PSI-MS MS:1001330 X!Tandem:expect
          peptide_evidence.setParam(
            PeptideEvidenceParam::tandem_expectation_value,
            QVariant(cvParam.value.toDouble()));
          peptide_evidence.setEvalue(cvParam.value.toDouble());
        }
      else if(cvParam.accession == "MS:1001950")
        {
          //<cvParam accession="MS:1001950" cvRef="PSI-MS" value="54.90"
          // name="PEAKS:peptideScore"/>

          peptide_evidence.setParam(PeptideEvidenceParam::peaks_peptide_score,
                                    QVariant(cvParam.value.toDouble()));
        }
      /*
      msgfplus_energy     = 10, ///< MS:1002051  "MS-GF energy score." [PSI:PI]
      msgfplus_SpecEValue = 11, ///< MS:1002052  "MS-GF spectral E-value."
      [PSI:PI] msgfplus_EValue     = 12, ///< MS:1002053  "MS-GF E-value."
      [PSI:PI] msgfplus_isotope_error = 13, ///< MS-GF isotope error
      comet_xcorr   = 14, ///< MS:1002252  "The Comet result 'XCorr'." [PSI:PI]
      comet_deltacn = 15, ///< MS:1002253  "The Comet result 'DeltaCn'."
      [PSI:PI] comet_deltacnstar = 16, ///< MS:1002254  "The Comet result
      'DeltaCnStar'." [PSI:PI] comet_spscore = 17, ///< MS:1002255  "The Comet
      result 'SpScore'." [PSI:PI] comet_sprank  = 18, ///< MS:1002256  "The
      Comet result 'SpRank'." [PSI:PI] comet_expectation_value = 19, ///<
      MS:1002257  "The Comet result 'Expectation value'." [PSI:PI]
        */
      else
        {
          m_qxmlStreamReader.raiseError(
            QObject::tr("cvParam %1 is not taken into account")
              .arg(cvParam.toString()));
        }
    }

  qDebug();
  for(auto userParam : spectrumIdentificationItem.userParamList)
    {
      if(userParam.name == "DeepProt:original_count")
        {
          // <userParam name="DeepProt:original_count" value="7"/>
          peptide_evidence.setParam(
            PeptideEvidenceParam::deepprot_original_count,
            QVariant(userParam.value.toUInt()));
        }
      else if(userParam.name == "DeepProt:fitted_count")
        {
          // <userParam name="DeepProt:fitted_count" value="7"/>
          peptide_evidence.setParam(PeptideEvidenceParam::deepprot_fitted_count,
                                    QVariant(userParam.value.toUInt()));
        }
      else if(userParam.name == "DeepProt:match_type")
        {
          peptide_evidence.setParam(
            PeptideEvidenceParam::deepprot_match_type,
            (std::uint8_t)pappso::DeepProtEnumStr::DeepProtMatchTypeFromString(
              userParam.value));
        }
      else if(userParam.name == "DeepProt:status")
        {
          peptide_evidence.setParam(
            PeptideEvidenceParam::deepprot_peptide_candidate_status,
            (std::uint8_t)
              pappso::DeepProtEnumStr::DeepProtPeptideCandidateStatusFromString(
                userParam.value));
        }
      else if(userParam.name == "DeepProt:mass_delta")
        {
          peptide_evidence.setParam(PeptideEvidenceParam::deepprot_mass_delta,
                                    QVariant(userParam.value.toDouble()));
        }
      else if(userParam.name == "DeepProt:delta_positions")
        {
          // DeepProt:delta_positions 4 5 6 7 8 9 10 11 12 13
          peptide_evidence.setParam(
            PeptideEvidenceParam::deepprot_delta_positions, userParam.value);
        }
      // <userParam name="IsotopeError" value="0"/>
      //<userParam name="AssumedDissociationMethod" value="HCD"/>
      /*
    else
      {
        m_errorStr = QObject::tr("userParam %1 is not taken into account")
                       .arg(userParam.toString());
        return false;
      }*/
    }


  qDebug();
  for(auto mz_peptide_evidence :
      spectrumIdentificationItem.mzidPeptideEvidenceList)
    {
      PeptideMatch peptide_match;
      peptide_match.setStart(mz_peptide_evidence.start);
      peptide_match.setPeptideEvidenceSp(
        spectrum_identification_result.mzident_source_sp.get()
          ->getPeptideEvidenceStore()
          .getInstance(&peptide_evidence));


      ProteinMatch *p_protein_match =
        spectrum_identification_result.identification_group_p
          ->getProteinMatchInstance(
            mz_peptide_evidence.protein.get()->getAccession());

      p_protein_match->setChecked(true);
      // qDebug() << "startElement_protein p_protein_match 3 " <<
      // _p_protein_match;
      p_protein_match->setProteinXtpSp(mz_peptide_evidence.protein);
      p_protein_match->addPeptideMatch(peptide_match);
    }
  qDebug();
}

const QString &
MzIdentMlReader::getIdentificationEngineVersion() const
{
  return m_analysisSoftwareVersion;
}
