/**
 * \file pappsomspp/filers/filterpass.cpp
 * \date 26/04/2019
 * \author Olivier Langella
 * \brief collection of filters concerned by Y selection
 */

/*******************************************************************************
 * Copyright (c) 2019 Olivier Langella <Olivier.Langella@u-psud.fr>.
 *
 * This file is part of the PAPPSOms++ library.
 *
 *     PAPPSOms++ 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.
 *
 *     PAPPSOms++ 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 PAPPSOms++.  If not, see <http://www.gnu.org/licenses/>.
 *
 ******************************************************************************/

#include "filterpass.h"
#include "../../trace/trace.h"
#include <algorithm>
#include <cmath>
#include "../../massspectrum/massspectrum.h"
#include "../../exception/exceptionoutofrange.h"
#include "../../exception/exceptionnotrecognized.h"

#include <QObject>

using namespace pappso;


FilterLowPass::FilterLowPass(double pass_y) : m_passY(pass_y)
{
}
FilterLowPass::FilterLowPass(const FilterLowPass &other)
  : m_passY(other.m_passY)
{
}

FilterLowPass &
FilterLowPass::operator=(const FilterLowPass &other)
{
  m_passY = other.m_passY;

  return *this;
}


Trace &
FilterLowPass::filter(Trace &data_points) const
{
  Trace new_data_points;
  for(auto &&data_point : data_points)
    {
      if(data_point.y < m_passY)
        {
          new_data_points.push_back(data_point);
        }
    }
  data_points = std::move(new_data_points);
  return data_points;
}

FilterHighPass::FilterHighPass(double pass_y) : m_passY(pass_y)
{
}
FilterHighPass::FilterHighPass(const FilterHighPass &other)
  : m_passY(other.m_passY)
{
}

FilterHighPass &
FilterHighPass::operator=(const FilterHighPass &other)
{
  m_passY = other.m_passY;

  return *this;
}


Trace &
FilterHighPass::filter(Trace &data_points) const
{
  Trace new_data_points;
  for(auto &&data_point : data_points)
    {
      if(data_point.y > m_passY)
        {
          new_data_points.push_back(data_point);
        }
    }
  data_points = std::move(new_data_points);
  return data_points;
}


FilterHighPassPercentage::FilterHighPassPercentage(double ratio_pass_y)
  : m_ratioPassY(ratio_pass_y)
{
}

FilterHighPassPercentage::FilterHighPassPercentage(
  const FilterHighPassPercentage &other)
  : m_ratioPassY(other.m_ratioPassY)
{
}

FilterHighPassPercentage &
FilterHighPassPercentage::operator=(const FilterHighPassPercentage &other)
{
  m_ratioPassY = other.m_ratioPassY;

  return *this;
}


Trace &
FilterHighPassPercentage::filter(Trace &data_points) const
{
  auto it_max = maxYDataPoint(data_points.begin(), data_points.end());
  if(it_max == data_points.end())
    return data_points;
  double pass = (it_max->y * m_ratioPassY);
  Trace new_data_points;
  for(auto &&data_point : data_points)
    {
      if(data_point.y > pass)
        {
          new_data_points.push_back(data_point);
        }
    }
  data_points = std::move(new_data_points);
  return data_points;
}


FilterGreatestY::FilterGreatestY(std::size_t number_of_points)
  : m_numberOfPoints(number_of_points)
{
}


FilterGreatestY::FilterGreatestY(const FilterGreatestY &other)
  : m_numberOfPoints(other.m_numberOfPoints)
{
}


FilterGreatestY &
FilterGreatestY::operator=(const FilterGreatestY &other)
{
  m_numberOfPoints = other.m_numberOfPoints;

  return *this;
}


Trace &
FilterGreatestY::filter(Trace &data_points) const
{

  // Reverse-sort the data points (in y decreasing order) so that we get the
  // greatest to the front of the vector and we'll then copy the first n data
  // points to the returned vector. See that return (b < a) ?
  if(m_numberOfPoints >= data_points.size())
    return data_points;

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

  data_points.erase(data_points.begin() + m_numberOfPoints, data_points.end());

  // And now sort the Trace conventionally, that is in x increasing order.
  std::sort(data_points.begin(),
            data_points.end(),
            [](const DataPoint &a, const DataPoint &b) { return (a.x < b.x); });


  return data_points;
}

std::size_t
FilterGreatestY::getNumberOfPoints() const
{
  return m_numberOfPoints;
}


FilterGreatestYperWindow::FilterGreatestYperWindow(
  double window_range, std::size_t number_of_points_per_window)
  : m_xWindowRange(window_range), m_numberOfPoints(number_of_points_per_window)
{

  qDebug();
  if(m_xWindowRange < 0.5)
    {
      throw pappso::ExceptionOutOfRange(
        QObject::tr("window_range must be greater than 0.5"));
    }

  qDebug();
}


