/***************************************************************************
**
**  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 "Point.h"
#include "Point2D.h"
#include "DoubleMatrix.h"

namespace QGpCoreMath {

  /*!
    \class Point Point.h

    Basic properties of a 3D point (coordinates in double precision)
  */

  /*!
    \fn Point::Point()
    Default constructor
  */

  /*!
    \fn Point::Point(double x, double y, double z)
    Constructor setting \a x and \a y, and optionally \a z
  */

  /*!
    \fn Point::Point(const Point2D& p)
    Copy constructor from Point2D
  */

  /*!
    Copy constructor from a vector
  */
  Point::Point(const QVector<double>& p)
   : Point2D(p)
  {
    ASSERT(p.count()>2);
    _z=p.at(2);
  }

  /*!
    \fn void Point::interpole(double valX, const Point& p1, const Point& p2)
    Interpole between two points
  */

  /*!
    \fn void Point::setValid(bool v)
    Does nothing. Used by Curve.
  */

  /*!
    \fn bool Point::isValid() const
    Return always true. Used by Curve.
  */

  /*!
    Returns the point as a string with space separation between coordinates.
    \a precision is the number of significant digits. \a format is 'g' or 'f'.
  */
  QString Point::toString(int precision, char format) const
  {
    TRACE;
    QString tmp;
    tmp+=QString::number(x(), format, precision);
    tmp+=" ";
    tmp+=QString::number(y(), format, precision);
    tmp+=" ";
    tmp+=QString::number(_z, format, precision);
    return tmp;
  }

  bool Point::fromString(const StringSection& str)
  {
    TRACE;
    const QChar * ptr=0;
    StringSection f;
    f=str.nextField(ptr);
    if(f.isValid()) setX(f.toDouble()); else return false;
    f=str.nextField(ptr);
    if(f.isValid()) setY(f.toDouble()); else return false;
    f=str.nextField(ptr);
    if(f.isValid()) _z=f.toDouble(); else _z=0.0;
    return true;
  }

  /*!
    Calculates distance to \a p.

    \sa azimuthTo(), polarAngleTo()
  */
  double Point::distanceTo(const Point &p) const
  {
    double tmp=x()-p.x();
    double dist=tmp*tmp;
    tmp=y()-p.y();
    dist+=tmp*tmp;
    tmp=_z-p.z();
    dist+=tmp*tmp;
    return ::sqrt(dist);
  }

  /*!
    Calculates the angle from horizontal plane (Z axis). This is the third spherical coordinate.

    \sa distanceTo(), azimuthTo()
  */
  double Point::elevationTo(const Point &p) const
  {
    double tmp=x()-p.x();
    double dist=tmp*tmp;
    tmp=y()-p.y();
    dist+=tmp*tmp;
    return atan2(p.z()-_z, ::sqrt(dist));
  }

  /*!
    Moves this point by \a distance in direction \a azimuth (radians, mathematical sense, 0 is east) and
    \a elevation (radians, mathematical sense from horizontal plane).
  */
  void Point::move(double distance, const Angle& azimuth, const Angle& elevation)
  {
    Point2D::move(distance*elevation.cos(), azimuth);
    _z+=distance*elevation.sin();
  }

  QTextStream& operator<< (QTextStream& s, const QList<Point>& plist)
  {
    TRACE;
    s << "%x      y      z"<< Qt::endl;
    QList<Point>::const_iterator it;
    for(it=plist.begin();it!=plist.end();++it) s << *it << Qt::endl;
    return s;
  }

  QTextStream& operator>> (QTextStream& s, QList<Point>& plist)
  {
    TRACE;
    QString str;
    plist.clear();
    while(!s.atEnd()) {
      str=s.readLine();
      while(str[0]!='%') str=s.readLine();
      QTextStream currentLine(&str, QIODevice::ReadOnly);
      Point * p=new Point;
      currentLine >> *p;
      plist.append(*p);
    }
    return s;
  }

  QTextStream& operator<< (QTextStream& s, const Point& p)
  {
    TRACE;
    s.setRealNumberNotation(QTextStream::SmartNotation);
    s.setRealNumberPrecision(20);
    s << p.x() << " " << p.y() << " " << p.z();
    return s;
  }

  QTextStream& operator>> (QTextStream& s, Point& p)
  {
    TRACE;
    double x, y, z;
    s >> x >> y >> z;
    p.setX(x);
    p.setY(y);
    p.setZ(z);
    return s;
  }

  QDataStream& operator<< (QDataStream& s, const Point& p)
  {
    TRACE;
    s << p.x() << p.y() << p.z();
    return s;
  }

  QDataStream& operator>> (QDataStream& s, Point& p)
  {
    TRACE;
    double x, y, z;
    s >> x >> y >> z;
    p.setX(x);
    p.setY(y);
    p.setZ(z);
    return s;
  }

  void Point::operator*=(const Matrix3x3& transformation)
  {
    *this=transformation*(*this);
  }

  void Point::operator*=(const Matrix4x4& transformation)
  {
    *this=transformation*(*this);
  }

  /*!
    Round so that the difference between two distinct values is less
    than \a maximum .
  */
  void Point::round(double maximum)
  {
    _z=Number::round(_z, maximum);
    Point2D::round(maximum);
  }

  double Point::at(int index) const
  {
    switch(index) {
    case 1:
      return y();
    case 2:
      return _z;
    default:
      return x();
    }
  }


} // namespace QGpCoreMath
