/***************************************************************************
**
**  This file is part of SciFigs.
**
**  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-05-11
**  Copyright: 2006-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include "LiveGridLayer.h"
#include "AutoAdjustScaleProperties.h"
#include "AxisWindow.h"
#include "LayerPainterRequest.h"
#include "IrregularGrid2DDraw.h"
#include "LayerLocker.h"
#include "XMLSciFigs.h"

namespace SciFigs {

LiveGridLayer::LiveGridLayer(AxisWindow * parent)
    : GridPlot(parent)
{
  TRACE;
  _function=nullptr;
  _sampling=1;
  _variables=CrossXY;
  _fixedVariable=0.0;
  _valueFactor=1.0;
  connect(this, SIGNAL(paletteAdjusted(const ColorMap&)),
          this, SLOT(showAdjustedPalette(const ColorMap&)), Qt::QueuedConnection);
}

LiveGridLayer::~LiveGridLayer()
{
  TRACE;
  delete _function;
}

void LiveGridLayer::setFunction(AbstractFunction * f)
{
  TRACE;
  LayerLocker ll(this);
  delete _function;
  _function=f;
}

AbstractFunction * LiveGridLayer::takeFunction()
{
  TRACE;
  LayerLocker ll(this);
  AbstractFunction * f=_function;
  _function=nullptr;
  return f;
}

void LiveGridLayer::showAdjustedPalette(const ColorMap& map)
{
  TRACE;
  _colorMap=map;
}

void LiveGridLayer::adjustPalette(const IrregularGrid2D& grid, ColorMap& pal) const
{
  TRACE;
  double min, max;
  if(forceMinimumValue()) {
    min=minimumValue();
  } else {
    min=grid.minimumValue();
  }
  if(forceMaximumValue()) {
    max=maximumValue();
  } else {
    max=grid.maximumValue();
  }
  if(forceRatio()) {
    min=max/ratio();
  }
  switch(scale()) {
  case SamplingParameters::Linear:
    if(symetric()) {
      if(fabs(max)>fabs(min)) {
        min=-max;
      } else {
        max=-min;
      }
    }
    pal.setVLinear(min, max);
    break;
  case SamplingParameters::Log:
    pal.setVLog(min, max);
    break;
  }
}

uint LiveGridLayer::_tab=PropertyProxy::uniqueId();

/*!
  Setup property editor
*/
void LiveGridLayer::addProperties(PropertyProxy * pp)
{
  TRACE;
  GridPlot::addProperties(pp);
  if(pp->setCurrentTab(_tab)) {
    pp->addReference(this);
  } else {
    AutoAdjustScaleProperties * w=new AutoAdjustScaleProperties;
    pp->addTab(_tab, tr("Palette update"), w, this);
  }
}

/*!
  Clean property editor
*/
void LiveGridLayer::removeProperties(PropertyProxy * pp)
{
  TRACE;
  pp->removeTab(_tab, this);
  GridPlot::removeProperties(pp);
}

void LiveGridLayer::properties(PropertyWidget * w) const
{
  TRACE;
  if(w->id()==_tab) {
    AutoAdjustScale::properties(w);
  } else {
    GridPlot::properties(w);
  }
}

void LiveGridLayer::setProperty(uint wid, int pid, QVariant val)
{
  TRACE;
  if(wid==_tab) {
    AutoAdjustScale::setProperty(pid, val);
  } else {
    GridPlot::setProperty(wid, pid, val);
  }
}

Rect LiveGridLayer::boundingRect() const
{
  TRACE;
  return Rect();
}

