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

#pragma once

#include "glmCoreDefinitions.h"
#include "glmCoreAPI.h"
#include "glmTypes.h"
#include "glmAssert.h"
#include <math.h>
#include <algorithm>
#include <float.h>
#include <xmmintrin.h>
#ifdef GLM_IS_COMPILER_GCC
#include <climits>
#endif

#define GLM_EPSILON 0.001f

// GLM_NORMALIZED_EPSILON_SQR IS 1-(1-GLM_EPSILON)^2, a specific use case for isNormalized comparing norm2 around 1
//static const float GLM_NORMALIZED_EPSILON_SQR = 1.f - ((1.f - GLM_EPSILON)*(1.f - GLM_EPSILON));
#define GLM_NORMALIZED_EPSILON_SQR 0.001999f

#define GLM_NUMERICAL_PRECISION 0.00001f

// NORMALIZED PRECISION IS 1-(1-GLM_NUMERICAL_PRECISION)^2, a specific use case for isNormalized comparing norm2 around 1
//static const float GLM_NORMALIZED_PRECISION_SQR = 1.f - ((1.f - GLM_NUMERICAL_PRECISION)*(1.f - GLM_NUMERICAL_PRECISION));
#define GLM_NORMALIZED_PRECISION_SQR 0.00001999989f

#define GLM_NUMERICAL_PRECISION_COARSE 0.0001f
#define GLM_UNKNOWN_REAL 9.9999e+33f
#define GLM_SQRT_2 1.414213562373095f
#define GLM_SQRT_2_DIV_2 0.70710678118654752440084436210485f
#define GLM_VERY_SMALL_FLOAT 1.0e-037f
#define GLM_PI 3.14159265358979323846f
#define GLM_PI_MULT_2 6.28318530717958647692f
#define GLM_PI_DIV_2 1.57079632679489661923f
#define GLM_PI_DIV_4 0.785398163397448309616f
#define GLM_DEG_2_RAD 0.0174532925f
#define GLM_RAD_2_DEG 57.29577951f
#define GLM_UINT32_MAX (0xffffffffUL)

#ifndef UINT8_MAX
#define UINT8_MAX 0xff
#endif
#ifndef INT8_MAX
#define INT8_MAX 0x7f
#endif

#ifndef UINT16_MAX
#define UINT16_MAX 0xffff
#endif
#ifndef INT16_MAX
#define INT16_MAX 0x7fff
#endif

#ifndef UINT32_MAX
#define UINT32_MAX (0xffffffffUL)
#endif
#ifndef INT32_MAX
#define INT32_MAX (0x7fffffffL)
#endif

#ifndef INT32_MIN
#define INT32_MIN (-INT32_MAX - 1)
#endif

#define GLM_MATH_IS_POWER_OF_TWO(number) (number == 1 || (number & (number - 1)) == 0)

