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


#pragma once

#include <QtSql>
#include <QSqlDatabase>
#include <QStringList>
#include <QXmlStreamReader>
#include <QString>


#include <minexpert/nongui/MassSpecDataSet.hpp>
#include <minexpert/nongui/MassSpecDataFileLoader.hpp>
#include <minexpert/nongui/MassSpecSqlite3Handler.hpp>
#include <minexpert/nongui/MassSpecFileMetaData.hpp>
#include <minexpert/nongui/History.hpp>

// For the compression enum and for the LogType enum.
#include <globals/globals.hpp>


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


namespace msXpSmineXpert
{

//! The MassSpecDataFileLoaderPwiz class provides a file loader.
/*!

  This class implements mass spectrometric data file loading using the
  ProteoWizard libpwiz library. The typical format handled by this class is
  the standard mass data format \e mzML.

  This class implements data loading with a number of backends. It is indeed
  possible to load mass data so as:

  - to fill-in a MassSpecDataSet
  - to fill-in m/z and i (intensity) vectors
  - to merely compute a total ion current intensity.

*/
class MassSpecDataFileLoaderPwiz : public MassSpecDataFileLoader
{

  friend class FileFormatConverter;

  public:
  //! Enumeration providing data kind identification.
  /*!

    In a mass spectrometry data file, different kinds of data might be found:

    - mass spectral
    - drift spectral
    - intensity

*/
  enum DataKind
  {
    DATA_KIND_UNSET,
    /*!< Data kind is unknown or unset */
    DATA_KIND_MZ,
    /*!< Data are m/z values */
    DATA_KIND_TIME,
    /*!< Data are drift time values */
    DATA_KIND_I,
    /*!< Data are intensity values */
  };

  private:
  pwiz::msdata::MSData m_msDataResult;

  //! Pointer to a pwiz-based data reader.
  pwiz::msdata::ReaderPtr mp_pwizReader = Q_NULLPTR;

  //! Pointer to the spectrum list
  pwiz::msdata::SpectrumListPtr mp_spectrumListPtr = Q_NULLPTR;

  //! Holder for details of the mass spectrometry data file.
  MassSpecFileMetaData m_msFileMetaData;

  //! Data compression type in use for the mass data.
  msXpS::DataCompression m_dataCompression =
    msXpS::DataCompression::DATA_COMPRESSION_NONE;

  //! Kind of data being handled at any given time during the load process.
  DataKind m_dataKind = DataKind::DATA_KIND_UNSET;

  //! Kind of integration type being processed.
  /*!

    When this loader is used to load data with a different backend than a
    MassSpecDataSet, the integration type specifies which kind of integration
    to perform while loading the data.

*/
  IntegrationType m_integrationType;

  //! History to record the various integration types that might be run.
  History m_history;

  //! Tells if the data should be loaded in streamed mode or not.
  /*!

    When loading data without passing a \c MassSpecDataSet pointer, then this
    is the way to know if the loading should be in streamed mode or not.
    Otherwise, the \c m_isStreamed member of MassSpecDataSet provides the same
    information.

*/
  bool m_isStreamed = false;

  //! Tells if the data should be loaded to vectors or not.
  bool m_isLoadToVectors = false;

  //! Tells if the data should be loaded to compute a TIC intensity single
  //! value.
  bool m_isLoadToTicIntensity = false;

  //! Tells if the data should be loaded to mass data files in xy format.
  /*!

    In this data load process, each time a new mass spectrum is loaded, it
    should be exported to a new file. The file naming is <data file
    name>-<spectrum index>.xy.

    Note that if a file contains ion mobility data, all the spectra for a
    given mobility cycle won't be lumped together. Each spectrum in the
    mobility cycle will be saved in a new file.

*/
  bool m_isLoadToXyFilesInSequence = false;

  //! Holds the spectrum index range (start value).
  int m_specIndexStart = -1;

  //! Holds the spectrum index range (end value).
  int m_specIndexEnd = -1;

  //! Holds the name of the file where to write the xy-formatted spectra.
  QString m_xyFileName;

  //! Holds the value of the computed total ion current single intensity value.
  double m_ticIntensity = 0.0;

  //! Pointer to a MassSpecDataSet instance.
  /*!

    When loading mass data for later analysis in mineXpert, the loaded data
    are typically stored in this MassSpecDataSet.

    Note that the data in \c mp_massSpecDataSet are \e not owned by \c this
    MassSpecDataFileLoaderPwiz instance.

*/
  MassSpecDataSet *mp_massSpecDataSet = Q_NULLPTR;

