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

#pragma once

#include "glmCoreDefinitions.h"
#include "glmMathBase.h"
#include "glmAssert.h"
#include "glmAngle.h"
#include <limits>
#include "glmMemory.h"
#include <ostream>

#if GLM_IS_COMPILER_MSVC

#ifdef min
// save min definition and remove it
#define minWasDefined
#pragma push_macro("min")
#undef min
#endif

#ifdef max
// save max definition and remove it
#define maxWasDefined
#pragma push_macro("max")
#undef max
#endif

#endif

namespace glm
{
    static const unsigned int _vector2ValuesSize = sizeof(float) * 2;

    /**
	* \brief Class representing a 2D vector.
	*
	* \ingroup types
	* \author Golaem <dev@golaem.com>
	*
	*/
    class GLM_CORE_API Vector2
    {
    public:
        GLM_CLASS_STAT_ID( glm::MemoryStatIds::GolaemShared);

        /**
		* \brief Data representing the vector
		*/
        float x;
        float y;

        /**
		* \brief Retrieve a vector having its coordinates to 0.
		*/
        static Vector2 getNullVector()
        {
            return Vector2(0.0);
        }

        /**
		* \name Construction / Destruction
		*/
        //@{
        /**
		* \brief Default constructor.
		*/
        Vector2()
#ifndef NDEBUG
            : x(FLT_MAX)
            , y(FLT_MAX)
#endif
        {
        }

        /**
		* \brief Fill constructor.
		*/
        explicit Vector2(float value)
            : x(value)
            , y(value)
        {
        }

        /**
		* \brief nullify vector
		*/
        void setNull()
        {
            x = y = 0.f;
        }

        /**
		* \brief Copy constructor.
		*/
        Vector2(const Vector2& vector)
            : x(vector.x)
            , y(vector.y)
        {
        }

        /**
		* \brief Constructor from coordinates.
		*/
        Vector2(float inX, float inY)
            : x(inX)
            , y(inY)
        {
        }

        /**
		* \brief Assignment.
		*/
        Vector2& operator=(const Vector2& vector)
        {
            x = vector.x;
            y = vector.y;

            return *this;
        }

        /**
		* \name Coordinates accessors
		*/
        //@{
        /**
		* \brief Fills the vector with zeros
		*/
        void fill(float value)
        {
            x = value;
            y = value;
        }

        /**
		* \brief operator[] (const version)
		*/
        const float& operator[](unsigned int index) const
        {
            GLM_DEBUG_ASSERT_MSG(index < 2, "This is a 2D vector, it doesn't have a #%lu index.", index);
            return *((&x) + index);
        }

        /**
		* \brief operator[]
		*/
        float& operator[](unsigned int index)
        {
            GLM_DEBUG_ASSERT_MSG(index < 2, "This is a 2D vector, it doesn't have a #%lu index.", index);
            return *((&x) + index);
        }

        /**
		* \brief access to members as a float pointer
		*/
        float* getFloatValues()
        {
            return &x;
        }

        /**
		* \brief Check if this vector is null (ie has its coordinate set to 0).
		*/
        bool isNull() const
        {
            return *this == getNullVector();
        }

        //@}

        /**
		* \name Norms
		*/
        //@{
        /**
		* \brief Computes and returns the squared norm (length) of the vector.
		*/
        float norm2() const
        {
            return (x * x + y * y);
        }

        /**
		* \brief Computes and returns the norm (length) of the vector.
		*/
        float norm() const
        {
            return sqrtf(x * x + y * y);
        }

        /**
		* \brief Normalize the current vector.
		* \param desiredNorm The desired norm for the vector (default is 1).
		*/
        void normalizeSelf(float desiredNorm = 1.f)
        {
            float very_small_float = 1.0e-037f; // from http://altdevblogaday.com/2011/08/21/practical-flt-point-tricks/, adding a very small float avoid testing if == 0
            float thisNorm2 = norm2() + very_small_float;

            float factor(desiredNorm * (1.f / sqrtf(thisNorm2))); // 1/ sqrtf must be clearly specified to compiler to enable optimisations

            x *= factor;
            y *= factor;
        }

        /**
		* \brief Compute and returns a normalized copy of this.
		* \param norm The desired norm for the vector (default is 1).
		* \return The normalized copy.
		*/
        Vector2 computeNormalize(float desiredNorm = 1.f) const
        {
            Vector2 normalizedCopy(*this);
            normalizedCopy.normalizeSelf(desiredNorm);
            return normalizedCopy;
        }

        /**
		* \brief Check if this vector is normalized.
		*/
        bool isNormalized(float precision = GLM_NORMALIZED_PRECISION_SQR) const
        {
            return approxEqual(norm2(), 1.f, precision);
        }
        //@}

