#include <numeric>
#include <limits>
#include <qnamespace.h>
#include <vector>
#include <map>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <iomanip>

#include <QDebug>
#include <QObject>

#include "../utils.h"
#include "trace.h"
#include "maptrace.h"
#include "../processing/combiners/tracepluscombiner.h"
#include "../processing/combiners/traceminuscombiner.h"
#include "../types.h"
#include "../pappsoexception.h"
#include "../exception/exceptionoutofrange.h"
#include "../exception/exceptionnotpossible.h"
#include "../processing/filters/filterresample.h"
#include "../processing/filters/filterpass.h"


int traceMetaTypeId    = qRegisterMetaType<pappso::Trace>("pappso::Trace");
int tracePtrMetaTypeId = qRegisterMetaType<pappso::Trace *>("pappso::Trace *");


namespace pappso
{

QDataStream &
operator<<(QDataStream &out, const Trace &trace)
{
  for(auto &dataPoint : trace)
    {
      out << dataPoint.x;
      out << dataPoint.y;
      out << "\n";
    }
  out << "\n";

  return out;
}


QDataStream &
operator>>(QDataStream &in, Trace &trace)
{
  if(in.atEnd())
    {
      throw PappsoException(
        QString("error in QDataStream unserialize operator>> of trace:\n"
                "read datastream failed status=%1")
          .arg(in.status()));
    }

  for(auto &dataPoint : trace)
    {
      in >> dataPoint.x;
      in >> dataPoint.y;
    }

  return in;
}


std::vector<DataPoint>::iterator
findFirstEqualOrGreaterX(std::vector<DataPoint>::iterator begin,
                         std::vector<DataPoint>::iterator end,
                         const double &value)
{
  return std::find_if(begin, end, [value](const DataPoint &to_compare) {
    if(to_compare.x < value)
      {
        return false;
      }
    return true;
  });
}

std::vector<DataPoint>::const_iterator
findFirstEqualOrGreaterX(std::vector<DataPoint>::const_iterator begin,
                         std::vector<DataPoint>::const_iterator end,
                         const double &value)
{
  return std::find_if(begin, end, [value](const DataPoint &to_compare) {
    if(to_compare.x < value)
      {
        return false;
      }
    return true;
  });
}

std::vector<DataPoint>::iterator
findFirstGreaterX(std::vector<DataPoint>::iterator begin,
                  std::vector<DataPoint>::iterator end,
                  const double &value)
{
  return std::find_if(begin, end, [value](const DataPoint &to_compare) {
    if(to_compare.x > value)
      {
        return true;
      }
    return false;
  });
}

std::vector<DataPoint>::const_iterator
findFirstGreaterX(std::vector<DataPoint>::const_iterator begin,
                  std::vector<DataPoint>::const_iterator end,
                  const double &value)
{
  return std::find_if(begin, end, [value](const DataPoint &to_compare) {
    if(to_compare.x > value)
      {
        return true;
      }
    return false;
  });
}

std::vector<DataPoint>::iterator
findDifferentYvalue(std::vector<DataPoint>::iterator begin,
                    std::vector<DataPoint>::iterator end,
                    const double &y_value)
{
  return std::find_if(begin, end, [y_value](const DataPoint &to_compare) {
    if(to_compare.y != y_value)
      {
        return true;
      }

    return false;
  });
}

std::vector<DataPoint>::const_iterator
findDifferentYvalue(std::vector<DataPoint>::const_iterator begin,
                    std::vector<DataPoint>::const_iterator end,
                    const double &y_value)
{
  return std::find_if(begin, end, [y_value](const DataPoint &to_compare) {
    if(to_compare.y != y_value)
      {
        return true;
      }

    return false;
  });
}


std::vector<DataPoint>::const_iterator
minYDataPoint(std::vector<DataPoint>::const_iterator begin,
              std::vector<DataPoint>::const_iterator end)
{
  return std::min_element(
    begin, end, [](const DataPoint &a, const DataPoint &b) {
      return a.y < b.y;
    });
}


std::vector<DataPoint>::iterator
minYDataPoint(std::vector<DataPoint>::iterator begin,
              std::vector<DataPoint>::iterator end)
{
  return std::min_element(
    begin, end, [](const DataPoint &a, const DataPoint &b) {
      return a.y < b.y;
    });
}


std::vector<DataPoint>::const_iterator
maxYDataPoint(std::vector<DataPoint>::const_iterator begin,
              std::vector<DataPoint>::const_iterator end)
{
  return std::max_element(
    begin, end, [](const DataPoint &a, const DataPoint &b) {
      return a.y < b.y;
    });
}


std::vector<DataPoint>::iterator
maxYDataPoint(std::vector<DataPoint>::iterator begin,
              std::vector<DataPoint>::iterator end)
{
  return std::max_element(
    begin, end, [](const DataPoint &a, const DataPoint &b) {
      return a.y < b.y;
    });
}


// As long as next DataPoint has its y value less or equal to prev's,
// move along down the container. That is, continue moving is
// direction is downhill to the end of the container (its back).
std::vector<DataPoint>::const_iterator
moveLowerYRigthDataPoint(const Trace &trace,
                         std::vector<DataPoint>::const_iterator begin)
{
  if(begin == trace.end())
    return begin;
  auto it     = begin + 1;
  auto result = begin;
  // Move along as long as next point's y value is less
  // or equal to prev point's y value (FR, check).
  while((it != trace.end()) && (it->y <= result->y))
    {
      it++;
      result++;
    }
  return result;
}

std::vector<DataPoint>::const_iterator
moveLowerYLeftDataPoint(const Trace &trace,
                        std::vector<DataPoint>::const_iterator begin)
{
  if(begin == trace.begin())
    return begin;
  auto it     = begin - 1;
  auto result = begin;

  // As long as prev datapoint has y value less or equal to next,
  // move along up the container. That is, continue moving if
  // direction is downhill to the beginning of the container (its front).
  while((it != trace.begin()) && (it->y <= result->y))
    {
      it--;
      result--;
    }
  return result;
}


double
sumYTrace(std::vector<DataPoint>::const_iterator begin,
          std::vector<DataPoint>::const_iterator end,
          double init)
{
  return std::accumulate(
    begin, end, init, [](double a, const DataPoint &b) { return a + b.y; });
}

double
meanYTrace(std::vector<DataPoint>::const_iterator begin,
           std::vector<DataPoint>::const_iterator end)
{
  pappso_double nb_element = distance(begin, end);
  if(nb_element == 0)
    throw ExceptionOutOfRange(
      QObject::tr("unable to compute mean on a trace of size 0"));
  return (sumYTrace(begin, end, 0) / nb_element);
}


double
quantileYTrace(std::vector<DataPoint>::const_iterator begin,
               std::vector<DataPoint>::const_iterator end,
               double quantile)
{
  std::size_t nb_element = distance(begin, end);
  if(nb_element == 0)
    throw ExceptionOutOfRange(
      QObject::tr("unable to compute quantile on a trace of size 0"));


  std::size_t ieth_element = std::round((double)nb_element * quantile);
  if(ieth_element > nb_element)
    throw ExceptionOutOfRange(
      QObject::tr("quantile value must be lower than 1"));


  std::vector<DataPoint> data(begin, end);
  std::nth_element(
    data.begin(),
    data.begin() + ieth_element,
    data.end(),
    [](const DataPoint &a, const DataPoint &b) { return a.y < b.y; });
  return data[ieth_element].y;
}

double
medianYTrace(std::vector<DataPoint>::const_iterator begin,
             std::vector<DataPoint>::const_iterator end)
{
  std::size_t nb_element = distance(begin, end);
  if(nb_element == 0)
    throw ExceptionOutOfRange(
      QObject::tr("unable to compute median on a trace of size 0"));

  std::vector<DataPoint> data(begin, end);
  std::nth_element(
    data.begin(),
    data.begin() + data.size() / 2,
    data.end(),
    [](const DataPoint &a, const DataPoint &b) { return a.y < b.y; });
  return data[data.size() / 2].y;
}

double
areaTrace(std::vector<DataPoint>::const_iterator begin,
          std::vector<DataPoint>::const_iterator end)
{

  if(begin == end)
    return 0;
  auto previous = begin;
  auto next     = begin + 1;
  double area   = 0;
  while(next != end)
    {
      area += ((next->x - previous->x) * (previous->y + next->y)) / (double)2;
      previous++;
      next++;
    }
  return area;
}


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

