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

#ifndef POINT2D_H
#define POINT2D_H

#include <QGpCoreTools.h>

#include "Angle.h"
#include "QGpCoreMathDLLExport.h"

namespace QGpCoreMath {

class CurvePointOptions;
class Matrix2x2;
class UtmZone;

class QGPCOREMATH_EXPORT Point2D
{
public:
  inline Point2D();
  inline Point2D(double x, double y);
  inline Point2D(const QPoint& p);
  inline Point2D(const QPointF& p);
  inline Point2D(const Point2D& p);
  Point2D(const QVector<double>& p);

  double x() const {return _x;}
  double y() const {return _y;}
  double y(const CurvePointOptions *) const {return _y;}
  void setX(double v) {_x=v;}
  void setY(double v) {_y=v;}
  void setY(double v, const CurvePointOptions *) {_y=v;}
  double at(int index) const;
  void setValid(bool) {}
  bool isValid() const {return true;}
  void average(const Point2D& p) {_y= 0.5*(_y+p._y);}

  void degreesToDMS();
  void DMSToDegrees();
  void degreesToDM();
  void DMToDegrees();
  void geographicalToRectangular(const Point2D& ref, double rotation=0.0);
  void rectangularToGeographical(const Point2D& ref, double rotation=0.0);
  void geographicalToUtm(UtmZone& zone);
  void utmToGeographical(const UtmZone& zone);

  // Copy operators
  inline Point2D& operator=(const Point2D& p);
  inline void set(double xi, double yi);
  inline QPointF pointF() const {return QPointF(_x, _y);}

  // Comparison operators
  inline bool operator<(const Point2D& p) const;
  inline bool operator==(const Point2D& p) const;
  inline bool operator!=(const Point2D& obj) const;
  inline short compare (const Point2D& p) const;
  inline bool isSimilar(const Point2D& p, double tolerance) const;

  // I/O functions
  bool fromString(const StringSection& str);
  QString toString(int precision=6, char format='g') const;

  // Arithmetic operation
  inline void operator+=(const Point2D& p);
  inline void operator-=(const Point2D& p);
  inline void operator*=(const Point2D& p);
  inline void operator*=(double mul);
  inline void operator/=(const Point2D& p);
  inline void operator/=(double div);
  void operator*=(const Matrix2x2& transformation);
  inline double scalarProduct(const Point2D& p) const;
  inline void interpole(double valX, const Point2D& p1, const Point2D& p2);
  void translate(const Point2D& p) {operator+=(p);}
  void rotate(const Angle& a);

  inline Point2D operator+(const Point2D& p) const;
  inline Point2D operator-(const Point2D& p) const;
  inline Point2D operator-() const;
  inline Point2D operator*(double mul) const;
  inline Point2D operator*(const Point2D& p) const;
  inline Point2D operator/(double div) const;
  inline Point2D operator/(const Point2D& p) const;

  // Miscellaneous
  bool isHit(const Point2D &p,double tolX, double tolY) const;
  double length() const {return ::sqrt(_x*_x+_y*_y);}
  double distanceTo(const Point2D &p) const;
  double squareDistanceTo(const Point2D &p) const;
  double distanceToSegment(const Point2D &p1, const Point2D &p2) const;
  double azimuth() const;
  double azimuthTo(const Point2D &p) const;
  double geographicalDistanceTo(const Point2D &p) const;
  double geographicalAzimuthTo(const Point2D &p) const;
  void move(double distance, const Angle& azimuth);
  void round(double maximum=1.0);
  inline double abs2() const;
  inline void abs();
  inline void yLog10();
  inline void yExp10();
private:
  static double utmM(double phi);
  // Coordinates of the point
  double _x, _y;
};

QGPCOREMATH_EXPORT uint qHash(Point2D key);

} // namespace QGpCoreMath

