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

#pragma once

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

namespace glm
{
    class Matrix4;

    static const unsigned int _vector4ValuesSize = sizeof(float) * 4;

    /**
	* \brief Class representing a 4D vector.
	*
	* \ingroup types
	* \author Golaem <dev@golaem.com>
	*
	* \tparam float Manipulated scalar type (double, float...)
	* \tparam dimension The dimension of the vector
	*/
#if GLM_IS_COMPILER_MSVC && (defined(_M_X64) || defined(_M_IA64))
    class GLM_CORE_API GLM_ALIGN_16 Vector4
#else
    class GLM_CORE_API /*GLM_ALIGN_16*/ Vector4
#endif
    {
    public:
        GLM_CLASS_STAT_ID( glm::MemoryStatIds::GolaemShared);

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

        /**
		* \brief Retrieve a vector having its coordinates to 0.
		*/
        static const Vector4& getNullVector();

        inline Vector4();
        inline explicit Vector4(float value);
        inline Vector4(const Vector4& vector);
        inline Vector4(float inX, float inY, float inZ, float inW);
        inline explicit Vector4(const float* inFloatArray);

        inline Vector4& operator=(const Vector4& vector);

        inline void fill(float value);

        inline const float& operator[](unsigned int index) const;

        inline float& operator[](unsigned int index);

        inline float* getFloatValues();
        inline const float* getFloatValues() const;

        inline bool isNull() const;
        inline float norm2() const;

        inline float norm() const;

        inline void normalizeSelf(float desiredNorm = 1.f);

        inline Vector4 computeNormalize(float desiredNorm = 1.f) const;

        inline bool isNormalized(float precision = GLM_NORMALIZED_PRECISION_SQR) const;

        inline Vector4& operator+=(const Vector4& vector);

        inline Vector4& operator-=(const Vector4& vector);

        inline Vector4 operator-() const;
        inline Vector4& operator*=(float scalar);

        inline Vector4& operator/=(float scalar);
        inline bool operator<(const Vector4& vector) const;

        inline bool operator==(const Vector4& vector) const;
        inline bool operator!=(const Vector4& vector) const;
        inline void setValues(float inX, float inY, float inZ, float inW);

        inline void setValues(const float* values);
    };

    //-----------------------------------------------------------------------------
    inline Vector4::Vector4()
#ifndef NDEBUG
        : x(std::numeric_limits<float>::infinity())
        , y(std::numeric_limits<float>::infinity())
        , z(std::numeric_limits<float>::infinity())
        , w(std::numeric_limits<float>::infinity())
#endif
    {
    }

    //-----------------------------------------------------------------------------
    inline Vector4::Vector4(float value)
        : x(value)
        , y(value)
        , z(value)
        , w(value)
    {
    }

    //-----------------------------------------------------------------------------
    inline Vector4::Vector4(const Vector4& vector)
    {
        memcpy(&x, &(vector.x), _vector4ValuesSize);
    }

    //-----------------------------------------------------------------------------
    inline Vector4::Vector4(float inX, float inY, float inZ, float inW)
        : x(inX)
        , y(inY)
        , z(inZ)
        , w(inW)
    {
    }

    //-----------------------------------------------------------------------------
    inline Vector4::Vector4(const float* inFloatArray)
    {
        memcpy(&x, inFloatArray, _vector4ValuesSize);
    }

    //-----------------------------------------------------------------------------
    inline Vector4& Vector4::operator=(const Vector4& vector)
    {
        GLM_DEBUG_ASSERT_MSG(this != &vector, "Vector assignment called with non finite value");
        memcpy(&x, &(vector.x), _vector4ValuesSize);

        return *this;
    }
    //-----------------------------------------------------------------------------
    inline void Vector4::fill(float value)
    {
        x = y = z = w = value;
    }

    //-----------------------------------------------------------------------------
    inline const float& Vector4::operator[](unsigned int index) const
    {
        GLM_DEBUG_ASSERT_MSG(index < 4, "This is a 3d vector, it doesn't have a #%lu index.", index);
        return *((&x) + index);
    }

    //-----------------------------------------------------------------------------
    inline float& Vector4::operator[](unsigned int index)
    {
        GLM_DEBUG_ASSERT_MSG(index < 4, "This is a 3d vector, it doesn't have a #%lu index.", index);
        return *((&x) + index);
    }

    //-----------------------------------------------------------------------------
    inline float* Vector4::getFloatValues()
    {
        return &x;
    }

    //-----------------------------------------------------------------------------
    inline const float* Vector4::getFloatValues() const
    {
        return &x;
    }

    //-----------------------------------------------------------------------------
    inline bool Vector4::isNull() const
    {
        return (x == 0 && y == 0 && z == 0 && w == 0);
    }

    //-----------------------------------------------------------------------------
    inline float Vector4::norm2() const
    {
        return (x * x + y * y + z * z + w * w);
    }

    //-----------------------------------------------------------------------------
    inline float Vector4::norm() const
    {
        return sqrtf(x * x + y * y + z * z + w * w);
    }