  Trace single_peak_trace;

  DataPoint previous_data_point;

  for(auto iter = begin; iter != end; ++iter)
    {
      DataPoint iterated_data_point(iter->x, iter->y);

      // qDebug().noquote() << "Current data point:"
      //<< iterated_data_point.toString();

      if(iterated_data_point.y < y_floor)
        {
          // qDebug() << "under the floor";

          if(single_peak_trace.size())
            {
              // qDebug() << "There was a single peak trace cooking";

              local_maxima_trace.push_back(single_peak_trace.maxYDataPoint());

              // qDebug().noquote() << "pushed back local maximum point:"
              //<< local_maxima_trace.back().toString();

              // Clean and set the context.
              single_peak_trace.clear();

              previous_data_point = iterated_data_point;

              continue;
            }
          else
            {
              // qDebug() << "no single peak trace cooking";

              previous_data_point = iterated_data_point;

              continue;
            }
        }
      else
        {
          // qDebug() << "over the floor";

          // The iterated value is greater than the y_floor value, so we need to
          // handle it.

          if(iterated_data_point.y == previous_data_point.y)
            {
              // We are in a flat region, no need to change anything to the
              // context, just skip the point.
              continue;
            }
          else if(iterated_data_point.y > previous_data_point.y)
            {
              // qDebug().noquote() << "ascending in a peak";

              // The previously iterated y value was smaller than the presently
              // iterated one, so we are ascending in a peak.

              // All we need to do is set the context.

              single_peak_trace.push_back(iterated_data_point);

              // qDebug().noquote() << "pushed back normal point:"
              //<< single_peak_trace.back().toString();

              previous_data_point = iterated_data_point;

              continue;
            }
          else
            {
              // qDebug().noquote() << "started descending in a peak";

              // No, the currently iterated y value is less than the previously
              // iterated value.

              single_peak_trace.push_back(iterated_data_point);

              // qDebug().noquote() << "pushed back normal point:"
              //<< single_peak_trace.back().toString();

              previous_data_point = iterated_data_point;

              continue;
            }
        }
    }
  // End of
  // for(auto iter = begin; iter != end; ++iter)

