/**
 * \file pappsomspp/msrun/private/pwizmsrunreader.cpp
 * \date 29/05/2018
 * \author Olivier Langella
 * \brief MSrun file reader base on proteowizard library
 */

/*******************************************************************************
 * Copyright (c) 2018 Olivier Langella <Olivier.Langella@u-psud.fr>.
 *
 * This file is part of the PAPPSOms++ library.
 *
 *     PAPPSOms++ 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.
 *
 *     PAPPSOms++ 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 PAPPSOms++.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Contributors:
 *     Olivier Langella <Olivier.Langella@u-psud.fr> - initial API and
 *implementation
 ******************************************************************************/


#include <QDebug>

#include "pwizmsrunreader.h"

#include <pwiz/data/msdata/DefaultReaderList.hpp>
#include "../../obo/obopsims.h"
#include "../../obo/filterobopsimodmap.h"


#include "pappsomspp/core/utils.h"
#include "pappsomspp/core/pappsoexception.h"
#include "pappsomspp/core/exception/exceptionnotfound.h"
#include "pappsomspp/core/exception/exceptionnotpossible.h"
#include "pappsomspp/config.h"

// int pwizMsRunReaderMetaTypeId =
// qRegisterMetaType<pappso::PwizMsRunReader>("pappso::PwizMsRunReader");