void LiveGridLayer::paintData(const LayerPainterRequest& lp, QPainter& p, double) const
{
  TRACE;
  const GraphContentOptions& gc=lp.options();
  if(!_function) {
    return;
  }
  IrregularGrid2D grid=calculateGrid(lp.size(), lp.options(), &lp);
  // Make a copy of current palette (mandatory for multi-thread, palette are implicitly shared
  // objects so deep copy probably is not occuring right now, only upon user request for modification)
  ColorMap map=colorMap();
  if(autoAdjust()) {
    adjustPalette(grid, map);
    emit paletteAdjusted(map);
  }
  IrregularGrid2DDraw d;
  // Get the limit of visible area in terms of grid indexes
  d.setVisibleArea(grid, gc);
  // Create two int vectors containing the x and y of nodes (centers of blocks)
  d.setCellNodes(grid, gc);

  if(smooth()) {
    drawGrid2DSmooth(grid, map, lp, p, d);
  } else {
    d.setCellLimits();
    drawGrid2DBlock(grid, map, lp, p, d);
  }
  /*GraphContent * gc=graphContent();
  QImage im(w, h, QImage::Format_ARGB32);
  uint * imPtr=(uint * ) im.bits() + w + 1;
  double y;
  int h1=h-1;
  int w1=w-1;
  int iy;
  for(iy=1;iy<h1; iy+=3) {
    y=gc->ys2r(iy);
    fillRow(gc, imPtr, y, w, w1);
    imPtr+=2*w;
  }
  // Add last pixel rows
  switch(iy-h1) {
  case 0:  // two rows are missing
    y=gc->xs2r(iy);
    fillRowSup2(gc, imPtr, y, w, w1);
    break;
  case 1:  // one row is missing
    y=gc->xs2r(iy);
    fillRowSup1(gc, imPtr, y, w, w1);
    break;
  default: // h is a multiple of 3
    break;
  }
  p.drawImage(0, 0, im);*/
}

