/* 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/MassSpecSqlite3Handler.hpp>
#include <minexpert/nongui/History.hpp>


namespace msXpSmineXpert
{


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

//! Fill-in the specifications to create the metaData table.
/*!

  When creating a new database, the creation of the tables is the very first
  step. This function  handles the creation of the metaDa table of the
  database.

  \return true upon success. False otherwise.

*/
bool
MassSpecSqlite3Handler::createMetaDataTableSpecifs()
{
  m_metaDataTableSpecifs.reset();

  m_metaDataTableSpecifs.setSqlStatementSeed(
    "CREATE TABLE metaData(metaData_id "
    "INTEGER PRIMARY KEY AUTOINCREMENT "
    "NOT NULL");

  SqlTableFieldSpecifs *sqlTableFieldSpecifs = new SqlTableFieldSpecifs(
    "metaData",
    "fileName",
    FieldQtType::QSTRING,
    FieldSqlType::TEXT,
    "mzML file name",
    "fileName TEXT NOT NULL UNIQUE CHECK(fileName != '')");
  m_metaDataTableSpecifs.appendTableFieldSpecif(sqlTableFieldSpecifs);

  sqlTableFieldSpecifs = new SqlTableFieldSpecifs(
    "metaData",
    "acquisitionDate",
    FieldQtType::QSTRING,
    FieldSqlType::TEXT,
    "data acquisition date",
    "acquisitionDate TEXT NOT NULL UNIQUE CHECK(acquisitionDate != '')");
  m_metaDataTableSpecifs.appendTableFieldSpecif(sqlTableFieldSpecifs);

  sqlTableFieldSpecifs =
    new SqlTableFieldSpecifs("metadata",
                             "spectrumListCount",
                             FieldQtType::INT,
                             FieldSqlType::INTEGER,
                             "total number of spectra",
                             "spectrumListCount INTEGER NOT NULL UNIQUE");
  m_metaDataTableSpecifs.appendTableFieldSpecif(sqlTableFieldSpecifs);

  sqlTableFieldSpecifs = new SqlTableFieldSpecifs("metadata",
                                                  "driftTimeMin",
                                                  FieldQtType::DOUBLE,
                                                  FieldSqlType::FLOAT,
                                                  "minimum drift time",
                                                  "driftTimeMin FLOAT");
  m_metaDataTableSpecifs.appendTableFieldSpecif(sqlTableFieldSpecifs);

  sqlTableFieldSpecifs = new SqlTableFieldSpecifs("metadata",
                                                  "driftTimeMax",
                                                  FieldQtType::DOUBLE,
                                                  FieldSqlType::FLOAT,
                                                  "maximum drift time",
                                                  "driftTimeMax FLOAT");
  m_metaDataTableSpecifs.appendTableFieldSpecif(sqlTableFieldSpecifs);

  sqlTableFieldSpecifs = new SqlTableFieldSpecifs("metadata",
                                                  "driftTimeStep",
                                                  FieldQtType::DOUBLE,
                                                  FieldSqlType::FLOAT,
                                                  "drift time step",
                                                  "driftTimeStep FLOAT");
  m_metaDataTableSpecifs.appendTableFieldSpecif(sqlTableFieldSpecifs);

  sqlTableFieldSpecifs =
    new SqlTableFieldSpecifs("metadata",
                             "compressionType",
                             FieldQtType::INT,
                             FieldSqlType::INTEGER,
                             "compression type (1 for zlib)",
                             "compressionType INTEGER");
  m_metaDataTableSpecifs.appendTableFieldSpecif(sqlTableFieldSpecifs);

  return true;
}


//! Craft the SQL statement for the creation of the metaData table.
/*!

  \param specMetaData reference to the MassSpecFileMetaData to use to search
  for values to INSERT in the database.

  \return a string containing the SQL INSERT statement to fill-in the metaData
  table.

*/
QString
MassSpecSqlite3Handler::metaDataTableInsertSqlCommand(
  const MassSpecFileMetaData &specMetaData)
{
  QString sqlCommandLine = "INSERT INTO metaData";
  QString variables      = "(";
  QString values         = "VALUES(";

  // Now iterate in all the various items of specMetaData

  QStringList keys;

  QStringList stringValues;
  QList<int> intValues;
  QList<double> doubleValues;
  QList<bool> boolValues;

  // The string.
  keys.clear();
  specMetaData.allStringItems(keys, stringValues);
  int listSize = keys.size();

  for(int iter = 0; iter < listSize; ++iter)
    {
      QString key   = keys.at(iter);
      QString value = stringValues.at(iter);

      // Now craft the INSERT sub-command:
      variables += QString("%1, ").arg(key);
      // Note that for string values, we need to put them
      // between single quotes so that sqlite3 is happy.
      values += QString("'%1', ").arg(value);

      // qDebug() << __FILE__ << __LINE__
      // << "variables:" << variables
      // << "values:" << values;
    }

  // The int.
  keys.clear();
  specMetaData.allIntItems(keys, intValues);
  listSize = keys.size();

  for(int iter = 0; iter < listSize; ++iter)
    {
      QString key = keys.at(iter);
      int value   = intValues.at(iter);

      // Now craft the INSERT sub-command:
      variables += QString("%1, ").arg(key);
      values += QString("%1, ").arg(value);

      // qDebug() << __FILE__ << __LINE__
      // << "variables:" << variables
      // << "values:" << values;
    }

  // The double.
  keys.clear();
  specMetaData.allDoubleItems(keys, doubleValues);
  listSize = keys.size();

  for(int iter = 0; iter < listSize; ++iter)
    {
      QString key  = keys.at(iter);
      double value = doubleValues.at(iter);

      // Now craft the INSERT sub-command:
      variables += QString("%1, ").arg(key);
      values += QString("%1, ").arg(value);

      // qDebug() << __FILE__ << __LINE__
      // << "variables:" << variables
      // << "values:" << values;
    }

  // The bool.
  keys.clear();
  specMetaData.allBoolItems(keys, boolValues);
  listSize = keys.size();

  for(int iter = 0; iter < listSize; ++iter)
    {
      QString key    = keys.at(iter);
      bool boolValue = boolValues.at(iter);
      int intValue   = (boolValue ? 1 : 0);

      // Now craft the INSERT sub-command:
      variables += QString("%1, ").arg(key);
      values += QString("%1, ").arg(intValue);

      // qDebug() << __FILE__ << __LINE__
      // << "variables:" << variables
      // << "values:" << values;
    }

  // At this point, close the strings. But before, remove the last ','
  // that does not belong to the end of the strings.

  QRegExp regExp(", *$");

  variables.remove(regExp);
  variables += ") ";

  values.remove(regExp);
  values += ") ";

  // And make the full command:
  sqlCommandLine += variables;
  sqlCommandLine += values;

  return sqlCommandLine;
}


