/***************************************************************************
*                                                                          *
*  Copyright (C) Golaem S.A.  All Rights Reserved.                         *
*                                                                          *
***************************************************************************/

#pragma once

#include "glmMathBase.h"
#include "glmAssert.h"
#include "glmMemory.h"

namespace glm
{
    class RandomNumberGenerator;
    class Vector3;
    class Vector4;
    typedef float Angle;

    /**
	* \brief Class utility for Angles
	*
	* The class manipulates radian values
	* The Angle's value can be modulo'ed in two ways :
	*	\li returns the current value transposed in [-pi, pi[ (AngleUtils::getModuloValue());
	*	\li returns the current value transposed in [0, 2pi[ (AngleUtils::getPositiveModuloValue()).
	*
	* \ingroup rotations
	* \author Golaem <dev@golaem.com>
	*/
    class GLM_CORE_API AngleUtils
    {
    public:
        class Comparator
        {
        public:
            bool operator()(Angle angle1, Angle angle2)
            {
                return getPositiveModuloValue(angle1) < getPositiveModuloValue(angle2);
            }
        };

        /**
		* \brief Check wether the angle is in the interval [-pi; pi[.
		*/
        static bool isModulo(Angle angle);

        /**
		* \brief Compute the corresponding angle in [-pi, pi[.
		*/
        static void moduloSelf(Angle& angle);

        /**
		* \brief Compute the corresponding angle in [-pi, pi[.
		*/
        static Angle getModuloValue(Angle angle);

        static float getModuloValueDegree(float angle);

        /**
		* \brief Compute the corresponding angle in [0, 2pi[.
		*/
        static Angle getPositiveModuloValue(Angle angle);
        //@}

        /**
		* \brief Approximate equality operator
		* \return true if the modulo values of the angles are approximately equals
		*/
        static bool approxEqual(Angle angle1, Angle angle2, float precision = GLM_NUMERICAL_PRECISION);

        static bool approxEqual(const Vector3& v1, const Vector3& v2, float precision = GLM_NUMERICAL_PRECISION);
        static bool approxEqual(const Vector4& v1, const Vector4& v2, float precision = GLM_NUMERICAL_PRECISION);

        /**
		* \brief Approximate difference operator
		*/
        static bool approxDiff(Angle angle1, Angle angle2, float precision = GLM_NUMERICAL_PRECISION);

        static bool approxDiff(const Vector3& v1, const Vector3& v2, float precision = GLM_NUMERICAL_PRECISION);
        static bool approxDiff(const Vector4& v1, const Vector4& v2, float precision = GLM_NUMERICAL_PRECISION);

        /**
		* \brief Returns a random angle between two given angles.
		*/
        static Angle randomAngle(RandomNumberGenerator& rng, Angle lowerBound, Angle upperBound);

        /**
		* \brief Returns a random angle between 0 and 2pi.
		*/
        static Angle randomAngle(RandomNumberGenerator& rng);

        /**
		* \brief Returns angular distance between too angles (angle difference)
		*/
        static Angle distance(Angle angle1, Angle angle2);

        /**
		* \brief Convert the radian value of an angle in degrees.
		*/
        static float radianToDegree(float radianValue)
        {
            return radianValue * GLM_RAD_2_DEG;
        }
        static double radianToDegreed(double radianValue)
        {
            return radianValue * GLM_RAD_2_DEG;
        }

        /**
		* \brief Convert the degree value of an angle in radians.
		*/
        static float degreeToRadian(float degreeValue)
        {
            return degreeValue * GLM_DEG_2_RAD;
        }
        static double degreeToRadiand(double degreeValue)
        {
            return degreeValue * GLM_DEG_2_RAD;
        }
    };

    Angle GLM_CORE_API angle(const Vector3& vector1, const Vector3& vector2);
    Angle GLM_CORE_API angle(const Vector3& vector1, const Vector3& vector2, const Vector3& vectorOrientation);
    // find the nearest unit axis to the given axis (ie: will return (0,0,1) if the source is (0.1,0.3,0.95), or (0,-1,0) if the source is (0.1,-2.3f,0.3), and so on )
    Angle GLM_CORE_API findNearestUnitAxis(Vector3& nearestAxis, const Vector3& sourceAxis);
} // namespace glm
