/**
 * \file pappsomspp/filers/filtermorpho.cpp
 * \date 02/05/2019
 * \author Olivier Langella
 * \brief collection of morphological filters
 */

/*******************************************************************************
 * 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 "filtermorpho.h"
#include "../../trace/trace.h"
#include <QDebug>
#include "pappsomspp/core/exception/exceptionoutofrange.h"
#include "pappsomspp/core/exception/exceptionnotrecognized.h"

using namespace pappso;

FilterMorphoWindowBase::FilterMorphoWindowBase(std::size_t half_window_size)
  : m_halfWindowSize(half_window_size)
{
}
FilterMorphoWindowBase::FilterMorphoWindowBase(const FilterMorphoWindowBase &other)
  : m_halfWindowSize(other.m_halfWindowSize)
{
}
std::size_t
FilterMorphoWindowBase::getHalfWindowSize() const
{
  return m_halfWindowSize;
}

FilterMorphoWindowBase &
FilterMorphoWindowBase::operator=(const FilterMorphoWindowBase &other)
{
  m_halfWindowSize = other.m_halfWindowSize;

  return *this;
}

void
pappso::FilterMorphoWindowBase::buildFilterFromString(
  [[maybe_unused]] const QString &strBuildParams)
{
}

QString
pappso::FilterMorphoWindowBase::name() const
{
  return "nose";
}

QString
pappso::FilterMorphoWindowBase::toString() const
{
  return "nose";
}


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

  qDebug() << " " << m_halfWindowSize << " data_points.size()" << data_points.size();
  if(m_halfWindowSize == 0)
    return data_points;
  if(data_points.size() <= m_halfWindowSize)
    return data_points;
  Trace old_trace(data_points);
  auto it        = old_trace.begin();
  auto itend     = old_trace.end() - m_halfWindowSize - 1;
  auto it_target = data_points.begin();


  std::size_t loop_begin = 0;
  while((it != itend) && (loop_begin < m_halfWindowSize))
    {
      // maxYDataPoint(it_begin, it + m_halfWindowSize + 1);
      // qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__ << " "
      //         << it->x << " " << m_halfWindowSize;
      // it_target->x = it->x;
      it_target->y = getWindowValue(old_trace.begin(), it + m_halfWindowSize + 1);
      it++;
      it_target++;
      loop_begin++;
    }
  // qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__;
  while(it != itend)
    {
      // it_target->x = it->x;
      // qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__ << " "
      //        << it->x;
      it_target->y = getWindowValue(it - m_halfWindowSize, it + m_halfWindowSize + 1);
      it++;
      it_target++;
    }
  // qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__;
  while(it != old_trace.end())
    {
      // qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__ << " "
      //        << it->x;
      // it_target->x = it->x;
      it_target->y = getWindowValue(it - m_halfWindowSize, old_trace.end());
      it++;
      it_target++;
    }
  // qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__;
  // problem with move or swap : this lead to segmentation faults in some cases
  // data_points = std::move(new_trace);
  qDebug();
  return data_points;
}

FilterMorphoSum::FilterMorphoSum(std::size_t half_window_size)
  : FilterMorphoWindowBase(half_window_size)
{
}
FilterMorphoSum::FilterMorphoSum(const FilterMorphoSum &other)
  : FilterMorphoWindowBase(other.m_halfWindowSize)
{
}

FilterMorphoSum &
FilterMorphoSum::operator=(const FilterMorphoSum &other)
{
  m_halfWindowSize = other.m_halfWindowSize;

  return *this;
}

double
FilterMorphoSum::getWindowValue(std::vector<DataPoint>::const_iterator begin,
                                std::vector<DataPoint>::const_iterator end) const
{

  qDebug();
  return sumYTrace(begin, end, 0);
}

FilterMorphoMax::FilterMorphoMax(std::size_t half_window_size)
  : FilterMorphoWindowBase(half_window_size)
{
}
FilterMorphoMax::FilterMorphoMax(const FilterMorphoMax &other)
  : FilterMorphoWindowBase(other.m_halfWindowSize)
{
}

FilterMorphoMax &
FilterMorphoMax::operator=(const FilterMorphoMax &other)
{
  m_halfWindowSize = other.m_halfWindowSize;

  return *this;
}

double
FilterMorphoMax::getWindowValue(std::vector<DataPoint>::const_iterator begin,
                                std::vector<DataPoint>::const_iterator end) const
{

  // qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__;
  return maxYDataPoint(begin, end)->y;
}

FilterMorphoMin::FilterMorphoMin(std::size_t half_window_size)
  : FilterMorphoWindowBase(half_window_size)
{
}
FilterMorphoMin::FilterMorphoMin(const FilterMorphoMin &other)
  : FilterMorphoWindowBase(other.m_halfWindowSize)
{
}

FilterMorphoMin &
FilterMorphoMin::operator=(const FilterMorphoMin &other)
{
  m_halfWindowSize = other.m_halfWindowSize;

  return *this;
}

double
FilterMorphoMin::getWindowValue(std::vector<DataPoint>::const_iterator begin,
                                std::vector<DataPoint>::const_iterator end) const
{
  return minYDataPoint(begin, end)->y;
}

FilterMorphoMinMax::FilterMorphoMinMax(std::size_t half_window_size)
  : m_filterMax(half_window_size), m_filterMin(half_window_size)
{
}
FilterMorphoMinMax::FilterMorphoMinMax(const FilterMorphoMinMax &other)
  : m_filterMax(other.m_filterMax), m_filterMin(other.m_filterMin)
{
}

FilterMorphoMinMax &
FilterMorphoMinMax::operator=(const FilterMorphoMinMax &other)
{
  m_filterMax = other.m_filterMax;
  m_filterMin = other.m_filterMin;

  return *this;
}

Trace &
FilterMorphoMinMax::filter(Trace &data_points) const
{
  qDebug();
  m_filterMax.filter(data_points);
  m_filterMin.filter(data_points);
  qDebug();
  return data_points;
}
std::size_t
FilterMorphoMinMax::getMinMaxHalfEdgeWindows() const
{
  return m_filterMax.getHalfWindowSize();
}


FilterMorphoMaxMin::FilterMorphoMaxMin(std::size_t half_window_size)
  : m_filterMin(half_window_size), m_filterMax(half_window_size)
{
}
FilterMorphoMaxMin::FilterMorphoMaxMin(const FilterMorphoMaxMin &other)
  : m_filterMin(other.m_filterMin), m_filterMax(other.m_filterMax)
{
}

FilterMorphoMaxMin &
FilterMorphoMaxMin::operator=(const FilterMorphoMaxMin &other)
{
  m_filterMin = other.m_filterMin;
  m_filterMax = other.m_filterMax;

  return *this;
}

Trace &
FilterMorphoMaxMin::filter(Trace &data_points) const
{
  qDebug();
  m_filterMin.filter(data_points);
  m_filterMax.filter(data_points);
  qDebug();
  return data_points;
}
std::size_t
FilterMorphoMaxMin::getMaxMinHalfEdgeWindows() const
{
  return m_filterMin.getHalfWindowSize();
}

FilterMorphoAntiSpike::FilterMorphoAntiSpike(std::size_t half_window_size)
  : m_halfWindowSize(half_window_size)
{
}

FilterMorphoAntiSpike::FilterMorphoAntiSpike(const FilterMorphoAntiSpike &other)
  : m_halfWindowSize(other.m_halfWindowSize)
{
}

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

void
pappso::FilterMorphoAntiSpike::buildFilterFromString(const QString &strBuildParams)
{
  //"antiSpike|2"
  if(strBuildParams.startsWith("antiSpike|"))
    {
      QStringList params = strBuildParams.split("|").back().split(";");

      m_halfWindowSize = params.at(0).toUInt();
    }
  else
    {
      throw pappso::ExceptionNotRecognized(
        QString("building FilterMorphoAntiSpike from string %1 is not possible")
          .arg(strBuildParams));
    }
}

QString
pappso::FilterMorphoAntiSpike::toString() const
{
  QString strCode = QString("antiSpike|%1").arg(m_halfWindowSize);

  return strCode;
}

QString
pappso::FilterMorphoAntiSpike::name() const
{
  return "antiSpike";
}


FilterMorphoAntiSpike &
FilterMorphoAntiSpike::operator=(const FilterMorphoAntiSpike &other)
{
  m_halfWindowSize = other.m_halfWindowSize;

  return *this;
}

std::size_t
FilterMorphoAntiSpike::getHalfWindowSize() const
{
  return m_halfWindowSize;
}
Trace &
FilterMorphoAntiSpike::filter(Trace &data_points) const
{
  // qDebug();
  if(m_halfWindowSize == 0)
    return data_points;
  if(data_points.size() < m_halfWindowSize)
    return data_points;
  Trace old_trace(data_points);
  auto it        = old_trace.begin();
  auto it_target = data_points.begin();
  auto itw       = old_trace.begin();

  auto itend = old_trace.end() - m_halfWindowSize - 1;
  // new_trace.reserve(data_points.size());

  // qDebug();
  while((it != old_trace.end()) && (std::distance(old_trace.begin(), it) < (int)m_halfWindowSize))
    {
      // no anti spike at the begining of the signal
      it++;
      it_target++;
    }
  // qDebug();
  while((it != itend) && (it != old_trace.end()))
    {
      // qDebug();
      auto itwend = it + m_halfWindowSize + 1;
      itw         = findDifferentYvalue(it - m_halfWindowSize, it + 1, 0);
      if(itw == it)
        {
          // qDebug();
          itw = findDifferentYvalue(it + 1, itwend, 0);
          if(itw == itwend)
            {
              it_target->y = 0;
            }
          // qDebug();
        }

      // qDebug();
      it++;
      it_target++;
    }

  return data_points;
}


FilterMorphoMedian::FilterMorphoMedian(std::size_t half_window_size)
  : FilterMorphoWindowBase(half_window_size)
{
}
FilterMorphoMedian::FilterMorphoMedian(const FilterMorphoMedian &other)
  : FilterMorphoWindowBase(other.m_halfWindowSize)
{
}

void
pappso::FilterMorphoMean::buildFilterFromString(const QString &strBuildParams)
{
  if(strBuildParams.startsWith(QString("%1|").arg(name())))
    {
      QStringList params = strBuildParams.split("|").back().split(";");

      m_halfWindowSize = params.at(0).toDouble();
    }
  else
    {
      throw pappso::ExceptionNotRecognized(
        QString("Building of FilterMorphoMean from string %1 failed").arg(strBuildParams));
    }
}

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

  return strCode;
}

QString
pappso::FilterMorphoMean::name() const
{
  return "morphoMean";
}

FilterMorphoMedian &
FilterMorphoMedian::operator=(const FilterMorphoMedian &other)
{
  m_halfWindowSize = other.m_halfWindowSize;

  return *this;
}


double
FilterMorphoMedian::getWindowValue(std::vector<DataPoint>::const_iterator begin,
                                   std::vector<DataPoint>::const_iterator end) const
{
  return medianYTrace(begin, end);
}


FilterMorphoMean::FilterMorphoMean(std::size_t half_window_size)
  : FilterMorphoWindowBase(half_window_size)
{
}
FilterMorphoMean::FilterMorphoMean(const FilterMorphoMean &other)
  : FilterMorphoWindowBase(other.m_halfWindowSize)
{
}

FilterMorphoMean &
FilterMorphoMean::operator=(const FilterMorphoMean &other)
{
  m_halfWindowSize = other.m_halfWindowSize;

  return *this;
}

double
FilterMorphoMean::getWindowValue(std::vector<DataPoint>::const_iterator begin,
                                 std::vector<DataPoint>::const_iterator end) const
{
  return meanYTrace(begin, end);
}

pappso::FilterMorphoMean::FilterMorphoMean(const QString &strBuildParams)
  : FilterMorphoWindowBase(0)
{
  buildFilterFromString(strBuildParams);
}


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

FilterMorphoBackground::FilterMorphoBackground(std::size_t median_half_window_size,
                                               std::size_t minmax_half_window_size)

{
  mpa_filterMorphoMedian = new FilterMorphoMedian(median_half_window_size);
  mpa_filterMorphoMinMax = new FilterMorphoMinMax(minmax_half_window_size);
}

FilterMorphoBackground::FilterMorphoBackground(const FilterMorphoBackground &other)
{
  mpa_filterMorphoMedian =
    new FilterMorphoMedian(other.mpa_filterMorphoMedian->getHalfWindowSize());
  mpa_filterMorphoMinMax =
    new FilterMorphoMinMax(other.mpa_filterMorphoMinMax->getMinMaxHalfEdgeWindows());
}


void
pappso::FilterMorphoBackground::buildFilterFromString(const QString &strBuildParams)
{

  if(strBuildParams.startsWith(QString("%1|").arg(name())))
    {
      QStringList params = strBuildParams.split("|").back().split(";");

      mpa_filterMorphoMedian = new FilterMorphoMedian(params.at(0).toDouble());
      mpa_filterMorphoMinMax = new FilterMorphoMinMax(params.at(1).toDouble());
    }
  else
    {
      throw pappso::ExceptionNotRecognized(
        QString("Building of FilterMorphoBackground from string %1 failed").arg(strBuildParams));
    }
}


QString
pappso::FilterMorphoBackground::toString() const
{
  QString strCode = QString("morphoBackground|%1;%2")
                      .arg(mpa_filterMorphoMedian->getHalfWindowSize())
                      .arg(mpa_filterMorphoMinMax->getMinMaxHalfEdgeWindows());

  return strCode;
}

QString
pappso::FilterMorphoBackground::name() const
{
  return "morphoBackground";
}

FilterMorphoBackground &
FilterMorphoBackground::operator=(const FilterMorphoBackground &other)
{

  mpa_filterMorphoMedian =
    new FilterMorphoMedian(other.mpa_filterMorphoMedian->getHalfWindowSize());
  mpa_filterMorphoMinMax =
    new FilterMorphoMinMax(other.mpa_filterMorphoMinMax->getMinMaxHalfEdgeWindows());

  return *this;
}

Trace &
FilterMorphoBackground::filter(Trace &data_points) const
{
  mpa_filterMorphoMedian->filter(data_points);
  mpa_filterMorphoMinMax->filter(data_points);

  // finally filter negative values
  for(DataPoint &point : data_points)
    {
      if(point.y < 0)
        {
          point.y = 0;
        }
    }
  return data_points;
}
const FilterMorphoMedian &
FilterMorphoBackground::getFilterMorphoMedian() const
{
  return *mpa_filterMorphoMedian;
}
const FilterMorphoMinMax &
FilterMorphoBackground::getFilterMorphoMinMax() const
{
  return *mpa_filterMorphoMinMax;
}

pappso::FilterMorphoBackground::~FilterMorphoBackground() noexcept
{
  delete mpa_filterMorphoMedian;
  delete mpa_filterMorphoMinMax;
}