  // Attention, we might arrive here with a peak being created, we need to get
  // its maximum if that peak is non-empty;

  if(single_peak_trace.size())
    {

      local_maxima_trace.push_back(single_peak_trace.maxYDataPoint());

      // qDebug().noquote()
      //<< "was cooking a peak: pushed back local maximum point:"
      //<< local_maxima_trace.back().toString();
    }

  return local_maxima_trace;
}


Trace::Trace()
{
}


Trace::Trace(const QString &text)
{
  initialize(text);
}


Trace::Trace(const std::vector<pappso_double> &xVector,
             const std::vector<pappso_double> &yVector)
{
  initialize(xVector, yVector);
}


size_t
Trace::initialize(const QString &space_sep_text)
{
  QStringList string_list = space_sep_text.split("\n", Qt::SkipEmptyParts);

  // qDebug() << "Split text into" << string_list.size()
  //<< "substrings:" << string_list;

  for(int iter = 0; iter < string_list.size(); ++iter)
    {
      QString line = string_list.at(iter);
      // qDebug() << "Now processing line:" << line;

      // Returns a string that has whitespace removed from the start and the
      // end, and that has each sequence of internal whitespace replaced with a
      // single space.
      line = line.simplified();

      // Remove any parentheses and replace comma and semicolumn with space.
      // line.remove(QChar('('));
      // line.remove(QChar(')'));

      // line.replace(QChar(','), QChar(' '));
      // line.replace(QChar(';'), QChar(' '));

      append(DataPoint(line));
    }

  return size();
}


