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

#ifndef GRID3D_H
#define GRID3D_H

#include <math.h>

#include "Grid2D.h"
#include "Point.h"
#include "Plane.h"
#include "QGpCoreMathDLLExport.h"

namespace QGpCoreMath {

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

  virtual const QString& xml_tagName() const {return xmlGrid3DTag;}
  static const QString xmlGrid3DTag;

  int nx() const {return _nx;}
  int ny() const {return _ny;}
  int nz() const {return _nz;}
  void clear();
  void init(int nx, int ny, int nz);
  void init(ValueType value);
  inline void init(int nx, int ny, int nz, ValueType value);

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

  void setOrigin(const Point& p) {_origin=p;}
  const Point& 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);}
  void setDeltaZ(double dz) {_delta.setZ(dz); _invDelta.setZ(1.0/dz);}
  double deltaX() const {return _delta.x();}
  double deltaY() const {return _delta.y();}
  double deltaZ() const {return _delta.z();}
  double x(int ix) const {return _origin.x()+ix*_delta.x();}
  double y(int iy) const {return _origin.y()+iy*_delta.y();}
  double z(int iz) const {return _origin.z()+iz*_delta.z();}
  double east(int ix) const {return x(ix)-_delta.x()*0.5;}
  double west(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;}
  double top(int iz) const {return z(iz)-_delta.z()*0.5;}
  double bottom(int iz) const {return z(iz)+_delta.z()*0.5;}
  Point coordinates(int ix, int iy, int iz) 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());}
  int indexOfZ(double z) const {return (int) round((z - _origin.z()) * _invDelta.z());}

  ValueType minimumValue() const;
  ValueType maximumValue() const;

  Grid2D<ValueType> * crossSectionX(double x) const;
  Grid2D<ValueType> * crossSectionY(double y) const;
  Grid2D<ValueType> * crossSectionZ(double z) 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, _nz;
  Point _origin, _delta, _invDelta;
  ValueType *_values;
};

template <class ValueType>
const QString Grid3D<ValueType>::xmlGrid3DTag="Grid3D";

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

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

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

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

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

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

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

template <class ValueType>
ValueType Grid3D<ValueType>::minimumValue() const
{
  TRACE;
  int n=_nx*_ny*_nz;
  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 Grid3D<ValueType>::maximumValue() const
{
  TRACE;
  int n=_nx*_ny*_nz;
  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>
Point Grid3D<ValueType>::coordinates(int ix, int iy, int iz) const
{
  TRACE;
  double x=_origin.x() + (double) ix * _delta.x();
  double y=_origin.y() + (double) iy * _delta.y();
  double z=_origin.z() + (double) iz * _delta.z();
  return (Point(x, y, z));
}

template <class ValueType>
Grid2D<ValueType> * Grid3D<ValueType>::crossSectionX(double x) const
{
  int ix=indexOfX(x);
  if(ix<0 || ix>=_nx) {
    return 0;
  }
  Grid2D<ValueType> * section=new Grid2D<ValueType>(_ny, _nz);
  section->setDeltaX(_delta.y());
  section->setDeltaY(_delta.z());
  section->setOrigin(Point2D(_origin.y(), _origin.z()));
  ValueType * gridPtr=_values;
  ValueType * sectionPtr=section->valuePointer(0, 0);
  gridPtr+=ix;
  for(int iz=0; iz<_nz; iz++) {
    for(int iy=0; iy<_ny; iy++) {
      *(sectionPtr++)=*gridPtr;
      gridPtr+=_nx;
    }
  }
  return section;
}

template <class ValueType>
Grid2D<ValueType> * Grid3D<ValueType>::crossSectionY(double y) const
{
  int iy=indexOfY(y);
  if(iy<0 || iy>=_ny) {
    return 0;
  }
  Grid2D<ValueType> * section=new Grid2D<ValueType>(_nx, _nz);
  section->setDeltaX(_delta.x());
  section->setDeltaY(_delta.z());
  section->setOrigin(Point2D(_origin.x(), _origin.z()));
  ValueType * gridPtr=_values;
  ValueType * sectionPtr=section->valuePointer(0, 0);
  gridPtr+=iy*_nx;
  int d=(_ny-1)*_nx;
  for(int iz=0; iz<_nz; iz++) {
    for(int ix=0; ix<_nx; ix++) {
      *(sectionPtr++)=*(gridPtr++);
    }
    gridPtr+=d;
  }
  return section;
}

template <class ValueType>
Grid2D<ValueType> * Grid3D<ValueType>::crossSectionZ(double z) const
{
  int iz=indexOfZ(z);
  if(iz<0 || iz>=_nz) {
    return 0;
  }
  Grid2D<ValueType> * section=new Grid2D<ValueType>(_nx, _ny);
  section->setDeltaX(_delta.x());
  section->setDeltaY(_delta.y());
  section->setOrigin(Point2D(_origin.x(), _origin.y()));
  ValueType * gridPtr=_values;
  ValueType * sectionPtr=section->valuePointer(0, 0);
  gridPtr+=iz*_nx*_ny;
  for(int iy=0; iy<_ny; iy++) {
    for(int ix=0; ix<_nx; ix++) {
      *(sectionPtr++)=*(gridPtr++);
    }
  }
  return section;
}

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

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

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

template <class ValueType>
XMLMember Grid3D<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);
  else if(tag=="nz" ) return XMLMember(4);
  return XMLMember(XMLMember::Unknown);
}

