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

#include "IrregularGrid2D.h"

namespace QGpCoreMath {

const QString IrregularGrid2D::xmlIrregularGrid2DTag="IrregularGrid2D";

/*!
  \class IrregularGrid2D IrregularGrid2d.h
  \brief A 2D grid where the coordinates of the rectangular cell are not regularly distributed

  Full description of class still missing
*/

QTextStream& operator>>(QTextStream& s, IrregularGrid2D& grd)
{
  TRACE;
  QString str;
  bool ok;
  int i;
  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.startsWith("nx=")) { // Historical format for grids
    int nx=str.section( "=", 1, 1).toInt(&ok);
    if( !ok || nx<=0) {
      return s;
    }
    str=s.readLine(); line++;
    int ny=str.section( "=", 1, 1).toInt(&ok);
    if( !ok || ny<=0) {
      return s;
    }
    grd.init(nx, ny);
    //  X coordinates of nodes
    str=s.readLine(); line++;
    if(str!="x" ) {
      App::log(IrregularGrid2D::tr("Wrong format at line 3, expected: x\n"));
      return s;
    }
    for(i=0;i < grd.nx();i++ ) {
      str=s.readLine();
      grd.setX(i, str.section( " ", 0, 0).toDouble(&ok));
      if( !ok) {
        App::log(IrregularGrid2D::tr("Wrong format at line %1, expected: float value\n").arg(line));
        return s;
      }
    }
    // Y coordinates of nodes
    str=s.readLine(); line++;
    if(str!="y" ) {
        App::log(IrregularGrid2D::tr("Wrong format at line %1, expected: y\n").arg(line));
        return s;
    }
    for(i=0;i < grd.ny();i++ ) {
      str=s.readLine(); line++;
      grd.setY(i, str.section( " ", 0, 0).toDouble(&ok));
      if( !ok) {
        App::log(IrregularGrid2D::tr("Wrong format at line %1, expected: float value\n").arg(line));
        return s;
      }
    }
    // Values
    int n=grd.nx() * grd.ny();
    str=s.readLine().trimmed(); line++;
    if(str=="values" ) {
      double * ptr=grd.valuePointer(0, 0);
      for(i=0;i < n;i++ ) {
        str=s.readLine(); line++;
        *(ptr++ )=str.section( " ", 0, 0).toDouble(&ok);
        if( !ok) {
          App::log(IrregularGrid2D::tr("Wrong format at line %1, expected: float value\n").arg(line));
          return s;
        }
      }
    } else if(str=="binary values" ) { // No longer exported like this but kept for compatibility
      QDataStream sb(s.device());
      sb.readRawData ((char * ) grd.valuePointer(0, 0), n * sizeof(double));
      str=s.readLine(); line++;
    } else {
      App::log(IrregularGrid2D::tr("Wrong format at line %1, expected: values\n").arg(line));
    }
  } else if(str=="x y val" || // More practical format for grids
              str=="x y z" ) {  // Compatibility with snapshot release
    QList<Point> values;
    Point p;
    while( !s.atEnd()) {
      str=s.readLine().trimmed(); line++;
      if(str.isEmpty() || str[0]=='#') break;
      StringSection pStr(str);
      if( !p.fromString(pStr)) {
        App::log(IrregularGrid2D::tr("Wrong format at line %1, expected: float value\n").arg(line));
        return s;
      }
      values.append(p);
    }
    // Get nx and ny
    QMap<double,int> xMap, yMap;
    QMap<double,int>::iterator itMap;
    for(QList<Point>::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.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(QList<Point>::iterator it=values.begin();it!=values.end(); it++) {
      grd.setValue(xMap[it->x()], yMap[it->y()], it->z());
    }
  } else if(str=="binary values" ) { // No longer exported like this but kept for compatibility
    QDataStream sb(s.device());
    sb.readRawData ((char * ) grd.valuePointer(0, 0), grd.nx() * grd.ny() * sizeof(double));
    str=s.readLine(); line++;
  } else {
    App::log(IrregularGrid2D::tr("Wrong format at line %1, expected: values\n").arg(line));
  }
  return s;
}

QTextStream& operator<<(QTextStream& s, const IrregularGrid2D& grd)
{
  TRACE;
  s << "x y val" << Qt::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++ ) << Qt::endl;
  }
#if 0
  // Old format for grid, not practical for processing with matlab or gnuplot for instance
  s << "nx=" << grd.nx() << endl;
  s << "ny=" << grd.ny() << endl;
  s << "x" << endl;
  int i;
  for(i=0;i < grd.nx();i++ ) s << grd.x(i) << endl;
  s << "y" << endl;
  for(i=0;i < grd.ny();i++ ) s << grd.y(i) << endl;
  s << "values" << endl;
  double * ptr=grd.valueAddress(0, 0);
  for(i=0;i < grd.ny();i++ ) {
    for(int ix=0;ix < grd.nx();ix++ )
      s << *(ptr++ ) << " " << ix << " " << i << endl;
  }
