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

/////////////////////// stdlib includes
#include <map>


/////////////////////// Qt includes
#include <QObject>
#include <QQmlEngine>


/////////////////////// Local includes
#include "pappsomspp/core/js_qml/jsclassregistrar.h"
#include "pappsomspp/core/precision.h"
#include "pappsomspp/core/massspectrum/massspectrum.h"

namespace pappso
{

/*! The MzIntegrationParams class provides the parameters definining how m/z
 * integrations must be performed.
 *
 * Depending on the various mass spectrometer vendors, the mass spectrometry
 * data files are structured in different ways and the software for mass data
 * format conversion from raw files to mzML or mzXML produce mass data
 * characterized by different behaviours.
 *
 * The different characteristics of mass spectrometry data set are:
 *
 * The size of the various mass spectra in the file is constant or variable;
 *
 * The first m/z value of the various spectra is identical or not (that is,
 * the spectra are root in a constant or variable root m/z value);
 *
 * The m/z delta between two consecutive m/z values of a given spectrum are
 * constant or variable;
 *
 * The spectra contain or not 0-value m/z data points;
 */

/* BEGIN CLASS JS REFERENCE
 * namespace: pappso
 * class name: MzIntegrationParams
 *
 * BEGIN DESCRIPTION
 * This class configures the way mass spectra are computed,
 * in particular with respect of the m/z binning.
 * END DESCRIPTION
 */
class PMSPP_LIB_DECL MzIntegrationParams: public QObject
{
  Q_OBJECT

  /*$ JS PROP REF Smallest m/z value of the set of mass spectra. This value is
   * used as the smallest m/z value upon calculation of the bins.*/
  Q_PROPERTY(double smallestMz READ getSmallestMz WRITE setSmallestMz NOTIFY
               smallestMzChanged)
  /*$ JS PROP REF Greatest m/z value of the set of mass spectra. This value is
   * used as the greatest m/z value upon calculation of the bins.*/
  Q_PROPERTY(double greatestMz READ getGreatestMz WRITE setGreatestMz NOTIFY greatestMzChanged)
  Q_PROPERTY(BinningType binningType READ getBinningType WRITE setBinningType
               NOTIFY binningTypeChanged)
  Q_PROPERTY(double indicativeBinSize READ getIndicativeBinSize WRITE setIndicativeBinSize NOTIFY
               indicativeBinSizeChanged)
  Q_PROPERTY(int decimalPlaces READ getDecimalPlaces WRITE setDecimalPlaces
               NOTIFY decimalPlacesChanged)
  Q_PROPERTY(pappso::PrecisionPtr binSizeModel READ getBinSizeModel WRITE
               setBinSizeModel NOTIFY binSizeModelChanged)
  Q_PROPERTY(int binSizeDivisor READ getBinSizeDivisor WRITE setBinSizeDivisor
               NOTIFY binSizeDivisorChanged)
  Q_PROPERTY(bool removeZeroValDataPoints READ isRemoveZeroValDataPoints WRITE
               setRemoveZeroValDataPoints NOTIFY removeZeroValDataPointsChanged)

  QML_ELEMENT

  public:
  enum class BinningType
  {
    //! < no binning
    NONE = 0,

    //! binning based on mass spectral data
    DATA_BASED,

    //! binning based on arbitrary bin size value
    ARBITRARY,

    LAST,
  };
  Q_ENUM(BinningType)

  enum class InitializationResult : uint32_t
  {
    // The order is important.
    // See end of class declaration for overload of bitwise operators
    DEFAULT               = 0x000,
    BINNING_TYPE          = 1 << 0,
    BIN_SIZE_MODEL        = 1 << 1,
    BIN_SIZE_DIVISOR      = 1 << 2,
    DECIMAL_PLACES        = 1 << 3,
    BINNING_LOGIC_PARTIAL = (BINNING_TYPE | BIN_SIZE_MODEL),
    BINNING_LOGIC_FULL =
      (BINNING_LOGIC_PARTIAL | BIN_SIZE_DIVISOR | DECIMAL_PLACES),
    REMOVE_ZERO_DATA_POINTS = 1 << 4,
    FULL                    = (REMOVE_ZERO_DATA_POINTS | BINNING_LOGIC_FULL),
  };
  Q_ENUM(InitializationResult)