template <class ValueType>
bool Grid3D<ValueType>::xml_setProperty(XML_SETPROPERTY_ARGS)
{
  TRACE;
  Q_UNUSED(attributes)
  Q_UNUSED(context)
  Q_UNUSED(tag)
  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());
    _invDelta.setZ(1.0/_delta.z());
    return true;
  case 2: _nx=content.toInt(); return true;
  case 3: _ny=content.toInt(); return true;
  case 4: _nz=content.toInt(); return true;
  }
  return false;
}

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

template <class ValueType>
QTextStream& operator>>(QTextStream& s, Grid3D<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 z val" ) {
    QVector<Point> points;
    QVector<ValueType> values;
    Point 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()) p.setZ(f.toDouble()); else ok=false;
      f=pStr.nextField(ptr);
      if(f.isValid()) val=f.toDouble(); else ok=false;
      if( !ok) {
        App::log(QCoreApplication::translate("Grid3D", "Wrong format at line %1, expected: float value\n").arg(line));
        return s;
      }
      points.append(p);
      values.append(val);
    }
    // Deduce nx, ny, nz, delta and origin from x, y and z vectors
    QMap<double,int> xMap, yMap, zMap;
    QMap<double,int>::iterator itMap;
    for(QVector<Point>::iterator it=points.begin(); it!=points.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);
      }
      itMap=zMap.find(it->z());
      if(itMap==zMap.end()) {
        zMap.insert(it->z(),0);
      }
    }
    if(xMap.isEmpty() || yMap.isEmpty() || zMap.isEmpty()) {
      return s;
    }
    grd.setOrigin(Point(xMap.begin().value(), yMap.begin().value(), zMap.begin().value()));
    grd.setDeltaX(((--xMap.end()).value() - xMap.begin().value())/xMap.count());
    grd.setDeltaY(((--yMap.end()).value() - yMap.begin().value())/yMap.count());
    grd.setDeltaZ(((--zMap.end()).value() - zMap.begin().value())/zMap.count());
    grd.init(xMap.count(), yMap.count(), zMap.count(), 0.0);
    // Set index in x, y and z 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());
    }
    i=0;
    for(QMap<double,int>::iterator it=zMap.begin();it!=zMap.end(); it++, i++) {
      grd.setZ(i, it.key());
      it.value()=i;
      //printf("z[%i]=%lf\n",i,it.key());
    }
    // Set values
    for(int i=points.count()-1; i>=0; i--) {
      const Point& p=points.at(i);
      grd.setValue(xMap[p.x()], yMap[p.y()], zMap[p.z()], values.at(i));
    }
  } else {
    App::log(QCoreApplication::translate("Grid3D", "Wrong format at line %1, expected: values\n").arg(line));
  }
  return s;
}

} // namespace QGpCoreMath

#endif // GRID3D.H
