/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2018 Filippo Rusconi
 *
 * http://www.msxpertsuite.org
 *
 * This file is part of the msXpertSuite project.
 *
 * The msXpertSuite project is the successor of the massXpert project. This
 * project now includes various independent modules:
 *
 * - massXpert, model polymer chemistries and simulate mass spectrometric data;
 * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner;
 *
 * This program 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.
 *
 * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * END software license
 */


#include <omp.h>

#include <QtSql>
#include <QSqlDatabase>
#include <QStringList>
#include <QFile>
#include <QByteArray>

#include <minexpert/nongui/MassSpecDataFileLoaderPwiz.hpp>
#include <minexpert/nongui/MassSpecFileMetaData.hpp>
#include <libmass/MassSpectrum.hpp>

#include <pwiz/data/msdata/MSDataFile.hpp>
#include <pwiz/data/msdata/DefaultReaderList.hpp>


namespace msXpSmineXpert
{

///////////////////////// PRIVATE ////////////////////
///////////////////////// PRIVATE ////////////////////
///////////////////////// PRIVATE ////////////////////

//! Provide a description of the file format handled by \c this loader.
/*!

  The MassSpecDataFileLoaderPwiz class is most specialized in load mzML data.

  \return A string stating that this loader loads mzML-formatted data.

*/
QString
MassSpecDataFileLoaderPwiz::formatDescription() const
{
  QString description = QString(
    "The mzml file format describes mass data with lines\n"
    "formatted according to the mzML format specification.\n");

  return description;
}


//! Update the data load progression using value.
/*!

  The \p count value is the number of the currently loaded mass spectrum.

  \param count number of the currently loaded spectrum. This is a sequence
  number, that is the ordinal number of the loaded spectra in the sequence of
  spectra loaded from file. This number is 1-based.

*/
void
MassSpecDataFileLoaderPwiz::updateLoadProgression(int count)
{
  // Maintain a count of the parsed spectra, so that we can give the
  // user a feedback each 100 spectra.

  m_parseProgression++;

  // qDebug() << __FILE__ << __LINE__ << "m_parseProgression "
  //<< m_parseProgression << "and "
  //<< "m_parseProgressionNumber" << m_parseProgressionNumber;

  if(m_parseProgression > m_parseProgressionNumber)
    {
      m_parseProgression = 1;

      QString msg = QString(
                      "Parsing spectrum number %1 -- "
                      "skipped empty spectra : %2\n")
                      .arg(m_spectrumIndex + 1)
                      .arg(m_totalSkippedSpectra);

      emit updateFeedbackSignal(msg,
                                m_spectrumIndex + 1,
                                true /* setVisible */,
                                LogType::LOG_TO_STATUS_BAR,
                                m_progressFeedbackStartValue,
                                m_progressFeedbackEndValue);

      // We may also use this function with a terminal-only program,
      // so it might be asked that we send the feedback to the
      // terminal Stdout.

      if(m_feedbackToStdout)
        printf("%s@%d: %s\n", __FILE__, __LINE__, msg.toLatin1().data());
    }
}


//! Reset spectral data that change at each new parsed spectrum.
void
MassSpecDataFileLoaderPwiz::resetSpectralData()
{
  // Spectral stuff

  // Do not delete mp_newSpectrum, we may not own that spectrum anymore if
  // it has been appended to the MassSpecDataSet instance!

  m_spectrumIndex = -1;
  m_msLevel       = -1;
  m_spectrumTitle = "NOT_SET";
  m_specPeakCount = -1;
  m_tic           = -1;
  m_scanStartTime = -1;

  // Scan stuff
  m_scanWindowListCount  = -1;
  m_scanWindowLowerLimit = -1;
  m_scanWindowUpperLimit = -1;
}


//! Reset drift data that change at each new parsed spectrum.
void
MassSpecDataFileLoaderPwiz::resetMobilityData()
{
  // Mobility stuff
  m_driftDataId = -1;

  m_isMobilityExperiment = false;
  m_curDriftTime         = -1;
  m_driftTimeMin         = -1;
  m_driftTimeMax         = -1;
  m_driftTimeStep        = -1;
  m_prevDriftTime        = -1;
}


//! Establish if the data are for an ion mobility MS experiment.
/*!

  This function tries to figure out if the data being loaded from file are for
  an ion mobility mass spectrometry experiment. If so, it set some drift data
  to this class members.

  In particular, this function explores the drift times of the first two
  spectra of the file and determines what drfit time interval separates the
  two drift bins. This function is also responsible for determining the number
  of drift bins in a drift cycle and also to determine the shortest drift time
  value and the longest drift time value in a drift cycle.

*/
void
MassSpecDataFileLoaderPwiz::seedMobilityData()
{

  // Depending on the index of the spectrum we have just worked on, we may
  // want to compute some mobility data for later export to the database.

  // Only deal with mobility data if there are mobility data.
  if(m_curDriftTime == -1)
    return;

  if(m_spectrumIndex == 0)
    {
      m_driftTimeMin = m_curDriftTime;

      // qDebug() << __FILE__ << __LINE__
      // << "First spectrum extraction with minimum drift time: "
      // << m_driftTimeMin;
    }
  else if(m_spectrumIndex == 1)
    {
      m_driftTimeStep = (m_curDriftTime - m_prevDriftTime);

      // qDebug()
      //<< __FILE__ << __LINE__
      //<< "Second spectrum extraction allows calculation of drift time step:"
      //<< m_driftTimeStep;
    }
  else
    {
      // qDebug() << __FILE__ << __LINE__
      // << "Spectrum index: " << m_spectrumIndex
      // << "with m_curDriftTime: " << m_curDriftTime;

      // When the software was initially developed the reference data
      // material was from Waters Synapt HDMS G2 instrument, and the drift
      // times were extremely reproducible. Thus we could write the following
      // if statement:
      // if(m_curDriftTime == m_driftTimeMin)
      //
      // When I started looking at Agilent data, I discovered that the very
      // first drift time of cycle 1 and cycle 2 were not identical. So I had
      // to change the if statement to this:
      if(m_curDriftTime <= m_driftTimeMin)
        {
          // We are starting a new drift cycle because we are not
          // at the 0-index iteration (specIndex != 0) and the
          // drift time is nonetheless the same as the one on the
          // first iteration.

          // And also set the max drift time, since we stored that value
          // during the previous iteration.
          m_driftTimeMax = m_prevDriftTime;

          // At this point, we have all the data required, we can thus set the
          // boolean value below to true so that we do not need to reperform
          // this calculation over all the spectra.

          m_isMobilityExperiment = true;

          // FIXME
          // emit dataLoadMessageSignal(
          //"The data are for a mobility experiment.", LOG_TO_BOTH);
        }
    }
  // Store the drift time value for the next round, if we need it.
  m_prevDriftTime = m_curDriftTime;

  // Now that some of the data are definitive, we can set them to the
  // metaData member:
  m_msFileMetaData.setDoubleItem("driftTimeMin", m_driftTimeMin);
  m_msFileMetaData.setDoubleItem("driftTimeMax", m_driftTimeMax);
  m_msFileMetaData.setDoubleItem("driftTimeStep", m_driftTimeStep);
}


//! Determines if the mass data loaded from file are form ion mobility MS.
/*!

  This function quickly scans through the spectrum list of the file and
  interrogates the data of the first mass spectrum of the list. If drift data
  are associated to it, then it is considered that the file is for an ion
  mobility mass spectrometry experiment.

  \return true if the file contains drift data (ion mobility data).

*/
bool
MassSpecDataFileLoaderPwiz::containsMobilityData()
{
  // We want to know if this file contains mobility data. Let's go very
  // quickly to the first <spectrum> element where that data should be
  // located.

  std::string head;
  pwiz::msdata::MSData result;

  if(!canLoadData(m_fileName, this))
    return false;

  if(mp_pwizReader == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. No reader has been set. Program aborted.",
           __FILE__,
           __LINE__);

  std::string fileType = mp_pwizReader->getType();
  QString readerTypeString(QString::fromStdString(fileType));
  // qDebug() << __FILE__ << __LINE__
  //<< "reader type:" << readerTypeString;

  mp_pwizReader->read(m_fileName.toStdString(),
                      head,
                      result,
                      /* int runIndex */ 0);

  pwiz::msdata::SpectrumListPtr spectrumListPtr = result.run.spectrumListPtr;

  if(!spectrumListPtr->size())
    qFatal(
      "Fatal error at %s@%d. Failed to read the number of spectra or "
      "file is empty. "
      "Program aborted.",
      __FILE__,
      __LINE__);

  pwiz::msdata::SpectrumPtr spectrumPtr =
    spectrumListPtr->spectrum(/*index*/ 0, false /*getBinaryData*/);

  pwiz::msdata::ScanList scanList = spectrumPtr->scanList;

  // Then look at the param of each single scan of the scanList

  // <cvParam cvRef="MS" accession="MS:1000016" name="scan start time"
  // value="0.032866668"
  // unitCvRef="UO" unitAccession="UO:0000031" unitName="minute"/>

  // <cvParam cvRef="MS" accession="MS:1002476" name="ion mobility drift time"
  // value="0.0"
  // unitCvRef="UO" unitAccession="UO:0000028" unitName="millisecond"/>

  // <cvParam cvRef="MS" accession="MS:1000796" name="spectrum title"
  // value="MOBILITY.raw, NativeID:&quot;function=1 process=0 scan=36&quot;"/>

  for(uint lter = 0; lter < scanList.scans.size(); ++lter)
    {
      pwiz::msdata::Scan scan = scanList.scans.at(lter);

      for(uint mter = 0; mter < scan.cvParams.size(); ++mter)
        {
          pwiz::msdata::CVParam cvParam = scan.cvParams.at(mter);

          if(cvParam.cvid == pwiz::msdata::MS_ion_mobility_drift_time)
            {
              // In mineXpert drift times are always in milliseconds.
              double driftTime = cvParam.timeInSeconds() * 1000;
              if(driftTime != -1)
                return true;
            }
        }
    }

  return false;
}


//! Loads the currently parsed spectrum to a file.
/*!

  This class can load data to a number of different destinations.  In this
  function, the destination of the data are xy-formatted files. Each spectrum
  is loaded to a new data file.

  \return -1 upon error, 0 when no data were actually loaded (spectrum is
  empty, for example), 1 if the data of the spectrum were effectively loaded.

  \sa loadData(const History &history, IntegrationType integrationType,
  QVector<double> *keyVector, QVector<double> *valVector, double *intensity).

  \sa streamedTicIntensity(const History &history, double *intensity).

  \sa integrateToRt() <br> integrateToMz() <br> integrateToDt().

*/

bool
MassSpecDataFileLoaderPwiz::seedDataLoading()
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";