        /**
		* \name Operators
		*/
        //@{
        /**
		* \brief Vector addition/affectation.
		*/
        Vector2& operator+=(const Vector2& vector)
        {
            x += vector.x;
            y += vector.y;

            return *this;
        }

        /**
		* \brief Vector subtraction/affectation.
		*/
        Vector2& operator-=(const Vector2& vector)
        {
            x -= vector.x;
            y -= vector.y;
            return *this;
        }

        /**
		* \brief Negation
		*/
        Vector2 operator-() const
        {
            Vector2 result;
            result.x = -x;
            result.y = -y;

            return result;
        }

        /**
		* \brief Scalar multiplication/affectation.
		*/
        Vector2& operator*=(float scalar)
        {
            GLM_DEBUG_ASSERT_MSG(glmFinite(scalar), "Undefined scalar");
            x *= scalar;
            y *= scalar;
            return *this;
        }

        /**
		* \brief Scalar division/affectation.
		*/
        Vector2& operator/=(float scalar)
        {
            GLM_DEBUG_ASSERT_MSG(glmFinite(scalar), "Undefined scalar");
            x /= scalar;
            y /= scalar;
            return *this;
        }
        //@}

        /**
		* \name Comparison operators
		*/
        //@{
        /**
		* \brief Comparison operator (lexicographical order).
		*/
        bool operator<(const Vector2& vector) const
        {
            if (x < vector.x)
                return true;
            if (x > vector.x)
                return false;
            if (y < vector.y)
                return true;
            if (y > vector.y)
                return false;
            return false;
        }

        /**
		* \brief Equality operator.
		*/
        bool operator==(const Vector2& vector) const
        {
            // no memcmp, float +0.0 and -0.0 are endcoded differently
            return (x == vector.x && y == vector.y);
        }

        /**
		* \brief Difference operator.
		*/
        bool operator!=(const Vector2& vector) const
        {
            // no memcmp, float +0.0 and -0.0 are endcoded differently
            return (x != vector.x || y != vector.y);
        }
        //@}

        /**
		* \name Accessors
		*/
        //@{
        /**
		* \brief Setup the coordinates of the vector.
		*/
        void setValues(float inX, float inY)
        {
            GLM_DEBUG_ASSERT_MSG(glmFinite(inX) && glmFinite(inY), "Can't setup a vector using a non finite value");
            x = inX;
            y = inY;
        }
        //@}

        /**
		* \brief 2D rotation method from angle, return result as parameter
		*/
        void rotate(Angle angle, Vector2& result) const
        {
            GLM_DEBUG_ASSERT(&result != this);

            float cosThisRotation = cosf(angle);
            float sinThisRotation = sinf(angle);

            result.x = cosThisRotation * x - sinThisRotation * y;
            result.y = sinThisRotation * x + cosThisRotation * y;
        }

        /**
		* \brief 2D rotation method from angle cosf and sinf, return result as new value
		*/
        Vector2 rotate(float angleCos, float angleSin) const
        {
            Vector2 result;

            result.x = angleCos * x - angleSin * y;
            result.y = angleSin * x + angleCos * y;

            return result;
        }

        /**
		* \brief 2D rotation method from angle, return result as new value
		*/
        Vector2 rotate(Angle angle) const
        {
            Vector2 result;

            float cosThisRotation = cosf(angle);
            float sinThisRotation = sinf(angle);

            result.x = cosThisRotation * x - sinThisRotation * y;
            result.y = sinThisRotation * x + cosThisRotation * y;

            return result;
        }

        /**
         * \brief 2D 90 deg rotation method, return result as new value
         */
        void rotate90(Vector2& result) const
        {
            GLM_DEBUG_ASSERT(&result != this);

            result.x = -y;
            result.y = x;
        }

        /**
		* \brief 2D rotation method, rotate vector itself
		*/
        void rotateSelf(Angle angle)
        {
            float cosThisRotation = cosf(angle);
            float sinThisRotation = sinf(angle);

            float newValue0 = cosThisRotation * x - sinThisRotation * y;
            y = sinThisRotation * x + cosThisRotation * y;
            x = newValue0;
        }

        /**
		* \brief 2D rotation method, rotate vector itself
		*/
        void rotateSelf(float cosAngle, float sinAngle)
        {
            float newValue0 = cosAngle * x - sinAngle * y;
            y = sinAngle * x + cosAngle * y;
            x = newValue0;
        }

        /**
         * \brief 2D 90 deg rotation method, rotate vector itself
         */
        void rotateSelf90()
        {
            float newValue0 = -y;
            y = x;
            x = newValue0;
        }