IrregularGrid2D LiveGridLayer::calculateGrid(const QSize& s, const GraphContentOptions& gc,
                                             const LayerPainterRequest * lp) const
{
  TRACE;
  int nx=s.width()/_sampling;
  int ny=s.height()/_sampling;
  IrregularGrid2D grid(nx>1 ? nx : 2, ny>1 ? ny : 2);

  double x,y;
  int iStart=_sampling >> 1;
  int iyGrid, diyGrid, ixGrid0, ixGrid, dixGrid, ixEndGrid, iyEndGrid;
  if(graph()->yAxis()->isEffectivelyReversed()) {
    iyGrid=0;
    diyGrid=1;
    iyEndGrid=grid.ny();
  } else {
    iyGrid=grid.ny()-1;
    diyGrid=-1;
    iyEndGrid=-1;
  }
  if(graph()->xAxis()->isEffectivelyReversed()) {
    ixGrid0=grid.nx()-1;
    dixGrid=-1;
    ixEndGrid=-1;
  } else {
    ixGrid0=0;
    dixGrid=1;
    ixEndGrid=grid.nx();
  }
  ixGrid=ixGrid0;
  int iy=iStart;
  y=gc.ys2r(iy);
  grid.setY(iyGrid, y);
  if(lp) {
    if(lp->terminated()) {
      grid.init(0.0);
      return grid;
    }
    lp->graphContent()->setProgressMaximum(s.height());
  }
  for(int ix=iStart;ixGrid!=ixEndGrid; ix+=_sampling, ixGrid+=dixGrid) {
    x=gc.xs2r(ix);
    grid.setX(ixGrid, x);
  }
  for(int iy=iStart;iyGrid!=iyEndGrid; iy+=_sampling, iyGrid+=diyGrid) {
    if(lp) {
      if(lp->terminated()) {
        grid.init(0.0);
        return grid;
      }
      lp->graphContent()->setProgressValue(iy);
    }
    y=gc.ys2r(iy);
    grid.setY(iyGrid, y);
    ixGrid=ixGrid0;
    Point p2d;
    switch(_variables) {
    case CrossXY:
      for(;ixGrid!=ixEndGrid; ixGrid+=dixGrid) {
        grid.setValue(ixGrid, iyGrid, _valueFactor*_function->value(Point(grid.x(ixGrid), y, _fixedVariable)));
      }
      break;
    case InvCrossXY:
      for(;ixGrid!=ixEndGrid; ixGrid+=dixGrid) {
        grid.setValue(ixGrid, iyGrid, 1.0/(_valueFactor*_function->value(Point(grid.x(ixGrid), y, _fixedVariable))));
      }
      break;
    case CrossXZ:
      for(;ixGrid!=ixEndGrid; ixGrid+=dixGrid) {
        grid.setValue(ixGrid, iyGrid, _valueFactor*_function->value(Point(grid.x(ixGrid), _fixedVariable, y)));
      }
      break;
    case CrossYZ:
      for(;ixGrid!=ixEndGrid; ixGrid+=dixGrid) {
        grid.setValue(ixGrid, iyGrid, _valueFactor*_function->value(Point(_fixedVariable, grid.x(ixGrid), y)));
      }
      break;
    case Value_XYMaxZ:
      p2d.setY(y);
      for(;ixGrid!=ixEndGrid; ixGrid+=dixGrid) {
        p2d.setX(grid.x(ixGrid));
        grid.setValue(ixGrid, iyGrid, _valueFactor*_function->value2D(p2d, ZAxis));
      }
      break;
    case Value_XZMaxY:
      p2d.setZ(y);
      for(;ixGrid!=ixEndGrid; ixGrid+=dixGrid) {
        p2d.setX(grid.x(ixGrid));
        grid.setValue(ixGrid, iyGrid, _valueFactor*_function->value2D(p2d, YAxis));
      }
      break;
    case Value_YZMaxX:
      p2d.setZ(y);
      for(;ixGrid!=ixEndGrid; ixGrid+=dixGrid) {
        p2d.setY(grid.x(ixGrid));
        grid.setValue(ixGrid, iyGrid, _valueFactor*_function->value2D(p2d, XAxis));
      }
      break;
    case Z_XYMaxZ:
      p2d.setY(y);
      for(;ixGrid!=ixEndGrid; ixGrid+=dixGrid) {
        p2d.setX(grid.x(ixGrid));
        _function->value2D(p2d, ZAxis);
        grid.setValue(ixGrid, iyGrid, _valueFactor*p2d.z());
      }
      break;
    case Y_XZMaxY:
      p2d.setZ(y);
      for(;ixGrid!=ixEndGrid; ixGrid+=dixGrid) {
        p2d.setX(grid.x(ixGrid));
        _function->value2D(p2d, YAxis);
        grid.setValue(ixGrid, iyGrid, _valueFactor*p2d.y());
      }
      break;
    case X_YZMaxX:
      p2d.setZ(y);
      for(;ixGrid!=ixEndGrid; ixGrid+=dixGrid) {
        p2d.setY(grid.x(ixGrid));
        _function->value2D(p2d, XAxis);
        grid.setValue(ixGrid, iyGrid, _valueFactor*p2d.x());
      }
      break;
    case GradXYDirection:
      for(;ixGrid!=ixEndGrid; ixGrid+=dixGrid) {
        grid.setValue(ixGrid, iyGrid, _valueFactor*_function->gradientDirection2D(Point(grid.x(ixGrid), y, _fixedVariable), ZAxis));
      }
      break;
    case GradXZDirection:
      for(;ixGrid!=ixEndGrid; ixGrid+=dixGrid) {
        grid.setValue(ixGrid, iyGrid, _valueFactor*_function->gradientDirection2D(Point(grid.x(ixGrid), _fixedVariable, y), YAxis));
      }
      break;
    case GradYZDirection:
      for(;ixGrid!=ixEndGrid; ixGrid+=dixGrid) {
        grid.setValue(ixGrid, iyGrid, _valueFactor*_function->gradientDirection2D(Point(_fixedVariable, grid.x(ixGrid), y), XAxis));
      }
      break;
    case GradXYValue:
      for(;ixGrid!=ixEndGrid; ixGrid+=dixGrid) {
        grid.setValue(ixGrid, iyGrid, _valueFactor*_function->gradientLength2D(Point(grid.x(ixGrid), y, _fixedVariable), ZAxis));
      }
      break;
    case GradXZValue:
      for(;ixGrid!=ixEndGrid; ixGrid+=dixGrid) {
        grid.setValue(ixGrid, iyGrid, _valueFactor*_function->gradientLength2D(Point(grid.x(ixGrid), _fixedVariable, y), YAxis));
      }
      break;
    case GradYZValue:
      for(;ixGrid!=ixEndGrid; ixGrid+=dixGrid) {
        grid.setValue(ixGrid, iyGrid, _valueFactor*_function->gradientLength2D(Point(_fixedVariable, grid.x(ixGrid), y), XAxis));
      }
      break;
    case GradZvsXY:
      for(;ixGrid!=ixEndGrid; ixGrid+=dixGrid) {
        grid.setValue(ixGrid, iyGrid, _valueFactor*_function->gradient(Point(grid.x(ixGrid), y, _fixedVariable)).z());
      }
      break;
    case ConcavityXY:
      for(;ixGrid!=ixEndGrid; ixGrid+=dixGrid) {
        grid.setValue(ixGrid, iyGrid, _valueFactor*_function->concavity(Point(grid.x(ixGrid), y, _fixedVariable)));
      }
      break;
    case ConcavityXZ:
      for(;ixGrid!=ixEndGrid; ixGrid+=dixGrid) {
        grid.setValue(ixGrid, iyGrid, _valueFactor*_function->concavity(Point(grid.x(ixGrid), _fixedVariable, y)));
      }
      break;
    case ConcavityYZ:
      for(;ixGrid!=ixEndGrid; ixGrid+=dixGrid) {
        grid.setValue(ixGrid, iyGrid, _valueFactor*_function->concavity(Point(_fixedVariable, grid.x(ixGrid), y)));
      }
      break;
    case StepLengthXY:
      for(;ixGrid!=ixEndGrid; ixGrid+=dixGrid) {
        grid.setValue(ixGrid, iyGrid, _valueFactor*_function->stepLength2D(Point(grid.x(ixGrid), y, _fixedVariable), ZAxis));
      }
      break;
    case StepLengthXZ:
      for(;ixGrid!=ixEndGrid; ixGrid+=dixGrid) {
        grid.setValue(ixGrid, iyGrid, _valueFactor*_function->stepLength2D(Point(grid.x(ixGrid), _fixedVariable, y), YAxis));
      }
      break;
    case StepLengthYZ:
      for(;ixGrid!=ixEndGrid; ixGrid+=dixGrid) {
        grid.setValue(ixGrid, iyGrid, _valueFactor*_function->stepLength2D(Point(_fixedVariable, grid.x(ixGrid), y), XAxis));
      }
      break;
    case StepDirectionXY:
      for(;ixGrid!=ixEndGrid; ixGrid+=dixGrid) {
        grid.setValue(ixGrid, iyGrid, _valueFactor*_function->stepDirection2D(Point(grid.x(ixGrid), y, _fixedVariable), ZAxis));
      }
      break;
    case StepDirectionXZ:
      for(;ixGrid!=ixEndGrid; ixGrid+=dixGrid) {
        grid.setValue(ixGrid, iyGrid, _valueFactor*_function->stepDirection2D(Point(grid.x(ixGrid), _fixedVariable, y), YAxis));
      }
      break;
    case StepDirectionYZ:
      for(;ixGrid!=ixEndGrid; ixGrid+=dixGrid) {
        grid.setValue(ixGrid, iyGrid, _valueFactor*_function->stepDirection2D(Point(_fixedVariable, grid.x(ixGrid), y), XAxis));
      }
      break;
    case StepX:
      for(;ixGrid!=ixEndGrid; ixGrid+=dixGrid) {
        grid.setValue(ixGrid, iyGrid, _valueFactor*_function->step1D(Point(grid.x(ixGrid), y, _fixedVariable), XAxis));
      }
      break;
    case StepY:
      for(;ixGrid!=ixEndGrid; ixGrid+=dixGrid) {
        grid.setValue(ixGrid, iyGrid, _valueFactor*_function->step1D(Point(grid.x(ixGrid), y, _fixedVariable), YAxis));
      }
      break;
    case StepZ:
      for(;ixGrid!=ixEndGrid; ixGrid+=dixGrid) {
        grid.setValue(ixGrid, iyGrid, _valueFactor*_function->step1D(Point(grid.x(ixGrid), y, _fixedVariable), ZAxis));
      }
      break;
    }
  }
  if(lp) {
    lp->graphContent()->setProgressValue(s.height());
  }
  return grid;
}