  // If this object is tasked with more than one parsing, then, make sure we
  // start fresh.

  resetMobilityData();
  resetSpectralData();

  m_parseProgression   = 0;
  m_storedSpectraCount = 0;
  m_scanStartTime      = -1;
  m_curDriftTime       = -1;
  m_ticIntensity       = 0.0;

  std::string head;

  if(!canLoadData(m_fileName, this))
    {
      qDebug() << __FILE__ << __LINE__ << __FUNCTION__
               << "Data cannot be loaded.";

      return false;
    }

  if(mp_pwizReader == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. No reader has been set. Program aborted.",
           __FILE__,
           __LINE__);

  std::string fileType = mp_pwizReader->getType();
  QString readerTypeString(QString::fromStdString(fileType));

  // qDebug() << __FILE__ << __LINE__ << "reader type:" << readerTypeString;

  mp_pwizReader->read(m_fileName.toStdString(),
                      head,
                      m_msDataResult,
                      /* int runIndex */ 0);

  // This acquisition date:time datum is essential to record data later (if
  // required) to a SQLite3-formatted file.

  // We cannot construct a useful *empty* QDateTime instance.

  QDateTime dateTime(QDate(1, 1, 1));

  QString startTimeStamp =
    QString::fromStdString(m_msDataResult.run.startTimeStamp);

  // With mzXML, no date is provided in the file, while with the mzML file
  // yes.

  if(!startTimeStamp
        .isEmpty()) // Format of the startTimeStamp: "2016-02-22T10:29:15Z"
    dateTime = QDateTime::fromString(startTimeStamp, Qt::ISODate);

  if(!dateTime.isValid())
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  m_msFileMetaData.setStringItem("acquisitionDate",
                                 dateTime.toString("yyyy.MM.dd"));

  mp_spectrumListPtr = m_msDataResult.run.spectrumListPtr;
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "spectrum list ptr:" << mp_spectrumListPtr.get();
  pwiz::msdata::SpectrumPtr spectrumPtr =
    mp_spectrumListPtr->spectrum(/*index*/ 0, true /*getBinaryData*/);
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "spectrum ptr:" << spectrumPtr.get();

  m_spectrumCount = mp_spectrumListPtr->size();

  if(m_spectrumCount == 0)
    qFatal(
      "Fatal error at %s@%d. Failed to read the number of spectra or "
      "file is empty. "
      "Program aborted.",
      __FILE__,
      __LINE__);

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "The file contains" << m_spectrumCount << "spectra.";

  m_progressFeedbackStartValue = 1;
  m_progressFeedbackEndValue   = m_spectrumCount;
  emit updateFeedbackSignal("Parsing mzML/mzXML file",
                            -1,
                            true /* setVisible */,
                            LogType::LOG_TO_BOTH,
                            m_progressFeedbackStartValue,
                            m_progressFeedbackEndValue);

  // Reset the spectrum pointer holder.
  mp_newSpectrum = Q_NULLPTR;

  return true;
}


int
MassSpecDataFileLoaderPwiz::nextSpectrum(
  pwiz::msdata::SpectrumPtr *p_spectrumPtr)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";

  if(mp_spectrumListPtr == Q_NULLPTR)
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "The member spectrum list pointer cannot be nullptr. "
      "Please use seedDataLoading()."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  // Immediately increment the spectrum index.

  ++m_spectrumIndex;

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "Now handling spectrum index:" << m_spectrumIndex;

  if(m_spectrumIndex > m_spectrumCount - 1)
    {
      *p_spectrumPtr = Q_NULLPTR;
      return -1;
    }

  // We use this variable to check for NaN drift time values (Waters Synapt
  // files do have odd mzML elements.
  bool wasNanOrInf = false;

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "spectrum index:" << m_spectrumIndex
  //<< "spectrum list ptr:" << mp_spectrumListPtr.get()
  //<< "size of spectrum list:" << mp_spectrumListPtr->size();

  pwiz::msdata::SpectrumPtr spectrumPtr = mp_spectrumListPtr->spectrum(
    /*index*/ m_spectrumIndex, true /*getBinaryData*/);

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "spectrum index:" << m_spectrumIndex
  //<< "spectrum list ptr:" << mp_spectrumListPtr.get()
  //<< "spectrum ptr:" << spectrumPtr.get();

  // We need some cvParam data from the spectrum itself.
  for(uint iter = 0; iter < spectrumPtr->cvParams.size(); ++iter)
    {
      pwiz::msdata::CVParam cvParam = spectrumPtr->cvParams.at(iter);

      if(cvParam.cvid == pwiz::msdata::MS_spectrum_title)
        {
          m_spectrumTitle =
            QString::fromStdString(cvParam.valueAs<std::string>());
        }
      else if(cvParam.cvid == pwiz::msdata::MS_ms_level)
        {
          m_msLevel = cvParam.valueAs<int>();

          // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
          //<< "Current spectrum ms level:" << m_msLevel;
        }
    }

  m_specPeakCount = spectrumPtr->getMZArray()->data.size();

  pwiz::msdata::ScanList scanList = spectrumPtr->scanList;

  // Then look at the param of each single scan of the scanList

  // <cvParam cvRef="MS" accession="MS:1000016" name="scan start time"
  // value="0.032866668"
  // unitCvRef="UO" unitAccession="UO:0000031" unitName="minute"/>

  // <cvParam cvRef="MS" accession="MS:1002476" name="ion mobility drift
  // time" value="0.0"
  // unitCvRef="UO" unitAccession="UO:0000028" unitName="millisecond"/>

  // <cvParam cvRef="MS" accession="MS:1000796" name="spectrum title"
  // value="MOBILITY.raw, NativeID:&quot;function=1 process=0
  // scan=36&quot;"/>

  for(uint lter = 0; lter < scanList.scans.size(); ++lter)
    {
      pwiz::msdata::Scan scan = scanList.scans.at(lter);

      for(uint mter = 0; mter < scan.cvParams.size(); ++mter)
        {
          pwiz::msdata::CVParam cvParam = scan.cvParams.at(mter);

          if(cvParam.cvid == pwiz::msdata::MS_scan_start_time)
            {
              // Retention times are always in minutes.
              m_scanStartTime = cvParam.timeInSeconds() / 60;

              // In some cases, the scan start time value in the mzML file goes
              // back to smaller values. This happens for IM-MS data from the
              // Synapt2 instrument from Waters. It almost always goes along a
              // nan or inf value for the ion mobility drift time value, which
              // we trap later. So the test below is not useful at the moment.
              // We keep it for the record.

              // if(!lter)
              // m_firstScanStartTime = m_scanStartTime;

              // if(m_scanStartTime < m_firstScanStartTime)
              //{

              // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
              //<< "m_scanStartTime:" << m_scanStartTime;

              // wasNanOrInf   = true;

              // break;
              //}
            }
          else if(cvParam.cvid == pwiz::msdata::MS_ion_mobility_drift_time)
            {
              QString value = QString::fromStdString(cvParam.value);

              if(value == "" || value == "-nan" || value == "nan" ||
                 value == "inf" || value == "-inf")
                {
                  qDebug() << __FILE__ << __LINE__ << __FUNCTION__
                           << "Found a NaN drift time at index "
                           << m_spectrumIndex << ". Ignoring it.";

                  m_curDriftTime = -1;
                  wasNanOrInf    = true;

                  break;
                }

              // Drift times are always in milliseconds.
              m_curDriftTime = cvParam.timeInSeconds() * 1000;
            }
        }

      if(wasNanOrInf)
        break;

      // And now the scan window limits
      // Note that mzXML file do not have such stuff, so some vector might
      // be empty. We need to check for size.

      int scanWindowCount = scan.scanWindows.size();

      if(scanWindowCount >= 1)
        {
          int firstOne = 0;

          // We only deal with the first scan window.
          for(uint iter = 0;
              iter < scan.scanWindows.at(firstOne).cvParams.size();
              ++iter)
            {
              pwiz::msdata::CVParam cvParam =
                scan.scanWindows.at(firstOne).cvParams.at(iter);

              if(cvParam.cvid == pwiz::msdata::MS_scan_window_lower_limit)
                {
                  m_scanWindowLowerLimit = cvParam.valueAs<double>();
                }
              else if(cvParam.cvid == pwiz::msdata::MS_scan_window_upper_limit)
                {
                  m_scanWindowUpperLimit = cvParam.valueAs<double>();
                }
            }

          // But if there are more than one, then tell the user.
          if(scanWindowCount > 1)
            qDebug()
              << __FILE__ << __LINE__
              << "There are more than one scan window for this spectrum. "
                 "Only handling the first one.";
        }
      // else if(scanWindowCount == 0)
      // qDebug()
      //<< __FILE__ << __LINE__
      //<< "There is not a single scan window for this spectrum.";
    }
  // End of
  // for(uint lter = 0; lter < scanList.scans.size(); ++lter)

  if(wasNanOrInf)
    {
      qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
               << "The drift time was found to be nan or inf, skipping the "
               << "spectrum at index: m_spectrumIndex" << m_spectrumIndex;

      // We encountered a NaN drift time value. Do not return an error, but
      // set the pointer to nullptr;
      *p_spectrumPtr = Q_NULLPTR;
      return 0;
    }

  // Compute some stuff related to mobility drift times (if so is
  // required).

  if(!m_isMobilityExperiment)
    seedMobilityData();

  // Finally report the results:
  *p_spectrumPtr = spectrumPtr;
  return 1;
}


