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

#ifndef Angle_H
#define Angle_H

#include <math.h>

#include "QGpCoreMathDLLExport.h"

namespace QGpCoreMath {

  class Point2D;

  class QGPCOREMATH_EXPORT Angle
  {
  public:
    inline Angle();
    Angle(double dx, double dy) {set(dx, dy);}
    inline Angle(double dx, double dy, double r) {set(dx,dy,r);}
    ~Angle() {}

    inline Angle& operator=(const Angle& o);
    bool operator<(const Angle& o) const {return _rad<o._rad;}
    bool operator==(const Angle& o) const {return _rad==o._rad;}
    void operator+=(const Angle& o);
    bool isNull() const {return _rad==0.0;}

    inline void set(double dx, double dy);
    inline void set(double dx, double dy, double r);
    void setDegreeAzimuth(double azimuth) {setDegrees(90.0-azimuth);}
    void setRadianAzimuth(double azimuth) {setRadians(0.5*M_PI-azimuth);}
    inline void setDegrees(double degrees);
    inline void setRadians(double radians);

    inline void initDegrees();
    inline void initRadians();

    double degrees() const {return _deg;}
    double radians() const {return _rad;}
    double cos() const {return _cos;}
    double sin() const {return _sin;}
    double tan() const {return _sin/_cos;}
    double cotan() const {return _cos/_sin;}
    inline void chSign();
    inline void mirror();
    void normalize();

    static double degreesToDMS(double angle);
    static double DMSToDegrees(double angle);
    static double degreesToDM(double angle);
    static double DMToDegrees(double angle);
    static inline double radiansToDegrees(double angle);
    static inline double degreesToRadians(double angle);
    static inline double geographicToMath(double angle);
    static inline double mathToGeographic(double angle);
    static double aroundZero(double angle);

    static double canonicalRadians(double angle);
    static double canonicalDegrees(double angle);
    static double differenceRadians(double a1, double a2);
    static double differenceDegrees(double a1, double a2);
  private:
    double _deg;
    double _rad;
    double _cos;
    double _sin;
  };

  inline double Angle::radiansToDegrees(double angle)
  {
    return angle*180.0/M_PI;
  }

  inline double Angle::degreesToRadians(double angle)
  {
    return angle*M_PI/180.0;
  }

  inline double Angle::geographicToMath(double angle)
  {
    angle=degreesToRadians(angle);
    return angle<=0.5*M_PI ? 0.5*M_PI-angle : 2.5*M_PI-angle;
  }

  inline double Angle::mathToGeographic(double angle)
  {
    angle=radiansToDegrees(angle);
    return angle<=90.0 ? 90.0-angle : 450.0-angle;
  }

  inline Angle::Angle()
  {
    _deg=0.0;
    _rad=0.0;
    _cos=1.0;
    _sin=0.0;
  }

  inline void Angle::set(double dx, double dy)
  {
    _deg=0.0;
    _rad=0.0;
    double r=::sqrt(dx*dx+dy*dy);
    if(r>0.0) {
      _cos=dx/r;
      _sin=dy/r;
    } else {
      _cos=1.0;
      _sin=0.0;
    }
  }

  inline void Angle::set(double dx, double dy, double r)
  {
    _deg=0.0;
    _rad=0.0;
    if(r>0.0) {
      _cos=dx/r;
      _sin=dy/r;
    } else {
      _cos=1.0;
      _sin=0.0;
    }
  }

  inline Angle& Angle::operator=(const Angle& o)
  {
    _deg=o._deg;
    _rad=o._rad;
    _cos=o._cos;
    _sin=o._sin;
    return *this;
  }

  inline void Angle::initDegrees()
  {
    _deg=radiansToDegrees(_rad);
  }

  inline void Angle::initRadians()
  {
    _rad=::atan2(_sin, _cos);
  }

  inline void Angle::setDegrees(double degrees)
  {
    _deg=degrees;
    _rad=degreesToRadians(_deg);
    _cos=::cos(_rad);
    _sin=::sin(_rad);
  }

  inline void Angle::setRadians(double radians)
  {
    _rad=radians;
    _cos=::cos(_rad);
    _sin=::sin(_rad);
  }

  inline void Angle::chSign()
  {
    _deg=-_deg;
    _rad=-_rad;
    _sin=-_sin;
  }

  inline void Angle::mirror()
  {
    _cos=-_cos;
    _sin=-_sin;
    if(_deg>180.0) _deg-=180; else _deg+=180.0;
    if(_rad>M_PI) _rad-=M_PI; else _rad+=M_PI;
  }

} // namespace QGpCoreMath

#endif // ANGLE_H
