/**
 * \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 "../../utils.h"
#include "../../pappsoexception.h"
#include "../../exception/exceptionnotfound.h"
#include "../../exception/exceptionnotpossible.h"
#include "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() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
    //<< "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() << __FILE__ << "@" << __LINE__ << __FUNCTION__ <<
                // "()"
                //<< "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() == MsDataFormat::mzXML)
      {
        m_hasScanNumbers = true;
      }
  }

  PwizMsRunReader::~PwizMsRunReader()
  {
  }


  const OboPsiModTerm PwizMsRunReader::getOboPsiModTermNativeIDFormat() const
  {

    OboPsiModTerm term;

    term.m_accession = "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.m_accession = "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() == 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() ==
                       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() !=
                           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() == 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() == pappso::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()));
      }
  }

} // namespace pappso