FilterGreatestYperWindow::FilterGreatestYperWindow(
  const FilterGreatestYperWindow &other)
  : m_xWindowRange(other.m_xWindowRange),
    m_numberOfPoints(other.m_numberOfPoints)
{
  qDebug();
}

FilterGreatestYperWindow &
FilterGreatestYperWindow::operator=(const FilterGreatestYperWindow &other)
{
  qDebug();
  m_numberOfPoints = other.m_numberOfPoints;
  m_xWindowRange   = other.m_xWindowRange;

  return *this;
}


Trace &
FilterGreatestYperWindow::filter(Trace &data_points) const
{

  std::vector<DataPoint> new_trace(data_points);
  data_points.clear();

  int window_number                     = 0;
  int old_window_number                 = -1;
  std::size_t number_of_peaks_in_window = 0;
  auto itbegin                          = data_points.begin();
  std::vector<DataPoint>::iterator it_min;


  // std::sort(data_points.begin(),
  //          data_points.end(),
  //          [](const DataPoint &a, const DataPoint &b) { return (a.y > b.y);
  //          });

  qDebug() << " m_xWindowRange=" << m_xWindowRange
           << " m_numberOfPoints=" << m_numberOfPoints;
  for(const pappso::DataPoint &data_point : new_trace)
    {
      qDebug() << " data_point.x=" << data_point.x
               << " data_point.y=" << data_point.y;
      window_number = trunc(data_point.x / m_xWindowRange);
      qDebug() << window_number;
      if(window_number != old_window_number)
        {
          old_window_number         = window_number;
          number_of_peaks_in_window = 0;
          itbegin                   = data_points.end();
        }
      if(number_of_peaks_in_window < m_numberOfPoints)
        {
          qDebug();
          data_points.push_back(data_point);
          number_of_peaks_in_window++;
          if(number_of_peaks_in_window == 1)
            {
              itbegin = data_points.begin() + (data_points.size() - 1);
            }
        }
      else
        {
          qDebug();

          it_min = minYDataPoint(itbegin, data_points.end());
          if(it_min != data_points.end())
            {
              qDebug();
              if(it_min->y < data_point.y)
                {
                  qDebug();
                  *it_min = data_point;
                  // it_min->x = data_point.x;
                  // it_min->y = data_point.y;
                }
            }
        }
    }
  qDebug();
  // new_trace.sortX();
  // qDebug() << new_trace.size();
  // data_points.clear();
  // data_points = new_trace;
  // data_points = std::move(new_trace);
  // qDebug() << data_points.size();
  data_points.sortX();
  qDebug();
  return data_points;
}

std::size_t
FilterGreatestYperWindow::getNumberOfPoints() const
{
  return m_numberOfPoints;
}


FilterFloorY::FilterFloorY()
{
}
FilterFloorY::FilterFloorY([[maybe_unused]] const FilterFloorY &other)
{
}

FilterFloorY &
FilterFloorY::operator=([[maybe_unused]] const FilterFloorY &other)
{
  return *this;
}


Trace &
FilterFloorY::filter(Trace &data_points) const
{
  for(auto &&dataPoint : data_points)
    {
      dataPoint.y = std::floor(dataPoint.y);
    }
  return data_points;
}


FilterRoundY::FilterRoundY()
{
}
FilterRoundY::FilterRoundY([[maybe_unused]] const FilterRoundY &other)
{
}

FilterRoundY &
FilterRoundY::operator=([[maybe_unused]] const FilterRoundY &other)
{
  return *this;
}

Trace &
FilterRoundY::filter(Trace &data_points) const
{
  for(auto &&dataPoint : data_points)
    {
      dataPoint.y = std::round(dataPoint.y);
    }
  return data_points;
}


FilterRescaleY::FilterRescaleY(double dynamic) : m_dynamic(dynamic)
{
}
FilterRescaleY::FilterRescaleY(const FilterRescaleY &other)
  : m_dynamic(other.m_dynamic)
{
}
Trace &
FilterRescaleY::filter(Trace &data_points) const
{
  if(m_dynamic == 0)
    return data_points;
  auto it_max = maxYDataPoint(data_points.begin(), data_points.end());
  if(it_max == data_points.end())
    return data_points;
  double maximum = it_max->y;
  for(auto &&dataPoint : data_points)
    {
      dataPoint.y = (dataPoint.y / maximum) * m_dynamic;
    }
  return data_points;
}

FilterRescaleY &
FilterRescaleY::operator=(const FilterRescaleY &other)
{
  m_dynamic = other.m_dynamic;

  return *this;
}


double
FilterRescaleY::getDynamicRange() const
{
  return m_dynamic;
}


