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

#include "GridPlot.h"
#include "GridProperties.h"
#include "ColorMapProperties.h"
#include "AxisWindow.h"
#include "GraphContent.h"
#include "IrregularGrid2DDraw.h"
#include "LayerLocker.h"
#include "XMLSciFigs.h"

namespace SciFigs {

/*!
  \class GridPlot GridPlot.h
  \brief A GridPlot is the base class for all layers that draw colored grids
  
*/

const QString GridPlot::xmlGridPlotTag="GridViewer";

GridPlot::GridPlot(AxisWindow * parent) :
    GraphContentLayer(parent)
{
  TRACE;
  _colorMap.generateColorScale(20, ColorPalette::Hsv);
  _showGrid=false;
  _smooth=true;
}

GridPlot::~GridPlot()
{
  TRACE;
}

uint GridPlot::_tabColorMap=PropertyProxy::uniqueId();
uint GridPlot::_tabGrid=PropertyProxy::uniqueId();

/*!
  Setup property editor
*/
void GridPlot::addProperties(PropertyProxy * pp)
{
  TRACE;
  if(pp->setCurrentTab(_tabColorMap)) {
    pp->addReference(this);
  } else {
    ColorMapProperties * w=new ColorMapProperties;
    pp->addTab(_tabColorMap, tr("Palette"), w, this);
  }
  if(pp->setCurrentTab(_tabGrid)) {
    pp->addReference(this);
  } else {
    GridProperties * w=new GridProperties;
    pp->addTab(_tabGrid, tr("Grid format"), w, this);
  }
}

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

void GridPlot::properties(PropertyWidget * w) const
{
  TRACE;
  if(w->id()==_tabColorMap) {
    w->setValue(ColorMapProperties::Map, QVariant::fromValue(colorMap()));
  } else if(w->id()==_tabGrid) {
    w->setValue(GridProperties::ShowGrid, showGrid());
    w->setValue(GridProperties::Smooth, smooth());
    w->setValue(GridProperties::FillValue, fillValue());
    w->setValue(GridProperties::FillValueInversed, 1.0/fillValue());
    w->setValue(GridProperties::FillType, GridProperties::area2item(fillType()) );
  }
}

void GridPlot::setProperty(uint wid, int pid, QVariant val)
{
  TRACE;
  if(wid==_tabColorMap) {
    switch(pid) {
    case ColorMapProperties::Map:
      setColorMap(val.value<ColorMap>());
      break;
    default:
      break;
    }
  } else if(wid==_tabGrid) {
    LayerLocker ll(this);
    switch(pid) {
    case GridProperties::ShowGrid:
      setShowGrid(val.toBool());
      graphContent()->deepUpdate();
      break;
    case GridProperties::Smooth:
      setSmooth(val.toBool());
      graphContent()->deepUpdate();
      break;
    case GridProperties::FillType:
      setFillType(GridProperties::item2area(val.toInt()) );
      break;
    case GridProperties::FillValue:
      setFillValue(val.toDouble());
      break;
    case GridProperties::FillValueInversed:
      setFillValue(1.0/val.toDouble());
    default:
      break;
    }
  }
}

/*!
  Set the palette. Safe for painting threads.
*/
void GridPlot::setColorMap(const ColorMap& map)
{
  TRACE;
  // Avoid recurrent loop
  static bool locked=false;
  if(!locked) {
    locked=true;
    LayerLocker ll(this);
    _colorMap=map;
    // This layer takes generally time to repaint (useless to repaint only this layer) 
    graphContent()->deepUpdate();
    emit colorMapChanged(_colorMap);
    locked=false;
  }
}

ColorMap& GridPlot::beginColorMapChange()
{
  TRACE;
  lockDelayPainting();
  return _colorMap;
}

void GridPlot::endColorMapChange()
{
  TRACE;
  unlock();
  emit colorMapChanged(colorMap());
}

/*!
  Set a linear scale on palette. Safe for painting threads. For other modification of the palette
  see setColorMap().
*/
void GridPlot::setLinearColorMap(double minVal, double maxVal)
{
  TRACE;
  beginColorMapChange();
  _colorMap.setVLinear(minVal, maxVal);
  endColorMapChange();
}

void GridPlot::xml_writeChildren(XML_WRITECHILDREN_ARGS) const
{
  TRACE;
  _colorMap.xml_save(s, context);
  GraphContentLayer::xml_writeChildren(s, context);
}

XMLMember GridPlot::xml_member(XML_MEMBER_ARGS)
{
  TRACE;
  if(tag=="ColorMap" ||
     tag=="ColorPalette") {       // Kept for compatibility
    return XMLMember(&_colorMap);
  }
  return GraphContentLayer::xml_member(tag, attributes, context);
}

void GridPlot::xml_polishChild(XML_POLISHCHILD_ARGS)
{
  Q_UNUSED(context)
  if(child->xml_tagName()=="ColorMap") {
    setColorMap(*static_cast<ColorMap *>(child));
  }
}

/*!
   Use a copy of 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)
*/
void GridPlot::drawGrid2DBlock(const IrregularGrid2D& grid, ColorMap map,
                                   const LayerPainterRequest& lp, QPainter& p, const IrregularGrid2DDraw& d)
{
  TRACE;
  const GraphContentOptions& gc=lp.options();
  if(grid.nx()==0 || grid.ny()==0) return;

  // Draw all visible grid cells, taking care of axis order
  lp.graphContent()->setProgressMaximum(d.ny);
  const double * valx, * valy=grid.valuePointer(d.stopix, d.stopiy);
  int y, dy;
  QColor col;
  if(gc.scaleX().isEffectivelyReversed()) {
    if(gc.scaleY().isEffectivelyReversed()) {
      for(int iy=d.ny;iy > 0;iy-- ) {
        if(lp.terminated()) return;
        lp.graphContent()->setProgressValue(d.ny-iy);
        y=d.limy[ iy - 1 ];
        dy=d.limy[ iy ] - y;
        valx=valy;
        valy -= grid.nx();
        for(int ix=d.nx;ix > 0;ix--, valx-- ) {
          guiColor(map.color(*valx), col);
          if(col!=Qt::white) {
            p.fillRect(d.limx[ix], y, d.limx[ix-1]-d.limx[ix], dy, col);
          }
        }
      }
    } else {
      for(int iy=d.ny;iy > 0;iy-- ) {
        if(lp.terminated()) return;
        lp.graphContent()->setProgressValue(d.ny-iy);
        y=d.limy[ iy ];
        dy=d.limy[ iy - 1 ] - y;
        valx=valy;
        valy -= grid.nx();
        for(int ix=d.nx;ix > 0;ix--, valx-- ) {
          guiColor(map.color(*valx), col);
          if(col!=Qt::white) {
            p.fillRect(d.limx[ix], y, d.limx[ix-1]-d.limx[ix], dy, col);
          }
        }
      }
    }
  } else {
    if(gc.scaleY().isEffectivelyReversed()) {
      for(int iy=d.ny;iy > 0;iy-- ) {
        if(lp.terminated()) return;
        lp.graphContent()->setProgressValue(d.ny-iy);
        y=d.limy[ iy - 1 ];
        dy=d.limy[ iy ] - y;
        valx=valy;
        valy -= grid.nx();
        for(int ix=d.nx;ix > 0;ix--, valx-- ) {
          guiColor(map.color(*valx), col);
          if(col!=Qt::white) {
            p.fillRect (d.limx[ix-1], y, d.limx[ix]-d.limx[ix-1], dy, col);
          }
        }
      }
    } else {
      for(int iy=d.ny;iy > 0;iy-- ) {
        if(lp.terminated()) return;
        lp.graphContent()->setProgressValue(d.ny-iy);
        y=d.limy[ iy ];
        dy=d.limy[ iy - 1 ] - y;
        valx=valy;
        valy -= grid.nx();
        for(int ix=d.nx;ix > 0;ix--, valx-- ) {
          guiColor(map.color(*valx), col);
          if(col!=Qt::white) {
            p.fillRect(d.limx[ix-1], y, d.limx[ix]-d.limx[ix-1], dy, col);
          }
        }
      }
    }
  }
  if(lp.terminated()) return;
  lp.graphContent()->setProgressValue(d.ny);
}

/*!
  Draw the cell limits.

  All members of d must be initialized, so call getCellLimits() before!
*/
void GridPlot::drawGridLines(QPainter& p, const IrregularGrid2DDraw& d) const
{
  TRACE;
  int y;
  int x0=d.limx[ 0 ], x1=d.limx[ d.nx ];
  for(int iy=d.ny;iy >= 0;iy-- ) {
    y=d.limy[ iy ];
    p.drawLine (x0, y, x1, y);
  }
  x0=d.limy[ 0 ]; x1=d.limy[ d.ny ];
  for(int ix=d.nx;ix >= 0;ix-- ) {
    y=d.limx[ ix ];
    p.drawLine (y, x0, y, x1);
  }
  // Draw cell indexes (debug only)
  // Multi thread painting not compatible with draw text !!!
  /*QString iText( "(%1,%2)" );
  for(int iy=d.ny - 1;iy >= 0;iy-- ) {
    for(int ix=d.nx - 1;ix >= 0;ix-- ) {
      p.drawText (d.nodex[ ix ], d.nodey[ iy ], iText.arg(ix + d.startix).arg(iy + d.startiy) );
    }
  }*/
}

/*!
  \fn void GridPlot::drawGridLines(QPainter& p, const Grid2D<double>& grid) const
  Draw the cell limits.
*/

inline bool GridPlot::notEnd(int i, int stopi, int di)
{
  TRACE;
  if(di > 0) return i <= stopi; else return i >= stopi;
}

/*!
   Use a copy of 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)
*/
void GridPlot::drawGrid2DSmooth(const IrregularGrid2D& grid, ColorMap map,
                                const LayerPainterRequest& lp, QPainter & p, IrregularGrid2DDraw& d)
{
  TRACE;
  const GraphContentOptions& gc=lp.options();
  if(grid.nx()==0 || grid.ny()==0) return;
  /*
    Variable description:
    --------------------

    xsc, ysc                 screen coordinates of the current grid cell
    xs, ys                   screen coordinates inside the current grid cell
    nextXs, nextYs           screen coordinates of the next grid cell
    xi, yi                   grid index of the current cell
    dxi, dyi                 grid increments necessary to scan the grid from top-left to bottom-right (-1 or 1)
    nextXi, nextYi           grid index the next grid cell
    v1 to v4                 values of the grid in the neighbour cells, v1 for current cell, v4 for next cell
    v13, v24                 interpolated values at the current screen Y between, v1-v3 and v2-v4, respectivelly
    c13, c24                 slopes along Y axis
    c                        slope along X axis at the current screen Y
    coliY                    current color index at the current screen Y between v1 and v3
    coliX                    current color index (along X axis)
    nextColYs                next Y where the color index will change between v1 and v3
    nextColXs                next X where the color index will change on the current screen Y axis
    curCol                   current color
  */
  int xsc, xs, xi, nextXi, nextXs, nextXsR, dxi;
  int ysc, ys, yi, nextYi, nextYs, nextYsR, dyi;
  double v1, v2, v3, v4, c13, v13, c24, v24, c;
  int coliY, coliX, nextColYs, nextColXs;
  uint curCol;
  // Node vectors with global indexes
  int * gnodex, * gnodey;

  if(gc.scaleX().isEffectivelyReversed()) {
    dxi=-1;
    int tmp=d.startix;
    d.startix=d.stopix;
    d.stopix=tmp;
    gnodex=d.nodex-d.stopix;
  } else {
    dxi=1;
    gnodex=d.nodex-d.startix;
  }
  if(gc.scaleY().isEffectivelyReversed()) {
    dyi=1;
    gnodey=d.nodey-d.startiy;
  } else {
    dyi=-1;
    int tmp=d.startiy;
    d.startiy=d.stopiy;
    d.stopiy=tmp;
    gnodey=d.nodey-d.stopiy;
  }

  // Create an image that intersects the current visible rect and the grid rect
  int w=lp.size().width();
  int h=lp.size().height();
  int imageMinX=gnodex[d.startix];
  if(imageMinX<0) {
    imageMinX=0;
  } else if(imageMinX>w) {
    return; // nothing to draw
  }
  int imageMaxX=gnodex[d.stopix];
  if(imageMaxX<0) {
    return; // nothing to draw
  } else if(imageMaxX>w) {
    imageMaxX=w;
  }

  int imageMinY=gnodey[ d.startiy ];
  if(imageMinY<0) {
    imageMinY=0;
  } else if(imageMinY>h) {
    return; // nothing to draw
  }
  int imageMaxY=gnodey[ d.stopiy ];
  if(imageMaxY<0) {
    return; // nothing to draw
  } else if(imageMaxY>h) {
    imageMaxY=h;
  }

  w=imageMaxX-imageMinX+1;
  h=imageMaxY-imageMinY+1;
  QImage im(w, h, QImage::Format_ARGB32);
  // Translate node screen coordinates to have them in image coordinates
  for(int xi=d.startix; notEnd(xi, d.stopix, dxi); xi+=dxi) gnodex[ xi ]-=imageMinX;
  for(int yi=d.startiy; notEnd(yi, d.stopiy, dyi); yi+=dyi) gnodey[ yi ]-=imageMinY;
  uint * imPtr=(uint * ) im.bits();

  lp.graphContent()->setProgressMaximum(h);
  yi=d.startiy;
  ysc=gnodey[ yi ];
  while(notEnd( yi, d.stopiy, dyi) && ysc < h) {
    if(lp.terminated()) return;
    lp.graphContent()->setProgressValue(ysc);
    xi=d.startix;
    xsc=gnodex[ xi ];
    nextYi=yi + dyi;
    if(nextYi >= grid.ny() || nextYi < 0) break;
    nextYs=gnodey[ nextYi ];
    v2=grid.value(xi, yi);
    v4=grid.value(xi, nextYi);
    double invDy=1.0/static_cast<double>(nextYs-ysc);
    c24=(v4-v2)*invDy;
    while(notEnd( xi, d.stopix, dxi) && xsc < w) {
      nextXi=xi + dxi;
      if(nextXi >= grid.nx() || nextXi < 0) break;
      xsc=gnodex[ xi ];
      nextXs=gnodex[ nextXi ];
      v1=v2;
      v2=grid.value(nextXi, yi);
      v3=v4;
      v4=grid.value(nextXi, nextYi);
      c13=c24;
      c24=(v4-v2)*invDy;
      coliY=map.index(v1);
      if(ysc<0) ys=0; else ys=ysc;
      if(nextYs<h) nextYsR=nextYs; else nextYsR=h-1;
      uint * imCell=imPtr+ys*w;
      while(ys<=nextYsR) {
        double nextColYsD;
        if(v1<v3 && coliY<map.count()) {
          nextColYsD=ysc+round((map.upperValue(coliY)-v1)/c13);
          if(nextColYsD>nextYsR)
            nextColYs=nextYsR;
          else
            nextColYs=(int)nextColYsD;
        } else if(v1>v3 && coliY>0) {
          nextColYsD=ysc+round((map.lowerValue(coliY)-v1)/c13);
          if(nextColYsD>nextYsR)
            nextColYs=nextYsR;
          else
            nextColYs=(int)nextColYsD;
        } else nextColYs=nextYsR;
        while(ys<=nextColYs) {
          int dys=ys-ysc;
          v13=v1+c13*dys;
          v24=v2+c24*dys;
          c=(double)(nextXs-xsc)/(v24-v13);
          coliX=coliY;
          if(xsc < 0) xs=0; else xs=xsc;
          if(nextXs < w) nextXsR=nextXs; else nextXsR=w - 1;
          while(xs <= nextXsR) {
            curCol=static_cast<uint>(map.color(coliX).rgba());
            double nextColXsD;
            if(v13 < v24 && coliX<map.count()) {
              nextColXsD= xsc+round((map.upperValue(coliX)-v13)*c);
              if(nextColXsD > nextXsR)
                nextColXs=nextXsR;
              else
                nextColXs=(int)  nextColXsD;
            } else if(v13 > v24 && coliX > 0) {
              nextColXsD=xsc+round((map.lowerValue(coliX)-v13)*c);
              if(nextColXsD > nextXsR)
                nextColXs=nextXsR;
              else
                nextColXs=(int) nextColXsD;
            } else nextColXs=nextXsR;
            while(xs <= nextColXs) {
              imCell[xs]=curCol;
              xs++;
            }
            if(v13 < v24) coliX++;
            else if(v13 > v24) coliX--;
          }
          imCell+=w;
          ys++;
        }
        if(v1 < v3) coliY++;
        else if(v1 > v3) coliY--;
      }
      xi=nextXi;
      xsc=nextXs;
    }
    yi=nextYi;
    ysc=nextYs;
  }
  if(lp.terminated()) return;
  lp.graphContent()->setProgressValue(h);
  p.drawImage(imageMinX, imageMinY, im);
}

} // namespace SciFigs
