/***************************************************************************
**
**  This file is part of QGpCoreMath.
**
**  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-04-15
**  Copyright: 2002-2019
**    Marc Wathelet
**    Marc Wathelet (ULg, Liège, Belgium)
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <math.h>

#include "Rect.h"

namespace QGpCoreMath {

const QString Rect::xmlRectTag="Rect";

/*!
  \class Rect Rect.h
  \brief Storage for a rectangle with coordinates in double precision
  A rectangle is defined by top-left corner (x1,y1) and bottom-right corner (x2,y2).
  All function that changes the coordinates of these two points check for validity.

*/

void Rect::operator*=(double fac)
{
  TRACE;
  _x1*=fac;
  _y1*=fac;
  _x2*=fac;
  _y2*=fac;
}

/*!
  Adjust the top-left and the bottom-rigth corners to include the points (\a nx1, \a ny1)
  and (\a nx2, \a ny2).
*/
void Rect::setLimits(double nx1, double ny1, double nx2, double ny2)
{
  TRACE;
  double temp;
  _x1=nx1;
  _y1=ny1;
  _x2=nx2;
  _y2=ny2;
  if(_x2<_x1) {
    temp=_x2;
    _x2=_x1;
    _x1=temp;
  }
  if(_y2<_y1) {
    temp=_y2;
    _y2=_y1;
    _y1=temp;
  }
  /*if(_x2-_x1<1e-10) { // avoid minuscule rectangles
    _x2+=1e-10;
    _x1-=1e-10;
  }
  if(_y2-_y1<1e-10) {
    _y2+=1e-10;
    _y1-=1e-10;
  }*/
}

/*!
  Tests if the rectangle r is entirely inside the rectangle
*/
bool Rect::includes(const Rect & r) const
{
  TRACE;
  return (_x1<=r._x1 && _y1<=r._y1 && _x2>=r._x2 && _y2>=r._y2);
}

/*!
  Tests if the point is inside the rectangle
*/
bool Rect::includes(const Point & p) const
{
  TRACE;
  return (_x1<=p.x() && p.x()<=_x2 && _y1<=p.y() && p.y()<=_y2);
}

/*!
  Tests if the point is inside the rectangle
*/
bool Rect::includes(const Point2D & p) const
{
  TRACE;
  return (_x1<=p.x() && p.x()<=_x2 && _y1<=p.y() && p.y()<=_y2);
}

/*!
  Tests if the point is inside the speified area of the rectangle
*/
bool Rect::includes(const Point2D & p, Area inArea) const
{
  TRACE;
  bool inRect=(_x1 <= p.x() && p.x() <= _x2 && _y1 <= p.y() && p.y() <= _y2);
  if(!inRect) return false;
  double xLoc=(p.x()-_x1)/(_x2-_x1);
  double yLoc=(p.y()-_y1)/(_y2-_y1);
  switch (inArea) {
  case AllRect:
    return true;
  case AboveAscDiag:
    return yLoc>xLoc;
  case BelowAscDiag:
    return yLoc<xLoc;
  case AboveDescDiag:
    return yLoc>(1.0-xLoc);
  case BelowDescDiag:
    return yLoc<(1.0-xLoc);
  }
  return false;
}

/*!
  Change rectangle size to include the new point, if already included, does nothing.
*/
void Rect::add(double x, double y)
{
  TRACE;
  if(x!=1e99 && x!=-1e99) { // Compatibility with old files... can be removed in a few years
    if((x>-std::numeric_limits<double>::infinity() && x<_x1) || _x1<=-std::numeric_limits<double>::infinity()) _x1=x;
    if((x<std::numeric_limits<double>::infinity() && x>_x2) || _x2>=std::numeric_limits<double>::infinity()) _x2=x;
  }
  if(y!=1e99 && y!=-1e99) { // Compatibility with old files... can be removed in a few years
    if((y>-std::numeric_limits<double>::infinity() && y<_y1) || _y1<=-std::numeric_limits<double>::infinity()) _y1=y;
    if((y<std::numeric_limits<double>::infinity() && y>_y2) || _y2>=std::numeric_limits<double>::infinity()) _y2=y;
  }
}

/*!
  Change rectangle size to include the new point, if already included, does nothing.
*/
void Rect::add(const Point& p)
{
  TRACE;
  add(p.x(), p.y());
}

/*!
  Change rectangle size to include the new point, if already included, does nothing.
*/
void Rect::add(const Point2D& p)
{
  TRACE;
  add(p.x(), p.y());
}

/*!
  Change rectangle size to include the new rectangle, if already included, does nothing.
*/
void Rect::add(const Rect& r)
{
  TRACE;
  add(r._x1, r._y1);
  add(r._x2, r._y2);
}

