/***************************************************************************
**
**  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)
**
***************************************************************************/

#ifndef GRIDPLOT_H
#define GRIDPLOT_H

#include "GraphContentLayer.h"
#include "LayerPainterRequest.h"

namespace SciFigs {

  class PaletteEditor;
  class GridEditProperties;
  class IrregularGrid2DDraw;

  class SCIFIGS_EXPORT GridPlot : public GraphContentLayer
  {
    Q_OBJECT
    Q_PROPERTY(bool smooth READ smooth WRITE setSmooth SCRIPTABLE true)
    Q_PROPERTY(bool showGrid READ showGrid WRITE setShowGrid SCRIPTABLE true)
  public:
    GridPlot(AxisWindow * parent);
    ~GridPlot();

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

    virtual bool hasProperties() {return true;}
    virtual void addProperties(PropertyProxy * pp);
    virtual void removeProperties(PropertyProxy * pp);
    virtual void properties(PropertyWidget * w) const;
    virtual void setProperty(uint wid, int pid, QVariant val);

    const ColorMap& colorMap() const {return _colorMap;}
    ColorMap& beginColorMapChange();
    void endColorMapChange();
    void setLinearColorMap(double minVal, double maxVal);

    bool showGrid() const {return _showGrid;}
    void setShowGrid(bool b) {_showGrid=b;}

    bool smooth() const {return _smooth;}
    void setSmooth(bool b) {_smooth=b;}

    Rect::Area fillType() const {return _fillType;}
    void setFillType(Rect::Area ft) {_fillType=ft;}

    double fillValue() const {return _fillValue;}
    void setFillValue(double v) {_fillValue=v;}
  public slots:
    void setColorMap(const ColorMap& map);
  signals:
    void colorMapChanged(const ColorMap& map);
  protected:
    template <class ValueType>
    static void drawGrid2DYSmooth(const Grid2D<ValueType>& grid, ColorMap map,
                                   const LayerPainterRequest& lp, QPainter& p);
    template <class ValueType>
    static void drawGrid2DBlock(const Grid2D<ValueType>& grid, ColorMap map,
                                 const LayerPainterRequest& lp, QPainter& p);
    static void drawGrid2DSmooth(const IrregularGrid2D& grid, ColorMap map,
                                   const LayerPainterRequest& lp, QPainter& p, IrregularGrid2DDraw& d);
    static void drawGrid2DBlock(const IrregularGrid2D& grid, ColorMap map,
                                 const LayerPainterRequest& lp, QPainter& p, const IrregularGrid2DDraw& d);
    void drawGridLines(QPainter& p, const IrregularGrid2DDraw& d) const;
    template <class ValueType>
    void drawGridLines(const Grid2D<ValueType>& grid,
                       const LayerPainterRequest& lp, QPainter& p) const;

    virtual void xml_writeChildren(XML_WRITECHILDREN_ARGS) const;
    virtual XMLMember xml_member(XML_MEMBER_ARGS);
    virtual void xml_polishChild(XML_POLISHCHILD_ARGS);

    Rect::Area _fillType;
    double _fillValue;
    ColorMap _colorMap;
  private:
    bool _showGrid;
    bool _smooth;

    static uint _tabColorMap, _tabGrid;
  private:
    static inline bool notEnd(int i, int stopi, int di);
  };