  //! Helper map to store key/val pairs while doing the computations.
  /*!

    When computing data integrations to TIC chromatogram (rt integrations),
    this map serves as a storage for rt,i pairs.

*/
  //
  QMap<double, double> m_keyValMap;

  //! Helper MassSpectrum instance pointer.
  /*!

    When loading mass data, mass spectra are heap-allocated and later either
    stored in \c mp_massSpecDataSet or processed to integrate them into other
    kinds of data (TIC intensities, ...). This helper mass spectrum pointer
    stores the address of the spectra that are dynamically allocated while
    loading data in a lasting manner so that it can be accessed across mzML
    file-parsing functions. Only delete it locally, when its use it known to
    be terminated.  Attention ! We may not own that new spectrum if it has
    been appended to the \c mp_massSpecDataSet's list of spectra.  That
    spectrum only gets deleted when we know that we can do it, because it is
    no longer of use. This is why we do not free that spectrum in the
    destructor.

*/
  msXpSlibmass::MassSpectrum *mp_newSpectrum = Q_NULLPTR;

  //! Helper MassSpectrum instance.
  /*!

    Helper mass spectrum to store the mass spectrum being created by
    successive combinations.

*/
  msXpSlibmass::MassSpectrum m_massSpectrum;

  //! Number of spectra actually parsed while loading data.
  int m_storedSpectraCount = 0;

  //! Number of parsed spectra before updating the feedback message.
  /*!

    Upon loading mass spectrometry data files, which might be lengthy, we
    provide feedback messages to be displayed to the user. This number indicates
    at what mass spectrum load interval the feedback message is updated to the
    user.

*/
  int m_parseProgressionNumber = 50;

  //! Counter of parsing cycles.
  /*!

    This counter is reset each time the m_parseProgressionNumber value is
    reached.

*/
  int m_parseProgression = 0;

  //! Number of spectra that were skipped.
  /*!

    Upon loading of mass data, empty spectra might be encountered. In this
    case, these non-data spectra are skipped. This variable holds the number
    of such skipped spectra.

*/
  int m_totalSkippedSpectra = 0;

  //! Index of the spectrum being processed.
  /*!

    This index is actually the iteration counter over the spectra in the file
    in the <spectrum> mzMl element.  It is made available throughout the class
    because its value is useful in a number of different places.

    This is the index that is used when crafting SQL statements that create
    records for the spectra when converting mzML data to SQLite3-based db
    files.

    \sa MassSpecSqlite3Handler::writeDbSpectralData()
    */
  int m_spectrumIndex = -1;

  //! MS level of the mass data being loaded.
  /*!

    This is the integer representation of the MS level: 1 stands for full
    scan, 2 for MS/MS, and more for MSn. This value is read from the mzML file
    and is used when crafting SQL statements that create records for the
    spectra when converting mzML data to SQLite3-based db files.

    \sa MassSpecSqlite3Handler::writeDbSpectralData()

*/
  int m_msLevel = -1;

  //! Spectrum title of the spectrum being loaded.
  /*!

    This string is read from the mzML file and is used when crafting SQL
    statements that create records for the spectra when converting mzML data
    to SQLite3-based db files.

    \sa MassSpecSqlite3Handler::writeDbSpectralData()

*/
  QString m_spectrumTitle = "NOT_SET";

  //! Number of peaks in the mass spectrum currently being loaded.
  /*!

    This value is obtained by looking at the m/z list obtained after reading a
    mass spectrum from file. It is used when crafting SQL statements that
    create records for the spectra when converting mzML data to SQLite3-based
    db files.

    \sa MassSpecSqlite3Handler::writeDbSpectralData()

*/
  int m_specPeakCount = -1;

  //! Total ion current value for the spectrum currently being loaded.
  /*!

    This value is computed right after loading each mass spectrum. It is used
    when crafting SQL statements that create records for the spectra when
    converting mzML data to SQLite3-based db files.

    \sa MassSpectrum::tic()

    \sa MassSpecSqlite3Handler::writeDbSpectralData()

*/
  double m_tic = -1;