#endif
  return s;
}

/*!
  \fn template <class CurveClass, class PointClass> CurveClass IrregularGrid2D::followMaximumX()
  Returns a curve that follows the highest maximum of the grid. The returned curve is function y(x).
*/

/*!
  \fn template <class CurveClass, class PointClass> CurveClass IrregularGrid2D::followMaximumX(int ixMin, int ixMax, int iyMax)
  Returns a curve that follows a maximum of the grid with x starting from \a ixMin to \a ixMax. \a ixMin can be less than \a ixMax.
  The ridge to track is passing by (\a ixMin, \a iyMax). The returned curve is function y(x).
*/

/*!
  \fn template <class CurveClass, class PointClass> void IrregularGrid2D::followMaximumX(CurveClass& curve, double min, double max)
  Returns a curve that follows a maximum of the grid between \a min to \a max along X axis. \a curve is modified to fit the closest
  maxima of the grid. The returned curve is function x(y).
*/

/*!
  \fn template <class CurveClass, class PointClass> CurveClass IrregularGrid2D::followMaximumY()
  Returns a curve that follows the highest maximum of the grid. The returned curve is function x(y).
*/

/*!
  \fn template <class CurveClass, class PointClass> CurveClass IrregularGrid2D::followMaximumY(int ixMin, int ixMax, int iyMax)
  Returns a curve that follows a maximum of the grid with x starting from \a ixMin to \a ixMax. The ridge to track is passing by
  (\a ixMin, \a iyMax). The returned curve is function x(y).
*/

/*!
  \fn template <class CurveClass, class PointClass> void IrregularGrid2D::followMaximumY(CurveClass& curve, double min, double max)
  Returns a curve that follows a maximum of the grid between \a min to \a max along Y axis. \a curve is modified to fit the closest
  maxima of the grid.
*/

void IrregularGrid2D::xml_writeProperties(XML_WRITEPROPERTIES_ARGS) const
{
  TRACE;
  writeProperty(s, "nx", nx());
  writeProperty(s, "ny", ny());
  writeBinaryData(s, context);
}

void IrregularGrid2D::xml_writeBinaryData(XML_WRITEBINARYDATA_ARGS) const
{
  TRACE;
  Q_UNUSED(context)
  s << nx();
  s << ny();
  s.writeRawData((const char *)_d->xPointer(), sizeof(double)* nx());
  s.writeRawData((const char *)_d->yPointer(), sizeof(double)* ny());
  s.writeRawData((const char *)_d->valuePointer(0,0), sizeof(double)* nx() * ny());
}

bool IrregularGrid2D::xml_setBinaryData(XML_SETBINARYDATA_ARGS)
{
  TRACE;
  Q_UNUSED(context)
  int nrx, nry;
  s >> nrx;
  s >> nry;
  if(nrx!=nx() || nry!=ny()) {
    App::log(tr("IrregularGrid2D size in binary file: %1x%2\n"
                "                     in XML data   : %3x%4\n")
                   .arg(nrx).arg(nry).arg(nx()).arg(ny()));
  }
  init(nrx, nry);
  s.readRawData((char *)_d->xPointer(), sizeof(double)* nrx);
  s.readRawData((char *)_d->yPointer(), sizeof(double)* nry);
  s.readRawData((char *)_d->valuePointer(0,0), sizeof(double) * nrx * nry);
  return true;
}

bool IrregularGrid2D::xml_setBinaryData200411(XML_SETBINARYDATA_ARGS)
{
  TRACE;
  Q_UNUSED(context)
  s.setByteOrder(QDataStream::LittleEndian);
  bool ret=xml_setBinaryData(s, context);
  s.setByteOrder(QDataStream::BigEndian);
  return ret;
}

XMLMember IrregularGrid2D::xml_member(XML_MEMBER_ARGS)
{
  TRACE;
  Q_UNUSED(attributes)
  Q_UNUSED(context)
  if(tag=="nx" ) return XMLMember(0);
  else if(tag=="ny" ) return XMLMember(1);
  else if(tag=="cells" ) return XMLMember(2);
  return XMLMember(XMLMember::Unknown);
}

bool IrregularGrid2D::xml_setProperty(XML_SETPROPERTY_ARGS)
{
  TRACE;
  Q_UNUSED(tag)
  Q_UNUSED(attributes)
  Q_UNUSED(context)
  switch (memberID) {
  case 0: init(content.toInt(), ny()); return true;
  case 1: init(nx(), content.toInt()); return true;
  default: return false;
  }
}

} // namespace QGpCoreMath