namespace pappso
{

QMutex PwizMsRunReader::m_mutex;

PwizMsRunReader::PwizMsRunReader(MsRunIdCstSPtr &msrun_id_csp) : MsRunReader(msrun_id_csp)
{
  // The initialization needs to be done immediately so that we get the pwiz
  // MsDataPtr corresponding to the right ms_run_id in the parameter. That
  // pointer will be set to msp_msData.

  initialize();
}


void
PwizMsRunReader::initialize()
{
  std::string file_name_std = Utils::toUtf8StandardString(mcsp_msRunId->getFileName());


  // Now actually search the useful MSDataPtr to the member variable.

  pwiz::msdata::DefaultReaderList defaultReaderList;

  std::vector<pwiz::msdata::MSDataPtr> msDataPtrVector;

  try
    {
      defaultReaderList.read(file_name_std, msDataPtrVector);
    }
  catch(std::exception &error)
    {
      qDebug() << QString("Failed to read the data from file %1")
                    .arg(QString::fromStdString(file_name_std));

      throw(PappsoException(QString("Error reading file %1 in PwizMsRunReader, for msrun %2:\n%3")
                              .arg(mcsp_msRunId->getFileName())
                              .arg(mcsp_msRunId.get()->toString())
                              .arg(error.what())));
    }

  qDebug() << "The number of runs is:" << msDataPtrVector.size()
           << "The number of spectra in first run is:"
           << msDataPtrVector.at(0)->run.spectrumListPtr->size();

  // Single-run file handling here.

  // Specific case of the MGF data format: we do not have a run id for that
  // kind of data. In this case there must be a single run!

  if(mcsp_msRunId->getRunId().isEmpty())
    {
      if(msDataPtrVector.size() != 1)
        throw(
          ExceptionNotPossible("For the kind of file at hand there can only be "
                               "one run in the file."));

      // At this point we know the single msDataPtr is the one we are looking
      // for.

      msp_msData = msDataPtrVector.front();
    }
  else
    {
      // Multi-run file handling here.
      for(const pwiz::msdata::MSDataPtr &msDataPtr : msDataPtrVector)
        {
          qDebug() << "msDataPtr->run.id=" << msDataPtr->run.id.c_str();
          qDebug() << "mcsp_msRunId->getRunId()=" << mcsp_msRunId->getRunId();
          qDebug() << "mcsp_msRunId->getXmlId()=" << mcsp_msRunId->getXmlId();
          qDebug() << "mcsp_msRunId->getSampleName()=" << mcsp_msRunId->getSampleName();

          if(msDataPtr->run.id == mcsp_msRunId->getRunId().toStdString())
            {
              msp_msData = msDataPtr;

              qDebug() << "Found the right MSDataPtr for run id.";

              break;
            }
        }
    }

  if(msp_msData == nullptr)
    {
      throw(ExceptionNotPossible(QString("Could not find a pwiz MSDataPtr "
                                         "matching the requested run id : %1")
                                   .arg(mcsp_msRunId.get()->toString())));
    }


  // check if this MS run can be used with scan numbers
  // MS:1000490 Agilent instrument model
  pwiz::cv::CVID native_id_format = pwiz::msdata::id::getDefaultNativeIDFormat(*msp_msData.get());

  // msp_msData.get()->getDefaultNativeIDFormat();

  if(native_id_format == pwiz::cv::CVID::MS_Thermo_nativeID_format)
    {
      m_hasScanNumbers = true;
    }
  else
    {
      m_hasScanNumbers = false;
    }

  if(mcsp_msRunId.get()->getMsDataFormat() == Enums::MsDataFormat::mzXML)
    {
      m_hasScanNumbers = true;
    }
}

PwizMsRunReader::~PwizMsRunReader()
{
}

const pwiz::msdata::MSDataPtr

PwizMsRunReader::getMsDataPtr() const
{
  return msp_msData;
}

const OboPsiModTerm
PwizMsRunReader::getOboPsiModTermNativeIDFormat() const
{

  OboPsiModTerm term;

  term.setAccession("MS:1000824");
  term.m_name = "no nativeID format";
  term.m_definition =
    "No nativeID format indicates that the file tagged with this term does "
    "not "
    "contain spectra that can have a nativeID format.";


  pwiz::cv::CVID cvid = pwiz::msdata::id::getDefaultNativeIDFormat(*(msp_msData.get()));

  switch(cvid)
    {
      case pwiz::cv::MS_Thermo_nativeID_format:
        term.setAccession("MS:1000768");
        term.m_name = "Thermo nativeID format";
        term.m_definition =
          "Native format defined by controllerType=xsd:nonNegativeInteger "
          "controllerNumber=xsd:positiveInteger scan=xsd:positiveInteger.";
        break;
      default:
        break;
    }
  return term;
}

pwiz::msdata::SpectrumPtr
PwizMsRunReader::getPwizSpectrumPtr(pwiz::msdata::SpectrumList *p_spectrum_list,
                                    std::size_t spectrum_index,
                                    bool want_binary_data) const
{
  pwiz::msdata::SpectrumPtr native_pwiz_spectrum_sp;

  try
    {
      native_pwiz_spectrum_sp = p_spectrum_list->spectrum(spectrum_index, want_binary_data);
    }
  catch(std::runtime_error &error)
    {
      qDebug() << "getPwizSpectrumPtr error " << error.what() << " " << typeid(error).name();

      throw ExceptionNotFound(QObject::tr("Pwiz spectrum index %1 not found in "
                                          "MS file std::runtime_error :\n%2")
                                .arg(spectrum_index)
                                .arg(error.what()));
    }
  catch(std::exception &error)
    {
      qDebug() << "getPwizSpectrumPtr error " << error.what() << typeid(error).name();

      throw ExceptionNotFound(QObject::tr("Pwiz spectrum index %1 not found in MS file :\n%2")
                                .arg(spectrum_index)
                                .arg(error.what()));
    }

  if(native_pwiz_spectrum_sp.get() == nullptr)
    {
      throw ExceptionNotFound(
        QObject::tr("Pwiz spectrum index %1 not found in MS file : null pointer")
          .arg(spectrum_index));
    }

  return native_pwiz_spectrum_sp;
}


bool
PwizMsRunReader::processRetentionTime(pwiz::msdata::Spectrum *spectrum_p,
                                      QualifiedMassSpectrum &qualified_mass_spectrum) const
{

  //  We now have to set the retention time at which this mass spectrum
  //  was acquired. This is the scan start time.

  if(!spectrum_p->scanList.scans[0].hasCVParam(pwiz::msdata::MS_scan_start_time))
    {
      if(mcsp_msRunId.get()->getMsDataFormat() == Enums::MsDataFormat::MGF)
        { // MGF could not have scan start time
          qualified_mass_spectrum.setRtInSeconds(-1);
        }
      else
        {
          throw(ExceptionNotPossible("The spectrum has no scan start time value set."));
        }
    }
  else
    {
      pwiz::data::CVParam retention_time_cv_param =
        spectrum_p->scanList.scans[0].cvParam(pwiz::msdata::MS_scan_start_time);

      // Try to get the units of the retention time value.

      std::string unit_name = retention_time_cv_param.unitsName();
      // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
      //<< "Unit name for the retention time:"
      //<< QString::fromStdString(unit_name);

      if(unit_name == "second")
        {
          qualified_mass_spectrum.setRtInSeconds(retention_time_cv_param.valueAs<double>());
        }
      else if(unit_name == "minute")
        {
          qualified_mass_spectrum.setRtInSeconds(retention_time_cv_param.valueAs<double>() * 60);
        }
      else
        throw(
          ExceptionNotPossible("Could not determine the unit for the "
                               "scan start time value."));
    }

  // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
  //<< "Retention time for spectrum is:"
  //<< qualified_mass_spectrum.getRtInSeconds();

  // Old version not checking unit (by default unit is minutes for RT,
  // not seconds)
  //
  // pappso_double retentionTime =
  // QString(spectrum_p->scanList.scans[0]
  //.cvParam(pwiz::msdata::MS_scan_start_time)
  //.value.c_str())
  //.toDouble();
  // qualified_mass_spectrum.setRtInSeconds(retentionTime);

  return true;
}


bool
PwizMsRunReader::processDriftTime(pwiz::msdata::Spectrum *spectrum_p,
                                  QualifiedMassSpectrum &qualified_mass_spectrum) const
{
  // Not all the acquisitions have ion mobility data. We need to test
  // that:

  if(spectrum_p->scanList.scans[0].hasCVParam(pwiz::msdata::MS_ion_mobility_drift_time))
    {

      // qDebug() << "as strings:"
      //<< QString::fromStdString(
      // spectrum_p->scanList.scans[0]
      //.cvParam(pwiz::msdata::MS_ion_mobility_drift_time)
      //.valueAs<std::string>());

      pappso_double driftTime = spectrum_p->scanList.scans[0]
                                  .cvParam(pwiz::msdata::MS_ion_mobility_drift_time)
                                  .valueAs<double>();

      // qDebug() << "driftTime:" << driftTime;

      // Old version requiring use of QString.
      // pappso_double driftTime =
      // QString(spectrum_p->scanList.scans[0]
      //.cvParam(pwiz::msdata::MS_ion_mobility_drift_time)
      //.value.c_str())
      //.toDouble();

      // Now make positively sure that the obtained value is correct.
      // Note that I suffered a lot with Waters Synapt data that
      // contained apparently correct drift time XML element that in
      // fact contained either NaN or inf. When such mass spectra were
      // encountered, the mz,i data were bogus and crashed the data
      // loading functions. We just want to skip this kind of bogus mass
      // spectrum by letting the caller  know that the drift time was
      // bogus ("I" is Filippo Rusconi).

      if(std::isnan(driftTime) || std::isinf(driftTime))
        {
          // qDebug() << "detected as nan or inf.";

          return false;
        }
      else
        {
          // The mzML standard stipulates that drift times are in
          // milliseconds.
          qualified_mass_spectrum.setDtInMilliSeconds(driftTime);
        }
    }
  // End of
  // if(spectrum_p->scanList.scans[0].hasCVParam(
  // pwiz::msdata::MS_ion_mobility_drift_time))
  else
    {
      // Not a bogus mass spectrum but also not a drift spectrum, set -1
      // as the drift time value.
      qualified_mass_spectrum.setDtInMilliSeconds(-1);
    }

  return true;
}


QualifiedMassSpectrum
PwizMsRunReader::qualifiedMassSpectrumFromPwizSpectrumPtr(const MassSpectrumId &massSpectrumId,
                                                          pwiz::msdata::Spectrum *spectrum_p,
                                                          bool want_binary_data,
                                                          bool &ok) const
{
  // qDebug();

  if(spectrum_p == nullptr)
    qFatal("Spectrum pointer cannot be nullptr.");

  std::string backup_env = setGlobalLocaleToEnglish();

  QualifiedMassSpectrum qualified_mass_spectrum(massSpectrumId);

  try
    {

      // We want to store the ms level for this spectrum

      int msLevel = (spectrum_p->cvParam(pwiz::msdata::MS_ms_level).valueAs<int>());

      qualified_mass_spectrum.setMsLevel(msLevel);

      if(!spectrum_p->scanList.scans[0].hasCVParam(pwiz::msdata::MS_peak_list_scans))
        {

          // qDebug() << spectrum_p->cvParam(pwiz::msdata::MS_peak_list_scans)
          //               .valueAs<double>();
          qualified_mass_spectrum.setParameterValue(
            QualifiedMassSpectrumParameter::ScanNumber,
            spectrum_p->cvParam(pwiz::msdata::MS_peak_list_scans).valueAs<double>());
        }
      // We want to know if this spectrum is a fragmentation spectrum obtained
      // from a selected precursor ion.

      std::size_t precursor_list_size = spectrum_p->precursors.size();

      // qDebug() << "For spectrum at index:" <<
      // massSpectrumId.getSpectrumIndex()
      //<< "msLevel:" << msLevel
      //<< "with number of precursors:" << precursor_list_size;

      if(precursor_list_size > 0)
        {

          // Sanity check
          if(msLevel < 2)
            {
              qDebug() << "Going to throw: msLevel cannot be less than two for "
                          "a spectrum that has items in its Precursor list.";

              throw(
                ExceptionNotPossible("msLevel cannot be less than two for "
                                     "a spectrum that has items in its Precursor list."));
            }

          // See what is the first precursor in the list.

          for(auto &precursor : spectrum_p->precursors)
            {

              // Set this variable ready as we need that default value in
              // certain circumstances.

              std::size_t precursor_spectrum_index = std::numeric_limits<std::size_t>::max();

              // The spectrum ID of the precursor might be empty.

              if(precursor.spectrumID.empty())
                {
                  // qDebug() << "The precursor's spectrum ID is empty.";

                  if(mcsp_msRunId.get()->getMsDataFormat() == Enums::MsDataFormat::MGF)
                    {
                      // qDebug()
                      //<< "Format is MGF, precursor's spectrum ID can be
                      // empty.";
                    }
                  else
                    {
                      // When performing Lumos Fusion fragmentation
                      // experiments in Tune mode and with recording, the
                      // first spectrum of the list is a fragmentation
                      // spectrum (ms level 2) that has no identity for the
                      // precursor spectrum because there is no full scan
                      // accquisition.
                    }
                }
              // End of
              // if(precursor.spectrumID.empty())
              else
                {
                  // We could get a native precursor spectrum id, so convert
                  // that native id to a spectrum index.

                  qualified_mass_spectrum.setPrecursorNativeId(
                    QString::fromStdString(precursor.spectrumID));

                  if(qualified_mass_spectrum.getPrecursorNativeId().isEmpty())
                    {
                      // qDebug() << "The native id of the precursor spectrum
                      // is empty.";
                    }

                  //  Get the spectrum index of the spectrum that contained
                  //  the precursor ion.

                  precursor_spectrum_index =
                    msp_msData->run.spectrumListPtr->find(precursor.spectrumID);

                  // Note that the Mascot MGF format has a peculiar handling
                  // of the precursor ion stuff so we cannot throw.
                  if(precursor_spectrum_index == msp_msData->run.spectrumListPtr->size())
                    {
                      if(mcsp_msRunId.get()->getMsDataFormat() != Enums::MsDataFormat::MGF)
                        {
                          throw(
                            ExceptionNotPossible("Failed to find the index of the "
                                                 "precursor ion's spectrum."));
                        }
                    }

                  qualified_mass_spectrum.setPrecursorSpectrumIndex(precursor_spectrum_index);

                  // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__
                  // <<
                  // "()"
                  //<< "Set the precursor spectrum index to:"
                  //<< qualified_mass_spectrum.getPrecursorSpectrumIndex()
                  //<< "for qualified mass spectrum:"
                  //<< &qualified_mass_spectrum;
                }

              if(!precursor.selectedIons.size())
                {
                  qDebug() << "Going to throw The spectrum has msLevel > 1 but the "
                              "precursor ions's selected ions list is empty..";

                  throw(
                    ExceptionNotPossible("The spectrum has msLevel > 1 but the "
                                         "precursor ions's selected ions "
                                         "list is empty."));
                }

              pwiz::msdata::SelectedIon &ion = *(precursor.selectedIons.begin());

              // selected ion m/z

              pappso_double selected_ion_mz =
                QString(ion.cvParam(pwiz::cv::MS_selected_ion_m_z).value.c_str()).toDouble();

              // selected ion peak intensity
              //<cvParam cvRef="MS" accession="MS:1000042"
              // value="910663.949707031" name="peak intensity"
              // unitAccession="MS:1000131" unitName="number of detector
              // counts" unitCvRef="MS" />

              pappso_double selected_ion_peak_intensity =
                QString(ion.cvParam(pwiz::cv::MS_peak_intensity).value.c_str()).toDouble();

              // charge state

              unsigned int selected_ion_charge_state =
                QString(ion.cvParam(pwiz::cv::MS_charge_state).value.c_str()).toUInt();

              // At this point we can craft a new PrecursorIonData instance
              // and push it back to the vector.

              PrecursorIonData precursor_ion_data(
                selected_ion_mz, selected_ion_charge_state, selected_ion_peak_intensity);

              qualified_mass_spectrum.appendPrecursorIonData(precursor_ion_data);

              // General sum-up

              // qDebug()
              //<< "Appended new PrecursorIonData:"
              //<< "mz:"
              //<< qualified_mass_spectrum.getPrecursorIonData().back().mz
              //<< "charge:"
              //<< qualified_mass_spectrum.getPrecursorIonData().back().charge
              //<< "intensity:"
              //<< qualified_mass_spectrum.getPrecursorIonData()
              //.back()
              //.intensity;
            }
          // End of
          // for(auto &precursor : spectrum_p->precursors)
        }
      // End of
      // if(precursor_list_size > 0)
      else
        {
          // Sanity check

          // Unfortunately, logic here is defeated by some vendors that have
          // files with MS2 spectra without <precursorList>. Thus we have
          // spectrum_p->precursors.size() == 0 and msLevel > 1.

          // if(msLevel != 1)
          //{
          // throw(
          // ExceptionNotPossible("msLevel cannot be different than 1 if "
          //"there is not a single precursor ion."));
          //}
        }

      // Sanity check.

      if(precursor_list_size != qualified_mass_spectrum.getPrecursorIonData().size())
        {
          qDebug() << "Going to throw The number of precursors in the file is "
                      "different from the number of precursors in memory.";

          throw pappso::PappsoException(
            QObject::tr("The number of precursors in the file is different "
                        "from the number of precursors in memory."));
        }

      // if(precursor_list_size == 1)
      //{
      // qDebug() << "Trying to get the mz value of the unique precursor ion:"
      //<< qualified_mass_spectrum.getPrecursorMz();
      //}

      processRetentionTime(spectrum_p, qualified_mass_spectrum);

      processDriftTime(spectrum_p, qualified_mass_spectrum);

      // for(pwiz::data::CVParam cv_param : ion.cvParams)
      //{
      // pwiz::msdata::CVID param_id = cv_param.cvid;
      // qDebug() << param_id;
      // qDebug() << cv_param.cvid.c_str();
      // qDebug() << cv_param.name().c_str();
      // qDebug() << cv_param.value.c_str();
      //}

      if(want_binary_data)
        {

          // Fill-in MZIntensityPair vector for convenient access to binary
          // data

          std::vector<pwiz::msdata::MZIntensityPair> pairs;
          spectrum_p->getMZIntensityPairs(pairs);

          MassSpectrum spectrum;
          double tic = 0;
          // std::size_t iterCount = 0;

          // Iterate through the m/z-intensity pairs
          for(std::vector<pwiz::msdata::MZIntensityPair>::const_iterator it  = pairs.begin(),
                                                                         end = pairs.end();
              it != end;
              ++it)
            {
              //++iterCount;

              // qDebug() << "it->mz " << it->mz << " it->intensity" <<
              // it->intensity;
              if(it->intensity)
                {
                  spectrum.push_back(DataPoint(it->mz, it->intensity));
                  tic += it->intensity;
                }
            }

          if(mcsp_msRunId.get()->getMsDataFormat() == Enums::MsDataFormat::MGF)
            {
              // Sort peaks by mz
              spectrum.sortMz();
            }

          //  qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()
          //  "<< spectrum.size();
          MassSpectrumSPtr spectrum_sp = spectrum.makeMassSpectrumSPtr();
          qualified_mass_spectrum.setMassSpectrumSPtr(spectrum_sp);

          // double sumY =
          // qualified_mass_spectrum.getMassSpectrumSPtr()->sumY(); qDebug()
          // <<
          // __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
          //<< "iterCount:" << iterCount << "Spectrum size "
          //<< spectrum.size() << "with tic:" << tic
          //<< "and sumY:" << sumY;
        }
      else
        qualified_mass_spectrum.setMassSpectrumSPtr(nullptr);
    }
  catch(PappsoException &errorp)
    {
      qDebug() << "Going to throw";

      throw pappso::PappsoException(
        QObject::tr("Error reading data using the proteowizard library: %1").arg(errorp.qwhat()));
    }
  catch(std::exception &error)
    {
      qDebug() << "Going to throw";

      throw pappso::PappsoException(
        QObject::tr("Error reading data using the proteowizard library: %1").arg(error.what()));
    }

  ok = true;

  // qDebug() << "QualifiedMassSpectrum: " <<
  // qualified_mass_spectrum.toString();
  return qualified_mass_spectrum;
}


QualifiedMassSpectrum
PwizMsRunReader::qualifiedMassSpectrumFromPwizMSData(std::size_t spectrum_index,
                                                     bool want_binary_data,
                                                     bool &ok) const
{

  std::string backup_env = setGlobalLocaleToEnglish();

  MassSpectrumId massSpectrumId(mcsp_msRunId);

  if(msp_msData == nullptr)
    {
      setGlobalLocaleToCurrentOs(backup_env);
      return (QualifiedMassSpectrum(massSpectrumId));
    }

  // const bool want_binary_data = true;

  pwiz::msdata::SpectrumListPtr spectrum_list_p = msp_msData->run.spectrumListPtr;

  if(spectrum_index == spectrum_list_p.get()->size())
    {
      setGlobalLocaleToCurrentOs(backup_env);
      throw ExceptionNotFound(
        QObject::tr("The spectrum index cannot be equal to the size of the "
                    "spectrum list."));
    }

  // At this point we know the spectrum index might be sane, so store it in
  // the mass spec id object.
  massSpectrumId.setSpectrumIndex(spectrum_index);

  pwiz::msdata::SpectrumPtr native_pwiz_spectrum_sp =
    getPwizSpectrumPtr(spectrum_list_p.get(), spectrum_index, want_binary_data);

  setGlobalLocaleToCurrentOs(backup_env);

  massSpectrumId.setNativeId(QString::fromStdString(native_pwiz_spectrum_sp->id));

  return qualifiedMassSpectrumFromPwizSpectrumPtr(
    massSpectrumId, native_pwiz_spectrum_sp.get(), want_binary_data, ok);
}


bool
PwizMsRunReader::accept(const QString &file_name) const
{
  // We want to know if we can handle the file_name.
  pwiz::msdata::ReaderList reader_list;

  std::string reader_type = reader_list.identify(file_name.toStdString());

  if(!reader_type.empty())
    return true;

  return false;
}


pappso::MassSpectrumSPtr
PwizMsRunReader::massSpectrumSPtr(std::size_t spectrum_index)
{
  // qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__;
  return qualifiedMassSpectrum(spectrum_index, true).getMassSpectrumSPtr();
}

pappso::MassSpectrumCstSPtr
PwizMsRunReader::massSpectrumCstSPtr(std::size_t spectrum_index)
{
  // qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__;
  return qualifiedMassSpectrum(spectrum_index, true).getMassSpectrumCstSPtr();
}

QualifiedMassSpectrum
PwizMsRunReader::qualifiedMassSpectrum(std::size_t spectrum_index, bool want_binary_data) const
{

  QualifiedMassSpectrum spectrum;
  bool ok = false;

  spectrum = qualifiedMassSpectrumFromPwizMSData(spectrum_index, want_binary_data, ok);

  if(mcsp_msRunId->getMsDataFormat() == Enums::MsDataFormat::MGF)
    {
      if(spectrum.getRtInSeconds() == 0)
        {
          // spectrum = qualifiedMassSpectrumFromPwizMSData(scan_num - 1);
        }
    }

  // if(!ok)
  // qDebug() << "Encountered a mass spectrum for which the status is bad.";

  return spectrum;
}


void
PwizMsRunReader::readSpectrumCollection(SpectrumCollectionHandlerInterface &handler)
{
  try
    {
      readSpectrumCollectionByMsLevel(handler, 0);
    }
  catch(std::exception &error)
    {
      qDebug() << "Going to throw";

      throw pappso::PappsoException(
        QObject::tr("Error reading data (spectrum collection) using the "
                    "proteowizard library: %1")
          .arg(error.what()));
    }
}

void
PwizMsRunReader::readSpectrumCollection2([[maybe_unused]] const MsRunReadConfig &config,
                                         SpectrumCollectionHandlerInterface &handler)
{
  qDebug();
  try
    {
      readSpectrumCollectionWithMsrunReadConfig(config, handler);
    }
  catch(std::exception &error)
    {
      qDebug() << "Going to throw";

      throw pappso::PappsoException(
        QObject::tr("Error reading data (spectrum collection2) using the "
                    "proteowizard library: %1")
          .arg(error.what()));
    }
}

void
PwizMsRunReader::readSpectrumCollectionByMsLevel(SpectrumCollectionHandlerInterface &handler,
                                                 unsigned int ms_level)
{

  acquireDevice();
  // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()";

  // We want to iterate in the pwiz-spectrum-list and for each pwiz-spectrum
  // create a pappso-spectrum (QualifiedMassSpectrum). Once the pappso mass
  // spectrum has been fully qualified (that is, the member data have been
  // set), it is transferred to the handler passed as parameter to this
  // function for the consumer to do what it wants with it.

  // Does the handler consuming the mass spectra read from file want these
  // mass spectra to hold the binary data arrays (mz/i vectors)?

  const bool want_binary_data = handler.needPeakList();

  std::string backup_env = setGlobalLocaleToEnglish();

  // We access the pwiz-mass-spectra via the spectrumListPtr that sits in the
  // run member of msp_msData.

  pwiz::msdata::SpectrumListPtr spectrum_list_p = msp_msData->run.spectrumListPtr;

  // We'll need it to perform the looping in the spectrum list.
  std::size_t spectrum_list_size = spectrum_list_p.get()->size();

  // qDebug() << "The spectrum list has size:" << spectrum_list_size;

  // Inform the handler of the spectrum list so that it can handle feedback to
  // the user.
  handler.spectrumListHasSize(spectrum_list_size);

  // Iterate in the full list of spectra.

  for(std::size_t iter = 0; iter < spectrum_list_size; iter++)
    {

      // If the user of this reader instance wants to stop reading the
      // spectra, then break this loop.
      if(handler.shouldStop())
        {
          qDebug() << "The operation was cancelled. Breaking the loop.";
          break;
        }

      // Get the native pwiz-spectrum from the spectrum list.
      // Note that this pointer is a shared pointer from pwiz.

      pwiz::msdata::SpectrumPtr native_pwiz_spectrum_sp =
        getPwizSpectrumPtr(spectrum_list_p.get(), iter, want_binary_data);
      /*
       * we want to load metadata of the spectrum even if it does not contain
       peaks

       * if(!native_pwiz_spectrum_sp->hasBinaryData())
              {
                // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ <<
       "
       ()"
                //<< "native pwiz spectrum is empty, continuing.";
                continue;
              }
              */

      // Instantiate the mass spectrum id that will hold critical information
      // like the the native id string and the spectrum index.

      MassSpectrumId massSpectrumId(mcsp_msRunId, iter /* spectrum index*/);

      // Get the spectrum native id as a QString to store it in the mass
      // spectrum id class. This is will allow later to refer to the same
      // spectrum starting back from the file.

      QString native_id = QString::fromStdString(native_pwiz_spectrum_sp->id);
      massSpectrumId.setNativeId(native_id);

      // Finally, instantiate the qualified mass spectrum with its id. This
      // function will continue performing pappso-spectrum detailed
      // qualification.

      bool ok = false;

      QualifiedMassSpectrum qualified_mass_spectrum = qualifiedMassSpectrumFromPwizSpectrumPtr(
        massSpectrumId, native_pwiz_spectrum_sp.get(), want_binary_data, ok);

      if(!ok)
        {
          // qDebug() << "Encountered a mass spectrum for which the returned "
          //"status is bad.";
          continue;
        }

      // Before handing the mass spectrum out to the handler, see if the
      // native mass spectrum was empty or not.

      // if(!native_pwiz_spectrum_sp->defaultArrayLength)
      // qDebug() << "The mass spectrum has not defaultArrayLength";

      qualified_mass_spectrum.setEmptyMassSpectrum(!native_pwiz_spectrum_sp->defaultArrayLength);

      // The handler will receive the index of the mass spectrum in the
      // current run via the mass spectrum id member datum.
      if(ms_level == 0)
        {
          handler.setQualifiedMassSpectrum(qualified_mass_spectrum);
        }
      else
        {
          if(qualified_mass_spectrum.getMsLevel() == ms_level)
            {
              handler.setQualifiedMassSpectrum(qualified_mass_spectrum);
            }
        }
    }

  setGlobalLocaleToCurrentOs(backup_env);
  // End of
  // for(std::size_t iter = 0; iter < spectrum_list_size; iter++)

  // Now let the loading handler know that the loading of the data has ended.
  // The handler might need this "signal" to perform additional tasks or to
  // cleanup cruft.

  // qDebug() << "Loading ended";
  handler.loadingEnded();
}

void
PwizMsRunReader::readSpectrumCollectionWithMsrunReadConfig(
  const MsRunReadConfig &config, SpectrumCollectionHandlerInterface &handler)
{
  acquireDevice();
  // qDebug();

  // We want to iterate in the pwiz-spectrum-list and for each pwiz-spectrum
  // create a pappso-spectrum (QualifiedMassSpectrum). Once the pappso mass
  // spectrum has been fully qualified (that is, the member data have been
  // set), it is transferred to the handler passed as parameter to this
  // function for the consumer to do what it wants with it.

  // Does the handler consuming the mass spectra read from file want these
  // mass spectra to hold the binary data arrays (mz/i vectors)?

  const bool want_binary_data = config.needPeakList();


  std::string backup_env = setGlobalLocaleToEnglish();

  // qDebug();
  // We access the pwiz-mass-spectra via the spectrumListPtr that sits in the
  // run member of msp_msData.

  pwiz::msdata::SpectrumListPtr spectrum_list_p = msp_msData->run.spectrumListPtr;

  // We'll need it to perform the looping in the spectrum list.
  std::size_t spectrum_list_size = spectrum_list_p.get()->size();

  // qDebug() << "The spectrum list has size:" << spectrum_list_size;

  // Inform the handler of the spectrum list so that it can handle feedback to
  // the user.
  handler.spectrumListHasSize(spectrum_list_size);

  // Iterate in the full list of spectra.

  qDebug();
  for(std::size_t iter = 0; iter < spectrum_list_size; iter++)
    {


      // If the user of this reader instance wants to stop reading the
      // spectra, then break this loop.
      if(handler.shouldStop())
        {
          qDebug() << "The operation was cancelled. Breaking the loop.";
          break;
        }

      // qDebug();
      // Get the native pwiz-spectrum from the spectrum list.
      // Note that this pointer is a shared pointer from pwiz.

      pwiz::msdata::SpectrumPtr native_pwiz_spectrum_sp =
        getPwizSpectrumPtr(spectrum_list_p.get(), iter, want_binary_data);
      // qDebug();
      /*
       * we want to load metadata of the spectrum even if it does not contain
       peaks

       * if(!native_pwiz_spectrum_sp->hasBinaryData())
              {
                // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ <<
       "
       ()"
                //<< "native pwiz spectrum is empty, continuing.";
                continue;
              }
              */

      // Instantiate the mass spectrum id that will hold critical information
      // like the the native id string and the spectrum index.

      MassSpectrumId massSpectrumId(mcsp_msRunId, iter /* spectrum index*/);

      // qDebug();
      // Get the spectrum native id as a QString to store it in the mass
      // spectrum id class. This is will allow later to refer to the same
      // spectrum starting back from the file.

      QString native_id = QString::fromStdString(native_pwiz_spectrum_sp->id);
      massSpectrumId.setNativeId(native_id);

      // Finally, instantiate the qualified mass spectrum with its id. This
      // function will continue performing pappso-spectrum detailed
      // qualification.

      bool ok = false;

      QualifiedMassSpectrum qualified_mass_spectrum = qualifiedMassSpectrumFromPwizSpectrumPtr(
        massSpectrumId, native_pwiz_spectrum_sp.get(), want_binary_data, ok);

      if(!ok)
        {
          // qDebug() << "Encountered a mass spectrum for which the returned "
          //"status is bad.";
          continue;
        }

      // qDebug();
      // Before handing the mass spectrum out to the handler, see if the
      // native mass spectrum was empty or not.

      // if(!native_pwiz_spectrum_sp->defaultArrayLength)
      // qDebug() << "The mass spectrum has not defaultArrayLength";

      qualified_mass_spectrum.setEmptyMassSpectrum(!native_pwiz_spectrum_sp->defaultArrayLength);

      // The handler will receive the index of the mass spectrum in the
      // current run via the mass spectrum id member datum.

      // qDebug();
      if(config.acceptMsLevel(qualified_mass_spectrum.getMsLevel()))
        {
          if(config.acceptRetentionTimeInSeconds(qualified_mass_spectrum.getRtInSeconds()))
            {
              handler.setQualifiedMassSpectrum(qualified_mass_spectrum);
            }
        }
    }


  qDebug();
  setGlobalLocaleToCurrentOs(backup_env);
  // End of
  // for(std::size_t iter = 0; iter < spectrum_list_size; iter++)

  // Now let the loading handler know that the loading of the data has ended.
  // The handler might need this "signal" to perform additional tasks or to
  // cleanup cruft.

  qDebug() << "Loading ended";
  handler.loadingEnded();
}

std::size_t
PwizMsRunReader::spectrumListSize() const
{
  return msp_msData->run.spectrumListPtr.get()->size();
}

bool
PwizMsRunReader::hasScanNumbers() const
{
  return m_hasScanNumbers;
}

bool
PwizMsRunReader::releaseDevice()
{
  msp_msData = nullptr;
  return true;
}

bool
PwizMsRunReader::acquireDevice()
{
  if(msp_msData == nullptr)
    {
      initialize();
    }
  return true;
}


XicCoordSPtr
PwizMsRunReader::newXicCoordSPtrFromSpectrumIndex(std::size_t spectrum_index,
                                                  pappso::PrecisionPtr precision) const
{

  QualifiedMassSpectrum mass_spectrum = qualifiedMassSpectrum(spectrum_index, false);

  return newXicCoordSPtrFromQualifiedMassSpectrum(mass_spectrum, precision);
}

XicCoordSPtr
PwizMsRunReader::newXicCoordSPtrFromQualifiedMassSpectrum(
  const pappso::QualifiedMassSpectrum &mass_spectrum, pappso::PrecisionPtr precision) const
{
  XicCoordSPtr xic_coord = std::make_shared<XicCoord>();

  xic_coord.get()->rtTarget = mass_spectrum.getRtInSeconds();

  xic_coord.get()->mzRange = MzRange(mass_spectrum.getPrecursorMz(), precision);

  return xic_coord;
}
std::string
PwizMsRunReader::setGlobalLocaleToEnglish()
{
  std::string env_backup;
  try
    {
#ifdef MXE
      // std::locale::global(std::locale("C")); // set locale to default
      // locale

      std::string env_backup = std::setlocale(LC_ALL, nullptr);
      std::setlocale(LC_ALL, "C");
#else
      std::locale::global(std::locale("C")); // set locale to default locale
#endif
    }
  catch(std::exception &error)
    {
      throw pappso::PappsoException(
        QObject::tr("Error trying to set local to C : %1").arg(error.what()));
    }
  return env_backup;
}

void
PwizMsRunReader::setGlobalLocaleToCurrentOs(const std::string &environment_locale)
{
  try
    {
#ifdef MXE
      // std::locale::global(std::locale("C")); // set locale to default
      // locale
      std::setlocale(LC_ALL, environment_locale.c_str());
#else
      std::locale::global(std::locale("")); // sets locale according to OS environment
#endif
    }
  catch(std::exception &error)
    {
      throw pappso::PappsoException(
        QObject::tr("Error trying to set local to original system one %1 : %2")
          .arg(environment_locale.c_str())
          .arg(error.what()));
    }
}

std::size_t
PwizMsRunReader::spectrumStringIdentifier2SpectrumIndex(const QString &spectrum_identifier)
{
  acquireDevice();
  std::size_t spectrum_id =
    msp_msData->run.spectrumListPtr->find(spectrum_identifier.toStdString());
  if(spectrum_id == msp_msData->run.spectrumListPtr->size())
    {
      throw pappso::ExceptionNotFound(
        QObject::tr("Spectrum identifier %1 not found").arg(spectrum_identifier));
    }
  return spectrum_id;
}

} // namespace pappso