  //! Time at which the mass spectral acquisition scan started.
  /*!

    This value is equivalent to the retention time of a mass spectral
    acquisition in profile mode.

    This is one of the fundamental values that characterize any given mass
    spectrum because it allows to craft a TIC chromatogram after the loading
    of a mass spectrometry data file. It is also at the basis of the mass data
    analysis as implemented in mineXpert, since that analysis typically start
    by integrating mass spectral data in a given retention time range.

    Note that this value must not be mistaken for a drift time. Indeed, in ion
    mobility mass spectrometry experiments, spectra are acquired all along a
    drift cycle. The start of a new drift cycle is that \c m_scanStartTime
    value. Each spectrum acquired during the drift cycle has a specific time
    value\: the drift time.

    \sa m_curDriftTime.

*/
  // double m_firstScanStartTime = 0;
  double m_scanStartTime = -1;

  //! Id of the driftData table corresponding to the currently loaded spectrum.
  int m_driftDataId = -1;

  //! Tells if the mass data file being loaded is for a mobility experiment.
  /*!

    This boolean value is set when the data file loading process encountered
    drift time data associated to spectra.

*/
  bool m_isMobilityExperiment = false;

  //! Drift time value associated to the mass spectrum being loaded.
  /*!

    This value is only updated if drift data are available for the current
    mass spectrum being loaded. It remains equal to -1 otherwise.

*/
  double m_curDriftTime = -1;

  //! Minimum drift time of a drift cycle.
  /*!

    This value is deduced from looking at the drift time value of the first
    mass spectrum of a drift cycle (if the experiment is a ion mobility mass
    spectrometry experiment.

*/
  double m_driftTimeMin = -1;


  //! Maximum drift time of a drift cycle.
  /*!

    This value is deduced from looking at the drift time value of the last
    mass spectrum of a drift cycle (if the experiment is a ion mobility mass
    spectrometry experiment.

*/
  double m_driftTimeMax = -1;

  //! Time interval that spans two consecutive drift times.
  /*!

    This value is computed as the drift time difference between two
    consecutive spectra of a same drift cyle.

*/
  double m_driftTimeStep = -1;

  //! Stores the previous mass spectrum's drift time.
  double m_prevDriftTime = -1;

  int m_scanWindowListCount = -1;

  //! Scan window lower limit, that is m/z range left border.
  /*!

    Left border value of the scan m/z range. This value is used when crafting
    SQL statements that create records for the spectra when converting mzML
    data to SQLite3-based db files (mzStart field in the spectralData table).

*/
  double m_scanWindowLowerLimit = -1;

  //! Scan window upper limit, that is m/z range right border.
  /*!

    Right border value of the scan m/z range. This value is used when crafting
    SQL statements that create records for the spectra when converting mzML
    data to SQLite3-based db files (mzEnd field in the spectralData table).

*/
  double m_scanWindowUpperLimit = -1;

  // Private functions that put the spectral data in various destinations.
  // These functions are called from within the MzML-parsing functions
  // (specifically the parse_spectrumList_element() function.

  QString formatDescription() const override;

  void updateLoadProgression(int count);

  void resetSpectralData();
  void resetMobilityData();

  void seedMobilityData();
  bool containsMobilityData();

  bool seedDataLoading();

  int nextSpectrum(pwiz::msdata::SpectrumPtr *p_spectrumPtr);

  msXpSlibmass::MassSpectrum *
  createNewSpectrum(pwiz::msdata::SpectrumPtr spectrumPtr);

  int loadSpectrumToMassSpecDataSet();

  int integrateToRt();
  int integrateToMz();
  int integrateToDt();

  public:
  MassSpecDataFileLoaderPwiz(const QString &fileName = QString());
  virtual ~MassSpecDataFileLoaderPwiz();

  void setFileName(const QString &fileName);

  void setMassSpecDataSet(MassSpecDataSet *massSpecDataSet);

  static int canLoadData(QString fileName,
                         MassSpecDataFileLoaderPwiz *pwiz = Q_NULLPTR);
  int readSpectrumCount() override;

  int loadDataToMassSpecDataSet(MassSpecDataSet *massSpecDataSet);

  int
  loadDataToXyFiles(const QString &xyFileName, int startIndex, int endIndex);

  // Loading data to database is a conversion to the SQLite3 db format
  int loadDataToDatabase(const QString &dbFileName);

  int streamedIntegration(const History &history,
                          IntegrationType integrationType,
                          QVector<double> *keyVector,
                          QVector<double> *valVector);

  int streamedTicIntensity(const History &history, double *intensity);
};


} // namespace msXpSmineXpert