    //-----------------------------------------------------------------------------
    inline void Vector4::normalizeSelf(float desiredNorm)
    {
        // note : if mostly used a float specialization based on
        // http://assemblyrequired.crashworks.org/2009/10/20/square-roots-in-vivo-normalizing-vectors/
        // adapted for 4D vectors could be much more efficient
        // currently, we mainly use doubles, so this is not useful

        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 / sqrtf(thisNorm2));
        x *= factor;
        y *= factor;
        z *= factor;
        w *= factor;
    }

    //-----------------------------------------------------------------------------
    inline Vector4 Vector4::computeNormalize(float desiredNorm) const
    {
        Vector4 normalizedCopy(*this);
        normalizedCopy.normalizeSelf(desiredNorm);
        return normalizedCopy;
    }

    //-----------------------------------------------------------------------------
    inline bool Vector4::isNormalized(float precision) const
    {
        return approxEqual(norm2(), 1.f, precision);
    }

    //@}
    //-----------------------------------------------------------------------------
    inline Vector4& Vector4::operator+=(const Vector4& vector)
    {
        x += vector.x;
        y += vector.y;
        z += vector.z;
        w += vector.w;
        return *this;
    }

    //-----------------------------------------------------------------------------
    inline Vector4& Vector4::operator-=(const Vector4& vector)
    {
        x -= vector.x;
        y -= vector.y;
        z -= vector.z;
        w -= vector.w;
        return *this;
    }

    //-----------------------------------------------------------------------------
    inline Vector4 Vector4::operator-() const
    {
        return Vector4(-x, -y, -z, -w);
    }

    //-----------------------------------------------------------------------------
    inline Vector4& Vector4::operator*=(float scalar)
    {
        GLM_DEBUG_ASSERT_MSG(glmFinite(scalar), "Undefined scalar");
        x *= scalar;
        y *= scalar;
        z *= scalar;
        w *= scalar;
        return *this;
    }

    //-----------------------------------------------------------------------------
    inline Vector4& Vector4::operator/=(float scalar)
    {
        GLM_DEBUG_ASSERT_MSG(glmFinite(scalar), "Undefined scalar");
        x /= scalar;
        y /= scalar;
        z /= scalar;
        w /= scalar;
        return *this;
    }

    //-----------------------------------------------------------------------------
    inline bool Vector4::operator<(const Vector4& 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;
        if (z < vector.z)
            return true;
        if (z > vector.z)
            return false;
        if (w < vector.w)
            return true;
        if (w > vector.w)
            return false;

        return false;
    }

    //-----------------------------------------------------------------------------
    inline bool Vector4::operator==(const Vector4& vector) const
    {
        // no memcmp, flaot +0.0 and -0.0 are endcoded differently
        return x == vector.x && y == vector.y && z == vector.z && w == vector.w;
    }

    //-----------------------------------------------------------------------------
    inline bool Vector4::operator!=(const Vector4& vector) const
    {
        return x != vector.x || y != vector.y || z != vector.z || w != vector.w;
    }

    //-----------------------------------------------------------------------------
    inline void Vector4::setValues(float inX, float inY, float inZ, float inW)
    {
        GLM_DEBUG_ASSERT_MSG(glmFinite(inX) && glmFinite(inY) & glmFinite(inZ) && glmFinite(inW), "Can't build a vector using a non finite value");
        x = inX;
        y = inY;
        z = inZ;
        w = inW;
    }

    //-----------------------------------------------------------------------------
    inline void Vector4::setValues(const float* inValues)
    {
        GLM_DEBUG_ASSERT_MSG(glmFinite(inValues[0]) && glmFinite(inValues[1]) & glmFinite(inValues[2]) && glmFinite(inValues[3]), "Can't build a vector using a non finite value");
        memcpy(&x, inValues, _vector4ValuesSize);
    }

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

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

    //-----------------------------------------------------------------------------
    inline Vector4 operator+(const Vector4& vector1, const Vector4& vector2)
    {
        return Vector4(vector1.x + vector2.x, vector1.y + vector2.y, vector1.z + vector2.z, vector1.w + vector2.w);
    }

    //-----------------------------------------------------------------------------
    inline Vector4 operator-(const Vector4& vector1, const Vector4& vector2)
    {
        return Vector4(vector1.x - vector2.x, vector1.y - vector2.y, vector1.z - vector2.z, vector1.w - vector2.w);
    }

    //-----------------------------------------------------------------------------
    inline Vector4 operator*(const Vector4& vector, const float& scalar)
    {
        return Vector4(vector.x * scalar, vector.y * scalar, vector.z * scalar, vector.w * scalar);
    }

    //-----------------------------------------------------------------------------
    inline Vector4 operator*(const float& scalar, const Vector4& vector)
    {
        return Vector4(vector.x * scalar, vector.y * scalar, vector.z * scalar, vector.w * scalar);
    }

    //-----------------------------------------------------------------------------
    inline Vector4 operator/(const Vector4& vector, const float& scalar)
    {
        return Vector4(vector.x / scalar, vector.y / scalar, vector.z / scalar, vector.w / scalar);
    }

    //-----------------------------------------------------------------------------
    inline float operator*(const Vector4& vector1, const Vector4& vector2)
    {
        return (vector1.x * vector2.x + vector1.y * vector2.y + vector1.z * vector2.z + vector1.w * vector2.w);
    }

    //-----------------------------------------------------------------------------
    Vector4 operator*(const Vector4& vector, const Matrix4& matrix);

    //-----------------------------------------------------------------------------
    inline std::ostream& operator<<(std::ostream& out, const Vector4& vector)
    {
        out << "(" << vector.x << "," << vector.y << "," << vector.z << "," << vector.w << ")";
        return out;
    }
} // namespace glm