  Q_INVOKABLE explicit MzIntegrationParams(QObject *parent = nullptr);
  Q_INVOKABLE explicit MzIntegrationParams(const QString &text,
                                           QObject *parent = nullptr);
  Q_INVOKABLE explicit MzIntegrationParams(double minMz,
                                           double maxMz,
                                           BinningType binningType,
                                           pappso::PrecisionPtr precisionPtr,
                                           int binSizeDivisor,
                                           int decimalPlaces,
                                           bool removeZeroValDataPoints,
                                           QObject *parent = nullptr);
  virtual ~MzIntegrationParams();

  Q_INVOKABLE MzIntegrationParams *clone(QObject *parent = nullptr) const;

  Q_INVOKABLE InitializationResult initialize(const QString &text);

  Q_INVOKABLE void initialize(double minMz,
                              double maxMz,
                              BinningType binningType,
                              pappso::PrecisionPtr precisionPtr,
                              int binSizeDivisor,
                              int decimalPlaces,
                              bool removeZeroValDataPoints,
                              QObject *parent = nullptr);

  void initialize(const MzIntegrationParams &other, QObject *parent = nullptr);
  Q_INVOKABLE void initialize(const MzIntegrationParams *other_p,
                              QObject *parent = nullptr);

  void initialize(MzIntegrationParams &other,
                  InitializationResult initialization_results);

  Q_INVOKABLE void setSmallestMz(double value);
  Q_INVOKABLE void updateSmallestMz(double value);
  Q_INVOKABLE double getSmallestMz() const;

  Q_INVOKABLE void setGreatestMz(double value);
  Q_INVOKABLE void updateGreatestMz(double value);
  Q_INVOKABLE double getGreatestMz() const;

  Q_INVOKABLE void setMzValues(double smallest, double greatest);

  Q_INVOKABLE void setBinningType(BinningType binningType);
  Q_INVOKABLE BinningType getBinningType() const;

  Q_INVOKABLE void setBinSizeModel(pappso::PrecisionPtr bin_size_model_p);
  Q_INVOKABLE pappso::PrecisionPtr getBinSizeModel() const;

  Q_INVOKABLE void setBinSizeDivisor(int divisor);
  Q_INVOKABLE int getBinSizeDivisor() const;

  Q_INVOKABLE void setIndicativeBinSize(double value);
  Q_INVOKABLE double getIndicativeBinSize() const;

  Q_INVOKABLE void setDecimalPlaces(int decimal_places);
  Q_INVOKABLE int getDecimalPlaces() const;

  Q_INVOKABLE void setRemoveZeroValDataPoints(bool removeOrNot = true);
  Q_INVOKABLE bool isRemoveZeroValDataPoints() const;

  Q_INVOKABLE void reset();

  Q_INVOKABLE bool isValid() const;

  Q_INVOKABLE bool hasValidMzRange() const;

  Q_INVOKABLE std::vector<double> createBins();
  Q_INVOKABLE std::vector<double>
  createBins(pappso::MassSpectrumCstSPtr mass_spectrum_csp);

  Q_INVOKABLE QString toString(int offset, const QString &spacer = " ") const;
  // The string below is used to recreate the MzIntegrationParams from string
  Q_INVOKABLE QString toString() const;
  Q_INVOKABLE QString
  binsToStringWithDeltas(const std::vector<double> bins) const;

  // # pragma message "This pragma is inside"
  static void registerJsConstructor(QJSEngine *engine);

