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

#include <math.h>

#include "Segment2D.h"

namespace QGpCoreMath {

/*!
  \class Segment2D Segment2D.h
  \brief A 2D segment made of two points

  Full description of class still missing
*/

Segment2D::Segment2D(const Segment2D& o)
{
  TRACE;
  _p1=o._p1;
  _p2=o._p2;
  _limits=o._limits;
}

void Segment2D::set(const Point2D& p1, const Point2D& p2)
{
  TRACE;
  _p1=p1;
  _p2=p2;
  _limits.setLimits(_p1.x(), _p1.y(), _p2.x(), _p2.y());
}

void Segment2D::set(double x1, double y1, double x2, double y2)
{
  TRACE;
  _p1.setX(x1);
  _p1.setY(y1);
  _p2.setX(x2);
  _p2.setY(y2);
  _limits.setLimits(_p1.x(), _p1.y(), _p2.x(), _p2.y());
}

/*!
  Returns true if this segment intersects segment \a seg. The intersection point is returned in \a p.
*/
bool Segment2D::intersects(const Segment2D& seg, Point2D& p) const
{
  TRACE;
  double dx1=_p2.x()-_p1.x();
  double dy1=_p2.y()-_p1.y();
  double dx2=seg._p2.x()-seg._p1.x();
  double dy2=seg._p2.y()-seg._p1.y();
  double fac=dx2*dy1-dx1*dy2;
  if(fabs(fac)<1e-20) return false; // parallel segments
  // Horizontal or vertical lines are handled separately to avoid precision errors
  if(dx1==0.0) {
    p.setX(_p1.x());
    p.setY(seg._p1.y()+dy2/dx2*(p.x()-seg._p1.x()));
  } else if(dx2==0.0) {
    p.setX(seg._p1.x());
    p.setY(_p1.y()+dy1/dx1*(p.x()-_p1.x()));
  } else if(dy1==0.0) {
    p.setY(_p1.y());
    p.setX(seg._p1.x()+dx2/dy2*(p.y()-seg._p1.y()));
  } else if(dy2==0.0) {
    p.setY(seg._p1.y());
    p.setX(_p1.x()+dx1/dy1*(p.y()-_p1.y()));
  } else {
    double tmp=dx1*dx2*(seg._p1.y()-_p1.y())+dx2*dy1*_p1.x()-dx1*dy2*seg._p1.x();
    p.setX(tmp/fac);
    if(fabs(dx1)>fabs(dx2)) {
      p.setY(_p1.y()+dy1/dx1*(p.x()-_p1.x()));
    } else {
      p.setY(seg._p1.y()+dy2/dx2*(p.x()-seg._p1.x()));
    }
  }
  if(_limits.includes(p) && seg._limits.includes(p)) return true;
  else return false;
}

/*!
  Returns true if \a p is located on the segment (1e-10 precision)
*/
bool Segment2D::includes(const Point2D& p, double precision) const
{
  TRACE;
  if(!_limits.includes(p)) return false;
  double dx=_p2.x()-_p1.x();
  double dy=_p2.y()-_p1.y();
  if(fabs(dx)>fabs(dy)) {
    if(fabs(p.y()-(_p1.y()+dy/dx*(p.x()-_p1.x())))<precision) {
      return true;
    } else {
      return false;
    }
  } else {
    if(fabs(p.x()-(_p1.x()+dx/dy*(p.y()-_p1.y())))<precision) {
      return true;
    } else {
      return false;
    }
  }
}

double Segment2D::distanceTo(const Point2D& p) const
{
  TRACE;
  double dx1=_p2.x()-_p1.x();
  double dy1=_p2.y()-_p1.y();
  double dx2=dy1;
  double dy2=-dx1;
  double fac=dx2*dy1-dx1*dy2;
  Point2D pi;
  pi.setX(dx1*dx2*(p.y()-_p1.y())+dx2*dy1*_p1.x()-dx1*dy2*p.x());
  pi.setX(pi.x()/fac);
  if(fabs(dx1)>fabs(dx2))
    pi.setY(_p1.y()+dy1/dx1*(pi.x()-_p1.x()));
  else
    pi.setY(p.y()+dy2/dx2*(pi.x()-p.x()));
  if(_limits.includes(pi)) {
    return p.distanceTo(pi);
  }
  else {
    double d1=p.distanceTo(_p1);
    double d2=p.distanceTo(_p2);
    if(d1<d2) return d1; else return d2;
  }
}

Point2D Segment2D::pointAtFrom1(double distance)
{
  TRACE;
  double ratio=distance/length();
  return _p1*(1.0-ratio)+_p2*ratio;
}

Point2D Segment2D::pointAtFrom2(double distance)
{
  TRACE;
  double ratio=distance/length();
  return _p1*ratio+_p2*(1.0-ratio);
}

Segment2D Segment2D::rotated(const Point2D& center, const Angle& a) const
{
  TRACE;
  Point2D p1(_p1), p2(_p2);
  p1-=center;
  p2-=center;
  p1.rotate(a);
  p2.rotate(a);
  p1+=center;
  p2+=center;
  return Segment2D(p1, p2);
}

} // namespace QGpCoreMath