size_t
Trace::initialize(const QString &x_text, const QString &y_text)
{
  QStringList x_string_list = x_text.split("\n", Qt::SkipEmptyParts);
  QStringList y_string_list = y_text.split("\n", Qt::SkipEmptyParts);

  if(x_string_list.size() != y_string_list.size())
    throw ExceptionNotPossible(
      "trace.cpp -- ERROR x_text and y_text must have the same number of "
      "lines.");

  for(int iter = 0; iter < x_string_list.size(); ++iter)
    {
      QString x_line = x_string_list.at(iter);
      QString y_line = y_string_list.at(iter);

      // Returns a string that has whitespace removed from the start and the
      // end, and that has each sequence of internal whitespace replaced with a
      // single space.
      x_line = x_line.simplified();
      y_line = y_line.simplified();

      append(DataPoint(x_line + " " + y_line));
    }
  return size();
}


Trace::Trace(
  const std::vector<std::pair<pappso_double, pappso_double>> &dataPoints)
{
  reserve(dataPoints.size());

  for(auto &dataPoint : dataPoints)
    {
      push_back(DataPoint(dataPoint));
    }

  sortX();
  // std::sort(begin(), end(), [](const DataPoint &a, const DataPoint &b) {
  // return (a.x < b.x);
  //});
}


Trace::Trace(const std::vector<DataPoint> &dataPoints)
  : std::vector<DataPoint>(dataPoints)
{
  sortX();
  // std::sort(begin(), end(), [](const DataPoint &a, const DataPoint &b) {
  // return (a.x < b.x);
  //});
}


Trace::Trace(const std::vector<DataPoint> &&dataPoints)
  : std::vector<DataPoint>(std::move(dataPoints))
{
  // This constructor used by the MassSpectrum && constructor.

  sortX();
  // std::sort(begin(), end(), [](const DataPoint &a, const DataPoint &b) {
  // return (a.x < b.x);
  //});
}


Trace::Trace(const MapTrace &map_trace)
{
  for(auto &&item : map_trace)
    push_back(DataPoint(item.first, item.second));

  // No need to sort, maps are sorted by key (that is, x).
}

Trace::Trace(const Trace &other) : std::vector<DataPoint>(other)
{
}


Trace::Trace(const Trace &&other) : std::vector<DataPoint>(std::move(other))
{
  // This constructor used by the MassSpectrum && constructor.
}


Trace::~Trace()
{
  // Calls the destructor for each DataPoint object in the vector.
  clear();
}


size_t
Trace::initialize(const std::vector<pappso_double> &xVector,
                  const std::vector<pappso_double> &yVector)
{
  // Sanity check
  if(xVector.size() != yVector.size())
    throw ExceptionNotPossible(
      "trace.cpp -- ERROR xVector and yVector must have the same size.");

  // We are initializing, not appending.
  erase(begin(), end());

  for(std::size_t iter = 0; iter < xVector.size(); ++iter)
    {
      push_back(DataPoint(xVector.at(iter), yVector.at(iter)));
    }

  sortX();
  // std::sort(begin(), end(), [](const DataPoint &a, const DataPoint &b) {
  // return (a.x < b.x);
  //});

#if 0
  for(auto &item : *this)
  {
    std::cout << item.x << "-" << item.y;
  }
#endif

  return size();
}


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

  // We are initializing, not appending.
  erase(begin(), end());

  for(auto &&item : map)
    {
      push_back(DataPoint(item.first, item.second));
    }

  // No need to sort, maps are sorted by key (that is, x).

  return size();
}


size_t
Trace::initialize(const Trace &other)
{
  *this = other;

  return size();
}


size_t
Trace::append(const DataPoint &data_point)
{
  push_back(data_point);

  return size();
}


Trace &
Trace::operator=(const Trace &other)
{
  assign(other.begin(), other.end());

  return *this;
}


