/* 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>


/////////////////////// Qt includes
#include <QDir>
#include <QDebug>
#include <QSqlDatabase>
#include <QRegExp>
#include <QSqlQuery>
#include <QSqlError>
#include <QDebug>


/////////////////////// Local includes
#include <minexpert/nongui/MassSpecDataFileLoaderSqlite3.hpp>
#include <globals/globals.hpp>
#include <minexpert/nongui/History.hpp>


namespace msXpSmineXpert
{

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


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

  The MassSpecDataFileLoaderSqlite3 class is specialized in loading data from a
  SQLite3-based mass spectrometry database format file.

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

*/
QString
MassSpecDataFileLoaderSqlite3::formatDescription() const
{
  QString description = QString(
    "The db or sqlite3 file format describes mass data using\n"
    "a SQLite3 database with a specific format designed for msXperSuite.\n");

  return description;
}


//! Tell if the data are from an ion mobility MS experiment.
/*!

  The database-format file is asked for records having a drift bin count field
  value. If there is at least one record found, then the experiment is
  considered to be an ion mobility mass spectrometry experiment.

  \return true if the database file is from an ion mobility MS experiment; false
  otherwise.

*/
bool
MassSpecDataFileLoaderSqlite3::isDriftExperiment(
  MassSpecSqlite3Handler &handler, QSqlDatabase &database)
{

  // By asking the database to tell us if how many driftTime records it has in
  // the driftData table we'll know if this database is for a mobility
  // drift time experiment.

  return handler.isDriftExperiment(database);
}


//! Craft a SQL statement for a streamed integration.
/*!

  The SQL statement is crafted around criteria defined in the \p history
  History instance. Note that, if \p withRecordCountStatement is true, then
  the SQL statement includes a COUNT request. This is useful for determining
  the number of records that match the SQL statement.

  The \p integrateToDt parameter tells if the SQL statement should include
  drift mobility requests. This parameter is required because it might be
  necessary to craft SQL statement including ion mobility requests event if \p
  history does not contain any such item.

  \param history reference to the History to use for the configuration of the
  SQL statement.

  \param withRecordCountStatement tells if this call is to determine the
  number of records complying with the rest of the SQL statement.

  \param integrateToDt tells if the SQL statement should include ion mobility
  requests.

  \return a string with the crafted SQL statement.

  \sa

*/
QString
MassSpecDataFileLoaderSqlite3::craftSqlSelectStatementForStreamedIntegration(
  const History &history, bool withRecordCountStatement, bool integrateToDt)
{
  if(!history.isValid())
    {
      qFatal(
        "Fatal error at %s@%d -- %s(). "
        "Programming error: the History passed as parameter is not valid."
        "Program aborted.",
        __FILE__,
        __LINE__,
        __FUNCTION__);

      return QString();
    }

  // RT integration.
  // ===============
  bool rtIntegration = false;
  double rtStart     = -1;
  double rtEnd       = -1;
  rtIntegration      = history.innermostRtRange(&rtStart, &rtEnd);

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

  // Not used, see below for an explanation.
  // MZ integration.
  // ===============
  // bool mzIntegration = false;
  // double mzStart = -1;
  // double mzEnd = -1;
  // mzIntegration = history.innermostMzRange(&mzStart, &mzEnd);

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

  // DT integration.
  // ===============
  bool dtIntegration = false;
  double dtStart     = -1;
  double dtEnd       = -1;
  dtIntegration      = history.innermostDtRange(&dtStart, &dtEnd);

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

  QString srcDbQueryString;

  QString openingStatement = "SELECT ";
  QString countStatement;

  if(withRecordCountStatement)
    countStatement = "COUNT(*), ";
  else
    countStatement = "";

  QString uncondFields;
  QString condFields;

  QString fromStatement;
  QString joinStatements;
  QString whereStatements;

  // The unconditional stuff.
  uncondFields += "idx, scanStartTime, ";
  // Need to be specific because precursorIons also has mzList and iList
  // fields.
  uncondFields +=
    "spectralData.mzList AS specMzList, spectralData.iList AS specIList ";

  // The drift time (driftTime) SQL data is particular because we may be
  // integrating to a DT spectrum even if history has not a single DT
  // history item. Thus, we rely on the caller to set to true the
  // integrateToDt parameter if we require drift time data in the SQL
  // output.
  //
  // We leave that part to condFields, as this is conditional stuff (note
  // how the comma is at the beginning now).

  if(integrateToDt || dtIntegration)
    condFields += ", driftTime ";

  fromStatement += "FROM spectralData ";

  if(integrateToDt || dtIntegration)
    joinStatements +=
      "JOIN driftData ON (spectralData.driftData_id = "
      "driftData.driftData_id) ";

  if(rtIntegration /* not useful here || mzIntegration */
     || dtIntegration)
    {
      bool oneAlready = false;

      // Seed the WHERE statements
      whereStatements += "WHERE ";

      if(rtIntegration)
        {
          whereStatements +=
            QString(" (scanStartTime >= %1) AND (scanStartTime <= %2) ")
              .arg(rtStart, 0, 'f', 12)
              .arg(rtEnd, 0, 'f', 12);

          oneAlready = true;
        }

      // This integration does not mean anything here, because the mzStart and
      // mzEnd fields in the spectralData record are simply the mz range over
      // which the data have been acquired. We cannot reasonably read each
      // spectralData record's mzList and check if it contains peaks in the
      // mzStart and mzEnd range selected by the user upon doing the export of
      // the data.
      // if(mzIntegration)
      //{
      // if(oneAlready)
      //{
      // whereStatements +=
      // QString(" AND (mzStart >= %1) AND (mzEnd <= %2) ")
      //.arg(mzStart, 0, 'f', 12)
      //.arg(mzEnd, 0, 'f', 12);
      //}
      // else
      //{
      // whereStatements +=
      // QString(" (mzStart >= %1) AND (mzEnd <= %2) ")
      //.arg(mzStart, 0, 'f', 12)
      //.arg(mzEnd, 0, 'f', 12);

      // oneAlready = true;
      //}
      //}
      //
      // Note that it will be possible later, when the spectra will be
      // actually loaded, to effectively ask for this MZ integration
      // criterium.

      if(dtIntegration)
        {
          if(oneAlready)
            {
              whereStatements +=
                QString(" AND (driftTime >= %1) AND (driftTime <= %2) ")
                  .arg(dtStart, 0, 'f', 12)
                  .arg(dtEnd, 0, 'f', 12);
            }
          else
            {
              whereStatements +=
                QString(" (driftTime >= %1) AND (driftTime <= %2) ")
                  .arg(dtStart, 0, 'f', 12)
                  .arg(dtEnd, 0, 'f', 12);

              oneAlready = true;
            }
        }
    }

  // qDebug() << __FILE__ << __LINE__
  //<< "whereStatements:" << whereStatements;

  // Now craft the whole query depending on the needs.

  // First ensure we get the actual number of records (note the
  // countStatement element). This query string is used
  srcDbQueryString = openingStatement + countStatement + uncondFields +
                     condFields + fromStatement + joinStatements +
                     whereStatements;

  // qDebug() << __FILE__ << __LINE__
  //<< "The returned query string is: " << srcDbQueryString;

  return srcDbQueryString;
}


