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

#include "PolarGridPlot.h"
#include "AxisWindow.h"
#include "LayerPainterRequest.h"

namespace SciFigs {

/*!
  \class PolarGridPlot PolarGridPlot.h
  \brief A PolarGridPlot is a layer to draw polar colored grids
  
*/

PolarGridPlot::PolarGridPlot(AxisWindow * parent) :
    GridPlot(parent)
{
  TRACE;
  _theGrid=new Grid2D<double>;
}

PolarGridPlot::~PolarGridPlot()
{
  TRACE;
  delete _theGrid;
}

void PolarGridPlot::paintData(const LayerPainterRequest& lp, QPainter& p, double) const
{
  TRACE;
  // Find the visible limits and draw the grid
  Rect limits;
  grid2DPolarLimits(lp, limits);
  drawGrid2DPolarBlock(lp, p, limits);
  drawCircularGridLines(lp, p,limits);
}

void PolarGridPlot::drawCircularGridLines(const LayerPainterRequest& lp, QPainter& p, Rect& limits) const
{
  TRACE;
  const GraphContentOptions& gc=lp.options();
  // Draw circular grid lines inside the limits
  QColor c(200,200,200);
  p.setBrush(Qt::NoBrush);
  double maxR=limits.x2();
  int minphi=(int)floor(Angle::radiansToDegrees(limits.y1())*16.0);
  int dphi=(int)ceil(Angle::radiansToDegrees(limits.y2()+0.5*_theGrid->deltaY())*16.0)-minphi;
  double val,dval;
  int x1,y1,x2,y2;
  // Major lines
  p.setPen(QPen(c,1, Qt::SolidLine));
  dval=gc.scaleX().majorTicks();
  val=floor(limits.x1()/dval)*dval;
  for(; val<=maxR; val+=dval) {
    x1=gc.xr2s(-val);
    y1=gc.yr2s(val);
    x2=gc.xr2s(val);
    y2=gc.yr2s(-val);
    p.drawArc(x1, y1, x2-x1, y2-y1,minphi,dphi);
  }
  if(lp.terminated()) return;
  double angle;
  for(angle=0;angle<2*M_PI;angle+=M_PI*0.25) {
    p.drawLine(gc.xr2s(0.0),gc.yr2s(0.0),
               gc.xr2s(maxR*cos(angle)),
               gc.yr2s(maxR*sin(angle)));
  }
  // Minor lines
  p.setPen(QPen(c,1,Qt::DotLine));
  dval=gc.scaleX().minorTicks();
  val=floor(limits.x1()/dval)*dval;
  if(lp.terminated()) return;
  for(; val<=maxR; val+=dval) {
    x1=gc.xr2s(-val);
    y1=gc.yr2s(val);
    x2=gc.xr2s(val);
    y2=gc.yr2s(-val);
    p.drawArc(x1, y1, x2-x1, y2-y1,minphi,dphi);
  }
  if(lp.terminated()) return;
  for(angle=M_PI/12.0;angle<2*M_PI;angle+=M_PI*0.25) {
    p.drawLine(gc.xr2s(0.0),gc.yr2s(0.0),
               gc.xr2s(maxR*cos(angle)),
               gc.yr2s(maxR*sin(angle)));
  }
  if(lp.terminated()) return;
  for(angle=M_PI/6.0;angle<2*M_PI;angle+=M_PI*0.25) {
    p.drawLine(gc.xr2s(0.0),gc.yr2s(0.0),
               gc.xr2s(maxR*cos(angle)),
               gc.yr2s(maxR*sin(angle)));
  }
}

bool PolarGridPlot::mouseReleaseEvent (QMouseEvent * e, int)
{
  TRACE;
  if(e->buttons() & Qt::LeftButton) {
    Point2D p=graphContent()->options().s2r2D(e->pos());
    Point2D origin(0,0);
    emit clicked(origin.distanceTo(p),origin.azimuthTo(p));
  }
  return true;
}

/*!
  Calculate the visible part of the grid (x->r, y->phi)
*/
void PolarGridPlot::grid2DPolarLimits(const LayerPainterRequest& lp, Rect& limits) const
{
  TRACE;
  const GraphContentOptions& gc=lp.options();
  Grid2D<double> & grid=*_theGrid;

  // Defines the zone of the grid to draw in terms of r and phi
  // We call a,b,c,d the four corners of the visible area
  Point2D a(gc.xVisMin(),gc.yVisMin());
  Point2D b(gc.xVisMin(),gc.yVisMax());
  Point2D c(gc.xVisMax(),gc.yVisMax());
  Point2D d(gc.xVisMax(),gc.yVisMin());
  Point2D centre=grid.origin();
  // Test if centre is inside the view rectangle
  double startr, stopr;
  double startphi, stopphi;
  double maxR=((double)grid.nx()-1)*grid.deltaY();
  Rect visLimits(gc.xVisMin(),gc.yVisMin(),gc.xVisMax(),gc.yVisMax());
  if(visLimits.includes(centre)) {
    startphi=0;
    stopphi=2*M_PI-0.5*grid.deltaY();
    startr=0;
    stopr=centre.distanceTo(a);
    if(stopr>=maxR) stopr=maxR;
    else {
      double tmp=centre.distanceTo(b);
      if(tmp>=maxR) stopr=maxR;
      else {
        if(tmp>stopr) stopr=tmp;
        tmp=centre.distanceTo(c);
        if(tmp>=maxR) stopr=maxR;
        else {
          if(tmp>stopr) stopr=tmp;
          tmp=centre.distanceTo(d);
          if(tmp>=maxR) stopr=maxR;
          else if(tmp>stopr) stopr=tmp;
        }
      }
    }
  } else {
    // Find angular limits
    // init with point a
    double az0=centre.azimuthTo(a);
    startphi=az0;
    stopphi=az0;
    // add point b
    double az=centre.azimuthTo(b);
    double daz=az-az0;
    if(daz>M_PI) daz=2*M_PI-daz;
    if(daz<-M_PI) daz=2*M_PI+daz;
    az=az0+daz;
    if(az>stopphi) stopphi=az0+daz;
    else if(az<startphi) startphi=az0+daz;
    // add point c
    az=centre.azimuthTo(c);
    daz=az-az0;
    if(daz>M_PI) daz=2*M_PI-daz;
    if(daz<-M_PI) daz=2*M_PI+daz;
    az=az0+daz;
    if(az>stopphi) stopphi=az0+daz;
    else if(az<startphi) startphi=az0+daz;
    // add point d
    az=centre.azimuthTo(d);
    daz=az-az0;
    if(daz>M_PI) daz=2*M_PI-daz;
    if(daz<-M_PI) daz=2*M_PI+daz;
    az=az0+daz;
    if(az>stopphi) stopphi=az0+daz;
    else if(az<startphi) startphi=az0+daz;
    // Find r limits
    // Max distance to corners
    stopr=centre.distanceTo(a);
    double tmp=centre.distanceTo(b);
    if(tmp>stopr) stopr=tmp;
    tmp=centre.distanceTo(c);
    if(tmp>stopr) stopr=tmp;
    tmp=centre.distanceTo(d);
    if(tmp>stopr) stopr=tmp;
    if(stopr>maxR) stopr=maxR;
    // Min distance to segmentes
    startr=centre.distanceToSegment(a,b);
    tmp=centre.distanceToSegment(b,c);
    if(tmp<startr) startr=tmp;
    tmp=centre.distanceToSegment(c,d);
    if(tmp<startr) startr=tmp;
    tmp=centre.distanceToSegment(d,a);
    if(tmp<startr) startr=tmp;
    if(startr>maxR) startr=maxR;
  }
  limits.setLimits(startr,startphi,stopr,stopphi);
}

/*!
  Plot the grid inside the visible limits (x->r, y->phi)
*/
void PolarGridPlot::drawGrid2DPolarBlock(const LayerPainterRequest& lp, QPainter& p, const Rect& limits) const
{
  TRACE;
  if(lp.terminated()) return;
  Grid2D<double> & grid=*_theGrid;
  const GraphContentOptions& gc=lp.options();
  // 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)
  const ColorMap map=colorMap();

  // Check if stopphi is not greater than 2 PI, if so, divide the job in 2
  double stopphi=limits.y2();
  if(stopphi>M_PI*2) {
    Rect tmp_limits(limits.x1(),0,limits.x2(),stopphi-M_PI*2);
    drawGrid2DPolarBlock(lp, p,tmp_limits);
    stopphi=2*M_PI-0.5*grid.deltaY();
  }
  // Now limits are always OK
  double stopr=limits.x2();
  double ddr=grid.deltaX();
  double ddphi=grid.deltaY();
  int startir=(int)floor(limits.x1()/ddr);
  if(startir>grid.nx()) return;
  int startiphi=(int)floor(limits.y1()/ddphi);
  double startr=(double)startir*ddr;
  double startphi=(double)startiphi*ddphi;
  // Adjust ddr, increase it if less than a pixel
  int dmr,dmphi;
  if(fabs(gc.ax()*ddr)<1.0) {
    dmr=(int)ceil((double)1.0/fabs(gc.ax()*ddr));
    ddr*=(double)dmr;
  } else dmr=1;

  // Adjust ddphi*stopr, increase it if less than a pixel
  if(fabs(gc.ay()*ddphi*stopr)<1.0) {
    dmphi=(int)ceil((double)1.0/fabs(gc.ay()*ddphi*stopr));
    ddphi*=(double)dmphi;
    dmphi*=grid.nx();
  } else dmphi=grid.nx();
  QPolygon pSeq(5);
  double phi,r,lastr;
  const double * valr,* valphi;
  valphi=grid.valuePointer(startir,startiphi);
  double x0=grid.origin().x();
  double y0=grid.origin().y();
  double cphi,clastphi,sphi,slastphi;
  phi=startphi;
  cphi=cos(phi);
  sphi=sin(phi);
  QColor col;
  for(; phi<stopphi;valphi+=dmphi) {
    if(lp.terminated()) return;
    clastphi=cphi;
    slastphi=sphi;
    phi+=ddphi;
    cphi=cos(phi);
    sphi=sin(phi);
    valr=valphi;
    for(r=startr; r<stopr; valr+=dmr) {
      guiColor(map.color(*valr), col);
      p.setBrush(col);
      p.setPen(col);
      if(r==0) {
        r+=ddr*0.5;
        pSeq.setPoint(0,gc.r2s(x0, y0));
        pSeq.setPoint(1,gc.r2s(x0+r*cphi, y0+r*sphi));
        pSeq.setPoint(2,gc.r2s(x0+r*clastphi, y0+r*slastphi));
        pSeq.setPoint(3,pSeq[0]);
        p.drawPolygon(pSeq.data(),4);
      } else {
        lastr=r;
        r+=ddr;
        pSeq.setPoint(0,gc.r2s(x0+lastr*clastphi, y0+lastr*slastphi));
        pSeq.setPoint(1,gc.r2s(x0+lastr*cphi, y0+lastr*sphi));
        pSeq.setPoint(2,gc.r2s(x0+r*cphi, y0+r*sphi));
        pSeq.setPoint(3,gc.r2s(x0+r*clastphi, y0+r*slastphi));
        pSeq.setPoint(4,pSeq[0]);
        p.drawPolygon(pSeq);
      }
    }
  }
}

} // namespace SciFigs