        /**
		* \name Polar coordinates
		*/
        //@{
        /**
		* \brief Retrieve the angle of the vector.
		* \warning This method computes the angle when called.
		*/
        Angle getAngle() const
        {
            return atan2f(y, x);
        }
        //@}
    };

    // Lexicographic order comparison
    bool lexicoGraphicCompare(const Vector2& vector1, const Vector2& vector2);
    GLM_CORE_API bool areCollinear(const Vector2& vector1, const Vector2& vector2, float precision = GLM_NUMERICAL_PRECISION);
    // compute the barycentric coordinates of a given vector with respect to the triangle <\p v0,\p v1,\p v2>.
    // vector = b0*V0 + b1*V1 + b2*V2, where b0 + b1 + b2 = 1.
    GLM_CORE_API void computeBarycentricsCoordinates(const Vector2& vector, const Vector2& v0, const Vector2& v1, const Vector2& v2, float barycentricCoordinates[3]);

    //-----------------------------------------------------------------------------
    inline Vector2 vector2FromPolar(Angle angle, float norm = 1.f)
    {
        return Vector2(cosf(angle) * norm, sinf(angle) * norm);
    }

    //-----------------------------------------------------------------------------
    inline bool approxEqual(const Vector2& v1, const Vector2& v2, float precision)
    {
        return approxEqual(v1.x, v2.x, precision) && approxEqual(v1.y, v2.y, precision);
    }

    //-----------------------------------------------------------------------------
    inline bool approxDiff(const Vector2& v1, const Vector2& v2, float precision)
    {
        return approxDiff(v1.x, v2.x, precision) || approxDiff(v1.y, v2.y, precision);
    }

    //-----------------------------------------------------------------------------
    inline Vector2 operator+(const Vector2& vector1, const Vector2& vector2)
    {
        return Vector2(vector1.x + vector2.x, vector1.y + vector2.y);
    }
    inline Vector2 operator-(const Vector2& vector1, const Vector2& vector2)
    {
        return Vector2(vector1.x - vector2.x, vector1.y - vector2.y);
    }
    inline Vector2 operator*(const Vector2& vector, const float& scalar)
    {
        return Vector2(vector.x * scalar, vector.y * scalar);
    }
    inline Vector2 operator*(const float& scalar, const Vector2& vector)
    {
        return Vector2(vector.x * scalar, vector.y * scalar);
    }
    inline Vector2 operator/(const Vector2& vector, const float& scalar)
    {
        return Vector2(vector.x / scalar, vector.y / scalar);
    }
    inline float operator*(const Vector2& vector1, const Vector2& vector2)
    {
        return (vector1.x * vector2.x + vector1.y * vector2.y);
    }
    inline float dot(const Vector2& vector1, const Vector2& vector2)
    {
        return (vector1.x * vector2.x + vector1.y * vector2.y);
    }

    //-----------------------------------------------------------------------------
    // compute the determinant of the given vectors.
    // geometrically, the determinant can be interpreted as the parallelogram defined by the two vectors.
    // if the area is positive the angle between vector1 and vector2 is positive (ie counterclockwise).
    inline float det(const Vector2& vector1, const Vector2& vector2)
    {
        return vector1.x * vector2.y - vector1.y * vector2.x;
    }

    //-----------------------------------------------------------------------------
    // compute the signed area of a parallelogram defined by an apex and two vectors.
    // if the area is positive, the angle between vector1 and vector2 around apex is positive (ie counterclockwise).
    // in other words, if the area is positive, vector2 is on the left of the oriented line segment from apex to vector1.
    inline float signedArea(const Vector2& apex, const Vector2& vector1, const Vector2& vector2)
    {
        return (vector1.x - apex.x) * (vector2.y - apex.y) - (vector2.x - apex.x) * (vector1.y - apex.y);
    }

    //-----------------------------------------------------------------------------
    inline std::ostream& operator<<(std::ostream& out, const Vector2& vector)
    {
        out << "(" << vector.x << "," << vector.y << ")";
        return out;
    }

    //-----------------------------------------------------------------------------
    inline float max(const Vector2& vector)
    {
        return max(vector.x, vector.y);
    }

    //-----------------------------------------------------------------------------
    inline Angle angle(const Vector2& vector1, const Vector2& vector2)
    {
        Angle angleBetween = vector2.getAngle() - vector1.getAngle();
        AngleUtils::moduloSelf(angleBetween);
        return angleBetween;
    }
} // namespace glm

#if GLM_IS_COMPILER_MSVC

#ifdef minWasDefined
// restore min definition
#pragma pop_macro("min")
#endif

#ifdef maxWasDefined
// restore max definition
#pragma pop_macro("max")
#endif

#endif