//! Load mass spectrometry data from a database MS file.
/*!

  This function loads mass spectra from a database file in the [\p firstID --
  \p lastId] range. The data are loaded to the \p massSpecDataSet
  MassSpecDataSet instance and the kind of data that could be loaded is set to
  \p &dataKind.

  \param firstId id of the first spectrum to load.

  \param lastId id of the last spectrum to load.

  \param massSpecDataSet pointer to the MassSpecDataSet instance in which to
  store the loaded data.

  \param dataKind kind of data that have been detected upon loading.

  \return 0 if the operation was cancelled; the number of stored spectra
  otherwise.

  \sa loadData().

  \sa loadSpectra().

*/
int
MassSpecDataFileLoaderSqlite3::loadSpectra(QSqlDatabase &database,
                                           MassSpecSqlite3Handler &handler,
                                           int firstId,
                                           int lastId,
                                           MassSpecDataSet *massSpecDataSet,
                                           int &dataKind)
{
  // We do not want to continue doing that work if the user cancelled the
  // operation.
  if(m_isOperationCancelled)
    return 0;

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

  // In this function we craft the proper SQL query SELECT string and
  // then we'll call another function to actually do the work.

  QString sqlQueryString;

  if(dataKind == MassDataKind::DRIFT_SPECTRA)
    sqlQueryString =
      QString(
        "SELECT mzList, iList, scanStartTime, driftTime "
        "FROM spectralData JOIN driftData ON "
        "(spectralData.driftData_id = driftData.driftData_id) "
        "WHERE (spectralData_id >= %1) AND (spectralData_id <= %2)")
        .arg(firstId)
        .arg(lastId);
  else if(dataKind == MassDataKind::MASS_SPECTRA)
    sqlQueryString =
      QString(
        "SELECT mzList, iList, scanStartTime "
        "FROM spectralData "
        "WHERE (spectralData_id >= %1) AND (spectralData_id <= %2)")
        .arg(firstId)
        .arg(lastId);
  else
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  return loadSpectra(
    database, handler, sqlQueryString, massSpecDataSet, dataKind);
}