//! Write to the database (\p db or \c m_db) the meta data.
/*!

  If \p db is nullptr, the database is \c m_db.

  \param specMetaData reference to the MassSpecFileMetaData to write into the
  database.

  \param db pointer to the database in which the meta data need to be written.

  \return true if the write operation succeeded, false otherwise.

*/
bool
MassSpecSqlite3Handler::writeDbMassSpecFileMetaData(
  QSqlDatabase &database, const MassSpecFileMetaData &specMetaData)
{

  // QString *text =
  // specMetaData.asText(MassSpecFileMetaDataType::MASS_SPEC_META_DATA_TYPE_ALL);
  // qDebug() << __FILE__ << __LINE__
  //<< "Getting specMetaData:" << *text;
  // delete text;

  if(!initializeDbForRead(database))
    {
      qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
               << "Failed to initialize database for read.";

      return false;
    }

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


  // Check if the metadata are already in the database. We do not want to
  // insert these data more than once.
  QString sqlQueryString = QString("SELECT * FROM metadata;");

  sqlQuery->exec(sqlQueryString);
  sqlQuery->first();

  if(sqlQuery->isValid())
    {
      qFatal(
        "Fatal error at %s@%d -- %s(). "
        "A metadata record is already found in the database."
        "Program aborted.",
        __FILE__,
        __LINE__,
        __FUNCTION__);
    }

  // No record is found in the metadata table. Make an INSERT.

  QString sqlCommandLine = metaDataTableInsertSqlCommand(specMetaData);

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

  bool ok = false;

  ok = sqlQuery->exec(sqlCommandLine);

  if(!ok)
    {
      qFatal(
        "Fatal error at %s@%d -- %s(). Error: %s."
        "Failed to store the metadata in the database.\n"
        "Program aborted.",
        __FILE__,
        __LINE__,
        __FUNCTION__,
        sqlQuery->lastError().text().toLatin1().data());
    }
  else
    {
      // qDebug() << __FILE__ << __LINE__
      //<< "Success adding the metadata to the database.";
    }

  delete sqlQuery;

  return true;
}


//! Fill-in the specifications to create the driftData table.
/*!

  When creating a new database, the creation of the tables is the very first
  step. This function  handles the creation of the driftData table of the
  database.

  \return true upon success. False otherwise.

*/
bool
MassSpecSqlite3Handler::createDriftDataTableSpecifs()
{
  m_driftDataTableSpecifs.reset();

  m_driftDataTableSpecifs.setSqlStatementSeed(
    "CREATE TABLE driftData(driftData_id INTEGER PRIMARY KEY AUTOINCREMENT "
    "NOT NULL");

  SqlTableFieldSpecifs *sqlTableFieldSpecifs =
    new SqlTableFieldSpecifs("driftData",
                             "driftTime",
                             FieldQtType::DOUBLE,
                             FieldSqlType::FLOAT,
                             "drift time",
                             "driftTime FLOAT UNIQUE");
  m_driftDataTableSpecifs.appendTableFieldSpecif(sqlTableFieldSpecifs);


  return true;
}