  template <class ValueType>
  void GridPlot::drawGrid2DYSmooth(const Grid2D<ValueType>& grid, ColorMap map,
                                   const LayerPainterRequest& lp, QPainter& p)
  {
    TRACE;
    const GraphContentOptions& gc=lp.options();

    double startx, ddx, stopx, x;
    double starty, ddy, stopy, y;
    double coef;
    int startix, startiy, dmx, dmy;
    int y1, y2, x1, x2, dx, dy, dlim, limx, limxdy, yn1, yn2;

    ddx=grid.deltaX();
    ddy=grid.deltaY();
    startx=grid.origin().x() - ddx * 0.5;
    starty=grid.origin().y();
    stopx=startx + (double) grid.nx() * ddx;
    stopy=starty + (double) (grid.ny() - 1) * ddy;

    // Find first x and stopx
    if(startx > gc.xVisMin()) {
      if(startx > gc.xVisMax()) return ;
      startix=0;
    } else {
      if(stopx < gc.xVisMin()) return ;
      startix=(int) floor(( gc.xVisMin() - startx)/ddx);
      startx += (double) startix * ddx;
    }
    stopx -= 0.5 * ddx;
    if(stopx > gc.xVisMax()) stopx=gc.xVisMax();

    // Find first starty and stopy
    if(starty > gc.yVisMin()) {
      if(starty > gc.yVisMax()) return ;
      y=starty;
      startiy=0;
    } else {
      if(stopy < gc.yVisMin()) return ;
      startiy=(int) floor(( gc.yVisMin() - starty)/ddy);
      y=starty + (double) startiy * ddy;
    }
    stopy -= 0.5 * ddy;
    if(stopy > gc.yVisMax()) stopy=gc.yVisMax();

    // Adjust ddx, increase it if less than a pixel
    if(fabs( gc.ax() * ddx) < 1.0) {
      dmx=(int) ceil(( double) 1.0/fabs(gc.ax() * ddx) );
      ddx *= (double) dmx;
    } else dmx=1;

    // Adjust ddy, increase it if less than a pixel
    if(fabs( gc.ay() * ddy) < 1.0) {
      dmy=(int) ceil(( double) 1.0/fabs(gc.ay() * ddy) );
      ddy *= (double) dmy;
      dmy *= grid.nx();
    } else dmy=grid.nx();

    const double * valx, * valy, * valxdy;
    valy=grid.valuePointer(startix, startiy);
    y2=gc.yr2s(y);

    QColor col;
    while(y<stopy) {
      if(lp.terminated()) return;
      y1=y2;
      y += ddy;
      y2=gc.yr2s(y);
      dy=y1 - y2;
      valx=valy;
      valy += dmy;
      valxdy=valy;
      x2=gc.xr2s(startx);
      for(x=startx; x < stopx; valx += dmx, valxdy += dmx) {
        x1=x2;
        x += ddx;
        x2=gc.xr2s(x);
        dx=x2 - x1;
        limx=map.index( *valx);
        limxdy=map.index( *valxdy);
        dlim=limxdy-limx;
        if(dlim==0) {
          guiColor(map.color(limx), col);
          p.fillRect (x1, y2, dx, dy, col);
        } else {
          coef=(( double) dy)/( *valx - *valxdy);
          yn2=y1;
          if(dlim > 0) {
            dlim=1;
            for(int lim=limx;lim < limxdy;lim += dlim) {
              yn1=yn2;
              yn2=(int) (coef * (map.upperValue(lim) - *valx) ) + y1;
              if(yn1!=yn2) {
                guiColor(map.color(lim), col);
                p.fillRect (x1, yn2, dx, yn1 - yn2, col);
              } else if(lim!=limx) {
                dlim=dlim << 1;
              }
            }
          } else {
            dlim=1;
            for(int lim=limx - 1;lim >= limxdy;lim -= dlim) {
              yn1=yn2;
              yn2=(int) (coef*(map.upperValue(lim) - *valx) ) + y1;
              if(yn1!=yn2) {
                guiColor(map.color(lim+1), col);
                p.fillRect (x1, yn2, dx, yn1 - yn2, col);
              } else if(lim!=limx-1) {
                dlim=dlim << 1;
              }
            }
          }
          if(yn2!=y2) {
            guiColor(map.color(limxdy), col);
            p.fillRect (x1, y2, dx, yn2 - y2, col);
          }
        }
      }
    }
  }