msXpSlibmass::MassSpectrum *
MassSpecDataFileLoaderPwiz::createNewSpectrum(
  pwiz::msdata::SpectrumPtr spectrumPtr)
{
  pwiz::msdata::BinaryDataArrayPtr mzArrayPtr = spectrumPtr->getMZArray();
  pwiz::msdata::BinaryDataArrayPtr iArrayPtr = spectrumPtr->getIntensityArray();

  // Sanity check:
  if(mzArrayPtr->data.size() != iArrayPtr->data.size())
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  // qDebug() << __FILE__ << __LINE__
  //<< "mzArray/iArray at index " << iter
  //<< "have size:" << mzArrayPtr->data.size();

  // Do not store empty spectra.

  if(!mzArrayPtr->data.size())
    {
      ++m_totalSkippedSpectra;

      return Q_NULLPTR;
    }

  // Read the new spectrum into an allocated spectrum such that if we are
  // going to store it in the mass spec data set, it is already available,
  // ownership will go to the set. If we won't use it in the long run,
  // we'll simply delete it after having done the required computation
  // (to drift time integration or tic intensity, for example).
  msXpSlibmass::MassSpectrum *newSpectrum = new msXpSlibmass::MassSpectrum;

  // mp_newSpectrum->Trace::initialize(
  newSpectrum->initialize(QVector<double>::fromStdVector(mzArrayPtr->data),
                          QVector<double>::fromStdVector(iArrayPtr->data));

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "The newly loaded spectrum has" << mp_newSpectrum->size() << "peaks";

  return newSpectrum;
}