MassSpectrumFilterGreatestItensities::MassSpectrumFilterGreatestItensities(
  std::size_t number_of_points)
  : m_filterGreatestY(number_of_points)
{
}

MassSpectrumFilterGreatestItensities::MassSpectrumFilterGreatestItensities(
  const MassSpectrumFilterGreatestItensities &other)
  : m_filterGreatestY(other.m_filterGreatestY)
{
}

MassSpectrumFilterGreatestItensities &
MassSpectrumFilterGreatestItensities::operator=(
  const MassSpectrumFilterGreatestItensities &other)
{
  m_filterGreatestY = other.m_filterGreatestY;

  return *this;
}


MassSpectrum &
MassSpectrumFilterGreatestItensities::filter(MassSpectrum &spectrum) const
{
  m_filterGreatestY.filter(spectrum);
  return spectrum;
}


FilterScaleFactorY::FilterScaleFactorY(double dynamic) : m_factor(dynamic)
{
}
FilterScaleFactorY::FilterScaleFactorY(const FilterScaleFactorY &other)
  : m_factor(other.m_factor)
{
}

FilterScaleFactorY &
FilterScaleFactorY::operator=(const FilterScaleFactorY &other)
{
  m_factor = other.m_factor;

  return *this;
}


Trace &
FilterScaleFactorY::filter(Trace &data_points) const
{
  if(m_factor == 1)
    return data_points;
  for(auto &&dataPoint : data_points)
    {
      dataPoint.y = dataPoint.y * m_factor;
    }
  return data_points;
}
double
FilterScaleFactorY::getScaleFactorY() const
{
  return m_factor;
}

FilterRemoveY::FilterRemoveY(double valueToRemove)
  : m_valueToRemove(valueToRemove)
{
}

FilterRemoveY::FilterRemoveY(const FilterRemoveY &other)
  : m_valueToRemove(other.m_valueToRemove)
{
}

FilterRemoveY &
FilterRemoveY::operator=(const FilterRemoveY &other)
{
  m_valueToRemove = other.m_valueToRemove;
  return *this;
}

double
FilterRemoveY::getValue() const
{
  return m_valueToRemove;
}

Trace &
FilterRemoveY::filter(Trace &data_points) const
{
  for(auto &&dataPoint : data_points)
    {
      if(dataPoint.y < m_valueToRemove)
        dataPoint.y = 0;
      else
        dataPoint.y = dataPoint.y - m_valueToRemove;
    }
  return data_points;
}


FilterQuantileBasedRemoveY::FilterQuantileBasedRemoveY(double quantile)
  : m_quantile(quantile)
{
}

FilterQuantileBasedRemoveY::FilterQuantileBasedRemoveY(
  const FilterQuantileBasedRemoveY &other)
  : m_quantile(other.m_quantile)
{
}

FilterQuantileBasedRemoveY &
FilterQuantileBasedRemoveY::operator=(const FilterQuantileBasedRemoveY &other)
{
  m_quantile = other.m_quantile;
  return *this;
}

double
FilterQuantileBasedRemoveY::getQuantileThreshold() const
{
  return m_quantile;
}

Trace &
pappso::FilterQuantileBasedRemoveY::filter(pappso::Trace &data_points) const
{

  if(data_points.size() == 0)
    return data_points;
  double value_to_temove =
    quantileYTrace(data_points.begin(), data_points.end(), m_quantile);
  for(auto &&dataPoint : data_points)
    {
      if(dataPoint.y < value_to_temove)
        dataPoint.y = 0;
      else
        dataPoint.y = dataPoint.y - value_to_temove;
    }
  return data_points;
}

pappso::FilterQuantileBasedRemoveY::FilterQuantileBasedRemoveY(
  const QString &strBuildParams)
{
  buildFilterFromString(strBuildParams);
}


void
pappso::FilterQuantileBasedRemoveY::buildFilterFromString(
  const QString &strBuildParams)
{
  //"passQuantileBasedRemoveY|0.6"
  qDebug();
  if(strBuildParams.startsWith("passQuantileBasedRemoveY|"))
    {
      QStringList params =
        strBuildParams.split("|").back().split(";", Qt::SkipEmptyParts);

      QString value = params.at(0);
      m_quantile    = value.toDouble();
    }
  else
    {
      throw pappso::ExceptionNotRecognized(
        QString(
          "building passQuantileBasedRemoveY from string %1 is not possible")
          .arg(strBuildParams));
    }
  qDebug();
}


QString
pappso::FilterQuantileBasedRemoveY::name() const
{
  return "passQuantileBasedRemoveY";
}


QString
pappso::FilterQuantileBasedRemoveY::toString() const
{
  QString strCode = QString("%1|%2").arg(name()).arg(m_quantile);

  return strCode;
}
