/***************************************************************************
**
**  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: 2008-07-15
**  Copyright: 2008-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#ifndef IRREGULARGRID2DDATA_H
#define IRREGULARGRID2DDATA_H

#include "Point2D.h"
#include "Curve.h"
#include "Segment2D.h"

namespace QGpCoreMath {

class GridSearch;
class GaussDistribution;

class QGPCOREMATH_EXPORT IrregularGrid2DData : public QSharedData
{
  TRANSLATIONS("IrregularGrid2DData")
public:
  IrregularGrid2DData();
  IrregularGrid2DData(int nx, int ny);
  IrregularGrid2DData(const IrregularGrid2DData& o);
  ~IrregularGrid2DData();

  void operator=(const IrregularGrid2DData& o);
  void operator+=(const IrregularGrid2DData& o);
  void copy(const GridSearch& o, AxisType normalTo, double at);

  int nx() const {return _nx;}
  int ny() const {return _ny;}
  void clear();
  void init(int nx, int ny);
  void init(double value=0.0);
  inline void init(int nx, int ny, double value);
  bool isLike(IrregularGrid2DData& o) const;
  // Function to set _x and _y values
  void setLinear(AxisType axis,double min, double max);
  void setLog(AxisType axis,double min, double max);
  // Resample the grid at x defined by xModel (e.g. from another curve)
  void resample(AxisType axis, const QVector<double>& xModel);
  void resample(AxisType axis, int n, double min, double max, SamplingOptions options);
  // Cut function between two x values
  void cut (AxisType axis, double min, double max, SamplingOptions options);
  void inverse(AxisType axis);
  void log10(AxisType axis);
  void log10();
  void pow10(AxisType axis);
  void pow10();
  void multiplyCoordinates(AxisType axis, double fac);
  void multiplyValues(AxisType along, int index, double fac);
  void multiplyValues(double fac);
  const double * axisData(AxisType axis) const {int n; return getAxis(axis,n);}
  // Function to acces the grid
  inline double value(int ix, int iy) const;
  inline void setValue(int ix, int iy, double val);

  double x(int ix) const {return _x[ix];}
  double y(int iy) const {return _y[iy];}
  void setX(int ix, double val) {_x[ix]=val;}
  void setY(int iy, double val) {_y[iy]=val;}
  QVector<double> x() const;
  QVector<double> y() const;
  inline double * valuePointer(int ix, int iy);
  inline const double * valuePointer(int ix, int iy) const;
  const double * xPointer() const {return _x;}
  double * xPointer() {return _x;}
  const double * yPointer() const {return _y;}
  double * yPointer() {return _y;}
  inline Point2D coordinates(int ix, int iy) const;
  int indexOfX(double x) const {return indexOf(_x, _nx, _n2x, x);}
  int indexOfY(double y) const {return indexOf(_y, _ny, _n2y, y);}
  bool belongs(const Point2D& p, int ix, int iy) const;

  double minimumValue() const;
  double maximumValue() const;
  double minimumValue(int& ix, int& iy) const;
  double maximumValue(int& ix, int& iy) const;
  double minimumAxis(AxisType axis) const;
  double maximumAxis(AxisType axis) const;
  double maximumValue(AxisType along, int index) const;
  double minimumValue(AxisType along, int index) const;
  double sum(AxisType along, int index) const;
  double mean(AxisType along, int index) const;
  double median(AxisType along, int index) const;
  double mode(AxisType along, int index) const;
  double variance(AxisType along, int index) const;
  double variance(AxisType along, int index, double average) const;
  double varianceFit(AxisType along, int index, double average) const;
  double misfit(AxisType along, int index, const GaussDistribution& gd) const;
  double normalizeArea(AxisType along, int index);
  void normalizeArea(AxisType along);
  double normalizeAmplitude(AxisType along, int index);
  void normalizeAmplitude(AxisType along);
  void smooth(AxisType along);
  void smooth(AxisType along, int index);
  double smoothInvalidCells(AxisType along, int index,
                            double invalidValue, double sigma2,
                            double RVal, bool modify);
  void printValues(AxisType along, int index);
  double * iterator(AxisType along, int index, int &di, int& n) const;
  /* Get values of a row or a column of the grid as a Curve object
      Return a row if along X_Axis, a column if along YAxis **/
  Curve<Point2D> crossSection(AxisType axis, int ixy) const;
  Curve<Point2D> crossSection(Segment2D seg, double deltaX=0) const;
  Curve<Point2D> crossSection(const Curve<Point2D>& path, double deltaX=0) const;
  Curve<Point2D> integralCrossSection(AxisType axis,int index) const;
  template <class CurveClass, class PointClass> CurveClass followMaximumX() const;
  template <class CurveClass, class PointClass> CurveClass followMaximumX(int ixMin, int ixMax, int iyMax) const;
  template <class CurveClass, class PointClass> void followMaximumX(CurveClass& curve, double min, double max) const;
  template <class CurveClass, class PointClass> CurveClass followMaximumY() const;
  template <class CurveClass, class PointClass> CurveClass followMaximumY(int iyMin, int iyMax, int ixMax) const;
  template <class CurveClass, class PointClass> void followMaximumY(CurveClass& curve, double min, double max) const;
  // Geometries of cells
  double width(int ix) const {return widthOrHeight(_x, _nx,ix);}
  double height(int iy) const {return widthOrHeight(_y, _ny,iy);}
  double left(int ix) const {return leftOrBottom(_x, _nx,ix);}
  double right(int ix) const {return rightOrTop(_x, _nx,ix);}
  double bottom(int iy) const {return leftOrBottom(_y, _ny,iy);}
  double top(int iy) const {return rightOrTop(_y, _ny,iy);}