const pappso::OboPsiModTerm
pappso::PwizMsRunReader::getOboPsiModTermInstrumentModelName() const
{


  pappso::OboPsiModTerm term;

  if(getOboPsiModTermNativeIDFormat().getAccession() == "MS:1000768")
    {

      pappso::FilterOboPsiModMap psims_map;

      pappso::OboPsiMs psims_file(psims_map);
      // it should be a Thermo instrument
      /*
       * [Term]
    id: MS:1000494
    name: Thermo Scientific instrument model
    def: "Thermo Scientific instrument model." [PSI:MS]
    is_a: MS:1000483 ! Thermo Fisher Scientific instrument model

      */
      term = psims_map.getOboPsiModTermWithAccession("MS:1000494");

      for(auto &param_group : msp_msData.get()->paramGroupPtrs)
        {
          for (const auto &cv_param : param_group.get()->cvParams) {
              if(cv_param.cvid == pwiz::cv::CVID::MS_Q_Exactive)
                {
                  term = psims_map.getOboPsiModTermWithAccession("MS:1001911");
                }
              else if(cv_param.cvid == pwiz::cv::CVID::MS_Velos_Plus)
                {
                  /// Velos Plus: Thermo Scientific second generation Velos.
                  term = psims_map.getOboPsiModTermWithAccession("MS:1001909");
                }
              else if(cv_param.cvid == pwiz::cv::CVID::MS_LTQ_Orbitrap_Elite)
                {
                  /// LTQ Orbitrap Elite: Thermo Scientific LTQ Orbitrap Elite, often just referred
                  /// to as the Orbitrap Elite.

                  term = psims_map.getOboPsiModTermWithAccession("MS:1001910");
                }

              /// Orbitrap Fusion: Thermo Scientific Orbitrap Fusion.
              else if(cv_param.cvid == pwiz::cv::CVID::MS_Orbitrap_Fusion)
                {
                  /// LTQ Orbitrap Elite: Thermo Scientific LTQ Orbitrap Elite, often just referred
                  /// to as the Orbitrap Elite.

                  term = psims_map.getOboPsiModTermWithAccession("MS:1002416");
                }
              /// Q Exactive HF: Thermo Scientific Q Exactive.
              // MS_Q_Exactive_HF = 1002523,
              else if(cv_param.cvid == pwiz::cv::CVID::MS_Q_Exactive_HF)
                {
                  /// LTQ Orbitrap Elite: Thermo Scientific LTQ Orbitrap Elite, often just referred
                  /// to as the Orbitrap Elite.

                  term = psims_map.getOboPsiModTermWithAccession("MS:1002523");
                }
              //
              /// Exactive Plus: Thermo Scientific Exactive Plus MS.
              else if(cv_param.cvid == pwiz::cv::CVID::MS_Exactive_Plus)
                {
                  term = psims_map.getOboPsiModTermWithAccession("MS:1002526");
                }
              /// Q Exactive Plus: Thermo Scientific Q Exactive Plus.
              else if(cv_param.cvid == pwiz::cv::CVID::MS_Q_Exactive_Plus)
                {
                  term = psims_map.getOboPsiModTermWithAccession("MS:1002634");
                }
              /// Orbitrap Fusion Lumos: Thermo Scientific Orbitrap Fusion Lumos mass spectrometer
              /// with Tribrid architecture consisting of quadrupole mass filter, linear ion trap
              /// and Orbitrap mass analyzers.
              else if(cv_param.cvid == pwiz::cv::CVID::MS_Orbitrap_Fusion_Lumos)
                {
                  term = psims_map.getOboPsiModTermWithAccession("MS:1002732");
                }
              /// Q Exactive HF-X: Thermo Scientific Q Exactive HF-X Hybrid Quadrupole Orbitrap MS.
              else if(cv_param.cvid == pwiz::cv::CVID::MS_Q_Exactive_HF_X)
                {
                  term = psims_map.getOboPsiModTermWithAccession("MS:1002877");
                }
              /// Orbitrap Exploris 480: Thermo Scientific Orbitrap Exploris 480 Quadrupole Orbitrap
              /// MS.

              else if(cv_param.cvid == pwiz::cv::CVID::MS_Orbitrap_Exploris_480)
                {
                  term = psims_map.getOboPsiModTermWithAccession("MS:1003028");
                }
              /// Orbitrap Eclipse: Thermo Scientific Orbitrap Eclipse mass spectrometer with
              /// Tribrid architecture consisting of quadrupole mass filter, linear ion trap and
              /// Orbitrap mass analyzers.
              else if(cv_param.cvid == pwiz::cv::CVID::MS_Orbitrap_Eclipse)
                {
                  term = psims_map.getOboPsiModTermWithAccession("MS:1003029");
                }
              /// Orbitrap Exploris 240: Thermo Scientific Orbitrap Exploris 240 Quadrupole Orbitrap
              /// MS.

              else if(cv_param.cvid == pwiz::cv::CVID::MS_Orbitrap_Exploris_240)
                {
                  term = psims_map.getOboPsiModTermWithAccession("MS:1003094");
                }
              /// Orbitrap Exploris 120: Thermo Scientific Orbitrap Exploris 120 Quadrupole Orbitrap
              /// MS.

              else if(cv_param.cvid == pwiz::cv::CVID::MS_Orbitrap_Exploris_120)
                {
                  term = psims_map.getOboPsiModTermWithAccession("MS:1003095");
                }
              /// LTQ Orbitrap Velos Pro: Thermo Scientific LTQ Orbitrap Velos Pro, often just
              /// referred to as the Orbitrap Velos Pro.
              else if(cv_param.cvid == pwiz::cv::CVID::MS_LTQ_Orbitrap_Velos_Pro)
                {
                  term = psims_map.getOboPsiModTermWithAccession("MS:1003096");
                }
              /// Q Exactive UHMR: Thermo Scientific Q Exactive UHMR (Ultra High Mass Range) Hybrid
              /// Quadrupole Orbitrap MS.
              else if(cv_param.cvid == pwiz::cv::CVID::MS_Q_Exactive_UHMR)
                {
                  term = psims_map.getOboPsiModTermWithAccession("MS:1003245");
                }
              /// TSQ Altis Plus: Thermo Scientific TSQ Altis Plus Triple Quadrupole MS.
              else if(cv_param.cvid == pwiz::cv::CVID::MS_TSQ_Altis_Plus)
                {
                  term = psims_map.getOboPsiModTermWithAccession("MS:1003292");
                }
              /// Orbitrap Ascend: Thermo Scientific Orbitrap Ascend mass spectrometer with Tribrid
              /// architecture consisting of quadrupole mass filter, linear ion trap and Orbitrap
              /// mass analyzers.
              else if(cv_param.cvid == pwiz::cv::CVID::MS_Orbitrap_Ascend)
                {
                  term = psims_map.getOboPsiModTermWithAccession("MS:1003356");
                }
              /// Orbitrap Astral: Thermo Scientific Orbitrap Astral mass spectrometer contains
              /// three mass analyzers: a quadrupole analyzer, an Orbitrap analyzer, and the Astral
              /// analyzer.
              else if(cv_param.cvid == pwiz::cv::CVID::MS_Orbitrap_Astral)
                {
                  term = psims_map.getOboPsiModTermWithAccession("MS:1003378");
                }
          }
        }
    }
  else
    {

      throw ExceptionNotFound(QObject::tr("instrument model name not found in %1")
                                .arg(mcsp_msRunId.get()->getFileName()));
    }

  return term;
}
