/***************************************************************************
**
**  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: 2002-04-15
**  Copyright: 2002-2019
**    Marc Wathelet
**    Marc Wathelet (ULg, Liège, Belgium)
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#ifndef GRID_H
#define GRID_H

#include <math.h>

#include "QGpCoreMathDLLExport.h"
#include "Point2D.h"

namespace QGpCoreMath {

template <class ValueType>
class QGPCOREMATH_EXPORT Grid2D : public XMLClass
{
  TRANSLATIONS( "Grid2D" )
public:
  Grid2D();
  Grid2D(int nx, int ny);
  Grid2D(const Grid2D& m);
  virtual ~Grid2D();

  virtual const QString& xml_tagName() const {return xmlGrid2DTag;}
  static const QString xmlGrid2DTag;
  
  int nx() const {return _nx;}
  int ny() const {return _ny;}
  void clear();
  void init(int nx, int ny);
  void init(ValueType value);
  inline void init(int nx, int ny, ValueType value);

  ValueType value(int ix, int iy) const {return _values[ix+_nx*iy];}
  void setValue(int ix, int iy, double val) {_values[ix+_nx*iy]=val;}
  ValueType * valuePointer(int ix, int iy) {return _values+ix+_nx*iy;}
  const ValueType * valuePointer(int ix, int iy) const {return _values+ix+_nx*iy;}

  void setOrigin(const Point2D& p) {_origin=p;}
  const Point2D& origin() const {return _origin;}
  void setDeltaX(double dx) {_delta.setX(dx); _invDelta.setX(1.0/dx);}
  void setDeltaY(double dy) {_delta.setY(dy); _invDelta.setY(1.0/dy);}
  double deltaX() const {return _delta.x();}
  double deltaY() const {return _delta.y();}
  double x(int ix) const {return _origin.x()+ix*_delta.x();}
  double y(int iy) const {return _origin.y()+iy*_delta.y();}
  double west(int ix) const {return x(ix)-_delta.x()*0.5;}
  double east(int ix) const {return x(ix)+_delta.x()*0.5;}
  double south(int iy) const {return y(iy)-_delta.y()*0.5;}
  double north(int iy) const {return y(iy)+_delta.y()*0.5;}
  Point2D coordinates(int ix, int iy) const;
  int indexOfX(double x) const {return (int) round((x - _origin.x()) * _invDelta.x());}
  int indexOfY(double y) const {return (int) round((y - _origin.y()) * _invDelta.y());}

  ValueType minimumValue() const;
  ValueType maximumValue() const;
protected:
  virtual void xml_writeProperties(XML_WRITEPROPERTIES_ARGS) const;
  virtual bool xml_setProperty(XML_SETPROPERTY_ARGS);
  virtual XMLMember xml_member(XML_MEMBER_ARGS);
  virtual void xml_writeBinaryData(XML_WRITEBINARYDATA_ARGS) const;
  virtual bool xml_setBinaryData(XML_SETBINARYDATA_ARGS);
private:
  int _nx, _ny;
  Point2D _origin, _delta, _invDelta;
  ValueType *_values;
};

template <class ValueType>
const QString Grid2D<ValueType>::xmlGrid2DTag="Grid2D";

template <class ValueType>
Grid2D<ValueType>::Grid2D()
{
  TRACE;
  _nx=_ny=0;
  _delta=Point2D(1., 1. );
  _invDelta=Point2D(1., 1. );
  _origin=Point2D(0., 0. );
  _values=0;
}

template <class ValueType>
Grid2D<ValueType>::Grid2D(int nx, int ny)
{
  TRACE;
  _values=0;
  _delta=Point2D(1., 1. );
  _invDelta=Point2D(1., 1. );
  _origin=Point2D(0., 0. );
  init(nx, ny);
}

template <class ValueType>
Grid2D<ValueType>::Grid2D(const Grid2D& m) : XMLClass()
{
  TRACE;
  _values=0;
  _delta=m._delta;
  _invDelta=m._invDelta;
  _origin=m._origin;
  init(m._nx, m._ny);
  int n=_ny * _nx;
  for(int i=0; i < n; i++ )
    _values[ i ]=m._values[ i ];
}

template <class ValueType>
Grid2D<ValueType>::~Grid2D()
{
  TRACE;
  if(_values) delete [] _values;
}

template <class ValueType>
void Grid2D<ValueType>::init(int nx, int ny)
{
  TRACE;
  if(_values) delete [] _values;
  _nx=nx;
  _ny=ny;
  _values=new ValueType[ _ny * _nx ];
}

template <class ValueType>
void Grid2D<ValueType>::init(ValueType value)
{
  TRACE;
  int n=_ny * _nx;
  for(int i=0; i < n; i++ ) _values[ i ]=value;
}

template <class ValueType>
inline void Grid2D<ValueType>::init(int nx, int ny, ValueType value)
{
  init(nx, ny);
  init(value);
}

template <class ValueType>
ValueType Grid2D<ValueType>::minimumValue() const
{
  TRACE;
  int n=_nx*_ny;
  if(n<1) return 0;
  ValueType val=_values[0];
  for(int i=1; i<n; i++) {
    if(_values[i]<val) val=_values[i];
  }
  return val;
}

template <class ValueType>
ValueType Grid2D<ValueType>::maximumValue() const
{
  TRACE;
  int n=_nx*_ny;
  if(n<1) return 0;
  ValueType val=_values[0];
  for(int i=1; i<n; i++) {
    if(_values[i]>val) val=_values[i];
  }
  return val;
}

template <class ValueType>
Point2D Grid2D<ValueType>::coordinates(int ix, int iy) const
{
  TRACE;
  double x=_origin.x() + (double) ix * _delta.x();
  double y=_origin.y() + (double) iy * _delta.y();
  return (Point2D(x, y));
}

template <class ValueType>
void Grid2D<ValueType>::xml_writeProperties(XML_WRITEPROPERTIES_ARGS) const
{
  TRACE;
  writeProperty(s, "nx", _nx);
  writeProperty(s, "ny", _ny);
  writeProperty(s, "origin", _origin.toString());
  writeProperty(s, "delta", _delta.toString());
  writeBinaryData(s, context);
}

template <class ValueType>
void Grid2D<ValueType>::xml_writeBinaryData(XML_WRITEBINARYDATA_ARGS) const
{
  TRACE;
  Q_UNUSED(context)
  s << _nx;
  s << _ny;
  s.writeRawData((const char *)_values, sizeof(double)*_nx * _ny);
}

template <class ValueType>
bool Grid2D<ValueType>::xml_setBinaryData(XML_SETBINARYDATA_ARGS)
{
  TRACE;
  Q_UNUSED(context)
  int nx, ny;
  s >> nx;
  s >> ny;
  if(nx!=_nx || ny!=_ny) {
    App::log(tr("Grid2D size in binary file: %1x%2\n"
                "            in XML data   : %3x%4\n")
                 .arg(nx).arg(ny).arg(_nx).arg(_ny));
    _nx=nx;
    _ny=ny;
  }
  init(_nx, _ny);
  s.readRawData((char *)_values, sizeof(double)*_nx * _ny);
  return true;
}

template <class ValueType>
XMLMember Grid2D<ValueType>::xml_member(XML_MEMBER_ARGS)
{
  TRACE;
  Q_UNUSED(attributes)
  Q_UNUSED(context)
  if(tag=="origin" ) return XMLMember(0);
  else if(tag=="delta" ) return XMLMember(1);
  else if(tag=="nx" ) return XMLMember(2);
  else if(tag=="ny" ) return XMLMember(3);
  return XMLMember(XMLMember::Unknown);
}

template <class ValueType>
bool Grid2D<ValueType>::xml_setProperty(XML_SETPROPERTY_ARGS)
{
  TRACE;
  Q_UNUSED(tag)
  Q_UNUSED(attributes)
  Q_UNUSED(context)
  switch (memberID) {
  case 0: _origin.fromString(content); return true;
  case 1:
    _delta.fromString(content);
    _invDelta.setX(1.0/_delta.x());
    _invDelta.setY(1.0/_delta.y());
    return true;
  case 2: _nx=content.toInt(); return true;
  case 3: _ny=content.toInt(); return true;
  }
  return false;
}

template <class ValueType>
QTextStream& operator<<(QTextStream& s, const Grid2D<double>& grd)
{
  TRACE;
  s << "x y val" << ::endl;
  const double * ptr=grd.valuePointer(0, 0);
  for(int iy=0;iy < grd.ny();iy++ ) {
    for(int ix=0;ix < grd.nx();ix++ )
      s << grd.x(ix) << " " << grd.y(iy) << " " << *(ptr++ ) << ::endl;
  }
  return s;
}

template <class ValueType>
QTextStream& operator>>(QTextStream& s, Grid2D<ValueType>& grd)
{
  TRACE;
  QString str;
  int line=0;
  str=s.readLine().trimmed(); line++;
  while(str.isEmpty() || str[0]=='#') {  // Accept blank lines and comments only before grid definition
    str=s.readLine().trimmed(); line++;
  }
  if(str=="x y val" ) {
    QVector<Point2D> points;
    QVector<ValueType> values;
    Point2D p;
    ValueType val;
    const QChar * ptr;
    StringSection f;
    bool ok=true;
    while( !s.atEnd()) {
      str=s.readLine().trimmed(); line++;
      if(str.isEmpty() || str[0]=='#') break;
      StringSection pStr(str);
      ptr=0;
      f=pStr.nextField(ptr);
      if(f.isValid()) p.setX(f.toDouble()); else ok=false;
      f=pStr.nextField(ptr);
      if(f.isValid()) p.setY(f.toDouble()); else ok=false;
      f=pStr.nextField(ptr);
      if(f.isValid()) val=f.toDouble(); else ok=false;
      if( !ok) {
        App::log(QCoreApplication::translate("Grid2D", "Wrong format at line %1, expected: float value\n").arg(line));
        return s;
      }
      points.append(p);
      values.append(val);
    }
    // Deduce nx, ny, delta and origin from x and y vectors
    QMap<double,int> xMap, yMap;
    QMap<double,int>::iterator itMap;
    for(QVector<Point2D>::iterator it=values.begin();it!=values.end(); it++) {
      itMap=xMap.find(it->x());
      if(itMap==xMap.end()) {
        xMap.insert(it->x(),0);
      }
      itMap=yMap.find(it->y());
      if(itMap==yMap.end()) {
        yMap.insert(it->y(),0);
      }
    }
    if(xMap.isEmpty() || yMap.isEmpty()) {
      return s;
    }
    grd.setOrigin(Point2D(xMap.begin().value(), yMap.begin().value()));
    grd.setDeltaX(((--xMap.end()).value() - xMap.begin().value())/xMap.count());
    grd.setDeltaY(((--yMap.end()).value() - yMap.begin().value())/yMap.count());
    grd.init(xMap.count(), yMap.count(), 0.0);
    // Set index in x and y maps
    int i;
    i=0;
    for(QMap<double,int>::iterator it=xMap.begin();it!=xMap.end(); it++, i++) {
      grd.setX(i, it.key());
      it.value()=i;
      //printf("x[%i]=%lf\n",i,it.key());
    }
    i=0;
    for(QMap<double,int>::iterator it=yMap.begin();it!=yMap.end(); it++, i++) {
      grd.setY(i, it.key());
      it.value()=i;
      //printf("y[%i]=%lf\n",i,it.key());
    }
    // Set values
    for(int i=points.count()-1; i>=0; i--) {
      const Point2D& p=points.at(i);
      grd.setValue(xMap[p.x()], yMap[p.y()], values.at(i));
    }
  } else {
    App::log(QCoreApplication::translate("Grid2D", "Wrong format at line %1, expected: values\n").arg(line));
  }
  return s;
}

} // namespace QGpCoreMath

#endif // GRID2D.H