private:
  // Init number of cells rounded to power of 2
  void initLookup();
  int indexOf(double * axis, int n, int n2, double xory) const;
  // Convert an axis type into its pointer and number of values
  double * getAxis(AxisType axis, int& n) const;
  // General geometries of cells
  double leftOrBottom(double * axis, int n, int ival) const;
  double rightOrTop(double * axis, int n, int ival) const;
  double widthOrHeight(double * axis, int n, int ival) const;

  // Values at each grid node
  double *_values;
  // Vector of x coordinades of nodes
  double * _x;
  // Vector of y coordinades of nodes
  double * _y;
  // Number of cells in Y direction
  int _ny;
  // Number of cells in X direction
  int _nx;
  // Number of cells in Y direction rounded to next power of 2, used for fast lookup
  int _n2y;
  // Number of cells in X direction rounded to next power of 2, used for fast lookup
  int _n2x;
};

inline double IrregularGrid2DData::value(int ix, int iy) const
{
  return(_values[iy*_nx+ix]);
}

inline void IrregularGrid2DData::setValue(int ix, int iy, double val)
{
  _values[iy*_nx+ix]=val;
}

inline const double * IrregularGrid2DData::valuePointer(int ix, int iy) const
{
  return _values+(iy*_nx+ix);
}

inline double * IrregularGrid2DData::valuePointer(int ix, int iy)
{
  return _values+(iy*_nx+ix);
}

inline Point2D IrregularGrid2DData::coordinates(int ix, int iy) const
{
  return Point2D(_x[ix],_y[iy]);
}

inline void IrregularGrid2DData::init(int nx, int ny, double value)
{
  init(nx, ny);
  init(value);
}

template <class CurveClass, class PointClass>
CurveClass IrregularGrid2DData::followMaximumX() const
{
  TRACE;
  CurveClass curve;
  if(_nx==0 || _ny==0) return curve;
  // Search the maximum value of grid
  int ix, iy;
  maximumValue(ix, iy);
  curve.append(followMaximumX<CurveClass,PointClass>(ix, _nx-1, iy));
  curve.append(followMaximumX<CurveClass,PointClass>(ix, 0, iy));
  curve.sort();
  return curve;
}

template <class CurveClass, class PointClass>
CurveClass IrregularGrid2DData::followMaximumX(int ixMin, int ixMax, int iyMax) const
{
  TRACE;
  CurveClass curve;
  int dx=ixMin<ixMax ? 1 : -1;
  ixMax+=dx;
  for(int i=ixMin; i!=ixMax; i+=dx) {
    Curve<Point2D> cs=crossSection(YAxis, i);
    iyMax=cs.closestMax(iyMax);
    curve.append(PointClass(x(i), y(iyMax)));
    curve.last().setValid(true);
  }
  return curve;
}

template <class CurveClass, class PointClass>
void IrregularGrid2DData::followMaximumX(CurveClass& curve, double min, double max) const
{
  TRACE;
  ASSERT(nx()>1); // Needed by Curve::indexAfter()
  ASSERT(ny()>1); // Needed by Curve::indexAfter()
  curve.resample(x());
  for(int i=0; i<_nx; i++) {
    if(min<=x(i) && x(i)<=max) {
      int curveIndex=curve.indexAfter(x(i));
      PointClass& p=curve[curveIndex];
      Curve<Point2D> cs=crossSection(YAxis, i);
      int iMax=cs.closestMax(cs.indexAfter(p.y()));
      p.setY(y(iMax));
      p.setValid(true);
    }
  }
}

template <class CurveClass, class PointClass> CurveClass IrregularGrid2DData::followMaximumY() const
{
  CurveClass curve;
  if(_nx==0 || _ny==0) return curve;
  // Search the maximum value of grid
  int ix, iy;
  maximumValue(ix, iy);
  curve.append(followMaximumY<CurveClass,PointClass>(iy, _ny-1, ix));
  curve.append(followMaximumY<CurveClass,PointClass>(iy, 0, ix));
  curve.sort();
  return curve;
}

template <class CurveClass, class PointClass> CurveClass IrregularGrid2DData::followMaximumY(int iyMin, int iyMax, int ixMax) const
{
  CurveClass curve;
  int dy=iyMin<iyMax ? 1 : -1;
  iyMax+=dy;
  for(int i=iyMin;i!=iyMax;i+=dy) {
    Curve<Point2D> cs=crossSection(XAxis, i);
    ixMax=cs.closestMax(ixMax);
    curve.append(PointClass(y(i), x(ixMax)));
    curve.last().setValid(true);
  }
  return curve;
}

template <class CurveClass, class PointClass> void IrregularGrid2DData::followMaximumY(CurveClass& curve, double min, double max) const
{
  ASSERT(nx()>1); // Needed by Curve::indexAfter()
  ASSERT(ny()>1); // Needed by Curve::indexAfter()
  curve.resample(y());
  for(int i=0; i<_ny; i++) {
    if(min<=y(i) && y(i)<=max) {
      int curveIndex=curve.indexAfter(y(i));
      PointClass& p=curve[curveIndex];
      Curve<Point2D> cs=crossSection(XAxis, i);
      int iMax=cs.closestMax(cs.indexAfter(p.y()));
      p.setY(x(iMax));
      p.setValid(true);
    }
  }
}

} // namespace QGpCoreMath

Q_DECLARE_OPERATORS_FOR_FLAGS(QGpCoreTools::AxisTypes)

#endif // IRREGULARGRID2DDATA.H