//! Load mass spectrometry data from a database MS file.
/*!

  This function loads mass spectra from a database file using the SQL
  statement passed in \p &sqlQueryString.  The data are loaded to the \p
  massSpecDataSet MassSpecDataSet instance and the kind of data that could be
  loaded is then set to \p &dataKind.

  \param sqlQueryString string containing the SQL statement to use to extract
  the data out of the database file.

  \param massSpecDataSet pointer to the MassSpecDataSet instance in which to
  store the loaded data.

  \param dataKind kind of data that have been detected upon loading.

  \return 0 if the operation was cancelled; the number of stored spectra
  otherwise or -1 upon error.

  \sa loadData().

  \sa loadSpectra().

*/
int
MassSpecDataFileLoaderSqlite3::loadSpectra(QSqlDatabase &database,
                                           MassSpecSqlite3Handler &handler,
                                           const QString &sqlQueryString,
                                           MassSpecDataSet *p_massSpecDataSet,
                                           int &dataKind)
{
  if(p_massSpecDataSet == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  QSqlQuery *sqlQuery = new QSqlQuery(database);

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

  bool ok = false;

  // Since we'll use next() we can use less memory and maybe be speedier.
  sqlQuery->setForwardOnly(true);

  ok = sqlQuery->exec(sqlQueryString);

  if(!ok)
    {
      qFatal(
        "Fatal error at %s@%d -- %s(). Error: %s."
        "Failed to get proper data from the database.\n"
        "Program aborted.",
        __FILE__,
        __LINE__,
        __FUNCTION__,
        sqlQuery->lastError().text().toLatin1().data());
    }

  if(!sqlQuery->isActive())
    {
      qFatal("Fatal error at %s@%d. The query is not active. Program aborted.",
             __FILE__,
             __LINE__);
    }

  int newSpectraAllocated = 0;

  // qDebug() << __FILE__ << __LINE__
  // << "Loading spectra" << firstId << "to" << lastId;

  // The idea here is to first get all the data obtained by the sql
  // query and store them in two lists of QByteArray instances. Then, using
  // parallelization, we'll crunch the data into spectra.

  QList<QByteArray *> mzByteArrayList;
  QList<QByteArray *> iByteArrayList;
  QList<double> rtList; /* retention time list */
  QList<double> dtList; /* drift time list */

  // We'll need to know if we had something to iterate into.
  bool goneInWhileLoop = false;

  while(sqlQuery->next())
    {
      QByteArray *p_mzByteArray =
        new QByteArray(sqlQuery->value(0).toByteArray());
      mzByteArrayList.append(p_mzByteArray);

      QByteArray *p_iByteArray =
        new QByteArray(sqlQuery->value(1).toByteArray());
      iByteArrayList.append(p_iByteArray);

      double retentionTime = sqlQuery->value(2).toDouble(&ok);

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

      rtList.append(retentionTime);

      // This is conditional to having drift time data.
      if(dataKind == MassDataKind::DRIFT_SPECTRA)
        {
          double driftTime = sqlQuery->value(3).toDouble(&ok);

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

          dtList.append(driftTime);
        }

      goneInWhileLoop = true;

      // qDebug() << __FILE__ << __LINE__
      // << "In the while loop";
    }

  // Delete the query instance because it holds lots of stuff.
  delete sqlQuery;

  // If we thought we had drift spectra, but the data actually did not
  // contain any, then, we could not iterate in the sqlQuery loop. Then
  // we may try to try this function with mass spectra data only.
  if(dataKind == MassDataKind::DRIFT_SPECTRA && !goneInWhileLoop)
    {
      qDebug()
        << __FILE__ << __LINE__
        << "We did not iterate in the query results in drift data mode. Retry "
           "in mass data mode.";

      int newDataKind     = MassDataKind::MASS_SPECTRA;
      newSpectraAllocated = loadSpectra(
        database, handler, sqlQueryString, p_massSpecDataSet, newDataKind);

      if(newSpectraAllocated != -1)
        {
          dataKind = MassDataKind::MASS_SPECTRA;
          return newSpectraAllocated;
        }
      else
        {
          return -1;
        }
    }

  // Now that all the data from the sql query are stuffed in their
  // respective slot, we can start converting all that stuff in mass spectra
  // and hashes.

  int byteArrayListSize = mzByteArrayList.size();

  if(byteArrayListSize != iByteArrayList.size() ||
     byteArrayListSize != rtList.size())
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  if(dataKind == MassDataKind::DRIFT_SPECTRA)
    {
      if(byteArrayListSize != dtList.size())
        qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);
    }

  // At this point we have to check if the data were compressed or not.
  ok = false;
  int compressionType;
  ok = handler.m_msFileMetaData.intItem("compressionType", compressionType);

  if(!ok)
    compressionType = msXpS::DataCompression::DATA_COMPRESSION_NONE;

    // Now iterate side-by-side into the two lists an for each QByteArry pair,
    // do the number crunching that will convert the data into a pair of
    // lists: (mz,i) and then into a mass spectrum.

#pragma omp parallel for

  for(int iter = 0; iter < byteArrayListSize; ++iter)
    {
      QByteArray *p_enc64MzByteArray = mzByteArrayList.at(iter);
      QByteArray *p_enc64IByteArray  = iByteArrayList.at(iter);

      msXpSlibmass::MassSpectrum *p_newSpectrum =
        new msXpSlibmass::MassSpectrum;

      if(p_newSpectrum == Q_NULLPTR)
        qFatal(
          "Fatal error at %s@%d -- %s. "
          "Could not create a new mass spectrum from base64-encoded byte "
          "arrays."
          "Program aborted.",
          __FILE__,
          __LINE__,
          __FUNCTION__);

      p_newSpectrum->initialize(
        p_enc64MzByteArray, p_enc64IByteArray, compressionType);

#if 0
				if(iter == 47)
				{
					QFile file1(QString("/tmp/%1-index-%2.enc")
							.arg(QFileInfo(m_fileName).fileName())
							.arg(iter));

					file1.open(QIODevice::WriteOnly | QIODevice::Truncate);
					QTextStream outStream1(&file1);
					outStream1 << "mz\n";
					outStream1 << *p_enc64MzByteArray;
					outStream1 << "\n";
					outStream1 << "i\n";
					outStream1 << *p_enc64IByteArray;
					outStream1 << "\n";

					file1.close();

					QString *text = p_newSpectrum->asText();

					if(text != Q_NULLPTR)
					{
						QFile file2(QString("/tmp/%1-spectrum-index-%2.xy")
								.arg(QFileInfo(m_fileName).fileName())
								.arg(iter));

						file2.open(QIODevice::WriteOnly | QIODevice::Truncate);

						QTextStream outStream2(&file2);

						outStream2 << *text;

						file2.close();

						delete text;
					}
				}
#endif

      // qDebug() << __FILE__ << __LINE__
      // << "Allocated new spectrum of " << mzList.size() << "peaks";

      // Free the memory that we do not use anymore. I don't think this
      // should go to omp critical section because by essence these pointers
      // are only used here.
      delete p_enc64MzByteArray;
      delete p_enc64IByteArray;

#pragma omp critical
      {

        // 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.
        p_massSpecDataSet->incrementStatistics(*p_newSpectrum);

        if(p_massSpecDataSet->isStreamed())
          {

            // This is the chance we have to calculate the TIC for the
            // spectrum:

            double newTic = p_newSpectrum->valSum();

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

            double oldTic =
              p_massSpecDataSet->m_ticChromMap.value(rtList.at(iter), 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;
              }

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

            // qDebug() << __FILE__ << __LINE__
            //<< "Inserting rt/tic in p_massSpecDataSet->m_ticChromMap.";

            p_massSpecDataSet->m_ticChromMap.insert(rtList.at(iter), 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(dataKind == MassDataKind::DRIFT_SPECTRA)
              {

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

                msXpSlibmass::Trace *p_trace =
                  p_massSpecDataSet->m_dtHash.value(dtList.at(iter), Q_NULLPTR);

                if(p_trace == Q_NULLPTR)
                  {

                    // 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 p_newSpectrum in
                    // it.

                    msXpSlibmass::MassSpectrum *spectrum =
                      new msXpSlibmass::MassSpectrum;
                    spectrum->rdecimalPlaces() = 0;
                    spectrum->combine(*p_newSpectrum);
                    p_massSpecDataSet->m_massSpectra.append(spectrum);

                    // Delete the p_newSpectrum that we do not use anymore.
                    delete p_newSpectrum;

                    // And now make the hash association with the current
                    // drift time
                    // value.
                    p_massSpecDataSet->m_dtHash.insert(dtList.at(iter),
                                                       spectrum);

                    // Finally, let's store the first mz value and the last
                    // one.
                    // Only if the values need an update.
                    double mz_first = spectrum->first()->key();

                    if(mz_first < p_massSpecDataSet->m_mzRangeStart)
                      p_massSpecDataSet->m_mzRangeStart = mz_first;

                    double mz_last = spectrum->last()->key();

                    if(mz_last > p_massSpecDataSet->m_mzRangeEnd)
                      p_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 = spectrum->size();

                    if(spectrumSize > p_massSpecDataSet->m_mzCellCount)
                      p_massSpecDataSet->m_mzCellCount = spectrumSize;
                  }
                else
                  {

                    // We already have inserted a mass spectrum by the current
                    // dt. So use it to combine the newly generated one.

                    p_trace->rdecimalPlaces() = 0;
                    p_trace->combine(*p_newSpectrum);

                    // Since we do not use this spectrum anymore, we can
                    // delete it.
                    delete p_newSpectrum;

                    // Finally, let's store the first mz value and the last
                    // one.
                    // Only if the values need an update.
                    double mz_first = p_trace->first()->key();

                    if(mz_first < p_massSpecDataSet->m_mzRangeStart)
                      p_massSpecDataSet->m_mzRangeStart = mz_first;

                    double mz_last = p_trace->last()->key();

                    if(mz_last > p_massSpecDataSet->m_mzRangeEnd)
                      p_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 = p_trace->size();

                    if(spectrumSize > p_massSpecDataSet->m_mzCellCount)
                      p_massSpecDataSet->m_mzCellCount = spectrumSize;
                  }
              }

            // End of
            // if(dataKind == MassDataKind::DRIFT_SPECTRA)
          } // End if
        // if(p_massSpecDataSet->isStreamed())
        else
          {
            // The data load mode is not streaming.
            p_massSpecDataSet->m_massSpectra.append(p_newSpectrum);
            p_newSpectrum->rrt() = rtList.at(iter);

            // Now that we have finished filling-in the mass spectrum, we need
            // to
            // work out the hashes. We want to associate the pointer to the
            // mass
            // spectrum to two values: the retention time (rtHash) and the
            // drift
            // time (dtHash).

            p_massSpecDataSet->m_rtHash.insert(rtList.at(iter), p_newSpectrum);

            if(dataKind == MassDataKind::DRIFT_SPECTRA)
              {
                p_newSpectrum->rdt() = dtList.at(iter);
                p_massSpecDataSet->m_dtHash.insert(dtList.at(iter),
                                                   p_newSpectrum);
              }

            // qDebug() << __FILE__ << __LINE__
            // << "Updated the hashes with new mass spectrum pointer.";
          }

        newSpectraAllocated++;
      }
      // End of
      // #pragma omp critical
    }

  // End of
  // for(int iter = 0; iter < byArrayListSize; ++iter)

  // We have finished parsing all the results returned by the database, just
  // return.

  // qDebug() << __FILE__ << __LINE__
  // << newSpectraAllocated << "were allocated";

  // We do not close the database, we do now own it, and we can recursively
  // be accessing it by the caller!
  // database.close();

  return newSpectraAllocated;
}


//! Load spectra using retention time as an extraction criterion.
/*!

  \param retentionTime retention time (scanStartTime in mzML parlance) to use
  as a criterion to extract the data out of the database file.

  \param massSpectra pointer to a mass spectrum list in which to store the
  loaded data.

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

*/
int
MassSpecDataFileLoaderSqlite3::loadSpectraByRt(
  double retentionTime /*scanStartTime */,
  QList<msXpSlibmass::MassSpectrum *> *massSpectra)
{
  if(massSpectra == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  //! Handler of SQLite3-based mass data.
  // This loader loads data from a SQLite3-based mass spectrometry format and
  // uses the features of the MassSpecSqlite3Handler class that is required
  // to craft SQL-based statements to extract data from the database file.

  MassSpecSqlite3Handler sqlite3Handler;

  // And now, make sure the member database, the source database is fine.

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

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

  // Now that we have the database ready initialize the metadata for it.
  // The initializeDbForRead ensures that m_specMetaData be filled from the
  // database, so we can check if the data were compressed or not.

  if(!sqlite3Handler.initializeDbForRead(database, true /* readMetaData */))
    {
      qDebug() << __FILE__ << __LINE__
               << "Failed to initialize the database for read.";

      return -1;
    }

  QString sqlQueryString = QString(
                             "SELECT mzList, iList "
                             "FROM spectralData WHERE (scanStartTime = %1)")
                             .arg(retentionTime, 0, 'f', 12);

  QSqlQuery *sqlQuery = new QSqlQuery(database);

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

  bool ok = false;

  ok = sqlQuery->exec(sqlQueryString);

  if(!ok)
    {
      qFatal(
        "Fatal error at %s@%d -- %s(). Error: %s."
        "Failed to get proper data from the database.\n"
        "Program aborted.",
        __FILE__,
        __LINE__,
        __FUNCTION__,
        sqlQuery->lastError().text().toLatin1().data());
    }

  // qDebug() << __FILE__ << __LINE__
  // << "sqlQueryString: " << sqlQueryString
  // << "with result size: " << sqlQuery.size();

  int newSpectraAllocated = 0;

  // The idea here is to first get all the data obtained by the sql
  // query and store them in two lists of QByteArray instances. Then, using
  // parallelization, we'll crunch the data into spectra.

  QList<QByteArray *> mzByteArrayList;
  QList<QByteArray *> iByteArrayList;
  QList<double> rtList; /* retention time list */
  QList<double> dtList; /* drift time list */

  while(sqlQuery->next())
    {
      QByteArray *p_mzByteArray =
        new QByteArray(sqlQuery->value(0).toByteArray());
      mzByteArrayList.append(p_mzByteArray);

      QByteArray *p_iByteArray =
        new QByteArray(sqlQuery->value(1).toByteArray());
      iByteArrayList.append(p_iByteArray);
    }

  // Delete the query instance because it holds lots of stuff.
  delete sqlQuery;

  // Now that all the data from the sql query are stuffed in their
  // respective slot, we can start converting all that stuff in mass spectra
  // and hashes.

  int byteArrayListSize = mzByteArrayList.size();

  if(byteArrayListSize != iByteArrayList.size())
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  // At this point we have to check if the data were compressed or not.
  ok = false;
  int compressionType;
  ok =
    sqlite3Handler.m_msFileMetaData.intItem("compressionType", compressionType);

  if(!ok)
    compressionType = msXpS::DataCompression::DATA_COMPRESSION_NONE;

  // Now iterate side-by-side into the two lists an for each QByteArry pair,
  // do the number crunching that will convert the data into a pair of
  // lists: (mz,i) and then into a mass spectrum.

  for(int iter = 0; iter < byteArrayListSize; ++iter)
    {
      // We do not want to continue doing that work if the user cancelled the
      // operation.
      if(m_isOperationCancelled)
        break;

      // Start by crunching the mz data.
      QByteArray *p_enc64MzByteArray = mzByteArrayList.at(iter);
      QByteArray *p_enc64IByteArray  = iByteArrayList.at(iter);

      msXpSlibmass::MassSpectrum *newSpectrum = new msXpSlibmass::MassSpectrum;
      newSpectrum->initialize(
        p_enc64MzByteArray, p_enc64IByteArray, compressionType);

      // qDebug() << __FILE__ << __LINE__
      // << "Allocated new spectrum of " << mzList.size() << "peaks";

      massSpectra->append(newSpectrum);

      // Free the memory that we do not use anymore:
      delete p_enc64MzByteArray;
      delete p_enc64IByteArray;

      // QString msg = QString("New spectrum loaded from the database with
      // drift time %1")
      //.arg(driftTime);
      // qDebug() << __FILE__ << __LINE__
      // << msg;

      newSpectraAllocated++;
    }

  // End of
  // for(int iter = 0; iter < byArrayListSize; ++iter)

  // We have finished parsing all the results returned by the database, just
  // return.

  database.close();

  return newSpectraAllocated;
}


//! Perform an mass data integration and yield a TIC chromatogram.
/*!

  The mass spectrometry data are extracted out of the database file according
  to \p history. The loaded data are then processed to craft a TIC
  chromatogram in the form of two double lists: retention time, first (\p
  keyVector) and intensity, second (\p valVector).

  \param history reference the History to use to craft the SQL statement that
  will extract the data out of the database file.

  \param keyVector pointer to a vector of double_s to hold the retention time
  values.

  \param valVector pointer to a vector of double_s to hold the intensity values.

  \return the number of spectra that were integrated from the database file.

  \sa streamedMzIntegration() <br> streamedDtIntegration().

*/
int
MassSpecDataFileLoaderSqlite3::streamedRtIntegration(const History &history,
                                                     QVector<double> *keyVector,
                                                     QVector<double> *valVector)
{
  if(keyVector == Q_NULLPTR || valVector == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  //! Handler of SQLite3-based mass data.
  // This loader loads data from a SQLite3-based mass spectrometry format and
  // uses the features of the MassSpecSqlite3Handler class that is required
  // to craft SQL-based statements to extract data from the database file.

  MassSpecSqlite3Handler sqlite3Handler;

  // And now, make sure the member database, the source database is fine.

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

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

  // Now that we have the database ready initialize the metadata for it.
  // The initializeDbForRead ensures that m_specMetaData be filled from the
  // database, so we can check if the data were compressed or not.

  if(!sqlite3Handler.initializeDbForRead(database, true /* readMetaData */))
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  bool ok = false;
  int compressionType;
  ok =
    sqlite3Handler.m_msFileMetaData.intItem("compressionType", compressionType);

  if(!ok)
    compressionType = msXpS::DataCompression::DATA_COMPRESSION_NONE;

  QSqlQuery *sqlQuery = new QSqlQuery(database);

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

  QString srcDbQueryString = craftSqlSelectStatementForStreamedIntegration(
    history,
    true /* with the record COUNT * statement */,
    false /* integrateToDt */);

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

  ok = sqlQuery->exec(srcDbQueryString);

  if(!ok)
    {
      qFatal(
        "Fatal error at %s@%d -- %s(). Error: %s."
        "Failed to get proper data from the database.\n"
        "Program aborted.",
        __FILE__,
        __LINE__,
        __FUNCTION__,
        sqlQuery->lastError().text().toLatin1().data());
    }

  if(!sqlQuery->isActive())
    {
      qFatal("Fatal error at %s@%d. The query is not active. Program aborted.",
             __FILE__,
             __LINE__);
    }

  // Get the number of records that were retrieved, because we'll need this
  // to provide feedback to the caller.
  if(!sqlQuery->first())
    {
      qDebug() << __FILE__ << __LINE__
               << "Failed to get the number of records. Returning.";
    }

  int recordCount =
    sqlQuery->value(sqlQuery->record().indexOf("COUNT(*)")).toInt();

  // qDebug() << __FILE__ << __LINE__
  // << "The recordCount:" << recordCount;

  // Now reset the query
  sqlQuery->finish();

  // And now craft anew the query string but without the COUNT statement.

  srcDbQueryString = craftSqlSelectStatementForStreamedIntegration(
    history,
    false /* with the record COUNT * statement */,
    false /* integrateToDt */);

  // qDebug() << __FILE__ << __LINE__
  // << "For data loading, now, the query string is: " << srcDbQueryString;

  ok = sqlQuery->exec(srcDbQueryString);

  if(!ok)
    {
      qFatal(
        "Fatal error at %s@%d -- %s(). Error: %s."
        "Failed to get proper data from the database.\n"
        "Program aborted.",
        __FILE__,
        __LINE__,
        __FUNCTION__,
        sqlQuery->lastError().text().toLatin1().data());
    }

  if(!sqlQuery->isActive())
    {
      qFatal("Fatal error at %s@%d. The query is not active. Program aborted.",
             __FILE__,
             __LINE__);
    }

  // Before going on with the actual results parsing, let's setup the
  // feedback routine.
  QString msgSkeleton = "Computing streamed rt integration";

  m_progressFeedbackStartValue = 0;
  m_progressFeedbackEndValue   = recordCount;

  emit updateFeedbackSignal(msgSkeleton,
                            -1,
                            true /* setVisible */,
                            LogType::LOG_TO_BOTH,
                            m_progressFeedbackStartValue,
                            m_progressFeedbackEndValue);

  int fieldNo;
  int spectraRead = 0;
  QMap<double, double> keyValMap;

  // I need to comment that out because it crashes the program below.
  // sqlQuery->setForwardOnly(true);

  // We will need to inspect the history to check for an existing
  // MZ range.

  // MZ integration.
  // ===============
  bool mzIntegration = false;
  double mzStart     = -1;
  double mzEnd       = -1;
  mzIntegration      = history.innermostMzRange(&mzStart, &mzEnd);

  while(sqlQuery->next())
    {
      QCoreApplication::processEvents();

      // We do not want to continue doing that work if the user cancelled the
      // operation.
      if(m_isOperationCancelled)
        break;

      fieldNo = sqlQuery->record().indexOf("specMzList");
      QByteArray *p_enc64MzByteArray =
        new QByteArray(sqlQuery->value(fieldNo).toByteArray());

      fieldNo = sqlQuery->record().indexOf("specIList");
      QByteArray *p_enc64IByteArray =
        new QByteArray(sqlQuery->value(fieldNo).toByteArray());

      fieldNo              = sqlQuery->record().indexOf("scanStartTime");
      double retentionTime = sqlQuery->value(fieldNo).toDouble(&ok);

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

      // We actually do not care of the drift time, because we are computing
      // at TIC chromatogram actually, so we'll never use the drift time
      // value.
      //
      // This is conditional to having drift time data.
      // if(dataKind == MassDataKind::DRIFT_SPECTRA)
      //{
      // fieldNo = sqlQuery->record().indexOf("driftTime");
      // double driftTime = sqlQuery->value(fieldNo).toDouble(&ok);
      // if(!ok)
      // qFatal("Fatal error at %s@%d. Program aborted.",
      //__FILE__, __LINE__);
      //}

      // And now actually perform the data crunching

      msXpSlibmass::MassSpectrum *newSpectrum = new msXpSlibmass::MassSpectrum;
      newSpectrum->initialize(
        p_enc64MzByteArray, p_enc64IByteArray, compressionType);

      // Free the memory that we do not use anymore.
      delete p_enc64MzByteArray;
      delete p_enc64IByteArray;

      // qDebug() << __FILE__ << __LINE__
      // << "Allocated new spectrum of " << newSpectrum->size() << "peaks";

      // Calculate the TIC for the spectrum:
      // We now have to deal with the mzIntegration, since we finally have the
      // mass spectrum data.


      double newTic;

      if(mzIntegration)
        newTic = newSpectrum->valSum(mzStart, mzEnd);
      else
        newTic = newSpectrum->valSum();

      delete newSpectrum;

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

      double oldTic = keyValMap.value(retentionTime, 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;
        }

      keyValMap.insert(retentionTime, newTic);

      // At this point we can increment the number of spectra that were
      // effectively read.
      ++spectraRead;

      QString msg = QString(" -- spectrum number %1\n").arg(spectraRead);

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

      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
      //"emitting update feedback signal with"
      //<< "start value:" << m_progressFeedbackStartValue
      //<< "end value:" << m_progressFeedbackEndValue
      //<< "current value:" << spectraRead;

      // qDebug() << __FILE__ << __LINE__ << msg;
    }
  // End of
  // while(sqlQuery->next())

  // Delete the query instance because it holds lots of stuff.
  delete sqlQuery;
  database.close();

  // Reset the operation cancelled flag:
  m_isOperationCancelled = false;

  // Now actually set the values to the vectors passed as parameter.

  *keyVector = QVector<double>::fromList(keyValMap.keys());
  *valVector = QVector<double>::fromList(keyValMap.values());

  return spectraRead;
}


//! Perform an mass data integration and yield a mass spectrum.
/*!

  The mass spectrometry data are extracted out of the database file according
  to \p history. The loaded data are then processed to craft a mass spectrum
  in the form of two double lists: m/z, first (\p keyVector) and intensity,
  second (\p valVector).

  \param history reference the History to use to craft the SQL statement that
  will extract the data out of the database file.

  \param keyVector pointer to a vector of double_s to hold the m/z values.

  \param valVector pointer to a vector of double_s to hold the intensity values.

  \return the number of spectra that were integrated from the database file.

  \sa streamedRtIntegration() <br> streamedDtIntegration().

*/
int
MassSpecDataFileLoaderSqlite3::streamedMzIntegration(const History &history,
                                                     QVector<double> *keyVector,
                                                     QVector<double> *valVector)
{
  if(keyVector == Q_NULLPTR || valVector == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  //! Handler of SQLite3-based mass data.
  // This loader loads data from a SQLite3-based mass spectrometry format and
  // uses the features of the MassSpecSqlite3Handler class that is required
  // to craft SQL-based statements to extract data from the database file.

  MassSpecSqlite3Handler sqlite3Handler;

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

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

  // Now that we have the database ready initialize the metadata for it.
  // The initializeDbForRead ensures that m_specMetaData be filled from the
  // database, so we can check if the data were compressed or not.

  if(!sqlite3Handler.initializeDbForRead(database, true /* readMetaData */))
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  bool ok = false;
  int compressionType;
  ok =
    sqlite3Handler.m_msFileMetaData.intItem("compressionType", compressionType);

  if(!ok)
    compressionType = msXpS::DataCompression::DATA_COMPRESSION_NONE;

  QSqlQuery *sqlQuery = new QSqlQuery(database);

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

  QString srcDbQueryString = craftSqlSelectStatementForStreamedIntegration(
    history,
    true /* with the record COUNT * statement */,
    false /* integrateToDt */);

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

  // qDebug() << __FILE__ << __LINE__
  // << "For data record counting, the query string is: " << srcDbQueryString;

  ok = sqlQuery->exec(srcDbQueryString);

  if(!ok)
    {
      qFatal(
        "Fatal error at %s@%d -- %s(). Error: %s."
        "Failed to get proper data from the database.\n."
        "Program aborted.",
        __FILE__,
        __LINE__,
        __FUNCTION__,
        sqlQuery->lastError().text().toLatin1().data());
    }

  if(!sqlQuery->isActive())
    {
      qFatal("Fatal error at %s@%d. The query is not active. Program aborted.",
             __FILE__,
             __LINE__);
    }

  // Get the number of records that were retrieved, because we'll need this
  // to provide feedback to the caller.
  if(!sqlQuery->first())
    {
      qDebug() << __FILE__ << __LINE__
               << "Failed to get the number of records. Returning.";
    }

  int recordCount =
    sqlQuery->value(sqlQuery->record().indexOf("COUNT(*)")).toInt();

  // qDebug() << __FILE__ << __LINE__
  //<< "The recordCount:" << recordCount;

  // Now reset the query
  sqlQuery->finish();

  // And now craft anew the query string but without the COUNT statement.

  srcDbQueryString = craftSqlSelectStatementForStreamedIntegration(
    history,
    false /* with the record COUNT * statement */,
    false /* integrateToDt */);

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

  // qDebug() << __FILE__ << __LINE__
  // << "For data loading, now, the query string is: " << srcDbQueryString;

  ok = sqlQuery->exec(srcDbQueryString);

  if(!ok)
    {
      qFatal(
        "Fatal error at %s@%d -- %s(). Error: %s."
        "Failed to get proper data from the database.\n"
        "Program aborted.",
        __FILE__,
        __LINE__,
        __FUNCTION__,
        sqlQuery->lastError().text().toLatin1().data());
    }

  if(!sqlQuery->isActive())
    {
      qFatal("Fatal error at %s@%d. The query is not active. Program aborted.",
             __FILE__,
             __LINE__);
    }

  // Before going on with the actual results parsing, let's setup the
  // feedback routine.
  QString msgSkeleton = "Computing streamed m/z integration";

  m_progressFeedbackStartValue = 0;
  m_progressFeedbackEndValue   = recordCount;

  emit updateFeedbackSignal(msgSkeleton,
                            0,
                            true /* setVisible */,
                            LogType::LOG_TO_BOTH,
                            m_progressFeedbackStartValue,
                            m_progressFeedbackEndValue);

  int fieldNo;
  int spectraRead = 0;
  msXpSlibmass::MassSpectrum combinedMassSpectrum;

  // I need to comment that out because it crashes the program below.
  // sqlQuery->setForwardOnly(true);

  // We will need to inspect the history to check for an existing
  // MZ range.

  // MZ integration.
  // ===============
  bool mzIntegration = false;
  double mzStart     = -1;
  double mzEnd       = -1;
  mzIntegration      = history.innermostMzRange(&mzStart, &mzEnd);

  while(sqlQuery->next())
    {
      QCoreApplication::processEvents();

      // We do not want to continue doing that work if the user cancelled the
      // operation.
      if(m_isOperationCancelled)
        break;

      fieldNo = sqlQuery->record().indexOf("specMzList");
      QByteArray *p_enc64MzByteArray =
        new QByteArray(sqlQuery->value(fieldNo).toByteArray());

      fieldNo = sqlQuery->record().indexOf("specIList");
      QByteArray *p_enc64IByteArray =
        new QByteArray(sqlQuery->value(fieldNo).toByteArray());

      // We actually do not care of the retention time, because all we need is
      // mass spectra, that we'll combine into a single mass spectrum.
      // fieldNo = sqlQuery->record().indexOf("scanStartTime");
      // double retentionTime = sqlQuery->value(fieldNo).toDouble(&ok);
      // if(!ok)
      // qFatal("Fatal error at %s@%d. Program aborted.",
      //__FILE__, __LINE__);

      // We actually do not care of the drift time, because we are computing
      // at TIC chromatogram actually, so we'll never use the drift time
      // value.
      //
      // This is conditional to having drift time data.
      // if(dataKind == MassDataKind::DRIFT_SPECTRA)
      //{
      // fieldNo = sqlQuery->record().indexOf("driftTime");
      // double driftTime = sqlQuery->value(fieldNo).toDouble(&ok);
      // if(!ok)
      // qFatal("Fatal error at %s@%d. Program aborted.",
      //__FILE__, __LINE__);
      //}

      // And now actually perform the data crunching

      msXpSlibmass::MassSpectrum *p_newSpectrum =
        new msXpSlibmass::MassSpectrum;
      p_newSpectrum->initialize(
        p_enc64MzByteArray, p_enc64IByteArray, compressionType);

      // Free the memory that we do not use anymore.
      delete p_enc64MzByteArray;
      delete p_enc64IByteArray;

      // qDebug() << __FILE__ << __LINE__
      // << "Allocated new spectrum of " << newSpectrum->size() << "peaks";

      // We are performing a combination of various mass spectra (the
      // mp_spectrum contains the spectra that are read from 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 && p_newSpectrum->size())
        {
          MzIntegrationParams mzIntegrationParams =
            history.mzIntegrationParams();

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

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

          if(combinedMassSpectrum.setupBinnability(*p_newSpectrum))
            m_isBinningSetup = true;
        }

      if(mzIntegration)
        // The history contains at least one element that limits the m/z range.
        combinedMassSpectrum.combine(*p_newSpectrum, mzStart, mzEnd);
      else
        combinedMassSpectrum.combine(*p_newSpectrum);

      delete p_newSpectrum;

      // At this point we can increment the number of spectra that were
      // effectively read.
      ++spectraRead;

      QString msg = QString(" -- spectrum number %1\n").arg(spectraRead);

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

      // qDebug() << __FILE__ << __LINE__ << msg;
    }
  // End of
  // while(sqlQuery->next())

  // Delete the query instance because it holds lots of stuff.
  delete sqlQuery;
  database.close();

  // We now need to conclude the combination of all the spectra into
  // m_spectrum and make sure we remove all the zero-val data points if
  // that was required.
  if(combinedMassSpectrum.isRemoveZeroValDataPoints())
    combinedMassSpectrum.removeZeroValDataPoints();

  // Now actually set the values to the vectors passed as parameter.

  *keyVector = QVector<double>::fromList(combinedMassSpectrum.keyList());
  *valVector = QVector<double>::fromList(combinedMassSpectrum.valList());

  return spectraRead;
}


//! Perform an mass data integration and yield a drift spectrum.
/*!

  The mass spectrometry data are extracted out of the database file according
  to \p history. The loaded data are then processed to craft a drift spectrum
  in the form of two double lists: drift time, first (\p keyVector) and
  intensity, second (\p valVector).

  \param history reference the History to use to craft the SQL statement that
  will extract the data out of the database file.

  \param keyVector pointer to a vector of double_s to hold the drift time
  values.

  \param valVector pointer to a vector of double_s to hold the intensity values.

  \return the number of spectra that were integrated from the database file.

  \sa streamedRtIntegration() <br> streamedMzIntegration().

*/
int
MassSpecDataFileLoaderSqlite3::streamedDtIntegration(const History &history,
                                                     QVector<double> *keyVector,
                                                     QVector<double> *valVector)
{
  if(keyVector == Q_NULLPTR || valVector == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  // Create the database connection that we'll need to check if the data are
  // from an ion mobility mass spectrometry experiment.
  QString connectionName =
    QString("%1/%2").arg(m_fileName).arg(msXpS::pointerAsString(this));

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

  //! Handler of SQLite3-based mass data.
  // This loader loads data from a SQLite3-based mass spectrometry format and
  // uses the features of the MassSpecSqlite3Handler class that is required
  // to craft SQL-based statements to extract data from the database file.

  MassSpecSqlite3Handler sqlite3Handler;
  if(!sqlite3Handler.isDriftExperiment(database))
    {
      return -1;
    }

  // Now that we have the database ready initialize the metadata for it.
  // The initializeDbForRead ensures that m_specMetaData be filled from the
  // database, so we can check if the data were compressed or not.

  if(!sqlite3Handler.initializeDbForRead(database, true /* readMetaData */))
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  bool ok = false;
  int compressionType;
  ok =
    sqlite3Handler.m_msFileMetaData.intItem("compressionType", compressionType);

  if(!ok)
    compressionType = msXpS::DataCompression::DATA_COMPRESSION_NONE;

  QSqlQuery *sqlQuery = new QSqlQuery(database);

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

  QString srcDbQueryString = craftSqlSelectStatementForStreamedIntegration(
    history,
    true /* with the record COUNT * statement */,
    true /* integrateToDt */);

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

  ok = sqlQuery->exec(srcDbQueryString);

  if(!ok)
    {
      qFatal(
        "Fatal error at %s@%d -- %s(). Error: %s."
        "Failed to get proper data from the database.\n"
        "Program aborted.",
        __FILE__,
        __LINE__,
        __FUNCTION__,
        sqlQuery->lastError().text().toLatin1().data());
    }

  if(!sqlQuery->isActive())
    {
      qFatal("Fatal error at %s@%d. The query is not active. Program aborted.",
             __FILE__,
             __LINE__);
    }

  // Get the number of records that were retrieved, because we'll need this
  // to provide feedback to the caller.
  if(!sqlQuery->first())
    {
      qDebug() << __FILE__ << __LINE__
               << "Failed to get the number of records. Returning.";
    }

  int recordCount =
    sqlQuery->value(sqlQuery->record().indexOf("COUNT(*)")).toInt();

  // qDebug() << __FILE__ << __LINE__
  //<< "The recordCount:" << recordCount;

  // Now reset the query
  sqlQuery->finish();

  // And now craft anew the query string but without the COUNT statement.

  srcDbQueryString = craftSqlSelectStatementForStreamedIntegration(
    history,
    false /* with the record COUNT * statement */,
    true /* integrateToDt */);

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

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__
  //<< "For data loading, now, the query string is: " << srcDbQueryString;

  ok = sqlQuery->exec(srcDbQueryString);

  if(!ok)
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "The query is not active."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  if(!sqlQuery->isActive())
    qFatal("Fatal error at %s@%d.  Program aborted.", __FILE__, __LINE__);

  // Before going on with the actual results parsing, let's setup the
  // feedback routine.
  QString msgSkeleton = "Computing streamed dt integration";

  m_progressFeedbackStartValue = 0;
  m_progressFeedbackEndValue   = recordCount;

  emit updateFeedbackSignal(msgSkeleton,
                            0,
                            true /* setVisible */,
                            LogType::LOG_TO_BOTH,
                            m_progressFeedbackStartValue,
                            m_progressFeedbackEndValue);

  int fieldNo;
  int spectraRead = 0;

  QMap<double, double> keyValMap;

  // I need to comment that out because it crashes the program below.
  // sqlQuery->setForwardOnly(true);

  // We will need to inspect the history to check for an existing
  // MZ range.

  // MZ integration.
  // ===============
  bool mzIntegration = false;
  double mzStart     = -1;
  double mzEnd       = -1;
  mzIntegration      = history.innermostMzRange(&mzStart, &mzEnd);

  while(sqlQuery->next())
    {
      QCoreApplication::processEvents();

      // We do not want to continue doing that work if the user cancelled the
      // operation.
      if(m_isOperationCancelled)
        break;

      fieldNo = sqlQuery->record().indexOf("specMzList");
      QByteArray *p_enc64MzByteArray =
        new QByteArray(sqlQuery->value(fieldNo).toByteArray());

      fieldNo = sqlQuery->record().indexOf("specIList");
      QByteArray *p_enc64IByteArray =
        new QByteArray(sqlQuery->value(fieldNo).toByteArray());

      // We actually do not care of the retention time, because all we need is
      // mass spectra, that we'll combine into a single mass spectrum.
      // fieldNo = sqlQuery->record().indexOf("scanStartTime");
      // double retentionTime = sqlQuery->value(fieldNo).toDouble(&ok);
      // if(!ok)
      // qFatal("Fatal error at %s@%d. Program aborted.",
      //__FILE__, __LINE__);

      fieldNo          = sqlQuery->record().indexOf("driftTime");
      double driftTime = sqlQuery->value(fieldNo).toDouble(&ok);

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

      // And now actually perform the data crunching

      msXpSlibmass::MassSpectrum *newSpectrum = new msXpSlibmass::MassSpectrum;
      newSpectrum->initialize(
        p_enc64MzByteArray, p_enc64IByteArray, compressionType);

      // Free the memory that we do not use anymore.
      delete p_enc64MzByteArray;
      delete p_enc64IByteArray;

      // qDebug() << __FILE__ << __LINE__
      // << "Combined new spectrum of " << newSpectrum->size() << "peaks";

      // Calculate the TIC for the spectrum:
      // We now have to deal with the mzIntegration, since we finally have the
      // mass spectrum data.

      double newTic;

      if(mzIntegration)
        newTic = newSpectrum->valSum(mzStart, mzEnd);
      else
        newTic = newSpectrum->valSum();

      delete newSpectrum;

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

      double oldTic = keyValMap.value(driftTime, 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;
        }

      keyValMap.insert(driftTime, newTic);

      // At this point we can increment the number of spectra that were
      // effectively read.
      ++spectraRead;

      QString msg = QString(" -- spectrum number %1\n").arg(spectraRead);

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

      // qDebug() << __FILE__ << __LINE__ << msg;
    }
  // End of
  // while(sqlQuery->next())

  // Delete the query instance because it holds lots of stuff.
  delete sqlQuery;
  database.close();

  // Now actually set the values to the vectors passed as parameter.

  *keyVector = QVector<double>::fromList(keyValMap.keys());
  *valVector = QVector<double>::fromList(keyValMap.values());

  return spectraRead;
}


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


//! Construct a MassSpecDataFileLoaderSqlite3 instance.
/*!

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

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

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


//! Destruct \c this MassSpecDataFileLoaderSQLite3 instance.
MassSpecDataFileLoaderSqlite3::~MassSpecDataFileLoaderSqlite3()
{
}


//! Tells if the MassSpecDataFileLoaderSqlite3 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.

*/
bool
MassSpecDataFileLoaderSqlite3::canLoadData(QString fileName)
{
  QFile file(fileName);

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

      return false;
    }

  QDataStream inStream(&file);
  char text[16];
  inStream.readRawData(text, 15);

  QString readText(text);

  if(!readText.startsWith("SQLite format 3"))
    {
      // qDebug() << __FILE__ << __LINE__ << "Could not find \"SQLite format 3\"
      // format marker"
      //<< "found this text instead:" << readText;

      file.close();

      return false;
    }

  file.close();

  // qDebug()
  //<< __FILE__ << __LINE__
  //<< "Found the SQLite format 3 string as the first line of the file.";

  // Go on with the format analysis, is it a true msXpertSuite database?

  QSqlDatabase db;
  db = QSqlDatabase::addDatabase("QSQLITE", "TEST_DATABASE");
  db.setDatabaseName(fileName);

  if(!db.open())
    return false;
  else
    db.close();

  return true;
}


// Not documented on purpose.
int
MassSpecDataFileLoaderSqlite3::readSpectrumCount()
{
  qFatal(
    "Fatal error at %s@%d -- %s(). "
    "We should never arrive here."
    "Program aborted.",
    __FILE__,
    __LINE__,
    __FUNCTION__);
}


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

  Reads the metaData table of the file and determines the size of the spectrum
  list. Note that the \c m_spectrumCount member is set to that value.

  \return an integer containing the number of spectra in the spectrum list or
  -1 upon error.

*/
int
MassSpecDataFileLoaderSqlite3::readSpectrumCount(
  QSqlDatabase &database, MassSpecSqlite3Handler &handler)
{
  if(!handler.initializeDbForRead(database))
    return -1;

  QSqlQuery sqlQuery(database);
  bool ok = false;

  QString queryString("SELECT spectrumListCount from metaData");

  ok = sqlQuery.exec(queryString);

  if(!ok)
    {
      qFatal(
        "Fatal error at %s@%d -- %s(). Error: %s."
        "Failed to get proper data from the database.\n"
        "Program aborted.",
        __FILE__,
        __LINE__,
        __FUNCTION__,
        sqlQuery.lastError().text().toLatin1().data());
    }

  if(!sqlQuery.first())
    {
      qFatal(
        "Fatal error at %s@%d -- %s(). Error: %s."
        "Failed to get proper data from the database.\n"
        "Program aborted.",
        __FILE__,
        __LINE__,
        __FUNCTION__,
        sqlQuery.lastError().text().toLatin1().data());
    }

  ok = false;

  int count = sqlQuery.value(0).toInt(&ok);

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

  m_spectrumCount = count;

  // qDebug() << __FILE__ << __LINE__
  //<< "spectrum count:" << m_spectrumCount;

  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
MassSpecDataFileLoaderSqlite3::loadData(QSqlDatabase &database,
                                        MassSpecSqlite3Handler &handler,
                                        MassSpecDataSet *massSpecDataSet)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";

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

  // We first try to know if the data in the database are for a
  // MassDataKind::DRIFT_SPECTRA experiment or only for a MASS_SPECTRA
  // experiment.
  int dataKind = MassDataKind::MASS_SPECTRA;

  bool isDriftExp = handler.isDriftExperiment(database);

  if(isDriftExp)
    dataKind = MassDataKind::DRIFT_SPECTRA;
  else
    dataKind = MassDataKind::MASS_SPECTRA;

  // The idea is that all the data m/z, i, dt should be available right in
  // the memory. The scheme at the moment is to iterate in the spectralData
  // table and for each spectrum, extract its mass data along with retention
  // time.
  //
  // The metaData table's field spectrumListCount holds the number of
  // spectra in the spectralData table.

  if(m_spectrumCount == -1)
    readSpectrumCount(database, handler);

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

  m_progressFeedbackStartValue = 0;
  m_progressFeedbackEndValue   = m_spectrumCount;

  QString msg = QString("Loading data: total number of spectra to load: %1\n")
                  .arg(m_spectrumCount);

  m_progressFeedbackStartValue = 0;
  m_progressFeedbackEndValue   = m_spectrumCount;

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //"emitting update feedback signal with"
  //<< "start value:" << m_progressFeedbackStartValue
  //<< "end value:" << m_progressFeedbackEndValue;

  emit updateFeedbackSignal(msg,
                            0,
                            true /* setVisible */,
                            LogType::LOG_TO_BOTH,
                            m_progressFeedbackStartValue,
                            m_progressFeedbackEndValue);

  // qDebug() << __FILE__ << __LINE__
  //<< "Loading data: total number of spectra to load:" << m_spectrumCount;

  // The way this function works is that is loads the spectra at positions
  // firstId to lastId in the spectralData table. Each time a new spectrum
  // is loaded, then it is appended to m_massSpectra and the corresponding
  // retention time is used to craft hash key:value pairs
  // that are set to thew hashes passed as parameter.

  // Beware, these are row ids, not idxes: (spectralData_id start at 1, not
  // 0).
  int firstId = 1;
  int lastId  = m_parseProgressionNumber > m_spectrumCount
                 ? m_spectrumCount
                 : m_parseProgressionNumber;

  for(int iter = firstId; iter <= m_spectrumCount;)
    {
      if(m_isOperationCancelled)
        {
          qDebug()
            << __FILE__ << __LINE__ << __FUNCTION__ << "()"
            << "Going to break while loop due to abort requested by the user.";

          break;
        }

      msg = QString("Loading data, spectra %1 to %2\n").arg(iter).arg(lastId);

      emit updateFeedbackSignal(msg,
                                lastId,
                                true /* setVisible */,
                                LogType::LOG_TO_BOTH,
                                m_progressFeedbackStartValue,
                                m_progressFeedbackEndValue);

      // FIXME
      // emit dataLoadMessageSignal(msg, LOG_TO_BOTH_OVERWRITE);
      // emit dataLoadProgressSignal(lastId);

      // qDebug() << __FILE__ << __LINE__
      // << "Loading spectra from " << iter << "to" << lastId;

      loadSpectra(database, handler, iter, lastId, massSpecDataSet, dataKind);

      iter   = lastId + 1;
      lastId = iter + m_parseProgressionNumber;
      lastId = lastId > m_spectrumCount ? m_spectrumCount : lastId;
    }

  // At this point we should have terminated loading all the data.

  // Reset the m_spectrumCount value to -1 in case we load later data from
  // another file.
  m_spectrumCount = -1;

  return massSpecDataSet->m_massSpectra.size();
}


//! Performs a streamed integration.
/*!

  The data are loaded from the database file using \p history to craft the SQL
  statement and integrate the loaded data into the \p integrationType. Put the
  results of the integration to \p keyVector and \p valVector.

  IntegrationType can be RT, MZ or DT, yielding respectively a TIC chromatogram,
  a mass spectrum or a drift spectrum.

  \param history History item to use to craft the SQL statement.

  \param integrationType IntegrationType value stating  which kind of data the
  integration should generate.

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

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

  \return the number of integrated spectra or -1 upon error.

  \sa streamedRtIntegration() <br> streamedMzIntegration() <br>
  streamedDtIntegration().

*/
int
MassSpecDataFileLoaderSqlite3::streamedIntegration(
  const History &history,
  IntegrationType integrationType,
  QVector<double> *keyVector,
  QVector<double> *valVector)
{
  if(!history.isValid())
    {
      qFatal(
        "Fatal error at %s@%d -- %s(). "
        "Programming error: the History passed as parameter is not valid."
        "Program aborted.",
        __FILE__,
        __LINE__,
        __FUNCTION__);
    }
  // else
  //{
  // qDebug() << __FILE__ << __LINE__
  //<< "history as text:" << history.asText();
  //}

  // qDebug() << __FILE__ << __LINE__
  //<< "At this point, the m_fileName:" << m_fileName;


  if(integrationType == IntegrationType::ANY_TO_RT)
    return streamedRtIntegration(history, keyVector, valVector);
  else if(integrationType == IntegrationType::ANY_TO_MZ)
    return streamedMzIntegration(history, keyVector, valVector);
  else if(integrationType == IntegrationType::ANY_TO_DT)
    return streamedDtIntegration(history, keyVector, valVector);
  else
    {
      qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
               << "No valid integration type is defined for the data to be "
                  "produced. Returning.";
    }

  return -1;
}


//! 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.

  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 spectra 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 using 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 integrated spectra, -1 on error.

*/
int
MassSpecDataFileLoaderSqlite3::streamedTicIntensity(const History &history,
                                                    double *intensity)
{
  if(intensity == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  //! Handler of SQLite3-based mass data.
  // This loader loads data from a SQLite3-based mass spectrometry format and
  // uses the features of the MassSpecSqlite3Handler class that is required
  // to craft SQL-based statements to extract data from the database file.

  MassSpecSqlite3Handler sqlite3Handler;

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

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

  // Now that we have the database ready initialize the metadata for it.
  // The initializeDbForRead ensures that m_specMetaData be filled from the
  // database, so we can check if the data were compressed or not.

  if(!sqlite3Handler.initializeDbForRead(database, true /* readMetaData */))
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  bool ok = false;
  int compressionType;
  ok =
    sqlite3Handler.m_msFileMetaData.intItem("compressionType", compressionType);

  if(!ok)
    compressionType = msXpS::DataCompression::DATA_COMPRESSION_NONE;

  QSqlQuery *sqlQuery = new QSqlQuery(database);

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

  QString srcDbQueryString = craftSqlSelectStatementForStreamedIntegration(
    history,
    true /* with the record COUNT * statement */,
    false /* integrateToDt */);

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

  // qDebug() << __FILE__ << __LINE__
  // << "For data record counting, the query string is: " << srcDbQueryString;

  ok = sqlQuery->exec(srcDbQueryString);

  if(!ok)
    {
      qFatal(
        "Fatal error at %s@%d -- %s(). Error: %s."
        "Failed to get proper data from the database.\n."
        "Program aborted.",
        __FILE__,
        __LINE__,
        __FUNCTION__,
        sqlQuery->lastError().text().toLatin1().data());
    }

  if(!sqlQuery->isActive())
    {
      qFatal("Fatal error at %s@%d. The query is not active. Program aborted.",
             __FILE__,
             __LINE__);
    }

  // Get the number of records that were retrieved, because we'll need this
  // to provide feedback to the caller.
  if(!sqlQuery->first())
    {
      qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
               << "Failed to get the number of records. Returning.";
    }

  int recordCount =
    sqlQuery->value(sqlQuery->record().indexOf("COUNT(*)")).toInt();

  // qDebug() << __FILE__ << __LINE__
  // << "The recordCount:" << recordCount;

  // Now reset the query
  sqlQuery->finish();

  // And now craft anew the query string but without the COUNT statement.

  srcDbQueryString = craftSqlSelectStatementForStreamedIntegration(
    history,
    false /* with the record COUNT * statement */,
    false /* integrateToDt */);

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

  // qDebug() << __FILE__ << __LINE__
  // << "For data loading, now, the query string is: " << srcDbQueryString;

  ok = sqlQuery->exec(srcDbQueryString);

  if(!ok)
    qFatal(
      "Fatal error at %s@%d -- %s() -- error: %s Exiting. "
      "Failed to get proper data from the database.\n"
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__,
      sqlQuery->lastError().text().toLatin1().data());

  if(!sqlQuery->isActive())
    {
      qFatal("Fatal error at %s@%d. The query is not active. Program aborted.",
             __FILE__,
             __LINE__);
    }

  // Before going on with the actual results parsing, let's setup the
  // feedback routine.
  QString msgSkeleton = "Computing streamed TIC integration";

  m_progressFeedbackStartValue = 0;
  m_progressFeedbackEndValue   = recordCount;

  emit updateFeedbackSignal(msgSkeleton,
                            0,
                            true /* setVisible */,
                            LogType::LOG_TO_BOTH,
                            m_progressFeedbackStartValue,
                            m_progressFeedbackEndValue);

  int fieldNo;
  int spectraRead = 0;
  QMap<double, double> keyValMap;

  // I need to comment that out because it crashes the program below.
  // sqlQuery->setForwardOnly(true);

  // We will need to inspect the history to check for an existing
  // MZ range.

  // MZ integration.
  // ===============
  bool mzIntegration = false;
  double mzStart     = -1;
  double mzEnd       = -1;
  mzIntegration      = history.innermostMzRange(&mzStart, &mzEnd);

  double localIntensity = 0.0;

  while(sqlQuery->next())
    {
      // We do not want to continue doing that work if the user cancelled the
      // operation.
      if(m_isOperationCancelled)
        break;

      fieldNo = sqlQuery->record().indexOf("specMzList");
      QByteArray *p_enc64MzByteArray =
        new QByteArray(sqlQuery->value(fieldNo).toByteArray());

      fieldNo = sqlQuery->record().indexOf("specIList");
      QByteArray *p_enc64IByteArray =
        new QByteArray(sqlQuery->value(fieldNo).toByteArray());

      fieldNo              = sqlQuery->record().indexOf("scanStartTime");
      double retentionTime = sqlQuery->value(fieldNo).toDouble(&ok);
      Q_UNUSED(retentionTime);

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

      // We actually do not care of the drift time, because we are computing
      // at TIC chromatogram actually, so we'll never use the drift time
      // value.
      //
      // This is conditional to having drift time data.
      // if(dataKind == DRIFT_SPECTRA)
      //{
      // fieldNo = sqlQuery->record().indexOf("driftTime");
      // double driftTime = sqlQuery->value(fieldNo).toDouble(&ok);
      // if(!ok)
      // qFatal("Fatal error at %s@%d. Program aborted.",
      //__FILE__, __LINE__);
      //}

      // And now actually perform the data crunching

      msXpSlibmass::MassSpectrum *newSpectrum = new msXpSlibmass::MassSpectrum;
      newSpectrum->initialize(
        p_enc64MzByteArray, p_enc64IByteArray, compressionType);

      // Free the memory that we do not use anymore.
      delete p_enc64MzByteArray;
      delete p_enc64IByteArray;

      // qDebug() << __FILE__ << __LINE__
      // << "Allocated new spectrum of " << newSpectrum->size() << "peaks";

      // Calculate the TIC for the spectrum:
      // We now have to deal with the mzIntegration, since we finally have the
      // mass spectrum data.

      if(mzIntegration)
        localIntensity += newSpectrum->valSum(mzStart, mzEnd);
      else
        localIntensity += newSpectrum->valSum();

      delete newSpectrum;

      // At this point we can increment the number of spectra that were
      // effectively read.
      ++spectraRead;

      QString msg = QString(" -- spectrum number %1\n").arg(spectraRead);

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

      // qDebug() << __FILE__ << __LINE__ << msg;
    }

  // Delete the query instance because it holds lots of stuff.
  delete sqlQuery;
  database.close();

  // Reset the operation cancelled flag:
  m_isOperationCancelled = false;

  // Finally, set the localIntensity to the passed parameter.
  *intensity = localIntensity;

  return spectraRead;
}


} // namespace msXpSmineXpert
