#pragma once

#include <vector>
#include <memory>

#include <QDataStream>


#include "../exportinmportconfig.h"
#include "../types.h"
#include "datapoint.h"
#include "../mzrange.h"
#include "../processing/filters/filterinterface.h"

namespace pappso
{


class Trace;
// @TODO function is not implemented :
PMSPP_LIB_DECL QDataStream &operator<<(QDataStream &out, const Trace &trace);
// @TODO function is not implemented :
PMSPP_LIB_DECL QDataStream &operator>>(QDataStream &out, Trace &trace);

/** @brief find the first element in which X is equal or greater than the value
 * searched important : it implies that Trace is sorted by X
 * */
PMSPP_LIB_DECL std::vector<DataPoint>::iterator
findFirstEqualOrGreaterX(std::vector<DataPoint>::iterator begin,
                         std::vector<DataPoint>::iterator end,
                         const double &value);

PMSPP_LIB_DECL std::vector<DataPoint>::const_iterator
findFirstEqualOrGreaterX(std::vector<DataPoint>::const_iterator begin,
                         std::vector<DataPoint>::const_iterator end,
                         const double &value);

/** @brief find the first element in which Y is different of value
 * */
PMSPP_LIB_DECL std::vector<DataPoint>::iterator
findDifferentYvalue(std::vector<DataPoint>::iterator begin,
                    std::vector<DataPoint>::iterator end,
                    const double &y_value);

PMSPP_LIB_DECL std::vector<DataPoint>::const_iterator
findDifferentYvalue(std::vector<DataPoint>::const_iterator begin,
                    std::vector<DataPoint>::const_iterator end,
                    const double &y_value);

/** @brief find the first element in which X is greater than the value
 * searched important : it implies that Trace is sorted by X
 * */
PMSPP_LIB_DECL std::vector<DataPoint>::iterator
findFirstGreaterX(std::vector<DataPoint>::iterator begin,
                  std::vector<DataPoint>::iterator end,
                  const double &value);

PMSPP_LIB_DECL std::vector<DataPoint>::const_iterator
findFirstGreaterX(std::vector<DataPoint>::const_iterator begin,
                  std::vector<DataPoint>::const_iterator end,
                  const double &value);

/** @brief find the element with the smallest Y value (intensity)
 * */

PMSPP_LIB_DECL std::vector<DataPoint>::iterator
minYDataPoint(std::vector<DataPoint>::iterator begin,
              std::vector<DataPoint>::iterator end);

PMSPP_LIB_DECL std::vector<DataPoint>::const_iterator
minYDataPoint(std::vector<DataPoint>::const_iterator begin,
              std::vector<DataPoint>::const_iterator end);

/** @brief find the element with the greatest Y value (intensity)
 * */
PMSPP_LIB_DECL std::vector<DataPoint>::iterator
maxYDataPoint(std::vector<DataPoint>::iterator begin,
              std::vector<DataPoint>::iterator end);

PMSPP_LIB_DECL std::vector<DataPoint>::const_iterator
maxYDataPoint(std::vector<DataPoint>::const_iterator begin,
              std::vector<DataPoint>::const_iterator end);

/** @brief Move right to the lower value
 * */
PMSPP_LIB_DECL std::vector<DataPoint>::const_iterator
moveLowerYRigthDataPoint(const Trace &trace,
                         std::vector<DataPoint>::const_iterator begin);
/** @brief Move left to the lower value
 * */
PMSPP_LIB_DECL std::vector<DataPoint>::const_iterator
moveLowerYLeftDataPoint(const Trace &trace,
                        std::vector<DataPoint>::const_iterator begin);

/** @brief calculate the sum of y value of a trace
 * */
PMSPP_LIB_DECL double sumYTrace(std::vector<DataPoint>::const_iterator begin,
                                std::vector<DataPoint>::const_iterator end,
                                double init);

/** @brief calculate the mean of y value of a trace
 * */
PMSPP_LIB_DECL double meanYTrace(std::vector<DataPoint>::const_iterator begin,
                                 std::vector<DataPoint>::const_iterator end);

/** @brief calculate the median of y value of a trace
 * */
PMSPP_LIB_DECL double medianYTrace(std::vector<DataPoint>::const_iterator begin,
                                   std::vector<DataPoint>::const_iterator end);


/** @brief calculate the quantile of y value of a trace
 * @param begin begin iterator
 * @param end end iterator
 * @param quantile the quantile value between 0 and 1
 * @return Y value at the quantile
 * */
PMSPP_LIB_DECL double
quantileYTrace(std::vector<DataPoint>::const_iterator begin,
               std::vector<DataPoint>::const_iterator end,
               double quantile);


/** @brief calculate the area of a trace
 * */
PMSPP_LIB_DECL double areaTrace(std::vector<DataPoint>::const_iterator begin,
                                std::vector<DataPoint>::const_iterator end);


PMSPP_LIB_DECL Trace
flooredLocalMaxima(std::vector<DataPoint>::const_iterator begin,
                   std::vector<DataPoint>::const_iterator end,
                   double y_floor);

typedef std::shared_ptr<Trace> TraceSPtr;
typedef std::shared_ptr<const Trace> TraceCstSPtr;

class MapTrace;
class TraceCombiner;
class TracePlusCombiner;
class TraceMinusCombiner;

/**
 * \class Trace
 * \brief A simple container of DataPoint instances
 */
class PMSPP_LIB_DECL Trace : public std::vector<DataPoint>
{