  template <class ValueType>
  void GridPlot::drawGrid2DBlock(const Grid2D<ValueType>& grid, ColorMap map,
                                 const LayerPainterRequest& lp, QPainter& p)
  {
    TRACE;
    const GraphContentOptions& gc=lp.options();

    double startx, ddx, stopx, x;
    double starty, ddy, stopy, y;
    int startix, startiy;
    int y1, y2, x1, x2, dy, dmx, dmy;

    ddx=grid.deltaX();
    ddy=grid.deltaY();
    startx=grid.origin().x() - ddx * 0.5;
    starty=grid.origin().y() - ddy * 0.5;
    stopx=startx + (double) grid.nx() * ddx;
    stopy=starty + (double) grid.ny() * ddy;

    // Find first x and stopx
    if(startx > gc.xVisMin()) {
      if(startx > gc.xVisMax()) return ;
      startix=0;
    } else {
      if(stopx < gc.xVisMin()) return ;
      startix=(int) floor(( gc.xVisMin() - startx)/ddx);
      startx += (double) startix * ddx;
    }
    stopx -= 0.5 * ddx;
    if(stopx > gc.xVisMax()) stopx=gc.xVisMax();

    // Find first starty and stopy
    if(starty > gc.yVisMin()) {
      if(starty > gc.yVisMax()) return ;
      y=starty;
      startiy=0;
    } else {
      if(stopy < gc.yVisMin()) return ;
      startiy=(int) floor(( gc.yVisMin() - starty)/ddy);
      y=starty + (double) startiy * ddy;
    }
    stopy -= 0.5 * ddy;
    if(stopy > gc.yVisMax()) stopy=gc.yVisMax();

    // Adjust ddx, increase it if less than a pixel
    if(fabs( gc.ax() * ddx) < 1.0) {
      dmx=(int) ceil(( double) 1.0/fabs(gc.ax() * ddx) );
      ddx *= (double) dmx;
    } else dmx=1;

    // Adjust ddy, increase it if less than a pixel
    if(fabs( gc.ay() * ddy) < 1.0) {
      dmy=(int) ceil(( double) 1.0/fabs(gc.ay() * ddy) );
      ddy *= (double) dmy;
      dmy *= grid.nx();
    } else dmy=grid.nx();

    const double * valx, *valy;
    valy=grid.valuePointer(startix, startiy);
    y2=gc.yr2s(y);
    QColor col;
    while(y<stopy) {
      if(lp.terminated()) return;
      y1=y2;
      y += ddy;
      y2=gc.yr2s(y);
      dy=y2 - y1;
      valx=valy;
      valy += dmy;
      x2=gc.xr2s(startx);
      for(x=startx; x<stopx; valx+=dmx) {
        x1=x2;
        x+=ddx;
        x2=gc.xr2s(x);
        guiColor(map.color(*valx), col);
        p.fillRect (x1, y1, x2-x1, dy, col);
      }
    }
  }

  template <class ValueType>
  void GridPlot::drawGridLines(const Grid2D<ValueType>& grid,
                               const LayerPainterRequest& lp, QPainter& p) const
  {
    TRACE;
    const GraphContentOptions& gc=lp.options();
    int x, y;
    int x0=gc.xr2s(grid.west(0)), x1=gc.xr2s(grid.east(grid.nx()-1));
    for(int iy=grid.ny()-1; iy>=0; iy--) {
      y=gc.yr2s(grid.south(iy));
      p.drawLine(x0, y, x1, y);
    }
    y=gc.yr2s(grid.north(0));
    p.drawLine(x0, y, x1, y);
    int y0=gc.yr2s(grid.south(0)), y1=gc.yr2s(grid.north(grid.ny()-1));
    for(int ix=grid.nx()-1;ix >= 0;ix-- ) {
      x=gc.xr2s(grid.east(ix));
      p.drawLine(x, y0, x, y1);
    }
    x=gc.xr2s(grid.west(0));
    p.drawLine(x, y0, x, y1);
  }

} // namespace SciFigs

#endif // GRIDPLOT_H