namespace QGpCoreMath {

inline Point2D::Point2D()
{
  _x=0.;
  _y=0.;
}

inline Point2D::Point2D(double xi, double yi)
{
  _x=xi;
  _y=yi;
}

inline Point2D::Point2D(const QPoint& p)
{
  _x=p.x();
  _y=p.y();
}

inline Point2D::Point2D(const QPointF& p)
{
  _x=p.x();
  _y=p.y();
}

inline Point2D::Point2D(const Point2D& p)
{
  _x=p.x();
  _y=p.y();
}

inline Point2D& Point2D::operator= (const Point2D& p)
{
  _x=p._x;
  _y=p._y;
  return *this;
}

inline void Point2D::set(double xi, double yi)
{
  _x=xi;
  _y=yi;
}

inline bool Point2D::operator<(const Point2D& p) const
{
  if(_x<p._x) {
    return true;
  } else if(_x==p._x && _y<p._y) {
    return true;
  }
  return false;
}

inline bool Point2D::operator==(const Point2D& p) const
{
  return _x==p._x && _y==p._y;;
}

inline bool Point2D::operator!=(const Point2D& p) const
{
  return _x!=p._x || _y!=p._y;;
}

inline short Point2D::compare (const Point2D& p) const
{
  if(_x<p._x) return(-1);
  else if(_x>p._x) return(1);
  else if(_y<p._y) return(-1);
  else if(_y>p._y) return(1);
  else return(0);
}

inline Point2D Point2D::operator+(const Point2D& p) const
{
  return Point2D(_x+p._x, _y+p._y);
}

inline Point2D Point2D::operator-(const Point2D& p) const
{
  return Point2D(_x-p._x, _y-p._y);
}

inline Point2D Point2D::operator-() const
{
  return Point2D(-_x, -_y);
}

inline Point2D Point2D::operator*(double mul) const
{
  return Point2D(_x*mul, _y*mul);
}

inline Point2D Point2D::operator*(const Point2D& p) const
{
  return Point2D(_x*p._x, _y*p._y);
}

inline Point2D Point2D::operator/(double div) const
{
  double f=1.0/div;
  return Point2D(_x*f, _y*f);
}

inline Point2D Point2D::operator/(const Point2D& p) const
{
  return Point2D(_x/p._x, _y/p._y);
}

inline void Point2D::operator+=(const Point2D& p)
{
  _x+=p._x;
  _y+=p._y;
}

inline void Point2D::operator-=(const Point2D& p)
{
  _x-=p._x;
  _y-=p._y;
}

inline void Point2D::operator*=(const Point2D& p)
{
  _x*=p._x;
  _y*=p._y;
}

inline void Point2D::operator*=(double mul)
{
  _x*=mul;
  _y*=mul;
}

inline void Point2D::operator/=(const Point2D& p)
{
  _x/=p._x;
  _y/=p._y;
}

inline void Point2D::operator/=(double div)
{
  double f=1.0/div;
  _x*=f;
  _y*=f;
}

inline double Point2D::scalarProduct(const Point2D& p) const
{
  return _x*p._x+_y*p._y;
}

inline void Point2D::interpole(double valX, const Point2D& p1, const Point2D& p2)
{
  double factor=(valX - p1.x())/(p2.x()-p1.x());
  setX(valX);
  setY(p1.y()+ (p2.y()-p1.y()) * factor);
}

inline bool Point2D::isSimilar(const Point2D& p, double tolerance) const
{
  return fabs(p.x()-x())<tolerance &&
         fabs(p.y()-y())<tolerance;
}

inline void Point2D::abs()
{
  _x=fabs(_x);
  _y=fabs(_y);
}

inline double Point2D::abs2() const
{
  return _x*_x+_y*_y;
}

inline void Point2D::yLog10()
{
  _y=::log10(_y);
}

inline void Point2D::yExp10()
{
  _y=::pow(10.0, _y);
}

QGPCOREMATH_EXPORT QTextStream& operator<< (QTextStream& s, const QList<Point2D>& plist);
QGPCOREMATH_EXPORT QTextStream& operator>> (QTextStream& s, QList<Point2D>& plist);
QGPCOREMATH_EXPORT QTextStream& operator<< (QTextStream& s, const Point2D& p);
QGPCOREMATH_EXPORT QTextStream& operator>> (QTextStream& s, Point2D& p);
QGPCOREMATH_EXPORT QDataStream& operator<< (QDataStream& s, const Point2D& p);
QGPCOREMATH_EXPORT QDataStream& operator>> (QDataStream& s, Point2D& p);

} // namespace QGpCoreMath

#endif // POINT2D.H
