/***************************************************************************
**
**  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: 2008-02-18
**  Copyright: 2008-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include "GraphContentOptions.h"

namespace SciFigs {

/*!
  \class GraphContentOptions GraphContentOptions.h
  \brief Brief description of class still missing

  Full description of class still missing
*/

/*!
  Description of constructor still missing
*/
GraphContentOptions::GraphContentOptions()
{
  TRACE;
  _gridLines=true;
  _gridLineColor=QColor(220,220,220);
  _gridLineWeight=0.1;
  _contourWeight=0;
  _transparentMask=false;
  _printBitmap=true;
}

GraphContentOptions::GraphContentOptions(const GraphContentOptions& o)
{
  _gridLines=o._gridLines;
  _transparentMask=o._transparentMask;
  _gridLineColor=o._gridLineColor;
  _contourWeight=o._contourWeight;
  _gridLineWeight=o._gridLineWeight;
  _contourWeight=o._contourWeight;
  _printBitmap=o._printBitmap;

  _scaleX=o._scaleX;
  _scaleY=o._scaleY;
}

/*!
  Description of destructor still missing
*/
GraphContentOptions::~GraphContentOptions()
{
}

/*!
  Swap scales (used by ColorMapWidget to change orientation)
*/
void GraphContentOptions::swapAxes()
{
  Scale s=_scaleX;
  _scaleX=_scaleY;
  _scaleY=s;
}

void GraphContentOptions::paintContour (QPainter& p, double dotpercm, int w, int h) const
{
  p.setBrush(Qt::NoBrush);
  int penWeight=qRound(contourWeight()*dotpercm*0.1);
  if(penWeight==0) {
    penWeight=1;
  }
  int halfPenWeight=penWeight >> 1;
  p.setPen(QPen (Qt::black, penWeight, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin) );
  p.drawRect(halfPenWeight,halfPenWeight,w-penWeight,h-penWeight);
}

void GraphContentOptions::paintGridLines (QPainter& p, double dotpercm, int w, int h) const
{
  TRACE;
  int x,y;
  int penWeight;

  // half of the weight of major grid lines
  // Multiplying penWeigh by two effectively multiply the line
  // thickness by 4, hence we divide by sqrt(2) (or multiply by 0.7)
  penWeight=qRound(gridLineWeight()*dotpercm*0.0707);
  if(penWeight==0) {
    penWeight=1;
  }
  QColor c=_gridLineColor;
  c.setHsv(c.hue(), c.saturation(), 127+c.value()/2);
  p.setPen(QPen(c, penWeight));

  // Draw minor grid lines in the X-direction
  for(int i=_scaleX.minorTickValues().count()-1; i>=0; i--) {
    x=xa2s(_scaleX.minorTickValues().at(i));
    p.drawLine(x, 0, x, h);
  }
  // Draw minor grid lines in the Y-direction
  for(int i=_scaleY.minorTickValues().count()-1; i>=0; i--) {
    y=ya2s(_scaleY.minorTickValues().at(i));
    p.drawLine(0, y, w, y);
  }

  penWeight=qRound(gridLineWeight()*dotpercm*0.1);
  if(penWeight==0) {
    penWeight=1;
  }
  p.setPen(QPen(_gridLineColor, penWeight));

  // Draw major grid lines in the X-direction
  for(int i=_scaleX.majorTickValues().count()-1; i>=0; i--) {
    x=xa2s(_scaleX.majorTickValues().at(i));
    p.drawLine(x, 0, x, h);
  }
  // Draw major grid lines in the  Y-direction
  for(int i=_scaleY.majorTickValues().count()-1; i>=0; i--) {
    y=ya2s(_scaleY.majorTickValues().at(i));
    p.drawLine(0, y, w, y);
  }
}