#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
{
    // Enumeration type defining the 3D axises.
    struct Axis
    {
        enum Value
        {
            BEGIN = 0,
            X = 0,
            Y = 1,
            Z = 2,
            END
        };
    };

    //-------------------------------------------------------------------------
    inline unsigned int nextPowerOf2(unsigned int n)
    {
        unsigned count = 0;

        // First n in the below condition
        // is for the case where n is 0
        if (n && !(n & (n - 1)))
            return n;

        while (n != 0)
        {
            n >>= 1;
            count += 1;
        }

        return 1 << count;
    }

    inline const char* toString(const Axis::Value type)
    {
        GLM_ASSERT(type < Axis::END);
        static const char* typeNames[] = {"x", "y", "z"};
        return typeNames[type];
    }

    // Enumeration type defining the 2 different side of a line.
    struct Side
    {
        enum Value
        {
            BEGIN = 0,
            RIGHT = 0,
            LEFT,
            END
        };
    };

    // Enumeration type defining the 4 different quadrant of a plane.
    struct Quadrant
    {
        enum Value
        {
            BEGIN = 0,
            SW = 0,
            SE,
            NE,
            NW,
            END
        };
    };

    // Comparison Operator
    struct ComparisonOperator
    {
    public:
        enum Value
        {
            EQUAL = 0,     /// ==
            NOT_EQUAL,     /// !=
            LOWER_EQUAL,   /// <=
            LOWER,         /// <
            GREATER,       /// >
            GREATER_EQUAL, /// >=
            END
        };
    };

    // Function checking wether a scalar is finite or not.
    inline bool glmFinite(float val)
    {
#if GLM_IS_COMPILER_MSVC
        return _finite(val) != 0;
#else
        return __finite(val);
#endif
    }

    inline bool glmFinite(double val)
    {
#if GLM_IS_COMPILER_MSVC
        return _finite(val) != 0;
#else
        return __finite(val);
#endif
    }

	//-----------------------------------------------------------------------------
	// optimized branchless sign function
	template <typename T>
	T sign(T val)
	{
		return (T)((T(0) < val) - (val < T(0)));
	}

    // Usable AlmostEqual function
    inline bool approxEqualFloatUlps(float A, float B, int maxFloatValuesBetweenThem) // max ULPS version: http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm
    {
        // Make sure maxUlps is non-negative and small enough that the
        // default NAN won't compare as equal to anything.
        GLM_DEBUG_ASSERT(maxFloatValuesBetweenThem > 0 && maxFloatValuesBetweenThem < 4 * 1024 * 1024);
        int aInt = *(int*)&A;
        // Make aInt lexicographically ordered as a twos-complement int
        if (aInt < 0)
            aInt = 0x80000000 - aInt;
        // Make bInt lexicographically ordered as a twos-complement int
        int bInt = *(int*)&B;
        if (bInt < 0)
            bInt = 0x80000000 - bInt;
        int intDiff = abs(aInt - bInt);
        if (intDiff <= maxFloatValuesBetweenThem)
            return true;
        return false;
    }

    inline bool approxEqual(float a, float b, float precision = GLM_NUMERICAL_PRECISION)
    {
        return (fabs(a - b) <= precision);
    }

    inline bool approxEqual(double a, double b, double precision = GLM_NUMERICAL_PRECISION)
    {
        return (fabs(a - b) <= precision);
    }

    inline bool approxDiff(float a, float b, float precision = GLM_NUMERICAL_PRECISION)
    {
        return !approxEqual(a, b, precision);
    }

    inline bool approxDiff(double a, double b, double precision = GLM_NUMERICAL_PRECISION)
    {
        return !approxEqual(a, b, precision);
    }

    inline float sqr(float value)
    {
        return value * value;
    }

    inline double min(double a, double b)
    {
        return (b < a) ? b : a;
    }

    inline double max(double a, double b)
    {
        return (b < a) ? a : b;
    }

    inline int min(int a, int b)
    {
        return (b < a) ? b : a;
    }

    inline int max(int a, int b)
    {
        return (b < a) ? a : b;
    }

    inline int64_t min(int64_t a, int64_t b)
    {
        return (b < a) ? b : a;
    }

    inline int64_t max(int64_t a, int64_t b)
    {
        return (b < a) ? a : b;
    }

    inline unsigned int min(unsigned int a, unsigned int b)
    {
        return (b < a) ? b : a;
    }

    inline unsigned int max(unsigned int a, unsigned int b)
    {
        return (b < a) ? a : b;
    }

    inline size_t min(size_t a, size_t b)
    {
        return (b < a) ? b : a;
    }

    inline size_t max(size_t a, size_t b)
    {
        return (b < a) ? a : b;
    }

    // Bound a value in an interval.
#if GLM_IS_COMPILER_MSVC && (defined(_M_X64) || defined(_M_IA64))
    inline float min(float a, float b)
    {
        // Branchless SSE min.
        _mm_store_ss(&a, _mm_min_ss(_mm_set_ss(a), _mm_set_ss(b)));
        return a;
    }

    inline float max(float a, float b)
    {
        // Branchless SSE max.
        _mm_store_ss(&a, _mm_max_ss(_mm_set_ss(a), _mm_set_ss(b)));
        return a;
    }

    inline float clamp(float value, float min, float max)
    {
        // Branchless SSE clamp.
        _mm_store_ss(&value, _mm_min_ss(_mm_max_ss(_mm_set_ss(value), _mm_set_ss(min)), _mm_set_ss(max)));
        return value;
    }
#else
    inline float min(float a, float b)
    {
        return (b < a) ? b : a;
    }

    inline float max(float a, float b)
    {
        return (b < a) ? a : b;
    }

    inline float clamp(float value, float min, float max)
    {
        if (value < min)
        {
            return min;
        }
        else if (value > max)
        {
            return max;
        }
        else
        {
            return value;
        }
    }
#endif

    inline int clamp(int value, int min, int max)
    {
        if (value < min)
        {
            return min;
        }
        else if (value > max)
        {
            return max;
        }
        else
        {
            return value;
        }
    }

    // Return the nearest integer to the given value.
    inline float round(float value)
    {
        return floorf(value + 0.5f);
    }

    inline double round(double value)
    {
        return floor(value + 0.5f);
    }

    inline uint32_t upperPowerOfTwo(uint32_t value)
    {
        GLM_DEBUG_ASSERT(value > 0);
        GLM_DEBUG_ASSERT(value < INT_MAX);

        uint32_t n = static_cast<uint32_t>(static_cast<int>(value) - 1);

        n = n | (n >> 1);
        n = n | (n >> 2);
        n = n | (n >> 4);
        n = n | (n >> 8);
        n = n | (n >> 16);

        return n + 1;
    }

    template <class T>
    bool compare(const T& val1, ComparisonOperator::Value comparison, const T& val2)
    {
        switch (comparison)
        {
        default:
        case ComparisonOperator::EQUAL:
        {
            return (val1 == val2);
        }
        break;
        case ComparisonOperator::NOT_EQUAL:
        {
            return (val1 != val2);
        }
        break;
        case ComparisonOperator::GREATER:
        {
            return (val1 > val2);
        }
        break;
        case ComparisonOperator::GREATER_EQUAL:
        {
            return (val1 >= val2);
        }
        break;
        case ComparisonOperator::LOWER:
        {
            return (val1 < val2);
        }
        break;
        case ComparisonOperator::LOWER_EQUAL:
        {
            return (val1 <= val2);
        }
        break;
        }
    }

	// compare with threshold
	inline bool compare(const double& val1, ComparisonOperator::Value comparison, const double& val2, const double& threshold)
	{	
		switch (comparison)
		{
		default:
		case ComparisonOperator::EQUAL:
		{
			return approxEqual(val1, val2, threshold);
		}
		break;
		case ComparisonOperator::NOT_EQUAL:
		{
			return approxDiff(val1, val2, threshold);
		}
		break;
		case ComparisonOperator::GREATER:
		{
			return (val1 + threshold > val2);
		}
		break;
		case ComparisonOperator::GREATER_EQUAL:
		{
			return (val1 + threshold >= val2);
		}
		break;
		case ComparisonOperator::LOWER:
		{
			return (val1 < val2 + threshold);
		}
		break;
		case ComparisonOperator::LOWER_EQUAL:
		{
			return (val1 <= val2 + threshold);
		}
		break;
		}
	}

#if GLM_IS_COMPILER_MSVC && (defined(_M_X64) || defined(_M_IA64))
    //-------------------------------------------------------------------------
    // vMathVfDot4
    //
    // Copyright (C) 2006, 2010 Sony Computer Entertainment Inc.
    // All rights reserved.
    //-------------------------------------------------------------------------
    inline __m128 vMathVfDot4(__m128 vec0, __m128 vec1)
    {
        __m128 result = _mm_mul_ps(vec0, vec1);
        return _mm_add_ps(_mm_shuffle_ps(result, result, _MM_SHUFFLE(0, 0, 0, 0)),
                          _mm_add_ps(_mm_shuffle_ps(result, result, _MM_SHUFFLE(1, 1, 1, 1)),
                                     _mm_add_ps(_mm_shuffle_ps(result, result, _MM_SHUFFLE(2, 2, 2, 2)), _mm_shuffle_ps(result, result, _MM_SHUFFLE(3, 3, 3, 3)))));
    }

    //-------------------------------------------------------------------------
    // newtonRapsonRsqrt4
    //
    // Copyright (C) 2006, 2010 Sony Computer Entertainment Inc.
    // All rights reserved.
    //-------------------------------------------------------------------------
    inline __m128 newtonRapsonRsqrt4(const __m128 v)
    {
        static const __m128 half4 = {.5f, .5f, .5f, .5f}; // aligned consts for fast load
        static const __m128 three = {3.f, 3.f, 3.f, 3.f}; // aligned consts for fast load
        const __m128 approx = _mm_rsqrt_ps(v);
        const __m128 muls = _mm_mul_ps(_mm_mul_ps(v, approx), approx);
        return _mm_mul_ps(_mm_mul_ps(half4, approx), _mm_sub_ps(three, muls));
    }
#endif
} // 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
