/***************************************************************************
**
**  This file is part of QGpCoreMath.
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**
**  This file 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 Lesser General Public
**  License for more details.
**
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2006-09-07
**  Copyright: 2006-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#ifndef CURVE_H
#define CURVE_H

#include <math.h>
#include <QGpCoreTools.h>

#include "Point2D.h"
#include "SmoothingParameters.h"
#include "CurvePointOptions.h"
#include "QGpCoreMathDLLExport.h"
#include "Circle.h"
#include "CurveSplitter.h"
#include "LeastSquare.h"
#include "SamplingParameters.h"

namespace QGpCoreMath {

  template <class pointType>
  class QGPCOREMATH_EXPORT Curve : private QVector<pointType>
  {
    TRANSLATIONS("Curve")
  public:
    Curve() {_sorted=false;}
    Curve(int n) : QVector<pointType>(n) {_sorted=false;}
    Curve(const Curve<pointType>& o) : QVector<pointType>(o) {_sorted=o._sorted;}
    Curve(const QVector<pointType>& o) : QVector<pointType>(o) {_sorted=false;}

    void sort();
    void checkSort();
    bool isSorted() const {return _sorted;}

    void unique();

    // Vector functions
    bool operator==(const Curve<pointType>& o) const {return QVector<pointType>::operator==(o);}
    int count() const {return QVector<pointType>::count();}
    bool isEmpty() const {return QVector<pointType>::isEmpty();}
    void resize(int n) {if(n>count()) {_sorted=false;} QVector<pointType>::resize(n);}
    int indexAfter(double valX) const;
    int indexOf(double valX, double relPrec) const;
    int indexOf(const pointType& p) const {return QVector<pointType>::indexOf(p);}
    void indexRange(double xMin, double xMax, int& indexMin, int& indexMax) const;
    pointType at(double x) const;
    bool isInsideRange(double x) const;
    const pointType& at(int index) const {return QVector<pointType>::at(index);}
    pointType& operator[](int index) {_sorted=false; return QVector<pointType>::operator[](index);}
    const pointType& operator[](int index) const {return QVector<pointType>::at(index);}
    const pointType& first() const {return QVector<pointType>::first();}
    const pointType& last() const {return QVector<pointType>::last();}
    pointType& first() {_sorted=false; return QVector<pointType>::first();}
    pointType& last() {_sorted=false; return QVector<pointType>::last();}
    void remove(int index) {QVector<pointType>::remove(index);}
    void insert(int index, const pointType& p) {_sorted=false; QVector<pointType>::insert(index, p);}
    void insert(const pointType& p);
    void clear() {_sorted=false; QVector<pointType>::clear();}
    void prepend(const pointType& p);
    void append(const pointType& p);
    void append(const Curve<pointType>& c);
    typedef pointType * iterator;
    typedef const pointType * const_iterator;
    const_iterator begin() const {return QVector<pointType>::begin();}
    const_iterator end() const {return QVector<pointType>::end();}
    iterator begin() {_sorted=false; return QVector<pointType>::begin();}
    iterator end() {_sorted=false; return QVector<pointType>::end();}

    bool sameSampling(const Curve<pointType>& o) const;

    void xLog10();
    void xExp10();
    void xMultiply(double factor);
    void xInverse();

    void ySetValue(double value, const CurvePointOptions * pointOptions=nullptr);
    void ySetMinimumValue(double value, const CurvePointOptions * pointOptions=nullptr);
    void ySetMaximumValue(double value, const CurvePointOptions * pointOptions=nullptr);
    void ySum(double value, const CurvePointOptions * pointOptions=nullptr);
    void ySum(const Curve<pointType>& o, const CurvePointOptions * pointOptions=nullptr);
    void yMultiply(double factor, const CurvePointOptions * pointOptions=nullptr);
    void yMultiply(const Curve<pointType>& o, const CurvePointOptions * pointOptions=nullptr);
    void yDivide(const Curve<pointType>& o, const CurvePointOptions * pointOptions=nullptr);
    void yMultiplyByX(double factor, const CurvePointOptions * pointOptions=nullptr);
    void yDivideByX(double factor, const CurvePointOptions * pointOptions=nullptr);
    void ySquare(const CurvePointOptions * pointOptions=nullptr);
    void ySqrt(const CurvePointOptions * pointOptions=nullptr);
    void yInverse(const CurvePointOptions * pointOptions=nullptr);
    void yLog10(const CurvePointOptions * pointOptions=nullptr);
    void yExp10(const CurvePointOptions * pointOptions=nullptr);
    void yAbs(const CurvePointOptions * pointOptions=nullptr);

    void line(const pointType& p1, const pointType& p2);
    void resample(int n, double min, double max, double valX, double valY,
                  SamplingOptions options=LinearScale,
                  const CurvePointOptions * pointOptions=nullptr);
    void resample(int n, double min, double max, SamplingOptions options=LinearScale);
    void resample(const Curve<pointType>& xModel);
    void resample(const QVector<double>& xModel);
    void cut(double min, double max, SamplingOptions options=LinearScale);
    void validate(double min, double max, bool value, SamplingOptions options=LinearScale);
    void smooth(const SamplingParameters& sampling, const SmoothingParameters& param);
    pointType smooth(double x, const SmoothingParameters& param);
    void average(const Curve<pointType>& o);
    bool extrapolateFromStart(int leastSquareCount, bool positiveSlopeOnly,
                              SamplingOptions options=LinearScale, const CurvePointOptions * pointOptions=nullptr);
    bool extrapolateFromEnd(int leastSquareCount, bool positiveSlopeOnly,
                            SamplingOptions options=LinearScale, const CurvePointOptions * pointOptions=nullptr);

    bool leastSquare(double& a, double& b, const CurvePointOptions * pointOptions=nullptr) const;
    double azimuth(const CurvePointOptions * pointOptions=nullptr) const;
    QVector<double> project(const CurvePointOptions * pointOptions=nullptr) const;

    void swapXY(const CurvePointOptions * pointOptions=nullptr);
    Curve<pointType> derivative(const CurvePointOptions * pointOptions=nullptr) const;
    Curve<pointType> curvature(const CurvePointOptions * pointOptions=nullptr) const;
    Curve<pointType> angles(const CurvePointOptions * pointOptions=nullptr) const;
    QList<Curve<pointType>> split(double maxX, SamplingOptions options,
                                  double maxErr, int minCount,
                                  const CurvePointOptions * pointOptions=nullptr) const;

    void setValid(bool v);
    void removeInvalid();
    int minimumX(int startIndex=0) const;
    int maximumX(int startIndex=0) const;
    int minimumY(int startIndex=0, const CurvePointOptions * pointOptions=nullptr) const;
    int maximumY(int startIndex=0, const CurvePointOptions * pointOptions=nullptr) const;
    int localMaximumY(int startIndex, const CurvePointOptions * pointOptions=nullptr) const;
    int localMinimumY(int startIndex, const CurvePointOptions * pointOptions=nullptr) const;
    double minimumStep(SamplingOptions options);
    int closestMax(int index);

    QVector<double> xVector() const;
    QString toString(int precision=6, char format='g') const;
    bool fromStream(QTextStream& s, QString * comments=nullptr);

    void operator+=(const Curve<pointType>& o);
    void operator*=(const Curve<pointType>& o);

    double sumX() const;
    double sumY(const CurvePointOptions * pointOptions=nullptr) const;
    double sumXY(const CurvePointOptions * pointOptions=nullptr) const;
    double sumX2() const;
    double sumY2(const CurvePointOptions * pointOptions=nullptr) const;
  private:
    void firstValid(int& startIndex) const;
    inline static bool lessThan(const pointType& p1, const pointType& p2);

    bool _sorted;
  };

  template <class pointType>
  void Curve<pointType>::prepend(const pointType& p)
  {
    if(isEmpty()) {
      _sorted=true;
    } else if(p.x()>=QVector<pointType>::first().x()) {
      _sorted=false;
    }
    QVector<pointType>::prepend(p);
  }

  template <class pointType>
  void Curve<pointType>::append(const pointType& p)
  {
    if(isEmpty()) {
      _sorted=true;
    } else if(p.x()<=QVector<pointType>::last().x()) {
      _sorted=false;
    }
    QVector<pointType>::append(p);
  }

  template <class pointType>
  void Curve<pointType>::append(const Curve<pointType>& c)
  {
    if(isEmpty()) {
      _sorted=c._sorted;
    } else if(!c.isEmpty() && QVector<pointType>::last().x()>=c.QVector<pointType>::first().x()) {
      _sorted=false;
    }
    QVector<pointType>::operator+=(c);
  }

  template <class pointType>
  void Curve<pointType>::sort()
  {
    if(!isEmpty()) {
      std::sort(begin(), end(), lessThan);
      // In case of lot of double entries, this is function was quite long with a remove()
      Curve<pointType> f;
      f.reserve(f.count());
      f.append(at(0));
      for(int i=1; i<count(); i++) {
        if(at(i-1).x()!=at(i).x()) {
          f.append(at(i));
        }
      }
      f.squeeze();
      // This is quite fast with implicit sharing
      *this=f;
      _sorted=true;
    }
  }

  template <class pointType>
  void Curve<pointType>::checkSort()
  {
    int n=count();
    for(int i=1; i<n; i++) {
      if(at(i-1).x()>=at(i).x()) {
        _sorted=false;
        return;
      }
    }
    _sorted=true;
  }

  template <class pointType>
  void Curve<pointType>::unique()
  {
    ::QGpCoreTools::unique(*this);
  }

  template <class pointType>
  inline bool Curve<pointType>::lessThan(const pointType& p1, const pointType& p2)
  {
    return p1.x()<p2.x();
  }

  template <class pointType>
  int Curve<pointType>::indexAfter(double valX) const
  {
    ASSERT(_sorted);
    ASSERT(!isEmpty());
    if(valX<=at(0).x()) {
      return 0;
    }
    int lasti=count()-1;
    if(valX>at(lasti).x()) {
      return lasti+1;
    }
    int i=1;
    while(i<count()) {
      i=i << 1;
    }
    int step2=i >> 1;
    while(step2>0) {
      if(i>lasti) {
        i-=step2;
      } else if(valX <= at(i).x()) {
        if(valX>at(i-1).x()) break;
        i-=step2;
      } else {
        i+=step2;
      }
      step2=step2 >> 1;
    }
    return i;
  }

  template <class pointType>
  int Curve<pointType>::indexOf(double valX, double relPrec) const
  {
    ASSERT(_sorted);
    double valXLow, valXHigh;
    if(valX>0.0) {
      valXLow=valX*(1.0-relPrec);
      valXHigh=valX*(1.0+relPrec);
    } else {
      valXLow=valX*(1.0+relPrec);
      valXHigh=valX*(1.0-relPrec);
    }
    ASSERT(!isEmpty());
    if(valXHigh<at(0).x()) {
      return -1;
    } else if(valXLow<at(0).x()) {
      return 0;
    }
    int lasti=count()-1;
    if(valXLow>at(lasti).x()) {
      return -1;
    }
    int i=1;
    while(i<count()) {
      i=i << 1;
    }
    int step2=i >> 1;
    while(step2>0) {
      if(i>lasti) {
        i-=step2;
      } else if(valXHigh<at(i).x()) {
        i-=step2;
      } else if(valXLow<at(i).x()) {
        return i;
      } else {
        i+=step2;
      }
      step2=step2 >> 1;
    }
    if(valXLow<at(i).x() && valXHigh>at(i).x()) {
      return i;
    } else {
      return -1;
    }
  }

  template <class pointType>
  void Curve<pointType>::indexRange(double xMin, double xMax, int& indexMin, int& indexMax) const
  {
    ASSERT(_sorted);
    int i=indexAfter(xMin)-1;
    if(i>indexMin) {
      indexMin=i;
      if(indexMin<0) {
        indexMin=0;
      }
    }
    i=indexAfter(xMax);
    if(i<indexMax) {
      indexMax=i;
      if(indexMax>=count()) {
        indexMax=count()-1;
      }
    }

  }

  template <class pointType>
  bool Curve<pointType>::isInsideRange(double x) const
  {
    ASSERT(_sorted);
    if(count()<2) {
      if(count()==1) {
        return at(0).x()==x;
      } else {
        return false;
      }
    } else {
      return first().x()<=x && x<=last().x();
    }
  }

  template <class pointType>
  pointType Curve<pointType>::at(double x) const
  {
    ASSERT(_sorted);
    if(count()<2) {
      if(count()==1) {
        return at(0);
      } else {
        return pointType();
      }
    } else {
      int i=indexAfter(x);
      if(i==0) i++;                // Force interpolation if outside range
      else if(i==count()) i--;
      pointType p;
      p.interpole(x, at(i-1), at(i));
      return p;
    }
  }

  template <class pointType>
  void Curve<pointType>::insert(const pointType& p)
  {
    TRACE;
    ASSERT(_sorted);
    if(count()<2) {
      if(isEmpty()) {
        QVector<pointType>::append(p);
      } else if(p.x()<at(0).x()) {
        QVector<pointType>::prepend(p);
      } else if(p.x()>at(0).x()) {
        QVector<pointType>::append(p);
      } else {
        QVector<pointType>::operator[](0)=p;
      }
    } else {
      int i=indexAfter(p.x());
      if(i<count() && at(i).x()==p.x()) {
        (*this)[i]=p;
      } else {
        if(at(p.x()).y()!=p.y()) {
          insert(i, p);
        }
      }
    }
  }

  template <class pointType>
  void Curve<pointType>::resample(int n, double min, double max, double valX, double valY,
                                  SamplingOptions options, const CurvePointOptions * pointOptions)
  {
    TRACE;
    // Transforms X scale according to options (log and inv)
    if(options & InversedScale) {
      xInverse();
      valX=1.0/valX;
    }
    if(options & LogScale) {
      xLog10();
      valX=::log10(valX);
      if(_sorted) {
        min=::log10(min);
        max=::log10(max);
      }
    }
    if(_sorted) {
      if(min<QVector<pointType>::first().x())
        min=QVector<pointType>::first().x();
      if(max>QVector<pointType>::last().x())
        max=QVector<pointType>::last().x();
    }
    int currentN=count();

    // Calculate the total "distance" curve
    Curve<Point2D> distances(currentN);
    distances[0].setX(at(0).x());
    distances[0].setY(0.0);
    for(int i=1; i<currentN; i++) {
      const pointType& p1=at(i-1);
      const pointType& p2=at(i);
      distances[i].setX(p2.x());
      double deltaX=(p2.x()-p1.x())/valX;
      double deltaY=(p2.y(pointOptions)-p1.y(pointOptions))/valY;
      distances[i].setY(distances[i-1].y()+::sqrt(deltaX*deltaX+deltaY*deltaY));
    }
    distances.sort();

    // Calculate the distance of min and max, and constant step
    double distMin, distMax;
    if(_sorted) {
      Point2D p;
      int i;
      i=distances.indexAfter(min);
      p.interpole(min, distances.at(i-1), distances.at(i));
      distMin=p.y();
      i=distances.indexAfter(max);
      p.interpole(max, distances.at(i-1), distances.at(i));
      distMax=p.y();
    } else {
      distMin=0;
      distMax=distances[currentN-1].y();
    }
    double cstStep=(distMax-distMin)/(currentN-1);

    // Map x as a function of distance, function monotoneous, then no need to setFunction()
    distances.swapXY();

    // Intepolate x for constant step distance
    QVector<double> x(n);
    double dist=distMin;
    Point2D p;
    for(int i=0; i<n; i++, dist+=cstStep) {
      int iDist=distances.indexAfter(dist );
      p.interpole(dist, distances.at(iDist-1), distances.at(iDist));
      x[i]=p.y();
    }
    resample(x);

    // Re-Transforms axis according options (log and inv)
    if(options & LogScale) {
      xExp10();
    }
    if(options & InversedScale) {
      xInverse();
    }
  }

  template <class pointType>
  void Curve<pointType>::resample(int n, double min, double max, SamplingOptions options)
  {
    TRACE;
    ASSERT(_sorted);
    // Transforms X scale according to options (log and inv)
    if(options & InversedScale) {
      xInverse();
    }
    if(options & LogScale) {
      xLog10();
      min=::log10(min);
      max=::log10(max);
    }
    // Calculate the constant step
    double cstStep=(max-min)/(n-1);
    QVector<double> x(n);
    for(int i=0; i<n; i++) {
      x[i]=min+(double)i*cstStep;
    }
    resample(x);

    // Re-Transforms axis according options (log and inv)
    if(options & LogScale) {
      xExp10();
    }
    if(options & InversedScale) {
      xInverse();
    }
  }

  template <class pointType>
  void Curve<pointType>::resample(const Curve<pointType>& xModel)
  {
    TRACE;
    ASSERT(_sorted);
    QVector<double> x(xModel.count());
    for(int i=0; i<xModel.count(); i++) {
      x[i]=xModel.at(i).x();
    }
    resample(x);
  }

  template <class pointType>
  void Curve<pointType>::resample(const QVector<double>& xModel)
  {
    TRACE;
    ASSERT(_sorted);
    int nCurve=count();
    int nModel=xModel.count();
    if(nCurve<2 || nModel<2) return;
    Curve<pointType> interpolated(nModel);
    // Insert values inside xModel
    int iCurve=1;
    int lastCurveIndex=nCurve-1;
    for(int i=0; i<nModel; i++) {
      double x=xModel.at(i);
      while(iCurve<lastCurveIndex && at(iCurve).x()<x) {
        iCurve++;
      }
      const pointType& p1=at(iCurve-1);
      const pointType& p2=at(iCurve);
      interpolated[i].interpole(x, p1, p2);
      if(p1.isValid()) {
        if(p2.isValid()) {
          // Both are valid, avoid extrapolation
          if(x<p1.x()*(1+(p1.x()>0 ? -1e-15 : 1e-15)) ||
             x>p2.x()*(1+(p2.x()>0 ? 1e-15 : -1e-15))) {
            interpolated[i].setValid(false);
          }
        } else {
          // First is valid, second is not valid
          if(x>p1.x()*(1+(p1.x()>0 ? 1e-15 : -1e-15))) {
            interpolated[i].setValid(false);
          }
        }
      } else {
        if(!p2.isValid()) {
          interpolated[i].setValid(false);
        } else {
          // First is not valid, second is valid
          if(x<p2.x()*(1+(p2.x()>0 ? -1e-15 : 1e-15))) {
            interpolated[i].setValid(false);
          }
        }
      }
    }
    // Insert all values outside xModel
    if(nModel>1) {
      double min=xModel.at(0)*(1+(xModel.at(0)>0 ? -1e-15 : 1e-15));
      double max=xModel.at(nModel-1)*(1+(xModel.at(nModel-1)>0 ? 1e-15 : -1e-15));
      for(int i=0;i<nCurve;i++) {
        const pointType& p=at(i);
        if(p.x()<min || max<p.x()) {
          interpolated.append(p);
        }
      }
    }
    // If xModel is not sorted, a sort may be necessary
    interpolated.sort();
    *this=interpolated;
  }

  template <class pointType>
  void Curve<pointType>::cut(double min, double max, SamplingOptions options)
  {
    TRACE;
    ASSERT(_sorted);
    // Assert that count()>1 is made by indexAfter()
    // Transforms X scale according to options (log and inv)
    if(options & InversedScale) {
      xInverse();
    }
    if(min<QVector<pointType>::first().x())
      min=QVector<pointType>::first().x();
    if(max>QVector<pointType>::last().x())
      max=QVector<pointType>::last().x();
    // Find the first point
    int ix=indexAfter(min);
    if(ix>0) {
      if(fabs(min-at(ix-1).x()) < 1e-15) {
        ix--;
      } else if((options & Interpole) && fabs(min - at(ix).x()) > 1e-15) {
        ix--;
        QVector<pointType>::operator[](ix).interpole(min, at(ix), at(ix+1));
      }
    }
    // Remove everything before
    for(int i=0; i<ix; i++) {
      remove(0);
    }
    // Find the last point
    ix=indexAfter(max);
    if(ix < count()) {
      if(fabs(max-at(ix).x())<1e-15) {
        ix++;
      } else if((options & Interpole) && fabs(max - at(ix-1).x()) > 1e-15) {
        QVector<pointType>::operator[](ix).interpole(max, at(ix-1), at(ix));
        ix++;
      }
    }
    // Remove everything after
    for(int i=count()-1; i>=ix; i--) {
      remove(i);
    }
    // Re-Transforms axis according options (log and inv)
    if(options & InversedScale) {
      xInverse();
    }
  }

  template <class pointType>
  void Curve<pointType>::validate(double min, double max, bool value, SamplingOptions options)
  {
    TRACE;
    ASSERT(_sorted);
    // Assert that count()>1 is made by indexAfter()
    // Transforms X scale according to options (log and inv)
    if(options & InversedScale) {
      xInverse();
    }
    if(min<QVector<pointType>::first().x())
      min=QVector<pointType>::first().x();
    if(max>QVector<pointType>::last().x())
      max=QVector<pointType>::last().x();
    // Find the first and last points
    int ixs=indexAfter(min);
    int ixe=indexAfter(max);
    if(ixs>0) { // convenient rounding errors
      if(fabs(min-at(ixs-1).x())<1e-15) {
        ixs--;
      }
    }
    if(ixe>0) { // convenient rounding errors
      if(fabs(min-at(ixe-1).x())<1e-15) {
        ixe--;
      }
    }
    if(ixe==count()) {
      ixe--;
    }
    for(int i=ixs; i<=ixe; i++) {
      QVector<pointType>::operator[](i).setValid(value);
    }
    // Re-Transforms axis according options (log and inv)
    if(options & InversedScale) {
      xInverse();
    }
  }

  template <class pointType>
  void Curve<pointType>::smooth(const SamplingParameters& sampling, const SmoothingParameters& param)
  {
    TRACE;
    ASSERT(_sorted);
    int n=sampling.count();
    Curve<pointType> interpolated(n);
    for(int i=0; i<n; i++) {
      interpolated[i]=smooth(sampling.value(i), param);
    }
    interpolated.sort();
    *this=interpolated;
  }

  template <class pointType>
  pointType Curve<pointType>::smooth(double x, const SmoothingParameters& param)
  {
    TRACE;
    pointType sum;
    if(count()<2) {
      if(count()==1) {
        return at(0);
      } else {
        return sum;
      }
    }
    double min=param.minimum(x);
    double max=param.maximum(x);
    int i0=indexAfter(min);
    int n=indexAfter(max);
    if(i0>=n) {
      if(i0==count()) {
        i0--;
      }
      if(i0==0) {
        i0++;
      }
      sum.interpole(x, at(i0-1), at(i0));
      return sum;
    }
    switch(param.method()) {
    case SmoothingParameters::Function: {
        double sumWeight=0.0;
        switch(param.scaleType()) {
        case SamplingParameters::Linear:
          for(int i=i0; i<n; i++) {
            pointType p=at(i);
            double weight=WindowFunction::value(p.x(), x, min, max, param.windowFunction());
            sumWeight+=weight;
            p*=weight;
            sum+=p;
          }
          sum/=sumWeight;
          break;
        case SamplingParameters::Log:
          for(int i=i0; i<n; i++) {
            pointType p=at(i);
            p.yLog10();
            double weight=WindowFunction::value(p.x(), x, min, max, param.windowFunction());
            sumWeight+=weight;
            p*=weight;
            sum+=p;
          }
          sum/=sumWeight;
          sum.yExp10();
          break;
        }
      }
      break;
    case SmoothingParameters::SavitzkyGolay: {
        LeastSquare s(n-i0, param.order());
        s.setScaleType(param.scaleType());
        for(int i=i0; i<n; i++) {
          const pointType& p=at(i);
          s.setData(i-i0, p.x(), p.y());
        }
        s.invert();
        sum.setY(s.value(x));
      }
      break;
    }
    sum.setX(x);
    return sum;
  }

  template <class pointType>
  void Curve<pointType>::average(const Curve<pointType>& o)
  {
    TRACE;
    if(o.count()<2) return;
    if(isEmpty()) {
      *this=o;
      return;
    }
    // Set the same sampling for both curves
    QVector<double> x=xVector();
    x+=o.xVector();
    std::sort(x.begin(), x.end());
    ::QGpCoreTools::unique(x);
    if(!isSorted()) {
      sort();
    }
    resample(x);
    Curve<pointType> oResamp=o;
    if(!oResamp.isSorted()) {
      oResamp.sort();
    }
    oResamp.resample(x);
    // Take the average of the two curves
    for(int i=x.count()-1; i>=0; i--) {
      QVector<pointType>::operator[](i).average(oResamp.at(i));
    }
    sort();
  }

  template <class pointType>
  void Curve<pointType>::setValid(bool v)
  {
    TRACE;
    iterator it;
    for(it=begin(); it!=end(); ++it) {
      it->setValid(v);
    }
  }

  template <class pointType>
  void Curve<pointType>::removeInvalid()
  {
    TRACE;
    for(int i=count() - 1; i>=0; i-- ) {
      if(!at(i).isValid()) {
        remove(i);
      }
    }
  }

  template <class pointType>
  void Curve<pointType>::swapXY(const CurvePointOptions * pointOptions)
  {
    TRACE;
    iterator it;
    for(it=QVector<pointType>::begin(); it!=QVector<pointType>::end(); ++it) {
      double tmp=it->x();
      it->setX(it->y(pointOptions));
      it->setY(tmp, pointOptions);
    }
    _sorted=false;
  }

  template <class pointType>
  Curve<pointType> Curve<pointType>::derivative(const CurvePointOptions * pointOptions) const
  {
    TRACE;
    ASSERT(_sorted);
    int n=count()-1;
    ASSERT(n>1);
    Curve<pointType> d(n-1);
    for(int i=1; i<n; i++) {
      double v=(at(i).y(pointOptions)-at(i-1).y(pointOptions))
                 /(at(i).x()-at(i-1).x());
      v+=(at(i+1).y(pointOptions)-at(i).y(pointOptions))
        /(at(i+1).x()-at(i).x());
      v*=0.5;
      pointType& p=d[i-1];
      p.setX(at(i).x());
      p.setY(v, pointOptions);
    }
    return d;
  }

  template <class pointType>
  Curve<pointType> Curve<pointType>::curvature(const CurvePointOptions * pointOptions) const
  {
    TRACE;
    ASSERT(_sorted);
    int n=count()-1;
    ASSERT(n>1);
    Curve<pointType> d(n-1);
    for(int i=1; i<n; i++) {
      Circle c(at(i-1).point2D(pointOptions),
               at(i).point2D(pointOptions),
               at(i+1).point2D(pointOptions));
      pointType& p=d[i-1];
      p.setX(at(i).x());
      p.setY(c.radius(), pointOptions);
    }
    return d;
  }

  template <class pointType>
  Curve<pointType> Curve<pointType>::angles(const CurvePointOptions * pointOptions) const
  {
    TRACE;
    ASSERT(_sorted);
    int n=count();
    ASSERT(n>1);
    Curve<pointType> d(n-1);
    for(int i=1; i<n; i++) {
      const pointType& p1=at(i-1);
      const pointType& p2=at(i);
      pointType& a=d[i-1];
      a.setX(p1.x());
      a.setY(p1.point2D(pointOptions).azimuthTo(p2.point2D(pointOptions)), pointOptions);
    }
    return d;
  }

  template <class pointType>
  void Curve<pointType>::xMultiply(double factor)
  {
    TRACE;
    int n=count();
    for(int i=0; i<n; i++) {
      pointType& p=QVector<pointType>::operator[](i); // Avoid touching _sorted flag
      p.setX(p.x()*factor);
    }
    if(factor<0.0 && _sorted) {
      sort();
    }
  }

  template <class pointType>
  void Curve<pointType>::xInverse()
  {
    TRACE;
    int n=count();
    for(int i=0; i<n; i++) {
      pointType& p=QVector<pointType>::operator[](i); // Avoid touching _sorted flag
      p.setX(1.0/p.x());
    }
    if(_sorted) {
      sort();
    }
  }

  template <class pointType>
  void Curve<pointType>::xExp10()
  {
    TRACE;
    int n=count();
    for(int i=0; i<n; i++) {
      pointType& p=QVector<pointType>::operator[](i); // Avoid touching _sorted flag
      p.setX(pow(10, p.x()));
    }
  }

  template <class pointType>
  void Curve<pointType>::xLog10()
  {
    TRACE;
    int n=count();
    for(int i=0; i<n; i++) {
      pointType& p=QVector<pointType>::operator[](i); // Avoid touching _sorted flag
      if(p.x()>0.0) {
        p.setX(::log10(p.x()));
      }
    }
  }

  template <class pointType>
  void Curve<pointType>::ySetValue(double v, const CurvePointOptions * pointOptions)
  {
    int n=count();
    for(int i=0; i<n; i++) {
      pointType& p=QVector<pointType>::operator[](i); // Avoid touching _sorted flag
      p.setY(v, pointOptions);
    }
  }

  template <class pointType>
  void Curve<pointType>::ySetMinimumValue(double v, const CurvePointOptions * pointOptions)
  {
    int n=count();
    for(int i=0; i<n; i++) {
      pointType& p=QVector<pointType>::operator[](i); // Avoid touching _sorted flag
      if(p.y(pointOptions)<v) {
        p.setY(v, pointOptions);
      }
    }
  }

  template <class pointType>
  void Curve<pointType>::ySetMaximumValue(double v, const CurvePointOptions * pointOptions)
  {
    int n=count();
    for(int i=0; i<n; i++) {
      pointType& p=QVector<pointType>::operator[](i); // Avoid touching _sorted flag
      if(p.y(pointOptions)>v) {
        p.setY(v, pointOptions);
      }
    }
  }

  template <class pointType>
  void Curve<pointType>::ySum(double value, const CurvePointOptions * pointOptions)
  {
    TRACE;
    int n=count();
    for(int i=0; i<n; i++) {
      pointType& p=QVector<pointType>::operator[](i); // Avoid touching _sorted flag
      p.setY(p.y(pointOptions)+value, pointOptions);
    }
  }

  template <class pointType>
  void Curve<pointType>::ySum(const Curve<pointType>& o, const CurvePointOptions * pointOptions)
  {
    if(isEmpty()) {
      *this=o;
      return;
    }
    int n=count();
    if(n!=o.count()) {
      App::log(tr("Curve::ySum(): uncompatible sampling (count=%1 & %2).\n").arg(n).arg(o.count()) );
      return;
    }
    for(int i=0; i<n; i++) {
      pointType& p=QVector<pointType>::operator[](i); // Avoid touching _sorted flag
      p.setY(p.y(pointOptions)+o.at(i).y(pointOptions), pointOptions);
    }
  }

  template <class pointType>
  void Curve<pointType>::yMultiply(double factor, const CurvePointOptions * pointOptions)
  {
    TRACE;
    int n=count();
    for(int i=0; i<n; i++) {
      pointType& p=QVector<pointType>::operator[](i); // Avoid touching _sorted flag
      p.setY(p.y(pointOptions)*factor, pointOptions);
    }
  }

  template <class pointType>
  void Curve<pointType>::yMultiply(const Curve<pointType>& o, const CurvePointOptions * pointOptions)
  {
    if(isEmpty()) {
      *this=o;
      return;
    }
    int n=count();
    if(n!=o.count()) {
      App::log(tr("Curve::yMultiply(): uncompatible sampling.\n") );
      return;
    }
    for(int i=0; i<n; i++) {
      pointType& p=QVector<pointType>::operator[](i); // Avoid touching _sorted flag
      p.setY(p.y(pointOptions)*o.at(i).y(pointOptions), pointOptions);
    }
  }

  template <class pointType>
  void Curve<pointType>::yDivide(const Curve<pointType>& o, const CurvePointOptions * pointOptions)
  {
    if(isEmpty()) {
      *this=o;
      return;
    }
    int n=count();
    if(n!=o.count()) {
      App::log(tr("Curve::yDivide(): uncompatible sampling.\n") );
      return;
    }
    for(int i=0; i<n; i++) {
      pointType& p=QVector<pointType>::operator[](i); // Avoid touching _sorted flag
      p.setY(p.y(pointOptions)/o.at(i).y(pointOptions), pointOptions);
    }
  }

  template <class pointType>
  void Curve<pointType>::yMultiplyByX(double factor, const CurvePointOptions * pointOptions)
  {
    TRACE;
    int n=count();
    for(int i=0; i<n; i++) {
      pointType& p=QVector<pointType>::operator[](i); // Avoid touching _sorted flag
      p.setY(p.y(pointOptions)*p.x()*factor, pointOptions);
    }
  }

  template <class pointType>
  void Curve<pointType>::yDivideByX(double factor, const CurvePointOptions * pointOptions)
  {
    TRACE;
    int n=count();
    for(int i=0; i<n; i++) {
      pointType& p=QVector<pointType>::operator[](i); // Avoid touching _sorted flag
      p.setY(p.y(pointOptions)/(p.x()*factor), pointOptions);
    }
  }

  template <class pointType>
  void Curve<pointType>::ySquare(const CurvePointOptions * pointOptions)
  {
    int n=count();
    for(int i=0; i<n; i++) {
      pointType& p=QVector<pointType>::operator[](i); // Avoid touching _sorted flag
      double v=p.y(pointOptions);
      p.setY(v*v, pointOptions);
    }
  }

  template <class pointType>
  void Curve<pointType>::ySqrt(const CurvePointOptions * pointOptions)
  {
    int n=count();
    for(int i=0; i<n; i++) {
      pointType& p=QVector<pointType>::operator[](i); // Avoid touching _sorted flag
      double v=p.y(pointOptions);
      if(v>=0.0) p.setY(::sqrt(v), pointOptions);
    }
  }

  template <class pointType>
  void Curve<pointType>::yInverse(const CurvePointOptions * pointOptions)
  {
    TRACE;
    int n=count();
    for(int i=0; i<n; i++) {
      pointType& p=QVector<pointType>::operator[](i); // Avoid touching _sorted flag
      double v=p.y(pointOptions);
      if(v!=0.0) p.setY(1.0/v, pointOptions);
    }
  }

  template <class pointType>
  void Curve<pointType>::yLog10(const CurvePointOptions * pointOptions)
  {
    TRACE;
    int n=count();
    for(int i=0; i<n; i++) {
      pointType& p=QVector<pointType>::operator[](i); // Avoid touching _sorted flag
      double v=p.y(pointOptions);
      if(v>0.0) p.setY(::log10(v), pointOptions);
    }
  }

  template <class pointType>
  void Curve<pointType>::yExp10(const CurvePointOptions * pointOptions)
  {
    TRACE;
    int n=count();
    for(int i=0; i<n; i++) {
      pointType& p=QVector<pointType>::operator[](i); // Avoid touching _sorted flag
      p.setY(pow(10, p.y(pointOptions)), pointOptions);
    }
  }

  template <class pointType>
  void Curve<pointType>::yAbs(const CurvePointOptions * pointOptions)
  {
    TRACE;
    int n=count();
    for(int i=0; i<n; i++) {
      pointType& p=QVector<pointType>::operator[](i); // Avoid touching _sorted flag
      p.setY(fabs(p.y(pointOptions)), pointOptions);
    }
  }

  template <class pointType>
  void Curve<pointType>::line(const pointType& p1, const pointType& p2)
  {
    TRACE;
    resize(2);
    if(p1.x()<p2.x()) {
      (*this)[0]=p1;
      (*this)[1]=p2;
    } else {
      (*this)[0]=p2;
      (*this)[1]=p1;
    }
    _sorted=true;
  }

  template <class pointType>
  QVector<double> Curve<pointType>::xVector() const
  {
    int n=count();
    QVector<double> x(n);
    for(int i=0; i<n; i++) {
      x[i]=at(i).x();
    }
    return x;
  }

  template <class pointType>
  void Curve<pointType>::firstValid(int& startIndex) const
  {
    int n=count();
    if(startIndex<0) {
      startIndex=0;
    } else if(startIndex>=n) {
      startIndex=n-1;
    }
    while(startIndex<n && !at(startIndex).isValid()) {
      startIndex++;
    }
  }

  template <class pointType>
  int Curve<pointType>::minimumX(int startIndex) const
  {
    int n=count();
    if(n<=0  || startIndex>=n) {
      return -1;
    }
    firstValid(startIndex);
    if(startIndex<n) {
      const pointType * p=&at(startIndex);
      int iMin=startIndex;
      for(int i=startIndex+1; i<n; i++) {
        const pointType& pi=at(i);
        if(pi.isValid() && pi.x()<p->x()) {
          p=&pi;
          iMin=i;
        }
      }
      return iMin;
    } else {
      return -1;
    }
  }

  template <class pointType>
  int Curve<pointType>::maximumX(int startIndex) const
  {
    int n=count();
    if(n<=0 || startIndex>=n) {
      return -1;
    }
    firstValid(startIndex);
    if(startIndex<n) {
      const pointType * p=&at(startIndex);
      int iMax=startIndex;
      for(int i=startIndex+1; i<n; i++) {
        const pointType& pi=at(i);
        if(pi.isValid() && pi.x()>p->x()) {
          p=&pi;
          iMax=i;
        }
      }
      return iMax;
    } else {
      return -1;
    }
  }

  template <class pointType>
  int Curve<pointType>::minimumY(int startIndex, const CurvePointOptions * pointOptions) const
  {
    int n=count();
    if(n<=0 || startIndex>=n) {
      return -1;
    }
    firstValid(startIndex);
    if(startIndex<n) {
      const pointType * p=&at(startIndex);
      int iMin=startIndex;
      for(int i=startIndex+1; i<n; i++) {
        const pointType& pi=at(i);
        if(pi.isValid() && pi.y(pointOptions)<p->y(pointOptions)) {
          p=&pi;
          iMin=i;
        }
      }
      return iMin;
    } else {
      return -1;
    }
  }

  template <class pointType>
  int Curve<pointType>::maximumY(int startIndex, const CurvePointOptions * pointOptions) const
  {
    int n=count();
    if(n<=0 || startIndex>=n) {
      return -1;
    }
    firstValid(startIndex);
    if(startIndex<n) {
      const pointType * p=&at(startIndex);
      int iMax=startIndex;
      for(int i=startIndex+1; i<n; i++) {
        const pointType& pi=at(i);
        if(pi.isValid() && pi.y(pointOptions)>p->y(pointOptions)) {
          p=&pi;
          iMax=i;
        }
      }
      return iMax;
    } else {
      return -1;
    }
  }

  template <class pointType>
  int Curve<pointType>::localMaximumY(int startIndex, const CurvePointOptions * pointOptions) const
  {
    int n=count();
    if(n<=0 || startIndex>=n) {
      return -1;
    }
    firstValid(startIndex);
    if(startIndex==n) {
      return -1;
    }
    const pointType * p0=&at(startIndex);
    firstValid(startIndex);
    if(startIndex==n) {
      return -1;
    }
    const pointType * p1=&at(startIndex);
    for(int i=startIndex; i<n; i++) {
      const pointType * p2=&at(i);
      if(p2->isValid()) {
        if(p1->y(pointOptions)>p0->y(pointOptions) &&
           p1->y(pointOptions)>p2->y(pointOptions)) {
          return i;
        }
      }
      p0=p1;
      p1=p2;
    }
    return -1;
  }

  template <class pointType>
  int Curve<pointType>::localMinimumY(int startIndex, const CurvePointOptions * pointOptions) const
  {
    int n=count();
    if(n<=0 || startIndex>=n) {
      return -1;
    }
    firstValid(startIndex);
    if(startIndex==n) {
      return -1;
    }
    const pointType * p0=&at(startIndex);
    firstValid(startIndex);
    if(startIndex==n) {
      return -1;
    }
    const pointType * p1=&at(startIndex);
    for(int i=startIndex; i<n; i++) {
      const pointType * p2=&at(i);
      if(p2->isValid()) {
        if(p1->y(pointOptions)<p0->y(pointOptions) &&
           p1->y(pointOptions)<p2->y(pointOptions)) {
          return i;
        }
      }
      p0=p1;
      p1=p2;
    }
    return -1;
  }

  template <class pointType>
  int Curve<pointType>::closestMax(int index)
  {
    TRACE;
    int n=count();
    int di;

    if(index==0) {
      di=1;
      index=1;
    }
    else if(index>=n) {
      index=n;
      di=-1;
    } else if(at(index).y()>at(index-1).y()) di=1;
    else if(at(index).y()<at(index-1).y()) di=-1;
    else return index;

    if(di>0) {
      while(index<n) {
        if(at(index).y()<at(index-1).y()) {
          index--;
          break;
        }
        index+=di;
      }
      if(index==n) index--;
    } else {
      while(index>0) {
        if(at(index).y()>at(index-1).y()) break;
        index+=di;
      }
    }
    return index;
  }

  template <class pointType>
  QString Curve<pointType>::toString(int precision, char format) const
  {
    TRACE;
    QString s;
    for(const_iterator it=begin(); it!=end(); ++it) {
      s+=it->toString(precision, format);
      s+="\n";
    }
    return s;
  }

  /*!
    Sum of all x (used in least square method)
  */
  template <class pointType>
  double Curve<pointType>::sumX() const
  {
    TRACE;
    double sum=0.0;
    const_iterator it;
    for(it=begin();it!=end();++it) sum+=it->x();
    return sum;
  }

  /*!
    Sum of all y (used in least square method)
  */
  template <class pointType>
  double Curve<pointType>::sumY(const CurvePointOptions * pointOptions) const
  {
    TRACE;
    double sum=0.0;
    const_iterator it;
    for(it=begin();it!=end();++it) sum+=it->y(pointOptions);
    return sum;
  }

  /*!
    Sum of all x*y (used in least square method)
  */
  template <class pointType>
  double Curve<pointType>::sumXY(const CurvePointOptions * pointOptions) const
  {
    TRACE;
    double sum=0.0;
    const_iterator it;
    for(it=begin();it!=end();++it) sum+=it->x()*it->y(pointOptions);
    return sum;
  }

  /*!
    Sum of all x*x (used in least square method)
  */
  template <class pointType>
  double Curve<pointType>::sumX2() const
  {
    TRACE;
    double sum=0.0;
    const_iterator it;
    for(it=begin();it!=end();++it) sum+=it->x()*it->x();
    return sum;
  }

  /*!
    Sum of all y*y (used in least square method)
  */
  template <class pointType>
  double Curve<pointType>::sumY2(const CurvePointOptions * pointOptions) const
  {
    TRACE;
    double sum=0.0;
    const_iterator it;
    for(it=begin();it!=end();++it) sum+=it->y()*it->y(pointOptions);
    return sum;
  }

  /*!
    Performs a least square regression on all points in the horizontal plane.

    Returns true if there is a sufficient number of points in curve (minimum=2) and
    if \a a is not inf.
    Returns coefficient \a a and \a b from expression y=a*x+b passed as reference in arguments.
  */
  template <class pointType>
  bool Curve<pointType>::leastSquare(double& a, double& b, const CurvePointOptions * pointOptions) const
  {
    TRACE;
    int n=count();
    if(n>1) {
      double invn=1.0/(double)n;
      double sx=sumX();
      double sy=sumY(pointOptions);
      double sxy=sumXY(pointOptions);
      double sx2=sumX2();
      double denom=sx2-invn*sx*sx;
      if(denom!=0.0) {
        a=(sxy-invn*sy*sx)/denom;
        b=(invn*sy*sx2-invn*sxy*sx)/denom;
        return true;
      } else {
        a=0.0;
        b=sx*invn;
      }
    }
    return false;
  }

  /*!
    Performs a least square regression on all points in the horizontal plane.

    Returns the azimuth of the regression line (mathematical sense).
  */
  template <class pointType>
  double Curve<pointType>::azimuth(const CurvePointOptions * pointOptions) const
  {
    TRACE;
    int n=count();
    if(n>1) {
      double a, b;
      if(leastSquare(a, b, pointOptions)) {
        return atan(a);
      } else {
        return 0.5*M_PI;
      }
    }
    return 0.0;
  }

  /*!
    Performs a least square regression on all points in the horizontal plane.

    Returns the distances along the profile
  */
  template <class pointType>
  QVector<double> Curve<pointType>::project(const CurvePointOptions * pointOptions) const
  {
    TRACE;
    int n=count();
    if(!n) return QVector<double>();
    QVector<double> projections(n);
    if(n>1) {
      double a, b;
      double min=std::numeric_limits<double>::infinity();
      if(leastSquare(a, b, pointOptions)) {
        for(int i=0; i<n; i++) {
          const pointType& p=at(i);
          double denom=a*a+1.0;
          double cp=-p.x()-a*p.y(pointOptions);
          double xp=(-cp-a*b)/denom;
          double yp=(-a*cp+b)/denom-b;
          double d=::sqrt(xp*xp+yp*yp);
          projections[i]=(xp>=0)? d : -d;
          if(projections[i]<min) min=projections[i];
        }
      } else {
        for(int i=0; i<n; i++) {
          const pointType& p=at(i);
          projections[i]=p.y(pointOptions);
          if(projections[i]<min) min=projections[i];
        }
      }
      // set min to abcsisse=0
      for(int i=0; i<n;i++) projections[i]-=min;
    } else {
      projections[0]=0;
    }
    return projections;
  }

  template <class pointType>
  bool Curve<pointType>::sameSampling(const Curve<pointType>& o) const
  {
    TRACE;
    int n=count();
    if(n!=o.count()) {
      return false;
    }
    for(int i=0; i<n; i++) {
      if(at(i).x()!=o.at(i).x()) {
        return false;
      }
    }
    return true;
  }

  template <class pointType>
  double Curve<pointType>::minimumStep(SamplingOptions options)
  {
    TRACE;
    ASSERT(_sorted);
    int n=count();
    double s, min=std::numeric_limits<double>::infinity();
    if(options & LogScale) {
      for(int i=1; i<n; i++) {
        s=at(i).x()/at(i-1).x();
        if(s<min) {
          min=s;
        }
      }
    } else {
      for(int i=1; i<n; i++) {
        s=at(i).x()-at(i-1).x();
        if(s<min) {
          min=s;
        }
      }
    }
    return min;
  }

  template <class pointType>
  QList<Curve<pointType>> Curve<pointType>::split(double maxX,
                                                  SamplingOptions options,
                                                  double maxErr,
                                                  int minCount,
                                                  const CurvePointOptions * pointOptions) const
  {
    TRACE;
    int n=count();
    CurveSplitter splitter;
    if(options & LogScale) {
      double invMaxX=1.0/maxX;
      if(maxX<invMaxX) {
        qSwap(maxX, invMaxX);
      }
      for(int i=0; i<n; i++) {
        if(at(i).isValid()) {
          double y0=at(i).y(pointOptions);
          for(int j=0; j<n; j++) {
            if(i!=j && at(j).isValid()) {
              double dx=at(j).x()/at(i).x();
              if((invMaxX<dx && dx<1.0-1e-12) || // Never add a pair at the same x value
                 (dx>1.0+1e-12 && dx<maxX)) {
                splitter.addPair(i, j, y0, ::log(dx), at(j).y(pointOptions));
              }
            }
          }
        }
      }
    } else {
      for(int i=0; i<n; i++) {
        if(at(i).isValid()) {
          double y0=at(i).y(pointOptions);
          for(int j=0; j<n; j++) {
            double dx=at(j).x()-at(i).x();
            if(i!=j  && at(j).isValid() && fabs(dx)<maxX) {
              if(fabs(dx)>1e-12) { // Never add a pair at the same x value
                splitter.addPair(i, j, y0, dx, at(j).y(pointOptions));
             }
            }
          }
        }
      }
    }
    splitter.clusterize(maxErr);
    n=splitter.clusterCount();
    QList<Curve<pointType> > list;
    for(int i=0; i<n; i++) {
      int nc=splitter.valueCount(i);
      if(nc>=minCount) {
        Curve<pointType> c;
        for(int j=0; j<nc; j++) {
          c.append(at(splitter.index(i, j)));
        }
        c.sort();
        if(c.count()>minCount) { // If several samples have the same x, sort may reduce the number of samples
          list.append(c);
        }
      }
    }
    return list;
  }

  template <class pointType>
  bool Curve<pointType>::extrapolateFromStart(int leastSquareCount,
                                              bool positiveSlopeOnly,
                                              SamplingOptions options,
                                              const CurvePointOptions * pointOptions)
  {
    TRACE;
    ASSERT(_sorted);
    int n=count();
    double step=minimumStep(options);
    double a, b;

    Curve<Point2D> curveBegin;
    for(int i=0; i<n; i++) {
      const pointType& p=at(i);
      curveBegin.append(Point2D(p.x(), p.y(pointOptions)));
      if(curveBegin.count()==leastSquareCount) {
        break;
      }
    }
    if(curveBegin.count()<leastSquareCount) {
      return false;
    }

    // first and last for a non-const function mark this curve as not sorted
    bool extrapolated=false;
    double x=first().x();
    if(options & LogScale) {
      curveBegin.xLog10();
      curveBegin.leastSquare(a, b, pointOptions);
      if(!positiveSlopeOnly || a>0.0) {
        for(int i=leastSquareCount-1; i>=0; i--) {
          pointType p;
          x/=step;
          p.setX(x);
          p.setY(a*log10(x)+b, pointOptions);
          prepend(p);
        }
        extrapolated=true;
      }
    } else {
      curveBegin.leastSquare(a, b, pointOptions);
      if(!positiveSlopeOnly || a>0.0) {
        for(int i=leastSquareCount-1; i>=0; i--) {
          pointType p;
          x-=step;
          p.setX(x);
          p.setY(a*x+b, pointOptions);
          prepend(p);
        }
        extrapolated=true;
      }
    }

    // Above operations did not change the sort
    _sorted=true;
    return extrapolated;
  }

  template <class pointType>
  bool Curve<pointType>::extrapolateFromEnd(int leastSquareCount,
                                            bool positiveSlopeOnly,
                                            SamplingOptions options,
                                            const CurvePointOptions * pointOptions)
  {
    TRACE;
    ASSERT(_sorted);
    int n=count();
    double step=minimumStep(options);
    double a, b;

    Curve<Point2D> curveEnd;
    for(int i=n-1; i>=0; i--) {
      const pointType& p=at(i);
      curveEnd.append(Point2D(p.x(), p.y(pointOptions)));
      if(curveEnd.count()==leastSquareCount) {
        break;
      }
    }
    if(curveEnd.count()<leastSquareCount) {
      return false;
    }

    // first and last for a non-const function mark this curve as not sorted
    bool extrapolated=false;
    double x=last().x();
    if(options & LogScale) {
      curveEnd.xLog10();
      curveEnd.leastSquare(a, b, pointOptions);
      if(!positiveSlopeOnly || a>0.0) {
        for(int i=leastSquareCount-1; i>=0; i--) {
          pointType p;
          x*=step;
          p.setX(x);
          p.setY(a*log10(x)+b, pointOptions);
          append(p);
        }
        extrapolated=true;
      }
    } else {
      curveEnd.leastSquare(a, b, pointOptions);
      if(!positiveSlopeOnly || a>0.0) {
        for(int i=leastSquareCount-1; i>=0; i--) {
          pointType p;
          x+=step;
          p.setX(x);
          p.setY(a*x+b, pointOptions);
          append(p);
        }
        extrapolated=true;
      }
    }
    // Above operations did not change the sort
    _sorted=true;
    return extrapolated;
  }

  template <class pointType>
  bool Curve<pointType>::fromStream(QTextStream& s, QString * comments)
  {
    TRACE;
    pointType p;
    QString buf;
    clear();
    while(!s.atEnd()) {
      buf=s.readLine();
      if(buf.isEmpty() || buf[0]=='\n' || buf[0]=='#') {
        if(isEmpty()) {
          if(comments) {
            (*comments)+=buf+"\n";
          }
        } else {
          return true;
        }
      } else {
        p.fromString(buf);
        append(p);
      }
    }
    return false;
  }

} // namespace QGpCoreMath

#endif // CURVE_H