  signals:
  void smallestMzChanged();
  void greatestMzChanged();
  void binningTypeChanged();
  void indicativeBinSizeChanged();
  void decimalPlacesChanged();
  void binSizeModelChanged();
  void binSizeDivisorChanged();
  void removeZeroValDataPointsChanged();

  protected:
  // That smallest value needs to be set to max, because it will be necessary
  // compare any new m/z valut to it.
  double m_smallestMz = std::numeric_limits<double>::max();

  // That greatest value needs to be set to min, because it will be necessary
  // compare any new m/z valut to it.
  double m_greatestMz = std::numeric_limits<double>::min();

  ///////////// The binning size logic ///////////////////
  ///////////// The binning size logic ///////////////////

  double m_indicativeBinSize = 0;

  BinningType m_binningType = BinningType::ARBITRARY;
  pappso::PrecisionPtr m_binSizeModel =
    pappso::PrecisionFactory::getResInstance(40000);
  // If one selects Res as the binning logic and sets a value of 40000, for
  // example, that binning logic will create bins (that is, final m/z data
  // points in the trace) apart of a delta of (m/z / 40000). But this interval
  // does not make a fully profiled peak, it only governs the distance between
  // two m/z adjacent values in the abscissae. Empirically, it takes between 5
  // and 8 points to shape reasonably well a mass peak. This is what
  // m_binSizeDivisor is about: divide the m/z delta computed using the m/z
  // value and the res value by this divisor value reduces by such factor the
  // distance between one m/z data point and the next in the abscissae. The
  // effect of this is that there will be enough data points to craft a
  // reasonably well shaped mass peak with a FWHM corresponding to Res.
  int m_binSizeDivisor = 6;
  // -1 means that there is no restriction on the number of digits after the
  // decimal point. Upon integration, the bin size will be checked to determine
  // the number of zero decimals it has (0.004 has two) and the number of
  // decimals used will be that value plus 1 (3 decimals in the example).
  int m_decimalPlaces  = -1;

  ///////////// The binning size logic ///////////////////
  ///////////// The binning size logic ///////////////////

  bool m_removeZeroValDataPoints = true;

  std::vector<double> createArbitraryBins();
  std::vector<double>
  createDataBasedBins(pappso::MassSpectrumCstSPtr massSpectrum);
  std::vector<double>
  createDataBasedBinsOld(pappso::MassSpectrumCstSPtr massSpectrum);
};

/*  END CLASS JS REFERENCE
 *  namespace: pappso
 *  class name: MzIntegrationParams
 */


// Overload bitwise operators
constexpr MzIntegrationParams::InitializationResult
operator|(MzIntegrationParams::InitializationResult a,
          MzIntegrationParams::InitializationResult b)
{
  return static_cast<MzIntegrationParams::InitializationResult>(
    static_cast<uint32_t>(a) | static_cast<uint32_t>(b));
}

constexpr MzIntegrationParams::InitializationResult
operator&(MzIntegrationParams::InitializationResult a,
          MzIntegrationParams::InitializationResult b)
{
  return static_cast<MzIntegrationParams::InitializationResult>(
    static_cast<uint32_t>(a) & static_cast<uint32_t>(b));
}

constexpr MzIntegrationParams::InitializationResult
operator|=(MzIntegrationParams::InitializationResult &a,
           MzIntegrationParams::InitializationResult b)
{
  a = a | b;
  return a;
}

extern std::map<MzIntegrationParams::BinningType, QString> binningTypeMap;
MzIntegrationParams::BinningType getBinningTypeFromString(const QString &text);

PAPPSO_REGISTER_JS_CLASS(pappso, MzIntegrationParams)

} // namespace pappso

/*
Q_DECLARE_METATYPE(pappso::MzIntegrationParams);
Q_DECLARE_METATYPE(pappso::MzIntegrationParams *);

extern int mzIntegrationParamsMetaTypeId;
extern int mzIntegrationParamsPtrMetaTypeId;*/