/*!
  Find intersection rect between two rects
*/
Rect Rect::intersect(const Rect& r) const
{
  TRACE;
  Rect rint;
  if(_x1<r._x1) rint._x1=r._x1; else rint._x1=_x1;
  if(_y1<r._y1) rint._y1=r._y1; else rint._y1=_y1;
  if(_x2<r._x2) rint._x2=_x2; else rint._x2=r._x2;
  if(_y2<r._y2) rint._y2=_y2; else rint._y2=r._y2;
  if(rint._x1>rint._x2) {
    rint._x2=rint._x1;
  }
  if(rint._y1>rint._y2) {
    rint._y2=rint._y1;
  }
  return rint;
}

/*!
  Tests if the segment defined by x1, y1 and x2, y2 is inside or crosses the rectangle
*/
bool Rect::includes(double x1, double y1, double x2, double y2) const
{
  TRACE;
  // One of the two points is inside the rectangle
  if((x1>_x1 && x1<_x2 && y1>_y1 && y1<_y2) ||
     (x2>_x1 && x2<_x2 && y2>_y1 && y2<_y2)) return true;
  // p1 and p2 are outside
  // Try to find one intersection with the sides of the rectangle
  if(x1!=x2) {                     // Intersections with vertical sides
    double coef=(y2-y1)/(x2-x1);
    double yi=y1+coef*(_x2-x1);
    if(_y1<=yi && yi<=_y2 &&
       ((x1<x2 && x1<=_x2 && _x2<=x2) ||
        (x1>x2 && x2<=_x2 && _x2<=x1))) return true;
    yi=y1+coef*(_x1-x1);
    if(_y1<=yi && yi<=_y2 &&
       ((x1<x2 && x1<=_x1 && _x1<=x2) ||
        (x1>x2 && x2<=_x1 && _x1<=x1))) return true;
  } else {                     // Segment is vertical, test if points are on both sides
    if(_x1<=x1 && x1<=_x2 &&
       ((y1<=y2 && y1<_y1 && y2>_y2) ||
        (y1>y2 && y2<_y1 && y1>_y2))) return true;
    else return false;
  }
  if(y1!=y2) {
    double coef=(x2-x1)/(y2-y1);
    double xi=x1+coef*(_y2-y1);
    if(_x1<=xi && xi<=_x2 &&
        ((y1<y2 && y1<=_y2 && _y2<=y2) ||
         (y1>y2 && y2<=_y2 && _y2<=y1))) return true;
    xi=x1+coef*(_y1-y1);
    if(_x1<=xi && xi<=_x2 &&
        ((y1<y2 && y1<=_y1 && _y1<=y2) ||
         (y1>y2 && y2<=_y1 && _y1<=y1))) return true;
  } else {
    if(_y1<=y1 && y1<=_y2 && 
        ((x1<=x2 && x1<_x1 && x2>_x2) ||
         (x1>x2 && x2<_x1 && x1>_x2))) return true;
    else return false;
  }
  return false;
}

/*!
  \fn bool Rect::includes (const Point2D& p1, const Point2D& p2) const
  Tests if the segment defined by p1 and p2 is inside or crosses the rectangle
*/

/*!
  Sets identical width and heigh keeping the center at the same coordinates.
  The rectangle is always enlarged to the enclosing square.
*/
void Rect::square()
{
  TRACE;
  double w=width();
  double h=height();
  if(w>h) {
    double dy=(w-h)*0.5;
    _y1-=dy;
    _y2+=dy;
  } else if(w<h) {
    double dx=(h-w)*0.5;
    _x1-=dx;
    _x2+=dx;
  } // else do nothing, already a square
}

/*!
  Enlarges rectange by \a dx and \a dy in both direction. Add or multiply according to the scale type.
  Inversed scales are considered like linear scales
*/
void Rect::enlarge(double dx, double dy, SamplingOptions xScale, SamplingOptions yScale)
{
  TRACE;
  switch(xScale) {
  case LogScale:
    _x1 *= 1.0-dx;
    _x2 *= 1.0+dx;
    break;
  case InversedScale:
    _x1=1.0/(1.0/_x1 + 1.0/dx);
    _x2=1.0/(1.0/_x2 - 1.0/dx);
    break;
  default:
    _x1-=dx;
    _x2+=dx;
    break;
  }
  switch(yScale) {
  case LogScale:
    _y1*=1.0-dy;
    _y2*=1.0+dy;
    break;
  case InversedScale:
    _y1=1.0/(1.0/_y1 + 1.0/dy);
    _y2=1.0/(1.0/_y2 - 1.0/dy);
    break;
  default:
    _y1-=dy;
    _y2+=dy;
    break;
  }
}

