/***************************************************************************
**
**  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 POINT_H
#define POINT_H

#include <QGpCoreTools.h>

#include "Point2D.h"
#include "QGpCoreMathDLLExport.h"

namespace QGpCoreMath {

class CurvePointOptions;
class Matrix3x3;
class Matrix4x4;

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

  double z() const {return _z;}
  void setZ(double v) {_z=v;}
  double at(int index) const;
  void setValid(bool) {}
  bool isValid() const {return true;}
  inline void average(const Point& p);

  // Copy operators
  inline Point& operator=(const Point& p);
  inline Point& operator=(const Point2D& p);
  inline void set(double xi, double yi, double zi=0.0);

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

  // Arithmetic operation
  inline void operator+=(const Point& p);
  inline void operator-=(const Point& p);
  inline void operator*=(const Point& p);
  inline void operator*=(double mul);
  inline void operator/=(const Point& p);
  inline void operator/=(double div);
  void operator*=(const Matrix3x3& transformation);
  void operator*=(const Matrix4x4& transformation);
  inline double scalarProduct(const Point& p) const;
  inline void vectorialProduct(const Point& p1,const Point& p2);
  inline void interpole(double valX, const Point& p1, const Point& p2);
  void translate(const Point& p) {operator+=(p);}

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

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

  double length() const {return ::sqrt(x()*x()+y()*y()+_z*_z);}
  double distanceTo(const Point &p) const;
  double distanceToSegment(const Point &p1, const Point &p2) const;
  double elevationTo(const Point &p) const;
  void move(double distance, const Angle& azimuth, const Angle& elevation);
  void round(double maximum);
  inline void abs();
private:
  double _z;
};

QGPCOREMATH_EXPORT uint qHash(Point key);

} // namespace QGpCoreMath

namespace QGpCoreMath {

inline Point::Point()
  : Point2D()
{
  _z=0.0;
}

inline Point::Point(double xi, double yi, double zi)
  : Point2D(xi, yi)
{
  _z=zi;
}

inline Point::Point(const QPoint& p)
  : Point2D(p)
{
  _z=0.0;
}

inline Point::Point(const QPointF& p)
  : Point2D(p)
{
  _z=0.0;
}

inline Point::Point(const Point2D& p)
  : Point2D(p)
{
  _z=0.0;
}

inline Point::Point(const Point& p)
  : Point2D(p)
{
  _z=p._z;
}

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

inline Point& Point::operator=(const Point2D& p)
{
  Point2D::operator=(p);
  _z=0.0;
  return *this;
}

inline void Point::set(double xi, double yi, double zi)
{
  Point2D::set(xi, yi);
  _z=zi;
}

inline bool Point::operator<(const Point& p) const
{
  if(x()<p.x()) {
    return true;
  } else if(x()==p.x()) {
    if(y()<p.y()) {
      return true;
    } else if(y()==p.y() && _z<p._z) {
      return true;
    }
  }
  return false;
}

inline bool Point::operator>(const Point& p) const
{
  if(x()>p.x()) {
    return true;
  } else if(x()==p.x()) {
    if(y()>p.y()) {
      return true;
    } else if(y()==p.y() && _z>p._z) {
      return true;
    }
  }
  return false;
}

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

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

inline short Point::compare (const Point& p) const
{
  int c=Point2D::compare(p);
  if(c==0) {
    if(_z < p._z) return -1;
    else if(_z > p._z) return 1;
    else return 0;
  } else {
    return c;
  }
}

inline void Point::average(const Point& p)
{
  Point2D::average(p);
  _z=0.5*(_z+p._z);
}

inline Point Point::operator+(const Point& p) const
{
  return Point(x()+p.x(), y()+p.y(), _z+p._z);
}

inline Point Point::operator-(const Point& p) const
{
  return Point(x()-p.x(), y()-p.y(), _z-p._z);
}

inline Point Point::operator-() const
{
  return Point(-x(), -y(), -_z);
}

inline Point Point::operator*(double mul) const
{
  return Point(x()*mul, y()*mul, _z*mul);
}

inline Point Point::operator*(const Point& p) const
{
  return Point (x()*p.x(), y()*p.y(), _z*p._z);
}

inline Point Point::operator/(double div) const
{
  double f=1.0/div;
  return Point (x()*f, y()*f, _z*f);
}

inline Point Point::operator/(const Point& p) const
{
  return Point (x()/p.x(), y()/p.y(), _z/p._z);
}

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

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

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

inline void Point::operator*=(double mul)
{
  Point2D::operator*=(mul);
  _z*=mul;
}

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

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

inline double Point::scalarProduct(const Point& p) const
{
  return x()*p.x()+y()*p.y()+_z*p._z;
}

inline void Point::vectorialProduct(const Point& p1,const Point& p2)
{
  setX(p1.y()*p2.z()-p2.y()*p1.z());
  setY(-p1.x()*p2.z()+p2.x()*p1.z());
  setZ(p1.x()*p2.y()-p2.x()*p1.y());
}

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


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

inline void Point::abs()
{
  Point2D::abs();
  _z=fabs(_z);
}

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

} // namespace QGpCoreMath

#endif // POINT_H