  friend class TraceCombiner;
  friend class TraceMinusCombiner;
  friend class TracePlusCombiner;

  friend class MassSpectrumCombinerInterface;

  public:
  Trace();
  Trace(const QString &text);
  Trace(const std::vector<pappso_double> &xVector,
        const std::vector<pappso_double> &yVector);
  Trace(const std::vector<std::pair<pappso_double, pappso_double>> &dataPoints);
  Trace(const std::vector<DataPoint> &dataPoints);
  Trace(const std::vector<DataPoint> &&dataPoints);
  explicit Trace(const MapTrace &map_trace);
  Trace(const Trace &other);
  Trace(const Trace &&other); // move constructor
  virtual ~Trace();

  size_t initialize(const std::vector<pappso_double> &xVector,
                    const std::vector<pappso_double> &yVector);

  size_t initialize(const QString &x_text, const QString &y_text);
  size_t initialize(const QString &space_sep_text);

  size_t initialize(const Trace &other);

  size_t initialize(const std::map<pappso_double, pappso_double> &map);

  virtual Trace &operator=(const Trace &x);
  virtual Trace &operator=(Trace &&x);

  TraceSPtr makeTraceSPtr() const;
  TraceCstSPtr makeTraceCstSPtr() const;

  /** @brief appends a datapoint and return new size
   */
  size_t append(const DataPoint &data_point);

  std::vector<pappso_double> xValues() const;
  std::vector<pappso_double> yValues() const;

  std::map<pappso_double, pappso_double> toMap() const;

  DataPoint containsX(pappso_double value,
                      PrecisionPtr precision_p = nullptr) const;

  // const Peak & Spectrum::getLowestIntensity() const;
  const DataPoint &minXDataPoint() const;
  // was const Peak & Spectrum::getMaxIntensity() const;
  const DataPoint &maxXDataPoint() const;

  // const Peak & Spectrum::getLowestIntensity() const;
  const DataPoint &minYDataPoint() const;
  // was const Peak & Spectrum::getMaxIntensity() const;
  const DataPoint &maxYDataPoint() const;

  pappso_double minX() const;
  pappso_double maxX() const;
  pappso_double minY() const;

  pappso_double maxY() const;
  pappso_double maxY(double mzStart, double mzEnd) const;

  pappso_double sumY() const;
  pappso_double sumY(double mzStart, double mzEnd) const;

  // was void Spectrum::sortByMz();
  void sort(SortType sort_type, SortOrder sort_order = SortOrder::ascending);
  void sortX(SortOrder sort_order = SortOrder::ascending);
  void sortY(SortOrder sort_order = SortOrder::ascending);
  void unique();
  std::size_t removeZeroYDataPoints();

  /** @brief apply a filter on this trace
   * @param filter to process the signal
   * @return reference on the modified Trace
   */
  virtual Trace &filter(const FilterInterface &filter) final;
  QString toString() const;

  QByteArray xAsBase64Encoded() const;
  QByteArray yAsBase64Encoded() const;

  /** @brief find datapoint with exactly x value
   */
  std::vector<DataPoint>::const_iterator
  dataPointCstIteratorWithX(pappso_double value) const;

  protected:
  //! Return a reference to the DataPoint instance that has its y member equal
  //! to \p value.
  // const DataPoint &dataPointWithX(pappso_double value) const;
  std::size_t dataPointIndexWithX(pappso_double value) const;
  std::vector<DataPoint>::iterator dataPointIteratorWithX(pappso_double value);
};


} // namespace pappso

Q_DECLARE_METATYPE(pappso::Trace);
Q_DECLARE_METATYPE(pappso::Trace *);

extern int traceMetaTypeId;
extern int tracePtrMetaTypeId;