Trace &
Trace::operator=(Trace &&other)
{
  vector<DataPoint>::operator=(std::move(other));
  return *this;
}


TraceSPtr
Trace::makeTraceSPtr() const
{
  return std::make_shared<Trace>(*this);
}


TraceCstSPtr
Trace::makeTraceCstSPtr() const
{
  return std::make_shared<const Trace>(*this);
}


std::vector<pappso_double>
Trace::xValues() const
{
  std::vector<pappso_double> values;

  for(auto &&dataPoint : *this)
    {
      values.push_back(dataPoint.x);
    }

  return values;
}


std::vector<pappso_double>
Trace::yValues() const
{
  std::vector<pappso_double> values;

  for(auto &&dataPoint : *this)
    {
      values.push_back(dataPoint.y);
    }

  return values;
}


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

  std::pair<std::map<pappso_double, pappso_double>::iterator, bool> ret;

  for(auto &&dataPoint : *this)
    {
      ret = map.insert(
        std::pair<pappso_double, pappso_double>(dataPoint.x, dataPoint.y));

      if(ret.second == false)
        {
          qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
                   << "It is odd that the Trace contains multiple same keys.";

          // No insertion, then increment the y value.
          ret.first->second += dataPoint.y;
        }
    }

  return map;
}


// const DataPoint &
// Trace::dataPointWithX(pappso_double value) const
//{
// auto iterator =
// std::find_if(begin(), end(), [value](const DataPoint &dataPoint) {
// return (dataPoint.x == value);
//});

// if(iterator != end())
//{
//// The returned data point is valid.
// return *iterator;
//}
// else
//{
//// The returned data point is invalid because it is not initialized.
// return DataPoint();
//}
//}


std::vector<DataPoint>::iterator
Trace::dataPointIteratorWithX(pappso_double value)
{
  auto iterator =
    std::find_if(begin(), end(), [value](const DataPoint &dataPoint) {
      return (dataPoint.x == value);
    });

  return iterator;
}


std::vector<DataPoint>::const_iterator
Trace::dataPointCstIteratorWithX(pappso_double value) const
{
  auto iterator =
    std::find_if(begin(), end(), [value](const DataPoint &dataPoint) {
      return (dataPoint.x == value);
    });

  return iterator;
}


std::size_t
Trace::dataPointIndexWithX(pappso_double value) const
{
  std::vector<DataPoint>::const_iterator iterator =
    dataPointCstIteratorWithX(value);

  if(iterator != end())
    return std::distance(begin(), iterator);

  return std::numeric_limits<std::size_t>::max();
}