/*!
  Calculate screen point when one point is inside the view rectangle. p1 is inside and p2 outside
*/
inline bool GraphContentOptions::oneInside(const Point2D& p1, const Point2D& p2, QPoint& ps1, QPoint& ps2) const
{
  TRACE;
  // p1 is inside, p2 is outside
  if(p2.x()!=p1.x()) {
    double coef=(p2.y() - p1.y())/(p2.x() - p1.x());
    if(p2.x() > p1.x()) {
      double yi=p1.y() + coef * (_scaleX.maximum() - p1.x());
      if(yi >= _scaleY.minimum() && yi <= _scaleY.maximum()) {
        ps1=r2s(p1);
        ps2=r2s(Point2D( _scaleX.maximum(), yi) );
        return true;
      }
    } else {
      double yi=p1.y() + coef * (_scaleX.minimum() - p1.x());
      if(yi >= _scaleY.minimum() && yi <= _scaleY.maximum()) {
        ps1=r2s(p1);
        ps2=r2s(Point2D( _scaleX.minimum(), yi) );
        return true;
      }
    }
  }
  if(p2.y()!=p1.y()) {
    double coef=(p2.x() - p1.x())/(p2.y() - p1.y());
    if(p2.y() > p1.y()) {
      double xi=p1.x ()+ coef * (_scaleY.maximum() - p1.y());
      if(xi >= _scaleX.minimum() && xi <= _scaleX.maximum()) {
        ps1=r2s(p1);
        ps2=r2s(Point2D( xi, _scaleY.maximum()) );
        return true;
      }
    } else {
      double xi=p1.x() + coef * (_scaleY.minimum() - p1.y());
      if(xi >= _scaleX.minimum() && xi <= _scaleX.maximum()) {
        ps1=r2s(p1);
        ps2=r2s(Point2D( xi, _scaleY.minimum()) );
        return true;
      }
    }
  }
#if(QT_VERSION > QT_VERSION_CHECK(5, 0, 0))
  if(std::isnan(p1.x()) ||
     std::isnan(p1.y()) ||
     std::isnan(p2.x()) ||
     std::isnan(p2.y())) {
#else
  if(isnan(p1.x()) ||
     isnan(p1.y()) ||
     isnan(p2.x()) ||
     isnan(p2.y())) {
#endif
    return false; // nan for one of the points, no need to issue a warning
  }
  App::log(tr("*** WARNING *** : cannot find intersection with graph content rectangle for segment\n"
               "                  ( %1 ;  %2) to (%3 ;  %4)\n").arg(p1.x()).arg(p1.y()).arg(p2.x()).arg(p2.y()));
  return false;
}

/*!
  Calculate screen point when the two points are outside the view rectangle.
*/
inline bool GraphContentOptions::bothOutside(const Point2D& p1, const Point2D& p2, QPoint& ps1,QPoint& ps2) const
{
  TRACE;
  int numInter=0;
  Point2D pi[2];
  // Try to find one intersection with sides of the rectangle
  if(p2.x()!=p1.x()) {
    double coef=(p2.y()-p1.y())/(p2.x()-p1.x());
    double yi=p1.y()+coef*(_scaleX.minimum()-p1.x());
    if(yi>_scaleY.minimum() && yi<_scaleY.maximum() &&
        ((p1.y()<=p2.y() && yi>=p1.y() && yi<=p2.y()) ||
          (p1.y()>p2.y() && yi>=p2.y() && yi<=p1.y()))) {
      pi[numInter].setX(_scaleX.minimum());
      pi[numInter].setY(yi);
      numInter++;
    }
    yi=p1.y()+coef*(_scaleX.maximum()-p1.x());
    if(yi>_scaleY.minimum() && yi<_scaleY.maximum() &&
        ((p1.y()<=p2.y() && yi>=p1.y() && yi<=p2.y()) ||
          (p1.y()>p2.y() && yi>=p2.y() && yi<=p1.y()))) {
      pi[numInter].setX(_scaleX.maximum());
      pi[numInter].setY(yi);
      numInter++;
    }
  } else {
    if(p1.x()>_scaleX.minimum() && p1.x()<_scaleX.maximum() &&
        ((p1.y()<=p2.y() && p1.y()<_scaleY.minimum() && p2.y()>_scaleY.maximum()) ||
          (p1.y()>p2.y() && p2.y()<_scaleY.minimum() && p1.y()>_scaleY.maximum()))) {
      ps1=r2s(Point2D(p1.x(),_scaleY.minimum()));
      ps2=r2s(Point2D(p1.x(),_scaleY.maximum()));
      return true;
    } else return false;
  }
  if(p2.y()!=p1.y()) {
    double coef=(p2.x()-p1.x())/(p2.y()-p1.y());
    double xi=p1.x()+coef*(_scaleY.maximum()-p1.y());
    if(xi>_scaleX.minimum() && xi<_scaleX.maximum() &&
        ((p1.x()<=p2.x() && xi>=p1.x() && xi<=p2.x()) ||
          (p1.x()>p2.x() && xi>=p2.x() && xi<=p1.x()))) {
      pi[numInter].setX(xi);
      pi[numInter].setY(_scaleY.maximum());
      numInter++;
    }
    xi=p1.x()+coef*(_scaleY.minimum()-p1.y());
    if(xi>_scaleX.minimum() && xi<_scaleX.maximum() &&
        ((p1.x()<=p2.x() && xi>=p1.x() && xi<=p2.x()) ||
          (p1.x()>p2.x() && xi>=p2.x() && xi<=p1.x()))) {
      pi[numInter].setX(xi);
      pi[numInter].setY(_scaleY.minimum());
      numInter++;
    }
  } else {
    if(p1.y()>_scaleY.minimum() && p1.y()<_scaleY.maximum() &&
        ((p1.x()<=p2.x() && p1.x()<_scaleX.minimum() && p2.x()>_scaleX.maximum()) ||
          (p1.x()>p2.x() && p2.x()<_scaleX.minimum() && p1.x()>_scaleX.maximum()))) {
      ps1=r2s(Point2D(_scaleX.minimum(),p1.y()));
      ps2=r2s(Point2D(_scaleX.maximum(),p1.y()));
      return true;
    } else return false;
  }
  if(numInter==2) {
    ps1=r2s(pi[0]);
    ps2=r2s(pi[1]);
    return true;
  } else return false;
}

bool GraphContentOptions::r2s(const Point2D& p1, const Point2D& p2, QPoint& ps1,QPoint& ps2) const
{
  TRACE;
  if(p1.x()>=_scaleX.minimum()
      && p1.x()<=_scaleX.maximum()
      && p1.y()>=_scaleY.minimum()
      && p1.y()<=_scaleY.maximum()) {
    if(p2.x()>=_scaleX.minimum()
        && p2.x()<=_scaleX.maximum()
        && p2.y()>=_scaleY.minimum()
        && p2.y()<=_scaleY.maximum()) {
      ps1=r2s(p1);
      ps2=r2s(p2);
      return true;
    } else return oneInside(p1,p2,ps1,ps2);
  } else {
    if(p2.x()>=_scaleX.minimum()
       && p2.x()<=_scaleX.maximum()
       && p2.y()>=_scaleY.minimum()
       && p2.y()<=_scaleY.maximum())
      return oneInside(p2,p1,ps2,ps1);
    else {
      return bothOutside(p2,p1,ps2,ps1);
    }
  }
}

/*!
  Same as r2s() except that ps1 and ps2 must have their x initialized to the correct transformation.
*/
bool GraphContentOptions::yr2s(const Point2D& p1, const Point2D& p2, QPoint& ps1,QPoint& ps2) const
{
  TRACE;
  if(p1.x()>=_scaleX.minimum()
      && p1.x()<=_scaleX.maximum()
      && p1.y()>=_scaleY.minimum()
      && p1.y()<=_scaleY.maximum()) {
    if(p2.x()>=_scaleX.minimum()
        && p2.x()<=_scaleX.maximum()
        && p2.y()>=_scaleY.minimum()
        && p2.y()<=_scaleY.maximum()) {
      ps1.setY(yr2s(p1.y()));
      ps2.setY(yr2s(p2.y()));
      return true;
    } else return oneInside(p1,p2,ps1,ps2);
  } else {
    if(p2.x()>=_scaleX.minimum()
       && p2.x()<=_scaleX.maximum()
       && p2.y()>=_scaleY.minimum()
       && p2.y()<=_scaleY.maximum())
      return oneInside(p2,p1,ps2,ps1);
    else {
      return bothOutside(p2,p1,ps2,ps1);
    }
  }
}

/*!
  Returns visible rectangle
*/
Rect GraphContentOptions::visibleRect() const
{
  Rect visRect;
  switch(scaleX().type()) {
  case Scale::Linear:
  case Scale::Log:
  case Scale::AbsoluteTime:
  case Scale::RelativeTime:
    visRect.setX1(xVisMin());
    visRect.setX2(xVisMax());
    break;
  case Scale::Inversed:
  case Scale::InversedLog:
    visRect.setX1(1.0/xVisMax());
    visRect.setX2(1.0/xVisMin());
    break;
  }
  switch(scaleY().type()) {
  case Scale::Linear:
  case Scale::Log:
  case Scale::AbsoluteTime:
  case Scale::RelativeTime:
    visRect.setY1(yVisMin());
    visRect.setY2(yVisMax());
    break;
  case Scale::Inversed:
  case Scale::InversedLog:
    visRect.setY1(1.0/yVisMax());
    visRect.setY2(1.0/yVisMin());
    break;
  }
  return visRect;
}

} // namespace SciFigs