void LiveGridLayer::xml_writeChildren(XML_WRITECHILDREN_ARGS) const
{
  TRACE;
  if(!_function) {
    return;
  }
  // Calculate grid with current plot limits
  IrregularGrid2D grid=calculateGrid(graphContent()->size(), graphContent()->options());
  XMLSciFigs * scifigsContext=static_cast<XMLSciFigs *>(context);
  if(scifigsContext->data()) {
    grid.xml_save(s, context);
  }
  GridPlot::xml_writeChildren(s, context);
}

double LiveGridLayer::value(const Point2D& p) const
{
  TRACE;
  if(_function) {
    Point p2d;
    switch(_variables) {
    case CrossXY:
      break;
    case InvCrossXY:
      return 1.0/_function->value(Point(p.x(), p.y(), _fixedVariable));
    case CrossXZ:
      return _function->value(Point(p.x(), _fixedVariable, p.y()));
    case CrossYZ:
      return _function->value(Point(_fixedVariable, p.x(), p.y()));
    case Value_XYMaxZ:
      p2d=Point(p.x(), p.y(), 0.0);
      return _function->value2D(p2d, ZAxis);
    case Value_XZMaxY:
      p2d=Point(p.x(), 0.0, p.y());
      return _function->value2D(p2d, YAxis);
    case Value_YZMaxX:
      p2d=Point(0.0, p.x(), p.y());
      return _function->value2D(p2d, XAxis);
    case Z_XYMaxZ:
      p2d=Point(p.x(), p.y(), 0.0);
      _function->value2D(p2d, ZAxis);
      return p2d.z();
    case Y_XZMaxY:
      p2d=Point(p.x(), 0.0, p.y());
      _function->value2D(p2d, YAxis);
      return p2d.y();
    case X_YZMaxX:
      p2d=Point(0.0, p.x(), p.y());
      _function->value2D(p2d, XAxis);
      return p2d.x();
    case GradXYDirection:
      return _function->gradientDirection2D(Point(p.x(), p.y(), _fixedVariable), ZAxis);
    case GradXZDirection:
      return _function->gradientDirection2D(Point(p.x(), _fixedVariable, p.y()), YAxis);
    case GradYZDirection:
      return _function->gradientDirection2D(Point(_fixedVariable, p.x(), p.y()), XAxis);
    case GradXYValue:
      return _function->gradientLength2D(Point(p.x(), p.y(), _fixedVariable), ZAxis);
    case GradXZValue:
      return _function->gradientLength2D(Point(p.x(), _fixedVariable, p.y()), YAxis);
    case GradYZValue:
      return _function->gradientLength2D(Point(_fixedVariable, p.x(), p.y()), XAxis);
    case GradZvsXY:
      return _function->gradient(Point(p.x(), p.y(), _fixedVariable)).z();
    case ConcavityXY:
      return _function->concavity(Point(p.x(), p.y(), _fixedVariable));
    case ConcavityXZ:
      return _function->concavity(Point(p.x(), _fixedVariable, p.y()));
    case ConcavityYZ:
      return _function->concavity(Point(_fixedVariable, p.x(), p.y()));
    case StepLengthXY:
      return _function->stepLength2D(Point(p.x(), p.y(), _fixedVariable), ZAxis);
    case StepLengthXZ:
      return _function->stepLength2D(Point(p.x(), _fixedVariable, p.y()), YAxis);
    case StepLengthYZ:
      return _function->stepLength2D(Point(_fixedVariable, p.x(), p.y()), XAxis);
    case StepDirectionXY:
      return _function->stepDirection2D(Point(p.x(), p.y(), _fixedVariable), ZAxis);
    case StepDirectionXZ:
      return _function->stepDirection2D(Point(p.x(), _fixedVariable, p.y()), YAxis);
    case StepDirectionYZ:
      return _function->stepDirection2D(Point(_fixedVariable, p.x(), p.y()), XAxis);
    case StepX:
      return _function->step1D(Point(p.x(), p.y(), _fixedVariable), XAxis);
    case StepY:
      return _function->step1D(Point(p.x(), p.y(), _fixedVariable), YAxis);
    case StepZ:
      return _function->step1D(Point(p.x(), p.y(), _fixedVariable), ZAxis);
    }
    return _function->value(Point(p.x(), p.y(), _fixedVariable));
  } else {
    return 0.0;
  }
}

QString LiveGridLayer::coordinateTipInfo(const Point2D&, const Point2D& pReal) const
{
  TRACE;
  static const QString str("(%2 ; %3 ; %1)");
  return str.arg(_valueFactor*value(pReal));
}

} // namespace SciFigs