DataPoint
Trace::containsX(pappso_double value, PrecisionPtr precision_p) const
{
  // std::cout << std::setprecision(10) << "getting value: " << value
  //<< " and precision: " << precision_p->getNominal() << std::endl;

  pappso_double delta = precision_p->delta(value);

  double left_most  = value - delta;
  double right_most = value + delta;

  // std::cout << std::setprecision(10) << "delta: " << delta
  //<< " left_most: " << left_most << " right_most: " << right_most
  //<< std::endl;

  auto iterator =
    std::find_if(begin(),
                 end(),
                 [value, precision_p, delta, left_most, right_most](
                   const DataPoint &data_point) {
                   if(precision_p)
                     {

                       // FIXME: unbelievable behaviour: when building in
                       // release mode this code, under i386 (but not x86_64),
                       // this code fails if the following%S statement is
                       // missing.

                       // std::cout << std::setprecision(10)
                       //<< "Testing data_point.x: " << data_point.x
                       //<< std::endl;

                       // For this reason I had to deactivate the related tests
                       // for i386 in tests/test_trace.cpp

                       double diff_to_left_most  = data_point.x - left_most;
                       double diff_to_right_most = data_point.x - right_most;

                       // std::cout << std::setprecision(10)
                       //<< "diff_to_left_most: " << diff_to_left_most
                       //<< " diff_to_right_most: " << diff_to_right_most <<
                       // std::endl;

                       // if(diff_to_left_most > 0)
                       //{
                       // std::cout << std::setprecision(10)
                       //<< " point is right of left_most: " <<
                       // diff_to_left_most
                       //<< std::endl;
                       //}
                       // if(diff_to_left_most < 0)
                       //{
                       // std::cout << std::setprecision(10)
                       //<< "point is left of left_most: " << diff_to_left_most
                       //<< std::endl;
                       //}
                       // if(!diff_to_left_most)
                       //{
                       // std::cout << std::setprecision(10)
                       //<< "point is spot on left_most: " << diff_to_left_most
                       //<< std::endl;
                       //}

                       // if(diff_to_right_most > 0)
                       //{
                       // std::cout << std::setprecision(10)
                       //<< "point is right of right_most: " <<
                       // diff_to_right_most
                       //<< std::endl;
                       //}
                       // if(diff_to_right_most < 0)
                       //{
                       // std::cout << std::setprecision(10)
                       //<< "point is left or of right_most: "
                       //<< diff_to_right_most << std::endl;
                       //}
                       // if(!diff_to_right_most)
                       //{
                       // std::cout << std::setprecision(10)
                       //<< "point is spot on right_most: " <<
                       // diff_to_right_most
                       //<< std::endl;
                       //}

                       if(diff_to_left_most >= 0 && diff_to_right_most <= 0)
                         {
                           // std::cout << "The point is inside the range,
                           // should return true."
                           //<< std::endl;
                           return true;
                         }
                       else
                         {
                           // std::cout
                           //<< "The point is outside the range, should return
                           // false."
                           //<< std::endl;
                           return false;
                         }
                     }
                   else
                     {
                       return (data_point.x == value);
                     }
                 });

  if(iterator != end())
    {
      // The returned data point is valid.
      return *iterator;
    }
  else
    {
      // The returned data point is invalid because it is not initialized.
      return DataPoint();
    }
}


const DataPoint &
Trace::minXDataPoint() const
{
  auto dataPoint = std::min_element(
    begin(), end(), [](const DataPoint &a, const DataPoint &b) {
      return (a.x < b.x);
    });

  if(dataPoint == end())
    {
      throw ExceptionOutOfRange(
        QObject::tr("unable to get min peak x on spectrum size %1")
          .arg(size()));
    }

  return (*dataPoint);
}


const DataPoint &
Trace::maxXDataPoint() const
{
  auto dataPoint = std::max_element(
    begin(), end(), [](const DataPoint &a, const DataPoint &b) {
      return (a.x < b.x);
    });

  if(dataPoint == end())
    {
      throw ExceptionOutOfRange(
        QObject::tr("unable to get max peak x on spectrum size %1")
          .arg(size()));
    }

  return (*dataPoint);
}


const DataPoint &
Trace::minYDataPoint() const
{
  auto dataPoint = std::min_element(
    begin(), end(), [](const DataPoint &a, const DataPoint &b) {
      return (a.y < b.y);
    });

  if(dataPoint == end())
    {
      throw ExceptionOutOfRange(
        QObject::tr("unable to get min peak intensity on spectrum size %1")
          .arg(size()));
    }

  return (*dataPoint);
}


const DataPoint &
Trace::maxYDataPoint() const
{
  auto dataPoint = std::max_element(
    begin(), end(), [](const DataPoint &a, const DataPoint &b) {
      return (a.y < b.y);
    });

  if(dataPoint == end())
    {
      throw ExceptionOutOfRange(
        QObject::tr("unable to get max peak intensity on spectrum size %1")
          .arg(size()));
    }

  return (*dataPoint);
}


pappso_double
Trace::minX() const
{
  return minXDataPoint().x;
}


pappso_double
Trace::maxX() const
{
  return maxXDataPoint().x;
}


pappso_double
Trace::minY() const
{
  return minYDataPoint().y;
}


pappso_double
Trace::maxY() const
{
  return maxYDataPoint().y;
}