//! Write to the database (\p db or \c m_db) the drift data.
/*!

  If \p db is nullptr, the database is \c m_db.

  \param driftTime drift time value.

  \param recordId pointer to an int in which to store the id of the record
  generated by this writing operation. Cannot be nullptr.

  \param db pointer to the database in which the meta data need to be written.

  \return true if the write operation succeeded, false otherwise.

*/
bool
MassSpecSqlite3Handler::writeDbDriftData(QSqlDatabase &database,
                                         double driftTime,
                                         int *recordId)
{
  if(recordId == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  // if(db != Q_NULLPTR)
  // qDebug() << __FILE__ << __LINE__
  // << "The destination database has pointer: " << db
  // << "and name:" << db->databaseName(); // Note
  // that it might happen that the mzML file contains

  // driftTime values like 'nan' or 'inf' (not yet understood why).
  // In these cases we set driftTime, so that
  // we can recognize these spectra later when working in the
  // database.

  if(driftTime <= -1)
    {
      qFatal(
        "Fatal error at %s@%d -- %s. "
        "Programming error:\n"
        "\tDrift time cannot be less than -1 in this function.\n"
        "Exiting.\n ."
        "Program aborted.",
        __FILE__,
        __LINE__,
        __FUNCTION__);
    }

  // qDebug() << __FILE__ << __LINE__
  //<< "writeDbDriftData: "
  //<< "driftTime: " << driftTime;

  // Look if the table has already an identical record.


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

  sqlQuery->prepare(
    "SELECT driftData_id FROM driftData "
    "WHERE (driftTime = :driftTime);");
  sqlQuery->bindValue(":driftTime", driftTime);

  sqlQuery->exec();

  sqlQuery->first();

  if(sqlQuery->isValid())
    {
      int driftDataId = sqlQuery->value(0).toInt();

      // qDebug() << __FILE__ << __LINE__
      // << "The drift time value :" << driftTime
      // << "is already found in the driftData table"
      // << "with driftDataId: " << driftDataId
      // << "reusing that value.";

      *recordId = driftDataId;
    }
  else
    {
      // We need to create a new row in that driftData table:

      sqlQuery->prepare(
        "INSERT INTO driftData(driftTime) "
        "VALUES (:driftTime)");
      sqlQuery->bindValue(":driftTime", driftTime);

      bool res = sqlQuery->exec();

      if(!res)
        {
          QSqlError sqlError = sqlQuery->lastError();
          QString error      = sqlError.text();

          qDebug() << __FILE__ << __LINE__
                   << "Failed to store the driftTime record " << driftTime
                   << "in the driftData table. Error:" << error;

          delete sqlQuery;

          return false;
        }
      else
        {
          // Manage to store the driftDataId of the new row.

          sqlQuery->prepare(
            "SELECT driftData_id FROM driftData "
            "WHERE (driftTime = :driftTime);");
          sqlQuery->bindValue(":driftTime", driftTime);

          sqlQuery->exec();

          sqlQuery->first();

          if(sqlQuery->isValid())
            {
              int driftDataId = sqlQuery->value(0).toInt();

              *recordId = driftDataId;
            }
          else
            {
              QSqlError sqlError = sqlQuery->lastError();
              QString error      = sqlError.text();

              qDebug() << __FILE__ << __LINE__
                       << "Failed to store the driftTime record " << driftTime
                       << "in the driftData table. Error:" << error;

              delete sqlQuery;

              return false;
            }
        }
    }

  delete sqlQuery;

  return true;
}


//! Fill-in the specifications to create the spectralData table.
/*!

  When creating a new database, the creation of the tables is the very first
  step. This function  handles the creation of the spectralData table of the
  database.

  \return true upon success. False otherwise.

*/
bool
MassSpecSqlite3Handler::createSpectralDataTableSpecifs()
{
  m_spectralDataTableSpecifs.reset();

  m_spectralDataTableSpecifs.setSqlStatementSeed(
    "CREATE TABLE spectralData(spectralData_id INTEGER PRIMARY KEY "
    "AUTOINCREMENT NOT NULL");

  SqlTableFieldSpecifs *sqlTableFieldSpecifs = new SqlTableFieldSpecifs(
    "spectralData",
    "driftData_id",
    FieldQtType::INT,
    FieldSqlType::INTEGER,
    "id of the corresponding driftData table row",
    "driftData_id INTEGER REFERENCES driftData(driftData_id)");
  m_spectralDataTableSpecifs.appendTableFieldSpecif(sqlTableFieldSpecifs);

  sqlTableFieldSpecifs =
    new SqlTableFieldSpecifs("spectralData",
                             "msLevel",
                             FieldQtType::INT,
                             FieldSqlType::INTEGER,
                             "MS level",
                             "msLevel INTEGER NOT NULL CHECK(msLevel > 0)");
  m_spectralDataTableSpecifs.appendTableFieldSpecif(sqlTableFieldSpecifs);

  sqlTableFieldSpecifs =
    new SqlTableFieldSpecifs("spectralData",
                             "idx",
                             FieldQtType::INT,
                             FieldSqlType::INTEGER,
                             "0-based spectrum index",
                             "idx INTEGER NOT NULL UNIQUE CHECK(idx >= 0)");
  m_spectralDataTableSpecifs.appendTableFieldSpecif(sqlTableFieldSpecifs);

  sqlTableFieldSpecifs =
    new SqlTableFieldSpecifs("spectralData",
                             "title",
                             FieldQtType::QSTRING,
                             FieldSqlType::TEXT,
                             "spectrum title",
                             "title TEXT NOT NULL CHECK(title != '')");
  m_spectralDataTableSpecifs.appendTableFieldSpecif(sqlTableFieldSpecifs);

  sqlTableFieldSpecifs =
    new SqlTableFieldSpecifs("spectralData",
                             "peakCount",
                             FieldQtType::INT,
                             FieldSqlType::INTEGER,
                             "number of peaks in the current spectrum",
                             "peakCount INTEGER NOT NULL");
  m_spectralDataTableSpecifs.appendTableFieldSpecif(sqlTableFieldSpecifs);

  sqlTableFieldSpecifs =
    new SqlTableFieldSpecifs("spectralData",
                             "scanStartTime",
                             FieldQtType::DOUBLE,
                             FieldSqlType::FLOAT,
                             "NOT_SET",
                             "scanStartTime FLOAT NOT NULL");
  m_spectralDataTableSpecifs.appendTableFieldSpecif(sqlTableFieldSpecifs);

  sqlTableFieldSpecifs = new SqlTableFieldSpecifs("spectralData",
                                                  "tic",
                                                  FieldQtType::DOUBLE,
                                                  FieldSqlType::FLOAT,
                                                  "total ion current",
                                                  "tic FLOAT");
  m_spectralDataTableSpecifs.appendTableFieldSpecif(sqlTableFieldSpecifs);

  sqlTableFieldSpecifs =
    new SqlTableFieldSpecifs("spectralData",
                             "mzStart",
                             FieldQtType::DOUBLE,
                             FieldSqlType::FLOAT,
                             "start-point of the m/z range",
                             "mzStart FLOAT NOT NULL");
  m_spectralDataTableSpecifs.appendTableFieldSpecif(sqlTableFieldSpecifs);

  sqlTableFieldSpecifs = new SqlTableFieldSpecifs("spectralData",
                                                  "mzEnd",
                                                  FieldQtType::DOUBLE,
                                                  FieldSqlType::FLOAT,
                                                  "end-point of the m/z range",
                                                  "mzEnd FLOAT NOT NULL");
  m_spectralDataTableSpecifs.appendTableFieldSpecif(sqlTableFieldSpecifs);

  sqlTableFieldSpecifs =
    new SqlTableFieldSpecifs("spectralData",
                             "mzList",
                             FieldQtType::QSTRING,
                             FieldSqlType::TEXT,
                             "list of m/z values",
                             // I have removed the NOT NULL CHECK(mzList != '')
                             // condition because sometimes spectra are empty.
                             "mzList TEXT");
  m_spectralDataTableSpecifs.appendTableFieldSpecif(sqlTableFieldSpecifs);

  sqlTableFieldSpecifs =
    new SqlTableFieldSpecifs("spectralData",
                             "iList",
                             FieldQtType::QSTRING,
                             FieldSqlType::TEXT,
                             "list of intensities",
                             // I have removed the NOT NULL CHECK(iList != '')
                             // condition because sometimes spectra are empty.
                             "iList TEXT");
  m_spectralDataTableSpecifs.appendTableFieldSpecif(sqlTableFieldSpecifs);

  return true;
}


//! Write to the database (\p db or \c m_db) the spectral data.
/*!

  If \p db is nullptr, the database is \c m_db. The parameters are
  self-explanatory, unless for these:

  \param idx index of the spectrum to be written to the database.

  \param title title of the spectrum.

  \param scanStartTime retention time at which the spectrum was recorded.

  \param driftDataId id of the matching driftData table record.

  \param mzStart and mzEnd first and last m/z value of the spectrum.

  \return true if the write operation succeeded, false otherwise.

*/
bool
MassSpecSqlite3Handler::writeDbSpectralData(QSqlDatabase &database,
                                            int idx,
                                            const QString &title,
                                            int peakCount,
                                            int msLevel,
                                            double tic,
                                            double scanStartTime,
                                            int driftDataId,
                                            double mzStart,
                                            double mzEnd,
                                            const QByteArray &mzByteArray,
                                            const QByteArray &iByteArray)
{
  // double start = omp_get_wtime();

  QSqlQuery *sqlQuery = new QSqlQuery(database);

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


  // The non conditional stuff first.
  QString sqlQueryStringFieldNames =
    "INSERT INTO spectralData("
    "idx,title,peakCount,msLevel,"
    "scanStartTime,"
    "mzStart,mzEnd,mzList,iList";

  QString sqlQueryStringValueNames =
    "VALUES("
    ":idx,:title,:peakCount,:msLevel,"
    ":scanStartTime,"
    ":mzStart,:mzEnd,:mzList,:iList";

  // Now the conditional stuff.

  if(driftDataId != -1)
    {
      sqlQueryStringFieldNames += ",driftData_id";
      sqlQueryStringValueNames += ",:driftData_id";
    }

  if(tic != -1)
    {
      sqlQueryStringFieldNames += ",tic";
      sqlQueryStringValueNames += ",:tic";
    }

  // And now close the command:

  sqlQueryStringFieldNames += ")";
  sqlQueryStringValueNames += ")";

  // And finally concatenate the command:

  QString sqlQueryString = sqlQueryStringFieldNames + sqlQueryStringValueNames;

  bool ok = sqlQuery->prepare(sqlQueryString);

  if(!ok)
    {
      QSqlError sqlError = sqlQuery->lastError();
      QString error      = sqlError.text();

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

  // The non-conditional fields.
  sqlQuery->bindValue(":idx", idx);
  sqlQuery->bindValue(":title", title);
  sqlQuery->bindValue(":peakCount", peakCount);
  sqlQuery->bindValue(":msLevel", msLevel);
  sqlQuery->bindValue(":scanStartTime", scanStartTime);
  sqlQuery->bindValue(":mzStart", mzStart);
  sqlQuery->bindValue(":mzEnd", mzEnd);
  sqlQuery->bindValue(":mzList", mzByteArray);
  sqlQuery->bindValue(":iList", iByteArray);

  if(driftDataId != -1)
    {
      sqlQuery->bindValue(":driftData_id", driftDataId);
    }

  if(tic != -1)
    {
      sqlQuery->bindValue(":tic", tic);
    }

#if 0
			// debug //////////////// Let's look at all the bound values

			QMapIterator<QString, QVariant> i(sqlQuery->boundValues());
			while (i.hasNext())
			{
				i.next();
				qDebug() <<__FILE__ << __LINE__
					<< i.key().toUtf8().data() << ": "
					<< i.value().toString().toUtf8().data();
			}
#endif

  ok = false;

  ok = sqlQuery->exec();

  if(!ok)
    {
      QSqlError sqlError = sqlQuery->lastError();
      QString error      = sqlError.text();

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

  sqlQuery->finish();

  delete sqlQuery;

  // double end = omp_get_wtime();

  // qDebug() << __FILE__ << __LINE__
  // << "Written in" <<
  // end - start
  // << "s spectral data for a spectrum that has" << peakCount << "peaks";

  return true;
}


//! Craft a string containing the SQL statement to create the table.
/*!

  \param tableName name of the table to create (must be one of metaData,
  spectralData or driftData).

  \return a string containing the SQL statement to be used to create the
  table.

*/
QString
MassSpecSqlite3Handler::tableCreationSqlCommand(QString tableName)
{
  QString sqlCommandLine = "";

  if(tableName == "metaData")
    sqlCommandLine = m_metaDataTableSpecifs.tableCreationSqlString();
  else if(tableName == "spectralData")
    sqlCommandLine = m_spectralDataTableSpecifs.tableCreationSqlString();
  else if(tableName == "driftData")
    sqlCommandLine = m_driftDataTableSpecifs.tableCreationSqlString();
  else
    {
      qFatal(
        "Fatal error at %s@%d -- %s. "
        "Failed to craft the sql command. Exiting\n."
        "Program aborted.",
        __FILE__,
        __LINE__,
        __FUNCTION__);
    }

  return sqlCommandLine;
}


//! Create the tables for the database \p db.
/*!

  If \p db is non-nullptr, create the tables in \p db. Else, create the table
  in \c m_db.

  \param db pointer to the database in which the tables must be created. That
  pointer might be null.

  \return true if the creation succeeded, false otherwise.

*/
bool
MassSpecSqlite3Handler::createDbTables(QSqlDatabase &database)
{
  QSqlQuery *sqlQuery = new QSqlQuery(database);

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

  // This PRAGMA synchronous call is only really useful when the program
  // runs on conventional hard disks, with plates that rotate. In the case
  // of solid state disks, the benefits are lower.

  if(!sqlQuery->exec("PRAGMA synchronous = OFF"))
    {
      QSqlError sqlError = sqlQuery->lastError();
      QString error      = sqlError.text();

      qDebug() << __FILE__ << __LINE__ << "Failed to execute an SQL command"
               << "with error:" << error;

      delete sqlQuery;

      return false;
    }

  if(!sqlQuery->exec("PRAGMA foreign_keys"))
    {
      QSqlError sqlError = sqlQuery->lastError();
      QString error      = sqlError.text();

      qDebug() << __FILE__ << __LINE__ << "Failed to execute an SQL command"
               << "with error:" << error;

      delete sqlQuery;

      return false;
    }

  if(!createMetaDataTableSpecifs())
    {
      qFatal(
        "Fatal error at %s@%d -- %s(). "
        "Failed to create the metaData table specifications.."
        "Program aborted.",
        __FILE__,
        __LINE__,
        __FUNCTION__);
    }

  // Ask for the sql string that will allow us to create the
  // metaData table.
  QString sqlQueryString = tableCreationSqlCommand("metaData");

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


  if(!sqlQuery->exec(sqlQueryString))
    {
      QSqlError sqlError = sqlQuery->lastError();
      QString error      = sqlError.text();

      qDebug() << __FILE__ << __LINE__ << "Failed to execute an SQL command"
               << "with error:" << error;

      delete sqlQuery;

      return false;
    }

  if(!createSpectralDataTableSpecifs())
    {
      qDebug() << __FILE__ << __LINE__
               << "Failed to create the spectralData "
                  "table specifications. Exiting.\n";
      exit(1);
    }

  // Ask for the sql string that will allow us to create the
  // spectralData table.
  sqlQueryString = tableCreationSqlCommand("spectralData");

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

  if(!sqlQuery->exec(sqlQueryString))
    {
      QSqlError sqlError = sqlQuery->lastError();
      QString error      = sqlError.text();

      qDebug() << __FILE__ << __LINE__ << "Failed to execute an SQL command"
               << "with error:" << error;

      delete sqlQuery;

      return false;
    }

  if(!createDriftDataTableSpecifs())
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "Failed to create the driftData table specifications."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  // Ask for the sql string that will allow us to create the
  // table.
  sqlQueryString = tableCreationSqlCommand("driftData");

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

  if(!sqlQuery->exec(sqlQueryString))
    {
      QSqlError sqlError = sqlQuery->lastError();
      QString error      = sqlError.text();

      qDebug() << __FILE__ << __LINE__ << "Failed to execute an SQL command"
               << "with error:" << error;

      delete sqlQuery;

      return false;
    }

  delete sqlQuery;

  return true;
}


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


//! Construct a MassSpecSqlite3Handler with a database file name.
/*!

  \param fileName name of the file that contains or will contain the database.

*/
MassSpecSqlite3Handler::MassSpecSqlite3Handler()
{
}


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


bool
MassSpecSqlite3Handler::initializeDbForRead(QSqlDatabase &database,
                                            bool readMetaData)
{
  if(!database.isOpen() || !database.isValid())
    {
      if(!database.open())
        {
          qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
                   << "The database was not open and could not be opened";

          return false;
        }
      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
      //<< "The database was not open and could be opened.";
    }

  // Now initialize the m_msFileMetaData with data from the database (if so
  // requested)
  if(readMetaData)
    m_msFileMetaData = getMassSpecFileMetaDataFromDb(database);

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "The list of tables:" << database.tables();

  return true;
}


//! Initialize the member database for writing records.
/*!

  If the database is open, close it, remove the connection because we'll
  connect it to the file for overwriting it while writing to it.

  Then the tables are created anew.

  \return true if the new connection could be done and the tables could be
  created anew. False otherwise.

*/
bool
MassSpecSqlite3Handler::initializeDbForWrite(QSqlDatabase &database)
{

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

        return false;
      }

  qWarning() << __FILE__ << __LINE__
             << "Database is now open. Now creating the tables.";

  if(!createDbTables(database))
    {
      qFatal(
        "Fatal error at %s@%d -- %s. "
        "Creating tables: Failure. Exiting.\n"
        "Program aborted.",
        __FILE__,
        __LINE__,
        __FUNCTION__);
    }

  // else
  //{
  // qDebug() << __FILE__ << __LINE__
  // << "Creating tables: Success. "
  // << "The database now has the proper structure.\n";
  //}

  return true;
}


// Set the SQLite3 data file name
void
MassSpecSqlite3Handler::setDbFileName(const QString &fileName)
{
  m_dbFileName = fileName;
}


//! Extract from the database the data in the metaData table.
/*!

  \return a MassSpecFileMetaData instance filled-in with the data extracted
  from the member database. If the database query fails, return an empty
  MassSpecFileMetaData instance .

*/
MassSpecFileMetaData
MassSpecSqlite3Handler::getMassSpecFileMetaDataFromDb(QSqlDatabase &database)
{
  // Get the metaData out of the database.

  MassSpecFileMetaData specMetaData;

  if(!initializeDbForRead(database))
    return specMetaData;

  QSqlQuery sqlQuery(database);
  bool ok = false;

  QString queryString("SELECT * 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());
    }

  sqlQuery.first();

  // The value at index 0 is the table row number, we
  // don't care. The format is like this:
  // sqlite3 20151207-tfana-esipos-post-calib-tofms-mob-without.db "select *
  // from metadata";
  // metaData_id|fileName|acquisitionDate|spectrumListCount|driftTimeMin|driftTimeMax|
  // <follows>
  // <followed> driftTimeStep|compressionType
  // 1|/media/disk/devel/dataForMzml/mzml/file.mzml|2015.11.30|1200|0.0|10.7959|0.0542505|200|0

  QSqlRecord sqlRecord = sqlQuery.record();

  int fieldCount = sqlRecord.count();

  for(int iter = 0; iter < fieldCount; ++iter)
    {
      QSqlField sqlField         = sqlRecord.field(iter);
      QString fieldTitle         = sqlField.name();
      QVariant::Type variantType = sqlField.type();
      QVariant value             = sqlField.value();

      if(variantType == QVariant::String)
        specMetaData.setStringItem(fieldTitle, value.toString());
      else if(variantType == QVariant::Int)
        specMetaData.setIntItem(fieldTitle, value.toInt());
      else if(variantType == QVariant::Int)
        specMetaData.setDoubleItem(fieldTitle, value.toDouble());
    }

  return specMetaData;
}


//! Extract from the database the number of spectra.
/*!

  \return The number of spectra recorded in the database. Upon failure, the
  program crashes.

*/
int
MassSpecSqlite3Handler::spectrumListCount(QSqlDatabase &database)
{
  if(!initializeDbForRead(database))
    return -1;

  QSqlQuery sqlQuery(database);
  bool ok = false;

  QString queryString("SELECT spectrumListCount from metaData");

  ok = sqlQuery.exec(queryString);

  if(!ok)
    {
      QString msg =
        QString(
          "Failed to get proper data from the database. Error: %1. Exiting.\n")
          .arg(sqlQuery.lastError().text());

      qFatal("Fatal error at %s@%d -- %s: %s",
             __FILE__,
             __LINE__,
             __FUNCTION__,
             msg.toLatin1().data());
    }

  if(!sqlQuery.first())
    {
      QString msg =
        QString(
          "Failed to get proper data from the database. Error: %1. Exiting.\n")
          .arg(sqlQuery.lastError().text());

      qFatal("Fatal error at %s@%d -- %s: %s",
             __FILE__,
             __LINE__,
             __FUNCTION__,
             msg.toLatin1().data());
    }

  ok = false;

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

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

  return count;
}


//! Extract from the database file the list of retention times.
/*!

  \return a list of double values containing all the retention times at which
  spectra were recorded.

  If the database cannot be queried, an empty list is returned.

*/
QList<double>
MassSpecSqlite3Handler::rtList(QSqlDatabase &database)
{
  QList<double> rtList;

  if(!initializeDbForRead(database))
    {
      qDebug() << __FILE__ << __LINE__
               << "Failed to initialize the database for read.";

      return rtList;
    }

  QSqlQuery sqlQuery(database);
  bool ok = false;

  QString queryString(
    "SELECT scanStartTime from spectralData "
    "GROUP BY scanStartTime ORDER BY scanStartTime");

  ok = sqlQuery.exec(queryString);

  if(!ok)
    {
      qDebug() << __FILE__ << __LINE__
               << "Failed to get proper data from the database.\n"
               << sqlQuery.lastError() << "Exiting.\n";

      exit(1);
    }

  while(sqlQuery.next())
    {
      double rt = sqlQuery.value(0).toDouble();

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

      rtList.append(rt);
    }

  // qDebug() << __FILE__ << __LINE__
  // << "Returning rtList: " << rtList;

  return rtList;
}


//! Extract the m/z range of the data in the database.
/*!

  The database is queried for the smallest and greatest m/z values in the
  whole data set; the values are assigned to \p mzStart and \p mzEnd,
  respectively.

  \param mzStart reference to the smallest m/z value in the data set.

  \param mzEnd reference to the greatest m/z value in the data set.

*/
void
MassSpecSqlite3Handler::mzRange(QSqlDatabase &database,
                                double &mzStart,
                                double &mzEnd)
{
  // We need to get the first and the last mz values of the spectrum.

  if(!initializeDbForRead(database))
    return;

  QSqlQuery sqlQuery(database);
  bool ok = false;

  QString queryString("SELECT mzStart,mzEnd from spectralData");

  ok = sqlQuery.exec(queryString);

  if(!ok)
    {
      qDebug() << __FILE__ << __LINE__
               << "Failed to get proper data from the database.\n"
               << sqlQuery.lastError() << "Exiting.\n";

      exit(1);
    }

  if(!sqlQuery.first())
    {
      qDebug() << __FILE__ << __LINE__
               << "Failed to get proper data from the database.\n"
               << sqlQuery.lastError() << "Exiting.\n";

      exit(1);
    }

  ok = false;

  mzStart = sqlQuery.value(0).toDouble(&ok);

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

  mzEnd = sqlQuery.value(1).toDouble(&ok);

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

  return;
}


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

  Crafts a SQL query to inspect potentially existing drift data in \c this \c
  m_db database.

  \return true if drift data are found, false otherwise. -1 is returned if the
  database cannot be initializeDbForRead().

*/
bool
MassSpecSqlite3Handler::isDriftExperiment(QSqlDatabase &database)
{
  // We can ask to know how many drift time values there are in the
  // driftData table.

  if(!initializeDbForRead(database, false /* readMetaData */))
    return false;

  QSqlQuery sqlQuery(database);
  bool ok = false;

  QString queryString("SELECT COUNT(*), driftTime FROM driftData");

  ok = sqlQuery.exec(queryString);

  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.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();

  return recordCount;
}


//! Extract from the driftData table of the database a list of drift times.
/*!

  The member database is queried for all the data in the driftData table. Then
  each drift time value is appended to a list of double values.

  \return a list of double values corresponding to all the drift time values
  recorded in the driftData table of the member database.

*/
QList<double> *
MassSpecSqlite3Handler::driftTimeList(QSqlDatabase &database)
{
  if(!initializeDbForRead(database))
    return 0;

  QSqlQuery sqlQuery(database);
  bool ok = false;

  QString queryString("SELECT * FROM driftData");
  ok = sqlQuery.exec(queryString);

  if(!ok)
    {
      qDebug() << __FILE__ << __LINE__
               << "Failed to get proper data from the database.\n"
               << sqlQuery.lastError() << "Exiting.\n";

      exit(1);
    }

  // The data output from the database should be of this format (the first
  // element is the row number, the second is the drift time in milliseconds
  // and the third is the drift bin.
  //
  // 1|0.0|1
  // 2|0.110254|2
  // 3|0.220507|3

  // Allocate the list of double values.
  QList<double> *driftTimeList = new QList<double>;

  QSqlRecord sqlRec = sqlQuery.record();

  int fieldNo = sqlQuery.record().indexOf("driftTime");

  while(sqlQuery.next())
    {
      double driftTime = sqlQuery.value(fieldNo).toDouble();
      driftTimeList->append(driftTime);
    }

  return driftTimeList;
}


//! Export the meta data from \c m_db to \p db.
/*!

  This function is called when exporting data from \c this \c m_db to \p db.
  The data being exported are read from \c this \c m_db and written to the
  other \p db. Note that these exported data may not be the full data set.
  Indeed, typically an export involves the user to select a subset of the data
  only (part of a TIC chromatogram, for example).  In this case, the number of
  spectra that are exported will not be equal to the number of spectra
  currently stored in \c this \c m_db database.  This is the reason why the
  spectrum list count value that must be written to the database in this
  function along with the othre \c m_msFileMetaData data is passed by
  parameter to this function by the caller function, which knows how many
  spectra it is exporting. See the caller function \c exportData() for
  details.

  \param db pointer to the receiving database.

  \param spectrumListCount number of spectra that are being exported. This
  value is recorded along other values of \c m_msFileMetaData.

  \return true if the export succeeds. Failure to execute SQL queries crash
  the program.

  \sa exportData() <br> exportData_driftData().

*/
bool
MassSpecSqlite3Handler::exportData_metaData(QSqlDatabase &srcDatabase,
                                            QSqlDatabase &dstDatabase,
                                            int spectrumListCount)
{

  QString srcDbQueryString = "SELECT * FROM metaData";

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

  QSqlQuery *srcSqlQuery = new QSqlQuery(srcDatabase);

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

  bool ok = srcSqlQuery->exec(srcDbQueryString);

  if(!ok)
    {
      QString msg =
        QString(
          "Failed to get proper data from the database. Error: %1. Exiting.\n")
          .arg(srcSqlQuery->lastError().text());

      qFatal("Fatal error at %s@%d -- %s: %s",
             __FILE__,
             __LINE__,
             __FUNCTION__,
             msg.toLatin1().data());
    }

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


  // Oddly, the statement below makes the program crash. Not sure why, yet.
  // Since we'll use next() we can use less memory and maybe be speedier.
  // srcSqlQuery->setForwardOnly(true);

  // Set some room for all the variables:

  MassSpecFileMetaData metaData;

  int fieldNo;

  while(srcSqlQuery->next())
    {
      fieldNo = srcSqlQuery->record().indexOf("fileName");
      metaData.setStringItem("fileName",
                             srcSqlQuery->value(fieldNo).toString());

      fieldNo = srcSqlQuery->record().indexOf("acquisitionDate");
      metaData.setStringItem("acquisitionDate",
                             srcSqlQuery->value(fieldNo).toString());

      fieldNo = srcSqlQuery->record().indexOf("driftTimeMin");

      if(!srcSqlQuery->value(fieldNo).isNull())
        metaData.setDoubleItem("driftTimeMin",
                               srcSqlQuery->value(fieldNo).toDouble());

      fieldNo = srcSqlQuery->record().indexOf("driftTimeMax");

      if(!srcSqlQuery->value(fieldNo).isNull())
        metaData.setDoubleItem("driftTimeMax",
                               srcSqlQuery->value(fieldNo).toDouble());

      fieldNo = srcSqlQuery->record().indexOf("driftTimeStep");

      if(!srcSqlQuery->value(fieldNo).isNull())
        metaData.setDoubleItem("driftTimeStep",
                               srcSqlQuery->value(fieldNo).toDouble());

      fieldNo = srcSqlQuery->record().indexOf("compressionType");

      if(!srcSqlQuery->value(fieldNo).isNull())
        metaData.setIntItem("compressionType",
                            srcSqlQuery->value(fieldNo).toInt());

      // Finally the datum that we got as a function parameter because we
      // cannot simply copy the value in the source database, we want that
      // value to actually match the number of spectralData records in the
      // destination database.

      metaData.setIntItem("spectrumListCount", spectrumListCount);

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

  delete srcSqlQuery;

  return true;
}


//! Export the drift data from \c m_db to \p db.
/*!

  The data in the driftData table of \c this \m m_db are read and immediately
  written to the same table of the other \p db database.

  The impossibility to write data to the receiving \p db database crashes the
  program.

  \return true if the operation succeeds.

*/
bool
MassSpecSqlite3Handler::exportData_driftData(QSqlDatabase &srcDatabase,
                                             QSqlDatabase &dstDatabase)
{

  if(!initializeDbForRead(srcDatabase))
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "Failed to initialize the database for reading."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  QSqlQuery *srcSqlQuery = new QSqlQuery(srcDatabase);

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

  QString srcDbQueryString = "SELECT driftTime FROM driftData";

  bool ok = srcSqlQuery->exec(srcDbQueryString);

  if(!ok)
    {
      QString msg =
        QString(
          "Failed to get proper data from the database. Error: %1. Exiting.\n")
          .arg(srcSqlQuery->lastError().text());

      qFatal("Fatal error at %s@%d -- %s: %s",
             __FILE__,
             __LINE__,
             __FUNCTION__,
             msg.toLatin1().data());
    }

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

  double driftTime;

  while(srcSqlQuery->next())
    {
      driftTime = srcSqlQuery->value(0).toDouble();

      // Now that we have the values, we can INSERT these in the destination
      // database driftData table. Their order in the destination database
      // needs to be the same as in the original database, because we'll
      // also dump the driftData_id value from the spectraData records.

      int recordId;

      if(!writeDbDriftData(dstDatabase, driftTime, &recordId))
        qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);
    }

  delete srcSqlQuery;

  return true;
}


//! Export data from \c this \c m_db to a database in file \p fileName.
/*!

  The criteria on which data are selected from \c this \c m_db database are
  described in \p history.  This function uses a number of helper functions to
  write the data in the various tables of the destination database file (See
  also section).

  \param fileName name of the destination database.

  \param history History instance describing the criteria for data selection
  in \c this \m_db database (source database).

  \return true if the operation succeeded. Failure to create tables in the
  destination database or to write data to the destination database crash the
  program.

  \sa writeDbMassSpecFileMetaData() <br> writeDbDriftData() <br>
  writeDbSpectralData()

*/
QString
MassSpecSqlite3Handler::exportData(QSqlDatabase &srcDatabase,
                                   QString dstFileName,
                                   History history)
{
  QString errors;

  // This is a huge task. We need to read data from m_dbFileName (that we
  // know from the constructor. m_dbFileName is the name of the database
  // that we call the source. But we get a fileName for the destination
  // database.

  // First handle the cas of the destination database that we need to
  // prepare (create it on the basis of the fileName param and create its
  // tables).

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

  QSqlDatabase dstDatabase =
    QSqlDatabase::addDatabase("QSQLITE", connectionName);

  dstDatabase.setDatabaseName(dstFileName);

  bool ok = false;

  ok = dstDatabase.open();

  if(!ok)
    {
      errors += QString("%1 %2 %3 %4")
                  .arg(__FILE__)
                  .arg(__LINE__)
                  .arg(__FUNCTION__)
                  .arg("Opening data export destination database: Failure\n");

      return errors;
    }
  else
    {
      // qDebug() << __FILE__ << __LINE__ << "Opening data export destination "
      //"database: Success. Now creating "
      //"the tables.";
    }

  if(!createDbTables(dstDatabase))
    {
      errors += QString("%1 %2 %3 %4")
                  .arg(__FILE__)
                  .arg(__LINE__)
                  .arg(__FUNCTION__)
                  .arg("Creating tables for destination database: Failure\n");

      return errors;
    }
  else
    {
      // qDebug() << __FILE__ << __LINE__
      //<< "Creating tables for destination database: Success. "
      //<< "The destination database now has the proper structure.\n";
    }

  // At this point, we need to make sure we have all the rt/dt
  // integrations so that we can take them into account while doing the
  // SELECT operations for m_db.

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

  // qDebug() << __FILE__ << __LINE__
  //<< "RT integration:" << rtIntegration << "[" << rtStart << "," << rtEnd <<
  //"]";

  // MZ integration.
  // ===============
  // bool mzIntegration = false;
  // mzStart and mzEnd required below for the SELECT statement.
  double mzStart = -1;
  double mzEnd   = -1;
  // mzIntegration = history.innermostMzRange(&mzStart, &mzEnd);

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

  // qDebug() << __FILE__ << __LINE__
  //<< "DT integration:" << dtIntegration << "[" << dtStart << "," << dtEnd <<
  //"]";

  // We need to craft a SqlQuery string that is specific of the experimental
  // situation: are we dealing with drift data or not.

  bool wasDriftExperiment = isDriftExperiment(srcDatabase);

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

  if(wasDriftExperiment)
    {
      if(!exportData_driftData(srcDatabase, dstDatabase))
        qFatal(
          "Fatal error at %s@%d -- %s(). "
          "Failed to export the driftData table."
          "Program aborted.",
          __FILE__,
          __LINE__,
          __FUNCTION__);
    }

  QString srcDbQueryString;

  QString openingStatement = "SELECT ";
  QString countStatement   = "COUNT(*), ";

  QString uncondFields;
  QString condFields;

  QString fromStatement;
  QString joinStatements;
  QString whereStatements;

  // The unconditional stuff.
  uncondFields += "msLevel, idx, title, peakCount, scanStartTime, ";
  uncondFields += "tic, mzStart, mzEnd, ";

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

  // The conditional stuff (note how the comma is at the beginning now).
  if(wasDriftExperiment)
    condFields += ", spectralData.driftData_id, driftTime ";

  fromStatement += "FROM spectralData ";

  if(wasDriftExperiment)
    joinStatements +=
      "JOIN driftData ON (spectralData.driftData_id = "
      "driftData.driftData_id) ";

  if(rtIntegration /* || 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;
      //}
      //}

      if(dtIntegration)
        {
          // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
          //<< "dtIntegration is true with dtStart:" << dtStart
          //<< "dtEnd:" << dtEnd;

          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;

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

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

  QSqlQuery *srcSqlQuery = new QSqlQuery(srcDatabase);

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

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

  // First ensure we get the actual number of records:
  srcDbQueryString = openingStatement + countStatement + uncondFields +
                     condFields + fromStatement + joinStatements +
                     whereStatements;

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

  ok = srcSqlQuery->exec(srcDbQueryString);

  if(!ok)
    {
      errors +=
        QString("%1 %2 %3 %4 %5")
          .arg(__FILE__)
          .arg(__LINE__)
          .arg(__FUNCTION__)
          .arg("Failed to get proper data from the database with errors:\n")
          .arg(srcSqlQuery->lastError().text());

      return errors;
    }

  if(!srcSqlQuery->isActive())
    {
      errors += QString("%1 %2 %3 %4")
                  .arg(__FILE__)
                  .arg(__LINE__)
                  .arg(__FUNCTION__)
                  .arg("The query is not active.");

      return errors;
    }

  // Get the number of records that were retrieved, because we'll need this
  // to provide feedback to the caller.
  if(!srcSqlQuery->first())
    {
      errors += QString("%1 %2 %3 %4")
                  .arg(__FILE__)
                  .arg(__LINE__)
                  .arg(__FUNCTION__)
                  .arg("Failed to get the number of records.");

      return errors;
    }

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

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

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

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

  srcDbQueryString = openingStatement + uncondFields + condFields +
                     fromStatement + joinStatements + whereStatements;

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

  ok = srcSqlQuery->exec(srcDbQueryString);

  if(!ok)
    {
      errors += QString("%1 %2 %3 %4")
                  .arg(__FILE__)
                  .arg(__LINE__)
                  .arg(__FUNCTION__)
                  .arg("Failed to get proper data from the database.");

      return errors;
    }

  if(!srcSqlQuery->isActive())
    {
      errors += QString("%1 %2 %3 %4")
                  .arg(__FILE__)
                  .arg(__LINE__)
                  .arg(__FUNCTION__)
                  .arg("The query is not active.");

      return errors;
    }

  // Commented out because it crashes the program below ? FIXME: Why ?
  // Since we'll use next() we can use less memory and maybe be speedier.
  // srcSqlQuery->setForwardOnly(true);

  // Before going on with the actual results parsing, let's setup the
  // feedback routine.
  m_progressFeedbackStartValue = 0;
  m_progressFeedbackEndValue   = recordCount;

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

  // Set some room for all the variables:

  int driftData_id = -1;
  int msLevel      = -1;
  int idx          = -1;
  int peakCount    = -1;

  QString title;

  double scanStartTime = -1;
  double tic           = -1;

  QByteArray mzByteArray;
  QByteArray iByteArray;

  int fieldNo;

  // We'll need to know how many spectra we export in the new database, so
  // that we'll be able to fill in the spectrumListCount field of the
  // metaData table when we'll export it also.
  int spectrumListCount = 0;
  int iteration         = 0;

  while(srcSqlQuery->next())
    {
      ++iteration;

      emit updateFeedbackSignal("Exporting data",
                                iteration,
                                true /* setVisible */,
                                LogType::LOG_TO_BOTH,
                                m_progressFeedbackStartValue,
                                m_progressFeedbackEndValue);

      // qDebug() << __FILE__ << __LINE__
      // << "updateFeedbackSignal: Exporting data..." << iteration;

      if(wasDriftExperiment)
        {
          fieldNo = srcSqlQuery->record().indexOf("driftData_id");

          if(fieldNo == -1)
            {
              errors += QString("%1 %2 %3 %4")
                          .arg(__FILE__)
                          .arg(__LINE__)
                          .arg(__FUNCTION__)
                          .arg(
                            "Drift experiment, but no driftData_id record "
                            "found in the database.");

              return errors;
            }
          driftData_id = srcSqlQuery->value(fieldNo).toInt();
        }
      else
        driftData_id = -1;

      fieldNo = srcSqlQuery->record().indexOf("msLevel");
      msLevel = srcSqlQuery->value(fieldNo).toInt();

      fieldNo = srcSqlQuery->record().indexOf("idx");
      idx     = srcSqlQuery->value(fieldNo).toInt();

      fieldNo = srcSqlQuery->record().indexOf("title");
      title   = srcSqlQuery->value(fieldNo).toString();

      fieldNo   = srcSqlQuery->record().indexOf("peakCount");
      peakCount = srcSqlQuery->value(fieldNo).toInt();

      fieldNo       = srcSqlQuery->record().indexOf("scanStartTime");
      scanStartTime = srcSqlQuery->value(fieldNo).toDouble();

      fieldNo = srcSqlQuery->record().indexOf("tic");

      if(!srcSqlQuery->value(fieldNo).isNull())
        {
          tic = srcSqlQuery->value(fieldNo).toDouble();

          // qDebug() << __FILE__ << __LINE__
          // << "fieldNo of tic at read time is:" << fieldNo
          // << "with value:" << tic;
        }
      else
        {
          // qDebug() << __FILE__ << __LINE__
          // << "fieldNo of tic at read time is:" << fieldNo
          // << "but the value is not NULL, setting to -1";

          tic = -1;
        }

      fieldNo = srcSqlQuery->record().indexOf("mzStart");
      mzStart = srcSqlQuery->value(fieldNo).toDouble();

      fieldNo = srcSqlQuery->record().indexOf("mzEnd");
      mzEnd   = srcSqlQuery->value(fieldNo).toDouble();

      fieldNo     = srcSqlQuery->record().indexOf("specMzList");
      mzByteArray = srcSqlQuery->value(fieldNo).toByteArray();

      fieldNo    = srcSqlQuery->record().indexOf("specIList");
      iByteArray = srcSqlQuery->value(fieldNo).toByteArray();

      // We now have all the data bits required to actually write the data to
      // the spectralData table of the destination database.

      if(!writeDbSpectralData(dstDatabase,
                              idx,
                              title,
                              peakCount,
                              msLevel,
                              tic,
                              scanStartTime,
                              driftData_id,
                              mzStart,
                              mzEnd,
                              mzByteArray,
                              iByteArray))
        {
          errors +=
            QString("%1 %2 %3 %4")
              .arg(__FILE__)
              .arg(__LINE__)
              .arg(__FUNCTION__)
              .arg("Failed to write the mass data to the spectralData table.");

          return errors;
        }

      ++spectrumListCount;
    }

  // End of
  // while(srcSqlQuery->next())

  delete srcSqlQuery;

  // Finally, we can export the metaData stuff:
  if(!exportData_metaData(srcDatabase, dstDatabase, spectrumListCount))
    {
      errors += QString("%1 %2 %3 %4")
                  .arg(__FILE__)
                  .arg(__LINE__)
                  .arg(__FUNCTION__)
                  .arg("Failed to write the meta data to the metaData table.");

      return errors;
    }
  // And now close the destination database.
  srcDatabase.close();
  dstDatabase.close();

  return errors;
}


//! Export data from \c this \c m_db to a database in file \p fileName.
/*!

  The criteria on which data are selected from \c this \c m_db database are
  described in \p history.  This function uses a number of helper functions to
  write the data in the various tables of the destination database file (See
  also section).

  \param fileName name of the destination database.

  \param history History instance describing the criteria for data selection
  in \c this \m_db database (source database).

  \return true if the operation succeeded. Failure to create tables in the
  destination database or to write data to the destination database crash the
  program.

  \sa writeDbMassSpecFileMetaData() <br> writeDbDriftData() <br>
  writeDbSpectralData()

*/
QString
MassSpecSqlite3Handler::exportData(QString srcFileName,
                                   QString dstFileName,
                                   History history)
{
  QString errors;

  // This is a huge task. We need to read data from m_dbFileName (that we
  // know from the constructor. m_dbFileName is the name of the database
  // that we call the source. But we get a fileName for the destination
  // database.

  // First handle the cas of the destination database that we need to
  // prepare (create it on the basis of the fileName param and create its
  // tables).

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

  QSqlDatabase srcDatabase =
    QSqlDatabase::addDatabase("QSQLITE", connectionName);

  srcDatabase.setDatabaseName(srcFileName);

  bool ok = false;

  ok = srcDatabase.open();

  if(!ok)
    {
      errors += QString("%1 %2 %3 %4")
                  .arg(__FILE__)
                  .arg(__LINE__)
                  .arg(__FUNCTION__)
                  .arg("Opening data export source database: Failure\n");

      return errors;
    }
  else
    {
      // qDebug() << __FILE__ << __LINE__ << "Opening data export source "
      //"database: Success. Now creating the tables.";
    }

  return exportData(srcDatabase, dstFileName, history);
}


} // namespace msXpSmineXpert