//! Loads the currently parsed spectrum to a MassSpecDataSet instance.
/*!

  This class can load data to a number of different destinations.  In this
  function, the destination of the data is the \c mp_massSpecDataSet
  MassSpecDataSet pointer.

  Note that the generated MassSpectrum entities that are stored in \c
  mp_massSpecDataSet <em>are not owned</em> by \c this instance.

  \sa loadSpectrumToVectors()

  \sa MassSpecDataSet for the structure of the loaded mass spectra.

*/
int
MassSpecDataFileLoaderPwiz::loadSpectrumToMassSpecDataSet()
{
  // At this point, there are two possibilities: either the file
  // is being read in full mode, the we append the spectrum to
  // the mass spec data set and thus we occupy more memory at
  // each read spectrum. Or we are reading the file in streamed
  // mode, in which case, we need to process the data a
  // bit and then let them go.
  //
  // Whatever the case, the statistics about the mass data are performed in
  // two steps: while loading data (streamed or not), each spectrum is used
  // to increment the statistical data set. At the end of the file loading,
  // the statistics need to be consolidated.

  // We will need the mass spectrum that was parsed lastly, right before
  // calling this function. The pointer to that spectrum cannot be
  // Q_NULLPTR.

  if(mp_newSpectrum == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  // This is the moment to increment the statistics with the data in the new
  // mass spectrum. It will be necessary to consolidate the statistics at
  // the end of the file loading.

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "Increment statistics for spectrum index" << m_spectrumIndex
  //<< "with spectrum size:" << mp_newSpectrum->size();

  mp_massSpecDataSet->incrementStatistics(*mp_newSpectrum);

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "Done increment statistics for spectrum index" << m_spectrumIndex;

  if(mp_massSpecDataSet->isStreamed())
    {

      // We are reading in streamed mode. This means that we are not loading
      // all the data in memory. Instead, we look at each new spectrum and we
      // compute its TIC which we store as a (rt,tic) pair in the
      // mp_massSpecDataSet->m_ticChromMap. This is the chance we have to
      // compute the TIC for the spectrum:

      double newTic = mp_newSpectrum->valSum();

      // Had already another spectrum have the same rt ? If so another TIC was
      // already computed.

      double oldTic =
        mp_massSpecDataSet->m_ticChromMap.value(m_scanStartTime, qSNaN());

      if(!qIsNaN(oldTic))
        {

          // A rt by the same value alreay existed, update the
          // tic value and insert it, that will erase the old
          // value, because the map is NOT a multimap.

          newTic += oldTic;
        }

      //// Only for debugging purposes:
      // double tic = mp_newSpectrum->valSum();

      // qDebug() << __FILE__ << __LINE__
      //<< "TIC value:" << tic;

      // And now store the key/value pair for later to use to
      // plot the TIC chromatogram.

      mp_massSpecDataSet->m_ticChromMap.insert(m_scanStartTime, newTic);

      // At this point, handle the case of the colorMap that
      // relates all the dt with the corresponding (mz,i) pairs,
      // that is, each dt has a single mass spectrum that
      // corresponds to the combination of all the spectra having
      // that dt.

      if(m_curDriftTime != -1)
        {
          // qDebug() << __FILE__ << __LINE__
          // << "This file is from a mobility experiment."
          // << "m_curDriftTime:" << m_curDriftTime;

          // Let's see if for the current drift time, a spectrum
          // was already encountered (and possibly undergoing
          // combinations).

          msXpSlibmass::MassSpectrum *massSpectrum =
            static_cast<msXpSlibmass::MassSpectrum *>(
              mp_massSpecDataSet->m_dtHash.value(m_curDriftTime, Q_NULLPTR));

          if(massSpectrum == Q_NULLPTR)
            {
              msXpSlibmass::MassSpectrum *newSpectrum =
                new msXpSlibmass::MassSpectrum;

              // This is the first spectrum that we set to the hash that has the
              // current dt value. We want to combine all the spectra that have
              // the same dt. We need to store the spectrum so that we can later
              // refer to it, to fill in the colormap. But remember, we want to
              // make a calculation such that there are less MassPeak elements
              // in
              // the spectra. So we need to allocated a new spectrum and combine
              // with rounding (0 decimals) the newSpectrum in it.

              newSpectrum->rdecimalPlaces() = 0; // See comment above.
              newSpectrum->combine(*mp_newSpectrum);
              mp_massSpecDataSet->m_massSpectra.append(newSpectrum);

              // Delete the newSpectrum that we do not use anymore, since we
              // already computed the TIC value and also made the color map
              // point.

              delete mp_newSpectrum;
              mp_newSpectrum = Q_NULLPTR;

              // And now make the hash association with the current drift time
              // value. This is the "degraded" resolution spectrum because we
              // want
              // to compute a low resolution map.

              mp_massSpecDataSet->m_dtHash.insert(m_curDriftTime, newSpectrum);

              // Finally, let's store the first mz value and the last one.  Only
              // if the values need an update.

              double mz_first = newSpectrum->first()->key();
              if(mz_first < mp_massSpecDataSet->m_mzRangeStart)
                mp_massSpecDataSet->m_mzRangeStart = mz_first;

              double mz_last = newSpectrum->last()->key();
              if(mz_last > mp_massSpecDataSet->m_mzRangeEnd)
                mp_massSpecDataSet->m_mzRangeEnd = mz_last;

              // We will need to know what is the number of cells on the mz
              // axis
              // of the colormap matrix.

              int spectrumSize = newSpectrum->size();
              if(spectrumSize > mp_massSpecDataSet->m_mzCellCount)
                mp_massSpecDataSet->m_mzCellCount = spectrumSize;
            }
          // End of
          // if(massSpectrum == Q_NULLPTR)
          else
            {

              // We already have inserted a mass spectrum by the current dt.  So
              // use it to combine the newly generated one. Remember we want to
              // do
              // a 0-decimals combination so as to have a low-resolution map.

              massSpectrum->rdecimalPlaces() = 0;
              massSpectrum->combine(*mp_newSpectrum);

              // Since we do not use this spectrum anymore, we can
              // delete it.
              delete mp_newSpectrum;
              mp_newSpectrum = Q_NULLPTR;

              // Finally, let's store the first mz value and the last
              // one.
              // Only if the values need an update.

              double mz_first = massSpectrum->first()->key();
              if(mz_first < mp_massSpecDataSet->m_mzRangeStart)
                mp_massSpecDataSet->m_mzRangeStart = mz_first;

              double mz_last = massSpectrum->last()->key();
              if(mz_last > mp_massSpecDataSet->m_mzRangeEnd)
                mp_massSpecDataSet->m_mzRangeEnd = mz_last;

              // We will need to know what is the number of cells on the mz
              // axis
              // of the colormap matrix.

              int spectrumSize = massSpectrum->size();
              if(spectrumSize > mp_massSpecDataSet->m_mzCellCount)
                mp_massSpecDataSet->m_mzCellCount = spectrumSize;
            }
        }
      // End of
      // if(m_curDriftTime != -1)

      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
      //<< "Done loading one spectrum in stream mode.";
    }
  // End of
  // if(mp_massSpecDataSet->isStreamed())
  else
    {

      // We are loading the data in full. Just store the spectrum
      // after having set to it the rt and dt values.

      // qDebug() << __FILE__ << __LINE__
      // << "Appending new mass spectrum to the data set with
      // retention time:" << m_scanStartTime;

      mp_massSpecDataSet->appendMassSpectrum(mp_newSpectrum);
      mp_massSpecDataSet->insertRtHash(m_scanStartTime, mp_newSpectrum);

      if(m_scanStartTime > 34.998)
        qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
                 << "Inserted new spectrum for m_scanStartTime:"
                 << m_scanStartTime;

      //// Only for debugging purposes:
      // double tic = mp_newSpectrum->valSum();

      // qDebug() << __FILE__ << __LINE__
      //<< "TIC value:" << tic;

      // Do not forget to set that retention time to the spectrum itself !

      mp_newSpectrum->rrt() = m_scanStartTime;

      // If the spectrum is from a drift time experiment, then store that data
      // also.

      if(m_curDriftTime != -1)
        {
          // qDebug() << __FILE__ << __LINE__
          // << "This file is from a mobility experiment."
          // << "m_curDriftTime:" << m_curDriftTime;

          mp_massSpecDataSet->insertDtHash(m_curDriftTime, mp_newSpectrum);

          // Do not forget to set that drift time to the spectrum itself !

          mp_newSpectrum->rdt() = m_curDriftTime;
        }

      // Do not destroy the spectrum: as we append it, its owner changes.
    }

  return 1;
}


//! Integrate the data loaded from file to a TIC chromatogram
/*!

  The data are loaded from the file in order to craft a TIC chromatogram.

  When running the integration, the \c m_history History instance member is
  checked. Any integration listed in the History is taken into account.

  The <rt,tic> value pairs are stocked in the \c m_keyValMap, that will later
  be used to craft two <rt> and <tic> vectors. Indeed, this function is called
  by loadSpectrumToVectors().

  Note that, if \c m_history contains an integration type limiting the m/z
  values to a given range, the TIC chromatogram actually becomes a XIC
  chromatogram. This is because when the TIC is computed for the spectrum,
  only the MassPeak instances having a m/z values contained in the range are
  accounted for.

  \return -1 upon error, 0 if no integration occurred, 1 otherwise.

  \sa integrateToMz() <br> integrateToDt().
  */
int
MassSpecDataFileLoaderPwiz::integrateToRt()
{

  // We are creating a TIC chromatogram. We may have a history to take into
  // account.

  // RT integration.
  // ===============
  bool rtIntegration = false;
  double rtStart     = -1;
  double rtEnd       = -1;

  rtIntegration = m_history.innermostRtRange(&rtStart, &rtEnd);
  if(rtIntegration)
    {
      // qDebug() << __FILE__ << __LINE__ << "rt integration accounted for:"
      //<< QString("[%1-%2]\n").arg(rtStart).arg(rtEnd) << "\n";
    }

  // MZ integration.
  // ===============
  bool mzIntegration = false;
  double mzStart     = -1;
  double mzEnd       = -1;

  mzIntegration = m_history.innermostMzRange(&mzStart, &mzEnd);
  if(mzIntegration)
    {
      // qDebug() << __FILE__ << __LINE__ << "mz integration accounted for:"
      //<< QString("[%1-%2]\n").arg(mzStart).arg(mzEnd) << "\n";
    }

  // DT integration.
  // ===============
  bool dtIntegration = false;
  double dtStart     = -1;
  double dtEnd       = -1;

  // If the history is comprised of many successive integrations on the
  // basis of dt value ranges, then we should make sure we do take into
  // account the more internal dt value range.
  dtIntegration = m_history.innermostDtRange(&dtStart, &dtEnd);
  if(dtIntegration)
    {
      // qDebug() << __FILE__ << __LINE__ << "dt integration accounted for:"
      //<< QString("[%1-%2]\n").arg(dtStart).arg(dtEnd) << "\n";
    }

  // We just have parsed a spectrum and we have all there relevant data in
  // the various data members.

  // We can immediately check for rt and dt integration:
  if(rtIntegration)
    {
      if(m_scanStartTime < rtStart || m_scanStartTime > rtEnd)
        return 0;
    }
  if(dtIntegration)
    {
      if(m_curDriftTime < dtStart || m_curDriftTime > dtEnd)
        return 0;
    }

  if(mp_newSpectrum == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  double newTic = 0;

  // Now the more specific mz integration
  if(mzIntegration)
    {

      // We need to integrate the intensity of the various mass peaks of the
      // spectrum into the TIC, but only for the mz integration range.

      newTic = mp_newSpectrum->valSum(mzStart, mzEnd);
    }
  else
    newTic = mp_newSpectrum->valSum();

  // Now check if there was already a mass spectrum that was processed with
  // the same m_scanStartTime (which should occur if the mass spectra were
  // acquired for a mobility experiment).

  double oldTic = m_keyValMap.value(m_scanStartTime, qSNaN());

  if(!qIsNaN(oldTic))
    {

      // There was a same retention time processed already. Simply make the
      // intensity increment.

      newTic += oldTic;
    }

  // Now, either overwrite the previous pair (not a QMultiMap) with the
  // incremented tic or simply insert the new one.

  // qDebug() << __FILE__ << __LINE__
  //<< "Inserting new m_keyValMap pair:" << m_scanStartTime << "-"
  //<< newTic;

  m_keyValMap.insert(m_scanStartTime, newTic);

  return 1;
}


//! Integrate the data loaded from file to a mass spectrum.
/*!

  The data are loaded from the file in order to craft a mass spectrum.

  When running the integration, the \c m_history History instance member is
  checked. Any integration listed in the History is taken into account.

  The <m/z,i> value pairs are stocked by combination in the \c m_massSpectrum
  MassSpectrum instance, that will later be used to craft two <mz> and <i>
  vectors. Indeed, this function is called by loadSpectrumToVectors().

  \return -1 upon error, 0 if no integration occurred, 1 otherwise.

  \sa integrateToRt() <br> integrateToDt().

*/
int
MassSpecDataFileLoaderPwiz::integrateToMz()
{

  // We are creating a mass spectrum. We may have a history to take into
  // account.

  // RT integration.
  // ===============
  bool rtIntegration = false;
  double rtStart     = -1;
  double rtEnd       = -1;

  rtIntegration = m_history.innermostRtRange(&rtStart, &rtEnd);
  if(rtIntegration)
    {
      // qDebug() << __FILE__ << __LINE__ << "rt integration accounted for: \n"
      //<< QString("[%1-%2]\n").arg(rtStart).arg(rtEnd);
    }

  // MZ integration.
  // ===============
  bool mzIntegration = false;
  double mzStart     = -1;
  double mzEnd       = -1;

  mzIntegration = m_history.innermostMzRange(&mzStart, &mzEnd);
  if(mzIntegration)
    {
      // qDebug() << __FILE__ << __LINE__ << "mz integration accounted for: \n"
      //<< QString("[%1-%2]\n").arg(mzStart).arg(mzEnd);
    }

  // DT integration.
  // ===============
  bool dtIntegration = false;
  double dtStart     = -1;
  double dtEnd       = -1;

  // If the history is comprised of many successive integrations on the
  // basis of dt value ranges, then we should make sure we do take into
  // account the more internal dt value range.
  dtIntegration = m_history.innermostDtRange(&dtStart, &dtEnd);
  if(dtIntegration)
    {
      // qDebug() << __FILE__ << __LINE__ << "dt integration accounted for: \n"
      //<< QString("[%1-%2]\n").arg(dtStart).arg(dtEnd);
    }

  // We just have parsed a spectrum and we have all there relevant data in the
  // various data members.

  // We can immediately check for rt and dt integration:

  if(rtIntegration)
    {
      if(m_scanStartTime < rtStart || m_scanStartTime > rtEnd)
        {
          // qDebug() << __FILE__ << __LINE__ << "returning because "
          //"m_scanStartTime < rtStart || "
          //"m_scanStartTime > rtEnd";

          return 0;
        }
    }
  if(dtIntegration)
    {
      if(m_curDriftTime < dtStart || m_curDriftTime > dtEnd)
        {
          // qDebug() << __FILE__ << __LINE__ << "returning because "
          //"m_curDriftTime < dtStart || "
          //"m_curDriftTime > dtEnd";

          return 0;
        }
    }

  if(mp_newSpectrum == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  // qDebug() << __FILE__ << __LINE__
  //<< "mp_newSpectrum has size:" << mp_newSpectrum->size();

  if(mzIntegration)
    // The history contains an item about mz ranges...
    m_massSpectrum.combine(*mp_newSpectrum, mzStart, mzEnd);
  else
    m_massSpectrum.combine(*mp_newSpectrum);

  // qDebug() << __FILE__ << __LINE__
  //<< "m_massSpectrum has size:" << m_massSpectrum.size();

  return 1;
}


//! Integrate the data loaded from file to a drift spectrum.
/*!

  The data are loaded from the file in order to craft a drift spectrum.

  When running the integration, the \c m_history History instance member is
  checked. Any integration listed in the History is taken into account.

  The <dt,i> value pairs are stocked by combination in the \c m_keyValMap
  member, that will later be used to craft two <dt> and <i> vectors. Indeed,
  this function is called by loadSpectrumToVectors().

  \return -1 upon error, 0 if no integration occurred, 1 otherwise.

  \sa integrateToMz() <br> integrateToDt().

*/
int
MassSpecDataFileLoaderPwiz::integrateToDt()
{

  // We are creating a drift spectrum. We may have a history to take into
  // account.

  // RT integration.
  // ===============
  bool rtIntegration = false;
  double rtStart     = -1;
  double rtEnd       = -1;

  rtIntegration = m_history.innermostRtRange(&rtStart, &rtEnd);
  if(rtIntegration)
    {
      // qDebug() << __FILE__ << __LINE__ << "rt integration accounted for:"
      //<< QString("[%1-%2]\n").arg(rtStart).arg(rtEnd) << "\n";

      // qDebug() << __FILE__ << __LINE__
      //<< "m_scanStartTime:" << m_scanStartTime
      //<< "rtStart:" << rtStart << "rtEnd:" << rtEnd;
    }

  // MZ integration.
  // ===============
  bool mzIntegration = false;
  double mzStart     = -1;
  double mzEnd       = -1;

  mzIntegration = m_history.innermostMzRange(&mzStart, &mzEnd);
  if(mzIntegration)
    {
      // qDebug() << __FILE__ << __LINE__ << "mz integration accounted for:"
      //<< QString("[%1-%2]\n").arg(mzStart).arg(mzEnd) << "\n";

      // qDebug() << __FILE__ << __LINE__ << "mzStart:" << mzStart
      //<< "mzEnd:" << mzEnd;
    }

  // DT integration.
  // ===============
  bool dtIntegration = false;
  double dtStart     = -1;
  double dtEnd       = -1;

  dtIntegration = m_history.innermostDtRange(&dtStart, &dtEnd);
  if(dtIntegration)
    {
      // qDebug() << __FILE__ << __LINE__ << "dt integration accounted for:"
      //<< QString("[%1-%2]\n").arg(dtStart).arg(dtEnd) << "\n";

      // qDebug() << __FILE__ << __LINE__ << "m_curDriftTime:" <<
      // m_curDriftTime
      //<< "dtStart:" << dtStart << "dtEnd:" << dtEnd;
    }

  // Now that we have all the start/end pairs, we can deal with the spectrum
  // that we have just parsed.

  // We can immediately check for rt and dt integration:
  if(rtIntegration)
    {
      if(m_scanStartTime < rtStart || m_scanStartTime > rtEnd)
        {
          // qDebug() << __FILE__ << __LINE__
          //<< "returning because m_scanStartTime "
          //"< rtStart || m_scanStartTime > "
          //"rtEnd";
          return 0;
        }
      else
        {
          // qDebug() << __FILE__ << __LINE__ << "Correct rt integration
          // value:"
          //<< "m_scanStartTime:" << m_scanStartTime
          //<< "rtStart:" << rtStart << "rtEnd:" << rtEnd;
        }
    }
  if(dtIntegration)
    {
      if(m_curDriftTime < dtStart || m_curDriftTime > dtEnd)
        {
          // qDebug() << __FILE__ << __LINE__
          //<< "returning because m_curDriftTime "
          //"< dtStart || m_curDriftTime > "
          //"dtEnd";
          return 0;
        }
      else
        {
          // qDebug() << __FILE__ << __LINE__ << "Correct dt integration
          // value:"
          //<< "m_curDriftTime:" << m_curDriftTime
          //<< "dtStart:" << dtStart << "dtEnd:" << dtEnd;
        }
    }

  // At this point we know that we'll need the data:

  if(mp_newSpectrum == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  // qDebug() << __FILE__ << __LINE__ << "for m_curDriftTime:" <<
  // m_curDriftTime
  //<< "newSpectrum size:" << newSpectrum->size();

  double newTic = 0;

  // Now the more specific mz integration
  if(mzIntegration)
    {

      // We need to integrate the intensity of the various mass peaks of the
      // spectrum into the TIC, but only for the mz integration range.

      newTic = mp_newSpectrum->valSum(mzStart, mzEnd);
    }
  else
    newTic = mp_newSpectrum->valSum();

  // Now check if there was already a mass spectrum that was processed with
  // the same m_curDriftTime (which should occur if the mass spectra were
  // acquired for a mobility experiment and there was more than one mobility
  // cycle acquisition.

  double oldTic = m_keyValMap.value(m_curDriftTime, qSNaN());

  if(!qIsNaN(oldTic))
    {

      // There was a same retention time processed already. Simply make the
      // intensity increment.

      // qDebug() << __FILE__ << __LINE__
      //<< "There was already a tic for drift time: "
      //<< m_curDriftTime;

      newTic += oldTic;
    }

  // Now, either overwrite the previous pair (not a QMultiMap) with the
  // incremented tic or simply insert the new one.

  m_keyValMap.insert(m_curDriftTime, newTic);

  // qDebug() << __FILE__ << __LINE__
  //<< "After insertion new m_keyValMap pair:" << m_curDriftTime << "-"
  //<< newTic << "New size of the map:" << m_keyValMap.size();

  return 1;
}


///////////////////////// PUBLIC ////////////////////
///////////////////////// PUBLIC ////////////////////
///////////////////////// PUBLIC ////////////////////


//! Construct a MassSpecDataFileLoaderPwiz instance.
/*!

  \param fileName name of the file from which to load the data.

*/
MassSpecDataFileLoaderPwiz::MassSpecDataFileLoaderPwiz(const QString &fileName)
  : MassSpecDataFileLoader{fileName}
{
  // Store the mzML filename for the metaData table of the database:

  m_msFileMetaData.setStringItem("fileName", fileName);
}

//! Destruct \c this MassSpecDataFileLoaderPwiz instance.
/*!

  The freeing process <em>does not involve</em> freeing the MassSpectrum
  instances in \c mp_massSpecDataSet because they are not owned by \c this
  MassSpecDataFileLoaderPwiz instance.

*/
MassSpecDataFileLoaderPwiz::~MassSpecDataFileLoaderPwiz()
{
  // Do not delete mp_newSpectrum: it might have been transferred *as is* to
  // the mp_massSpecDataSet!!!
  //
  // NO! delete mp_newSpectrum;
}


//! Set the name of the file from which to load the data.
/*!

  \param fileName name of the file from which to load the data.

*/
void
MassSpecDataFileLoaderPwiz::setFileName(const QString &fileName)
{
  m_fileName = fileName;
}


//! Set the MassSpecDataSet in which to store the mass data.
/*!

  When loading a mass spectrometry data file, the data are stored in various
  structures all belonging to the MassSpecDataSet instance passed as
  parameter.

  \param massSpecDataSet pointer to a MassSpecDataSet instance in which to
  store the mass data loaded from file.

*/
void
MassSpecDataFileLoaderPwiz::setMassSpecDataSet(MassSpecDataSet *massSpecDataSet)
{
  if(massSpecDataSet == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  mp_massSpecDataSet = massSpecDataSet;
}


//! Tells if the MassSpecDataFileLoaderPwiz load can load data.
/*!

  The file with file name \p fileName is analyzed to determine if its data can
  be loaded by \c this loader.

  \param fileName name of the mass data file to load data from.

  \return true if the format of the file can be read by \c this loader.

*/
int
MassSpecDataFileLoaderPwiz::canLoadData(QString fileName,
                                        MassSpecDataFileLoaderPwiz *pwiz)
{
  QFile file(fileName);

  if(!file.open(QFile::ReadOnly | QFile::Text))
    {
      qDebug() << __FILE__ << __LINE__ << "Failed to open file" << fileName;

      return MassSpecDataFileFormat::MASS_SPEC_DATA_FILE_FORMAT_NOT_SET;
    }
  else
    file.close();

  // We use the pwiz facilities to check if it can handle the file at hand.
  std::string head;
  pwiz::msdata::MSData result;

  // The default reader list handles mzML, mzXML, and other common formats.
  pwiz::msdata::ReaderPtr readerListPtr(new pwiz::msdata::DefaultReaderList);
  pwiz::msdata::CVID cvId =
    identifyFileFormat(readerListPtr, fileName.toStdString());

  // CVTermInfo cvInfo = cvTermInfo(cvId);
  // qDebug() << __FILE__ << __LINE__
  //<< "cvId:" << QString().fromStdString(cvInfo.shortName());

  if(cvId == pwiz::msdata::MS_mzML_format)
    {
      if(pwiz)
        pwiz->mp_pwizReader =
          static_cast<pwiz::msdata::ReaderPtr>(new pwiz::msdata::Reader_mzML);

      return MassSpecDataFileFormat::MASS_SPEC_DATA_FILE_FORMAT_MZML;
    }
  else if(cvId == pwiz::msdata::MS_ISB_mzXML_format)
    {
      if(pwiz)
        pwiz->mp_pwizReader =
          static_cast<pwiz::msdata::ReaderPtr>(new pwiz::msdata::Reader_mzXML);

      return MassSpecDataFileFormat::MASS_SPEC_DATA_FILE_FORMAT_MZXML;
    }

  return MassSpecDataFileFormat::MASS_SPEC_DATA_FILE_FORMAT_NOT_SET;
}


//! Determines from the mass data file the total number of spectra.
/*!

  Reads the file and determines the size of the spectrum list.

  \return an integer containing the number of spectra in the spectrum list.

*/
int
MassSpecDataFileLoaderPwiz::readSpectrumCount()
{
  std::string head;
  pwiz::msdata::MSData result;

  if(!canLoadData(m_fileName, this))
    return -1;

  if(mp_pwizReader == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. No reader has been set. Program aborted.",
           __FILE__,
           __LINE__);

  std::string fileType = mp_pwizReader->getType();
  QString readerTypeString(QString::fromStdString(fileType));

  // qDebug() << __FILE__ << __LINE__ << "reader type:" << readerTypeString;

  mp_pwizReader->read(m_fileName.toStdString(),
                      head,
                      result,
                      /* int runIndex */ 0);

  pwiz::msdata::SpectrumListPtr spectrumListPtr = result.run.spectrumListPtr;
  m_spectrumCount                               = spectrumListPtr->size();

  return m_spectrumCount;
}


//! Load mass spectrometry data from file into MassSpecDataSet.
/*!

  This function is typically an entry-point function into the class workings.

  The data are loaded and stored in a structured way into \p massSpecDataSet.

  Note that \p massSpecDataSet is assigned to the member \m mp_massSpecDataSet
  for later reference throughout almost all functions in this class. Indeed,
  the fact that \c mp_massSpecDataSet is non-nullptr is means that the mass
  data have to be stored in the MassSpecDataSet instance pointed to by it.

  \param pointer to the MassSpecDataSet in which to stored in a structured way
  the data loaded from file.

  \return the number of loaded spectra; -1 upon error.

  \sa loadData().

*/
int
MassSpecDataFileLoaderPwiz::loadDataToMassSpecDataSet(
  MassSpecDataSet *massSpecDataSet)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";

  mp_massSpecDataSet = massSpecDataSet;

  // Will make available the total spectrum count for us to use later.
  if(!seedDataLoading())
    return -1;

  delete mp_newSpectrum;
  mp_newSpectrum = Q_NULLPTR;
  m_massSpectrum.deleteDataPoints();
  m_massSpectrum.clear();

  m_scanStartTime = -1;
  m_curDriftTime  = -1;
  m_ticIntensity  = 0.0;

  // Counter of the effectively loaded spectra
  m_storedSpectraCount = 0;

  m_totalSkippedSpectra = 0;

  // The counter of spectra that we iterated over.
  int count = 0;

  pwiz::msdata::SpectrumPtr spectrumPtr = Q_NULLPTR;

  while(nextSpectrum(&spectrumPtr) != -1)
    {
      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
      //<< "got a new spectrumPtr:" << spectrumPtr.get();

      QCoreApplication::processEvents();

      // Increment the count, and then handle the load progression and the
      // feedback messaging.
      updateLoadProgression(++count);

      if(m_isOperationCancelled)
        break;

      if(m_msLevel != 1)
        continue;

      // At this point we really have a PWIZ spectrum pointer that we need to
      // check
      if(spectrumPtr != Q_NULLPTR)
        {

          // At this point we know that we should account the currently parsed
          // spectrum. We need to convert that to a MassSpectrum.
          mp_newSpectrum = createNewSpectrum(spectrumPtr);

          // If the pwiz spectrum is empty the returned pointer is nullptr.
          if(mp_newSpectrum == Q_NULLPTR)
            continue;

          // When parsing to the mass spec data set, we want to actually load
          // data from file, that is get *everything* no specific restrictions
          // apply.

          // qDebug() << __FILE__ << __LINE__
          //<< "We are parsing to the MassSpecDataSet.";

          // Actually perform the loading of mp_massSpectrum to the data set.

          if(loadSpectrumToMassSpecDataSet() != 1)
            qDebug() << __FILE__ << __LINE__
                     << "Failed to load a spectrum to the MassSpecDataSet.";
          else
            // The spectrum was effectively stored.
            ++m_storedSpectraCount;

          // Do not touch the mp_newSpectrum because it now belongs to
          // MassSpecDataSet.

          // qDebug() << __FILE__ << __LINE__
          //<< "Done loading spectrum to mass spec data set:"
          //<< "m_spectrumIndex:" << m_spectrumIndex
          //<< "m_scanStartTime:" << m_scanStartTime;
        }
      else
        {
          qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
                   << "received a nullptr spectrum pointer";
          ++m_totalSkippedSpectra;
        }
    }
  // End of
  // while(nextSpectrum(&spectrumPtr) != -1)

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "at this point, we parsed all the spectra:"
  //<< "total number of effectively stored spectral:" << m_storedSpectraCount
  //<< "total skipped spectra:" << m_totalSkippedSpectra;

  int rtHashSize = mp_massSpecDataSet->m_rtHash.size();
  int dtHashSize = mp_massSpecDataSet->m_dtHash.size();

  // Sanity check, that is only correct if we are not in streamed mode and
  // we load a drift data file.

  if(m_curDriftTime != -1 && !mp_massSpecDataSet->isStreamed())
    {
      if(rtHashSize != dtHashSize)
        qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);
    }

  QString msg = QString(
                  "Number of spectra in the set: %1\n"
                  "Number of retention time values: %2\n"
                  "Number of drift time values: %3\n"
                  "Number of effectively stored spectra: %4\n"
                  "Number of skipped spectra: %5\n")
                  .arg(mp_massSpecDataSet->m_massSpectra.size())
                  .arg(rtHashSize)
                  .arg(dtHashSize)
                  .arg(m_storedSpectraCount)
                  .arg(m_totalSkippedSpectra);

  // qDebug() << __FILE__ << __LINE__ << msg.toLatin1();

  emit updateFeedbackSignal(
    msg, -1, true /* setVisible */, LogType::LOG_TO_BOTH);

  // for(int iter = 0; iter < keyList.size(); ++iter)
  //{
  // msXpSlibmass::MassSpectrum *spectrum =
  // mp_massSpecDataSet->m_rtHash.value(keyList.at(iter));

  // qDebug() << __FILE__ << __LINE__
  // << "For retention time " << keyList.at(iter) << "the tic is: "
  // << spectrum->valSum();
  //}

  return m_storedSpectraCount;
}


int
MassSpecDataFileLoaderPwiz::loadDataToXyFiles(const QString &xyFileName,
                                              int startIndex,
                                              int endIndex)
{

  // Will make available the total spectrum count for us to use later.
  if(!seedDataLoading())
    return -1;

  int specIndexStart = std::min(startIndex, endIndex);
  int specIndexEnd   = std::max(startIndex, endIndex);

  // Sanity checks.
  if(specIndexStart <= -1)
    specIndexStart = 0;
  if(specIndexStart >= m_spectrumCount)
    return -1;

  if(endIndex <= -1)
    specIndexEnd = m_spectrumCount - 1;
  if(specIndexEnd >= m_spectrumCount)
    return -1;

  // Count of spectra actually written as XY files.
  m_storedSpectraCount = 0;

  // Count of the spectra that were iterated over
  int count = 0;

  pwiz::msdata::SpectrumPtr spectrumPtr;

  while(m_spectrumIndex >= specIndexStart - 1 &&
        m_spectrumIndex < specIndexEnd - 1 && nextSpectrum(&spectrumPtr) != -1)
    {
      QCoreApplication::processEvents();

      if(m_isOperationCancelled)
        break;

      // Increment the count, and then handle the load progression and the
      // feedback messaging.
      updateLoadProgression(++count);

      if(m_msLevel != 1)
        continue;

      // At this point we really have a PWIZ spectrum pointer that we need to
      // check
      if(spectrumPtr != Q_NULLPTR)
        {
          // We need to convert that to a MassSpectrum.
          mp_newSpectrum = createNewSpectrum(spectrumPtr);

          // If the pwiz spectrum is empty the returned pointer is nullptr.
          if(mp_newSpectrum == Q_NULLPTR)
            continue;

          QString text = mp_newSpectrum->asText(-1, -1);

          // Delete immediately, as we do not need it anymore.
          delete mp_newSpectrum;
          mp_newSpectrum = Q_NULLPTR;

          if(text.isEmpty())
            return 0;

          // Craft the destination file name or use it as it, depending...

          QString destFileName;

          if(xyFileName.isEmpty())
            {
              // There is no destination name nor dir, thus craft the name on
              // the
              // basis of the original file name.

              destFileName =
                QString("%1-%2.xy").arg(m_fileName).arg(m_spectrumIndex);

              qDebug() << __FILE__ << __LINE__ << __FUNCTION__
                       << "The xy file name is empty. Crafted file name:"
                       << destFileName;
            }
          else
            {
              QFileInfo fileInfo(m_xyFileName);

              if(fileInfo.isDir())
                {
                  // The name is of a directory. Craft the filename and prepend
                  // the
                  // directory.

                  destFileName =
                    QString("%1/%2-%3.xy")
                      .arg(QFileInfo(xyFileName).absoluteFilePath())
                      .arg(QFileInfo(m_fileName).fileName())
                      .arg(m_spectrumIndex);

                  qDebug()
                    << __FILE__ << __LINE__ << __FUNCTION__
                    << "The xy file name is a directory. Crafted file name:"
                    << destFileName;
                }
              else
                {
                  // The name is for a file.
                  destFileName = xyFileName;

                  qDebug() << __FILE__ << __LINE__ << __FUNCTION__
                           << "The xy file name is a file. Using it as is."
                           << destFileName;
                }
            }

          QFile file(destFileName);
          file.open(QIODevice::WriteOnly | QIODevice::Truncate);

          QTextStream out(&file);
          out << text;

          ++m_storedSpectraCount;

          file.close();
        }
      // End of
      // if(spectrumPtr != Q_NULLPTR)
    }
  // End of
  // while(m_spectrumIndex >= specIndexStart - 1 &&
  // m_spectrumIndex < specIndexEnd - 1 &&
  // nextSpectrum(&spectrumPtr) == -1)

  return m_storedSpectraCount;
}


//! Load mass spectrometry data from file into a SQLite3-based db file.
/*!

  This function is typically an entry-point function into the class workings.

  This function is used to perform the file format conversion from mzML to
  SQLite3-based db format.

  \param Name of the database file to create. Cannot be empty.

  \return the number of loaded spectra; -1 upon error.

*/
int
MassSpecDataFileLoaderPwiz::loadDataToDatabase(const QString &dbFileName)
{

  if(dbFileName.isEmpty())
    return -1;

  // Will make available the total spectrum count for us to use later.
  if(!seedDataLoading())
    return -1;

  QString connectionName =
    QString("%1/%2").arg(dbFileName).arg(msXpS::pointerAsString(this));

  QSqlDatabase database = QSqlDatabase::addDatabase("QSQLITE", connectionName);
  database.setDatabaseName(dbFileName);

  if(!database.open())
    {
      qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
               << "Failed to open database.";

      return -1;
    }

  MassSpecSqlite3Handler sqlite3Handler;
  sqlite3Handler.initializeDbForWrite(database);

  delete mp_newSpectrum;
  mp_newSpectrum = Q_NULLPTR;
  m_massSpectrum.deleteDataPoints();
  m_massSpectrum.clear();

  m_scanStartTime = -1;
  m_curDriftTime  = -1;
  m_ticIntensity  = 0.0;

  // Counter of the effectively loaded spectra
  m_storedSpectraCount = 0;

  // The counter of spectra that we iterated over.
  int count = 0;

  pwiz::msdata::SpectrumPtr spectrumPtr = Q_NULLPTR;

  while(nextSpectrum(&spectrumPtr) != -1)
    {
      QCoreApplication::processEvents();

      if(m_isOperationCancelled)
        break;

      // Increment the count, and then handle the load progression and the
      // feedback messaging.
      updateLoadProgression(++count);

      if(m_msLevel != 1)
        continue;

      // At this point we really have a PWIZ spectrum pointer that we need to
      // check
      if(spectrumPtr != Q_NULLPTR)
        {
          // For this task we need the Pwiz data
          pwiz::msdata::BinaryDataArrayPtr mzArrayPtr =
            spectrumPtr->getMZArray();
          pwiz::msdata::BinaryDataArrayPtr iArrayPtr =
            spectrumPtr->getIntensityArray();

          // Sanity check:
          if(mzArrayPtr->data.size() != iArrayPtr->data.size())
            qFatal(
              "Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

          // qDebug() << __FILE__ << __LINE__
          //<< "mzArray/iArray at index " << iter
          //<< "have size:" << mzArrayPtr->data.size();

          // qDebug() << __FILE__ << __LINE__ << "The data load is for "
          //"injecting data to the SQLite3 database.";

          // We only store the driftData to the database if this file is
          // actually about a mobility experiment.

          if(m_curDriftTime != -1)
            {

              // Now that we have settled down with proper values for
              // drift stuff, let's try to send these to the database (or
              // get a row number if the data were already in, that is if
              // we are iterating in a spectrum that belong not to the
              // first drift cycle run).

              bool sqlResult = sqlite3Handler.writeDbDriftData(
                database, m_curDriftTime, &m_driftDataId);

              if(!sqlResult || m_driftDataId == -1)
                {
                  qFatal(
                    "Fatal error at %s@%d -- %s(). "
                    "Failed to insert precursor ion data in the database."
                    "Program aborted.",
                    __FILE__,
                    __LINE__,
                    __FUNCTION__);
                }
            }

          // qDebug() << __FILE__ << __LINE__
          //<< "We are transferring the data to the SQLite3 database.";

          // At this point we know that we should account the currently parsed
          // spectrum. We need to convert that to a MassSpectrum.
          mp_newSpectrum = createNewSpectrum(spectrumPtr);

          // If the pwiz spectrum is empty the returned pointer is nullptr.
          if(mp_newSpectrum == Q_NULLPTR)
            continue;

          // Compute the TIC because we'll need it for the SQL statement below.
          m_tic = mp_newSpectrum->valSum();

          // Delete the allocated spectrum, because we'll use another mechanism
          // to feed the database.

          delete mp_newSpectrum;
          mp_newSpectrum = Q_NULLPTR;

          // Because pwiz provides the mass spectral data in the form of
          // std::vector<double> data, we need to encode these data before we
          // can inject them to the database.

          pwiz::msdata::BinaryDataEncoder::Config config;
          config.precision =
            pwiz::msdata::BinaryDataEncoder::Precision::Precision_64;

          // mzML standard stipulates that data are always
          // ByteOrder::ByteOrder_LittleEndian.

          config.byteOrder =
            pwiz::msdata::BinaryDataEncoder::ByteOrder::ByteOrder_LittleEndian;

          config.compression =
            pwiz::msdata::BinaryDataEncoder::Compression::Compression_Zlib;

          m_dataCompression = msXpS::DataCompression::DATA_COMPRESSION_ZLIB;

          pwiz::msdata::BinaryDataEncoder encoder(config);

          std::string result;
          size_t binaryByteCount;

          QByteArray mzQByteArray;
          QByteArray iQByteArray;

          // First, encode mz data ///////////////////////////////
          encoder.encode(mzArrayPtr->data, result, &binaryByteCount);

          if(binaryByteCount == 0)
            qFatal(
              "Fatal error at %s@%d. Failed to encode mz data. Program "
              "aborted.",
              __FILE__,
              __LINE__);

          mzQByteArray = QByteArray::fromStdString(result);

          // Second, encode i data ///////////////////////////////
          encoder.encode(iArrayPtr->data, result, &binaryByteCount);

          if(binaryByteCount == 0)
            qFatal(
              "Fatal error at %s@%d. Failed to encode i data. Program "
              "aborted.",
              __FILE__,
              __LINE__);

          iQByteArray = QByteArray::fromStdString(result);

          // This call fails with qFatal, so no need to test for error
          // here.

          sqlite3Handler.writeDbSpectralData(database,
                                             m_spectrumIndex,
                                             m_spectrumTitle,
                                             m_specPeakCount,
                                             m_msLevel,
                                             m_tic,
                                             m_scanStartTime,
                                             m_driftDataId,
                                             m_scanWindowLowerLimit,
                                             m_scanWindowUpperLimit,
                                             mzQByteArray,
                                             iQByteArray);

          // The spectrum was effectively stored.
          ++m_storedSpectraCount;
        }
      // End of
      // if(spectrumPtr != Q_NULLPTR)
    }
  // End of
  // while(nextSpectrum(&spectrumPtr) != -1)

  // We can finish sending the metadata to the corresponding database
  // table.

  // We use zlib compression systematically, see above.
  m_msFileMetaData.setIntItem("compressionType",
                              static_cast<int>(m_dataCompression));

  // Also, set the number of actual spectra stored in the database.
  m_msFileMetaData.setIntItem("spectrumListCount", m_storedSpectraCount);

  if(!sqlite3Handler.writeDbMassSpecFileMetaData(database, m_msFileMetaData))
    {
      qDebug() << __FILE__ << __LINE__
               << "Failed to insert the metaData in the database.";
      qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);
    }

  // Finally, close the database file.
  database.close();

  return m_storedSpectraCount;
}


//! Load mass data from file in a streamed mode, along with integration.
/*!

  The mass spectrometry data are loaded from the file. This function sets the
  stage for the loadData() function to actually start the job, which in turn
  will call the private loadData() function.

  This function mimicks the loadData(const History &history, IntegrationType
  integrationType,
  QVector<double> *keyVector, QVector<double> *valVector,
  double *intensity) function but performs the loading in a streamed manner.

  Loading mass spectrometry data in a streamed manner means that each spectrum
  that is read from the disk file undergoes the required process and then is
  freed. In this data loading mode, there is not storage of the data in
  memory. For example, when loading a data file in a streamed mode, with a
  retention time integration, that means that at the end of the process, the
  function yiels two vectors with data: the \p keyVector will contain the
  retention time values, the \p valVector will contain the intensity values.
  Altogether the two vectors make a TIC chromatogram. The same is true for a
  mz integration (make a combined mass spectrum out of the data in the file)
  or for a dt integration (make a combined drift spectrum out of the data in
  the file).

  \param history reference to the History instance to be used to parameterize
  the secondary integrations to be taken into account.

  \param integrationType Main integration type to use to determine the kind of
  data to be stored in the vectors (is this a retention time or mz or dt
  integration?).

  \param keyVector pointer to a vector in which to store the 'x-axis' of the
  result data.

  \param valVector pointer to a vector in which to store the 'y-axis' of the
  result data.

  \return the number of loaded spectra, -1 on error.

  \sa streamedTicIntensity()

*/
int
MassSpecDataFileLoaderPwiz::streamedIntegration(const History &history,
                                                IntegrationType integrationType,
                                                QVector<double> *keyVector,
                                                QVector<double> *valVector)
{
  qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";

  if(keyVector == Q_NULLPTR || valVector == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  if(!history.isValid())
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "The History is not valid."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  m_history = history;

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "Now loading data with history "
  //<< m_history.asText() << "to key/val vectors";

  if(integrationType != IntegrationType::ANY_TO_MZ &&
     integrationType != IntegrationType::ANY_TO_DT &&
     integrationType != IntegrationType::ANY_TO_RT)
    {
      qDebug() << __FILE__ << __LINE__ << __FUNCTION__
               << "No valid integration type is defined for the data to be "
                  "produced. Returning.";

      return -1;
    }

  m_integrationType = integrationType;

  // Will make available the total spectrum count for us to use later.
  if(!seedDataLoading())
    return -1;

  // Start with fresh containers:
  m_keyValMap.clear();

  delete mp_newSpectrum;
  mp_newSpectrum = Q_NULLPTR;
  m_massSpectrum.deleteDataPoints();
  m_massSpectrum.clear();

  // We have not done binning already, that will be performed at first
  // spectrum reading.
  m_isBinningSetup = false;

  // When parsing to vectors, we want to actually load
  // data from file, that is get *everything* no specific restrictions
  // apply.

  // qDebug() << __FILE__ << __LINE__
  //<< "We are parsing to the vectors.";

  pwiz::msdata::SpectrumPtr spectrumPtr = Q_NULLPTR;

  // Counter of the effectively loaded spectra
  m_storedSpectraCount = 0;

  // Count of the spectra that were iterated over
  int count = 0;

  while(nextSpectrum(&spectrumPtr) != -1)
    {
      QCoreApplication::processEvents();

      // Increment the count, and then handle the load progression and the
      // feedback messaging.
      updateLoadProgression(++count);

      if(m_isOperationCancelled)
        break;

      if(m_msLevel != 1)
        continue;

      // At this point we really have a PWIZ spectrum pointer that we need to
      // check
      if(spectrumPtr != Q_NULLPTR)
        {
          // We need to convert that to a MassSpectrum.
          mp_newSpectrum = createNewSpectrum(spectrumPtr);

          // If the pwiz spectrum is empty the returned pointer is nullptr.
          if(mp_newSpectrum == Q_NULLPTR)
            continue;

          // Depending on the integration that was requested, we need to compute
          // stuff in different containers: a mass spectrum for toMz, or a
          // keyVal map for toRt and toDT. The relevant computations are
          // performed for each useful spectrum using the corresponding
          // integrateToXy() functions.

          // qDebug() << __FILE__ << __LINE__ << __FUNCTION__
          //<< "m_integrationType:" << m_integrationType;

          if(m_integrationType == IntegrationType::ANY_TO_RT)
            {
              // We want to compute a TIC chrom.
              m_storedSpectraCount += integrateToRt();
            }
          else if(m_integrationType == IntegrationType::ANY_TO_MZ)
            {
              // We want to compute a mass spectrum.

              // We are performing a combination of various mass spectra (the
              // mp_newSpectrum contains the spectrum currently iterated from
              // the
              // file) into a single mass spectrum, the combined mass spectrum
              // m_massSpectrum.  When performing combinations, the user might
              // configure the way the binning (or no binning) should be setup.

              if(!m_isBinningSetup && m_specPeakCount)
                {
                  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
                  //<< "Setting up the bins.";

                  MzIntegrationParams mzIntegrationParams =
                    m_history.mzIntegrationParams();

                  m_massSpectrum.setMinMz(m_massSpecDataStats.m_minMz);
                  m_massSpectrum.setMaxMz(m_massSpecDataStats.m_maxMz);

                  m_massSpectrum.setBinningType(
                    mzIntegrationParams.m_binningType);
                  m_massSpectrum.setBinSize(mzIntegrationParams.m_binSize);
                  m_massSpectrum.setBinSizeType(
                    mzIntegrationParams.m_binSizeType);
                  m_massSpectrum.applyMzShift(
                    mzIntegrationParams.m_applyMzShift);
                  m_massSpectrum.setRemoveZeroValDataPoints(
                    mzIntegrationParams.m_removeZeroValDataPoints);

                  m_massSpectrum.setupBinnability(*mp_newSpectrum);

                  // Setting this variable to true ensures that this binning
                  // will
                  // not be performed anymore.
                  m_isBinningSetup = true;
                }

              // We want to compute a mass spectrum.
              m_storedSpectraCount += integrateToMz();
            }
          else if(m_integrationType == IntegrationType::ANY_TO_DT)
            {
              if(m_curDriftTime == -1)
                return 0;

              // We want to compute a drift spectrum.
              m_storedSpectraCount += integrateToDt();

              // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
              //<< "should have itegrated a new drift spectrum";
            }
          else
            qFatal(
              "Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

          delete mp_newSpectrum;
          mp_newSpectrum = Q_NULLPTR;
        }
    }
  // End of
  // while(m_spectrumIndex >= specIndexStart - 1)

  // At this point, the mass data have been crunched and are now stored in
  // the proper format. All we need to do is convert the intermediate format
  // to the vectors passed as parameter, depending on the main integration
  // asked for.

  // Depending on the integration type, the data are now in the form of a
  // m_massSpectrum that is the result of the combination of all the parsed
  // spectra (to MZ integration) or in the form of a m_keyValMap where the
  // relevant data were stored (to RT/DT integration).

  if(m_integrationType == IntegrationType::ANY_TO_MZ)
    {
      // At this point all the data have been integrated into m_massSpectrum.
      // However, we still have to makes we apply the post-combination process
      // if so is required (remove zero-val data points, for example).

      if(m_massSpectrum.isRemoveZeroValDataPoints())
        m_massSpectrum.removeZeroValDataPoints();

      // So we need to convert that to vectors:

      m_massSpectrum.toVectors(keyVector, valVector);

      MzIntegrationParams mzIntegrationParams = m_history.mzIntegrationParams();

      if(mzIntegrationParams.m_applySavGolFilter)
        {
          // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
          //<< "savGol is to be computed";

          SavGolFilter savGolFilter(mzIntegrationParams.m_savGolParams);

          savGolFilter.initializeData(*keyVector, *valVector);

          savGolFilter.filter();

          QVector<double> filtered;
          savGolFilter.filteredData(filtered);

          valVector->clear();
          valVector->append(filtered);
        }

      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
      //<< "At this point we should have filled-in vectors:"
      //<< "keyVector size:" << keyVector->size();
    }
  else if(m_integrationType == IntegrationType::ANY_TO_DT)
    {
      // The data were stored in m_keyValMap in the form of (dt, tic) pairs.

      QList<double> keyList = m_keyValMap.keys();

      for(int iter = 0; iter < keyList.size(); ++iter)
        {
          double key = keyList.at(iter);
          keyVector->append(key);
          valVector->append(m_keyValMap.value(key));
        }
    }
  else if(m_integrationType == IntegrationType::ANY_TO_RT)
    {
      // The data were stored in m_keyValMap in the form of (rt, tic) pairs.

      QList<double> keyList = m_keyValMap.keys();

      for(int iter = 0; iter < keyList.size(); ++iter)
        {
          double key = keyList.at(iter);
          keyVector->append(key);
          valVector->append(m_keyValMap.value(key));
        }
    }

  if(!keyVector->size())
    qDebug() << __FILE__ << __LINE__
             << "The key vector is empty. Is not that worrisome?";

  // Sanity check:
  if(keyVector->size() != valVector->size())
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  m_history.freeList();
  m_integrationType = IntegrationType::NOT_SET;

  return m_storedSpectraCount;
}


//! Load mass data from file in a streamed mode to a single TIC intensity value.
/*!

  This function is used to perform what can be compared to a mathematical
  integration. The data are loaded and for each spectrum its TIC intensity
  value is added to a variable that is returned at the end of the process in
  \p intensity.

  The mass spectrometry data are loaded from the file. This function sets the
  stage for the loadData() function to actually start the job, which in turn
  will call the private loadData() function.

  This function mimicks the loadData(const History &history, IntegrationType
  integrationType, QVector<double> *keyVector, QVector<double> *valVector,
  double *intensity) function but performs the loading in a streamed manner.

  Loading mass spectrometry data in a streamed manner means that each spectrum
  that is read from the disk file undergoes the required process and then is
  freed. In this data loading mode, there is not storage of the data in
  memory. For example, when loading a data file in a streamed mode, with a TIC
  intensity integration will iterate in the mass spectrum list of the file and
  for each spectrum compute its TIC intensity value. That value is added to a
  variable the value of which is then assigned to \p intensity as the total
  integration of the data file. If \p history contains integration ranges,
  then the integration is performed the same way, by iterating in the whole
  file data but only accounting the TIC value of mass spectra complying with
  the integration types and ranges specified in \p history.

  \param history reference to the History instance to be used to parameterize
  the secondary integrations to be taken into account. The primary integration
  type is TIC chromatogram, since we are computing a total TIC integration.

  \param intensity pointer to the double variable in which to set the obtained
  TIC intensity value.

  \return the number of loaded spectra, -1 on error.

  \sa streamedIntegration().

*/
int
MassSpecDataFileLoaderPwiz::streamedTicIntensity(const History &history,
                                                 double *intensity)
{
  qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";

  if(intensity == Q_NULLPTR)
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "The pointer cannot be nullptr."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  if(!history.isValid())
    {
      qDebug() << __FILE__ << __LINE__
               << "The History is not valid. Returning.";

      qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);
    }

  m_history = history;

  qDebug() << __FILE__ << __LINE__ << "Now loading data with history "
           << m_history.asText() << "to key/val vectors";

  // Will make available the total spectrum count for us to use later.
  if(!seedDataLoading())
    return -1;

  // Start with fresh containers:
  m_ticIntensity = 0.0;
  m_keyValMap.clear();

  delete mp_newSpectrum;
  mp_newSpectrum = Q_NULLPTR;
  m_massSpectrum.deleteDataPoints();
  m_massSpectrum.clear();

  pwiz::msdata::SpectrumPtr spectrumPtr = Q_NULLPTR;

  // Counter of the effectively loaded spectra
  m_storedSpectraCount = 0;

  // Count of the spectra that were iterated over
  int count = 0;

  while(nextSpectrum(&spectrumPtr) != -1)
    {
      QCoreApplication::processEvents();

      // Increment the count, and then handle the load progression and the
      // feedback messaging.
      updateLoadProgression(++count);

      if(m_isOperationCancelled)
        break;

      if(m_msLevel != 1)
        continue;

      // At this point we really have a PWIZ spectrum pointer that we need to
      // check
      if(spectrumPtr != Q_NULLPTR)
        {

          // When calculating the Tic intensity, we have to check for all the
          // various integration ranges found in history. Indeed, this is not
          // load of data from a file to either mass spec data set or vectors,
          // we are doing an integration because the user is analyzing the data.
          // Thus, we need to ensure we account for any range restriction in
          // history so that the computed Tic intensity actually means something
          // to the user.


          // RT integration.
          // ===============
          bool rtIntegration = false;
          double rtStart     = -1;
          double rtEnd       = -1;

          rtIntegration = m_history.innermostRtRange(&rtStart, &rtEnd);
          if(rtIntegration)
            {

              qDebug() << __FILE__ << __LINE__
                       << "rt integration accounted for:"
                       << QString("[%1-%2]\n").arg(rtStart).arg(rtEnd) << "\n";

              if(m_scanStartTime < rtStart || m_scanStartTime > rtEnd)
                {
                  // We cannot account for the current spectrum because if falls
                  // out of the
                  // innermost retention time range in history.

                  continue; // to next spectrum entity.
                }
            }

          // DT integration.
          // ===============
          bool dtIntegration = false;
          double dtStart     = -1;
          double dtEnd       = -1;

          // If the history is comprised of many successive integrations on the
          // basis of dt value ranges, then we should make sure we do take into
          // account the more internal dt value range.
          dtIntegration = m_history.innermostDtRange(&dtStart, &dtEnd);
          if(dtIntegration)
            {

              qDebug() << __FILE__ << __LINE__
                       << "dt integration accounted for:"
                       << QString("[%1-%2]\n").arg(dtStart).arg(dtEnd) << "\n";

              if(m_curDriftTime < dtStart || m_curDriftTime > dtEnd)
                {
                  // Same as above but this time for the drift time range.

                  continue; // to next spectrum entity.
                }
            }

          // At this point we know that we should account the currently parsed
          // spectrum. We need to convert that to a MassSpectrum.
          mp_newSpectrum = createNewSpectrum(spectrumPtr);

          // If the pwiz spectrum is empty the returned pointer is nullptr.
          if(mp_newSpectrum == Q_NULLPTR)
            continue;

          // Now is the time to test the last integration range: MZ, which we
          // can honour since the spectrum is actually allocated.

          // MZ integration.
          // ===============
          bool mzIntegration = false;
          double mzStart     = -1;
          double mzEnd       = -1;

          mzIntegration = m_history.innermostMzRange(&mzStart, &mzEnd);

          if(mzIntegration)
            {

              qDebug() << __FILE__ << __LINE__
                       << "mz integration accounted for:"
                       << QString("[%1-%2]\n").arg(mzStart).arg(mzEnd) << "\n";

              m_ticIntensity += mp_newSpectrum->valSum(mzStart, mzEnd);
            }
          else
            m_ticIntensity += mp_newSpectrum->valSum();

          qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
                   << "At this index:" << m_spectrumIndex
                   << "the intensity is:" << m_ticIntensity;

          // The spectrum was effectively stored.
          ++m_storedSpectraCount;

          delete mp_newSpectrum;
          mp_newSpectrum = Q_NULLPTR;
        }
    }
  // End of
  // while(m_spectrumIndex >= specIndexStart - 1)

  // Finally, we set the intensity value to the parameter

  *intensity = m_ticIntensity;

  return m_storedSpectraCount;
}


} // namespace msXpSmineXpert