/*!
  Enlarges rectangle by width and height times \a ratio times in both directions.
*/
void Rect::enlarge(double ratio, SamplingOptions xScale, SamplingOptions yScale)
{
  TRACE;
  double s1, s2;
  switch(xScale) {
  case LogScale:
    if(_x1<=0) {
      _x1=1e-16;
    }
    if(_x2<=0) {
      _x2=1e-16;
    }
    s1=pow(_x2/_x1, ratio);
    _x1/=s1;
    _x2*=s1;
    break;
  case InversedScale:
    {
      double t1=1/_x1;
      double t2=1/_x2;
      s1=ratio*(t1-t2);
      _x1=1/(t1+s1);
      _x2=1/(t2-s1);
    }
    /*s1=ratio*(_x2-_x1);
    s2=_x1*_x2;
    _x1=s2/(_x2+s1);
    _x2=s2/(_x1-s1);*/
    break;
  default:
    s1=ratio*(_x2-_x1);
    _x1-=s1;
    _x2+=s1;
    break;
  }
  switch(yScale) {
  case LogScale:
    if(_y1<=0) {
      _y1=1e-16;
    }
    if(_y2<=0) {
      _y2=1e-16;
    }
    s1=pow(_y2/_y1, ratio);
    _y1/=s1;
    _y2*=s1;
    break;
  case InversedScale:
    s1=ratio*(_y2-_y1);
    s2=_y1*_y2;
    _y1=s2/(_x2+s1);
    _y2=s2/(_x1-s1);
    break;
  default:
    s1=ratio*(_y2-_y1);
    _y1 -= s1;
    _y2 += s1;
    break;
  }
}

/*!
  Finds intersection points between a rect and a segment
*/
void Rect::intersect(Point2D& p1, Point2D& p2) const
{
  TRACE;
  Rect seg(p1.x(), p1.y(), p2.x(), p2.y());
  double c,dx,dy,x,y;
  dx=p2.x()-p1.x();
  dy=p2.y()-p1.y();
  if(fabs(dx)>1e-15) {
    c=dy/dx;
    y=p1.y()+c*(_x1-p1.x());
    if((y>=_y1 && y<=_y2) && (_x1>=seg._x1 && _x1<=seg._x2 && y>=seg._y1 && y<=seg._y2)) {
      if(p1.x()<=_x1) {p1.setX(_x1);p1.setY(y);}
      else {p2.setX(_x1);p2.setY(y);}
    }
    y=p1.y()+c*(_x2-p1.x());
    if((y>=_y1 && y<=_y2) && (_x2>=seg._x1 && _x2<=seg._x2 && y>=seg._y1 && y<=seg._y2)) {
      if(p1.x()>=_x2) {p1.setX(_x2);p1.setY(y);}
      else {p2.setX(_x2);p2.setY(y);}
    }
  }
  if(fabs(dy)>1e-15) {
    c=dx/dy;
    x=p1.x()+c*(_y1-p1.y());
    if((x>=_x1 && x<=_x2) && (x>=seg._x1 && x<=seg._x2 && _y1>=seg._y1 && _y1<=seg._y2)) {
      if(p1.y()<=_y1) {p1.setX(x);p1.setY(_y1);}
      else {p2.setX(x);p2.setY(_y1);}
    }
    x=p1.x()+c*(_y2-p1.y());
    if((x>=_x1 && x<=_x2) && (x>=seg._x1 && x<=seg._x2 && _y2>=seg._y1 && _y2<=seg._y2)) {
      if(p1.y()>=_y2) {p1.setX(x);p1.setY(_y2);}
      else {p2.setX(x);p2.setY(_y2);}
    }
  }
}

void Rect::printDebug() const
{
  printf("(%lg,%lg) to (%lg,%lg)\n",_x1, _y1, _x2, _y2);
}

void Rect::xml_writeProperties(XML_WRITEPROPERTIES_ARGS) const
{
  TRACE;
  Q_UNUSED(context)
  writeProperty(s, "x",_x1);
  writeProperty(s, "y",_y1);
  writeProperty(s, "w",_x2-_x1);
  writeProperty(s, "h",_y2-_y1);
}

XMLMember Rect::xml_member(XML_MEMBER_ARGS)
{
  TRACE;
  Q_UNUSED(attributes)
  Q_UNUSED(context)
  if(tag=="x1") return XMLMember(0);
  else if(tag=="y1") return XMLMember(1);
  else if(tag=="x2") return XMLMember(2);
  else if(tag=="y2") return XMLMember(3);
  else return XMLMember(XMLMember::Unknown);
}  

bool Rect::xml_setProperty(XML_SETPROPERTY_ARGS)
{
  TRACE;
  Q_UNUSED(tag)
  Q_UNUSED(attributes)
  Q_UNUSED(context)
  double val=content.toDouble();
  switch (memberID) {
  case 0:
    _x1=val;
    return true;
  case 1:
    _y1=val;
    return true;
  case 2:
    _x2=val;
    return true;
  case 3:
    _y2=val;
    return true;
  default:
    return false;
  }
}

} // namespace QGpCoreMath