pappso_double
Trace::sumY() const
{
  // double sum = 0;

  // for(auto &&dp : m_dataPoints)
  // sum += dp.y;

  // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << " ()"
  //<< "Returning sum/tic:" << sum;

  // return sum;

  return std::accumulate(begin(),
                         end(),
                         (double)0,
                         [](pappso_double sum, const DataPoint &dataPoint) {
                           return (sum + dataPoint.y);
                         });
}


pappso_double
Trace::sumY(double mzStart, double mzEnd) const
{
  auto begin_it = findFirstEqualOrGreaterX(this->begin(), this->end(), mzStart);
  auto end_it   = findFirstGreaterX(begin_it, this->end(), mzEnd);

  return sumYTrace(begin_it, end_it, 0);
}


pappso_double
Trace::maxY(double mzStart, double mzEnd) const
{
  std::vector<DataPoint>::const_iterator begin_it =
    findFirstEqualOrGreaterX(this->begin(), this->end(), mzStart);

  double max_y = 0;

  while(begin_it != findFirstGreaterX(begin_it, this->end(), mzEnd))
    {
      if(begin_it->y > max_y)
        max_y = begin_it->y;
      begin_it++;
    }
  return max_y;
}


void
Trace::sort(SortType sort_type, SortOrder sort_order)
{
  if(sort_type == SortType::no_sort)
    return;

  if(sort_type == SortType::x)
    return sortX(sort_order);
  else
    return sortY(sort_order);
}


void
Trace::sortX(SortOrder sort_order)
{
  if(sort_order == SortOrder::ascending)
    std::sort(begin(), end(), [](const DataPoint &a, const DataPoint &b) {
      return (a.x < b.x);
    });
  else
    std::sort(begin(), end(), [](const DataPoint &a, const DataPoint &b) {
      return (a.x > b.x);
    });
}


void
Trace::sortY(SortOrder sort_order)
{
  if(sort_order == SortOrder::ascending)
    std::sort(begin(), end(), [](const DataPoint &a, const DataPoint &b) {
      return (a.y < b.y);
    });
  else
    std::sort(begin(), end(), [](const DataPoint &a, const DataPoint &b) {
      return (a.y > b.y);
    });
}


void
Trace::unique()
{
  auto last =
    std::unique(begin(), end(), [](const DataPoint &a, const DataPoint &b) {
      return (a.x == b.x);
    });

  erase(last, end());
}


std::size_t
Trace::removeZeroYDataPoints()
{
  qDebug();

  auto it     = begin();
  auto end_it = end();

  std::size_t count = 0;

  while(it != end_it)
    {
      if(Utils::almostEqual(it->y, 0))
        {
          // erase returns an iterator to the next item in the vector.
          it = erase(it);
          ++count;
        }
      else
        // We need to increment (no call to erase here).
        ++it;
    }

  return count;
}


QString
Trace::toString() const
{
  // Even if the spectrum is empty, we should return an empty string.
  QString text;

  for(auto &&dataPoint : *this)
    {
      text.append(QString("%1\n").arg(dataPoint.toString()));
    }

  return text;
}


QByteArray
Trace::xAsBase64Encoded() const
{
  QByteArray unencoded_array;

  for(auto &&data_point : *this)
    {
      QByteArray local_array;
      local_array.setNum(data_point.x, 'f', 12);
      local_array.append("\n");

      unencoded_array.append(local_array);
    }

  QByteArray base64_encoded_array = unencoded_array.toBase64(
    QByteArray::Base64Encoding | QByteArray::OmitTrailingEquals);

  return base64_encoded_array;
}


QByteArray
Trace::yAsBase64Encoded() const
{
  QByteArray unencoded_array;

  for(auto &&data_point : *this)
    {
      QByteArray local_array;
      local_array.setNum(data_point.y, 'f', 12);
      local_array.append("\n");

      unencoded_array.append(local_array);
    }

  QByteArray base64_encoded_array = unencoded_array.toBase64(
    QByteArray::Base64Encoding | QByteArray::OmitTrailingEquals);

  return base64_encoded_array;
}


Trace &
Trace::filter(const FilterInterface &filter)
{
  return filter.filter(*this);
}

} // namespace pappso
