/**
 @file g3dmath.h
 
 Math util class.
 
 @maintainer Morgan McGuire, http://graphics.cs.williams.edu
 @cite highestBit by Jukka Liimatta
 
 @created 2001-06-02
 @edited  2013-01-27

 Copyright 2000-2013, Morgan McGuire.
 All rights reserved.
 */

#ifndef G3D_g3dmath_h
#define G3D_g3dmath_h

#ifdef _MSC_VER
// Disable conditional expression is constant, which occurs incorrectly on inlined functions
#   pragma warning (push)
#   pragma warning (disable : 4127)
// disable: "C++ exception handler used"
#   pragma warning (disable : 4530)
#endif

#include "G3D/platform.h"
#include <ctype.h>
#include <float.h>
#include <limits>
#include <stdlib.h>
#include <stdint.h>

#if defined(_MSC_VER) && (_MSC_VER < 1000)
    // Visual Studio is missing inttypes.h
#   ifndef PRId64
#       define PRId64 "I64d"
#   endif
#else
#include <inttypes.h>
#endif

/*These defines enable functionality introduced with the 1999 ISO C
**standard. They must be defined before the inclusion of math.h to
**engage them. If optimisation is enabled, these functions will be 
**inlined. With optimisation switched off, you have to link in the
**maths library using -lm.
*/

#define _ISOC9X_SOURCE1
#define _ISOC99_SOURCE1
#define __USE_ISOC9X1
#define __USE_ISOC991

#include <math.h>

#include "G3D/debug.h"

#undef min
#undef max

/**
\def G3D_DECLARE_SYMBOL(s)
Defines SYMBOL_s as a static const std::string with the value s.
Useful for avoiding heap allocation from C-string constants being
converted at runtime.
*/
#define G3D_DECLARE_SYMBOL(s) \
    static const std::string SYMBOL_##s = #s


namespace G3D {

#ifdef _MSC_VER
inline double __fastcall drand48() {
    return ::rand() / double(RAND_MAX);
}

#   ifdef _M_IX86
    // 32-bit
        /**
           Win32 implementation of the C99 fast rounding routines.
   
           @cite routines are
           Copyright (C) 2001 Erik de Castro Lopo <erikd AT mega-nerd DOT com>
   
           Permission to use, copy, modify, distribute, and sell this file for any 
           purpose is hereby granted without fee, provided that the above copyright 
           and this permission notice appear in all copies.  No representations are
           made about the suitability of this software for any purpose.  It is 
           provided "as is" without express or implied warranty.
        */


         __inline long int lrint (double flt) {
            int intgr;

            _asm {
                fld flt
                fistp intgr
            };

            return intgr;
        }

        __inline long int lrintf(float flt) {
            int intgr;

            _asm {
                fld flt
                fistp intgr
            };

            return intgr;
        }
#   else
    // 64-bit

    __inline long int lrintf(float flt) {        
        return (long int)(flt + 0.5f);
    }

    __inline long int lrint (double flt) {
        return (long int)(flt + 0.5);
    }

#   endif
#endif


#define fuzzyEpsilon64 (0.0000005)
#define fuzzyEpsilon32 (0.00001f)

/** 
    This value should not be tested against directly, instead
    G3D::isNan() and G3D::isFinite() will return reliable results. */
double inf();

/** This value should not be tested against directly, instead
    G3D::isNan() and G3D::isFinite() will return reliable results. */
double nan();

float finf();

float fnan();

inline double pi() {
    return 3.1415926535898;
}

inline float pif() {
    return 3.1415926535898f;
}

inline double halfPi() {
    return 1.57079633;
}

inline double twoPi() {
    return 6.28318531;
}

typedef int8_t      int8;
typedef uint8_t     uint8;
typedef int16_t     int16;
typedef uint16_t    uint16;
typedef int32_t     int32;
typedef uint32_t    uint32;
typedef int64_t     int64;
typedef uint64_t    uint64;

typedef float           float32;
typedef double          float64;

int iAbs(int iValue);
int iCeil(double fValue);

/**
 Clamps the value to the range [low, hi] (inclusive)
 */
int iClamp(int val, int low, int hi);
int16 iClamp(int16 val, int16 low, int16 hi);
double clamp(double val, double low, double hi);
float clamp(float val, float low, float hi);

/**
 Returns a + (b - a) * f;
 */
inline double lerp(double a, double b, double f) {
    return a + (b - a) * f;
}

inline float lerp(float a, float b, float f) {
    return a + (b - a) * f;
}

/**
 Wraps the value to the range [0, hi) (exclusive
 on the high end).  This is like the clock arithmetic
 produced by % (modulo) except the result is guaranteed
 to be positive.
 */
int iWrap(int val, int hi);

int iFloor(double fValue);

int iSign(int iValue);
int iSign(double fValue);

inline int iSign(float f) {
    return iSign((double)f);
}

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

inline float round(float f) {
    return floor(f + 0.5f);
}
    
/** 
    Fast round to integer using the lrint routine.
    Typically 6x faster than casting to integer.
 */
inline int iRound(double fValue) {
    return lrint(fValue);
}

/** 
    Fast round to integer using the lrint routine.
    Typically 6x faster than casting to integer.
 */
inline int iRound(float f) {
    return lrintf(f);
}

/**
 Returns a random number uniformly at random between low and hi
 (inclusive).
 @deprecated Use Random::integer
 */
int iRandom(int low, int hi);

double abs (double fValue);
double aCos (double fValue);
double aSin (double fValue);
double aTan (double fValue);
double aTan2 (double fY, double fX);
double sign (double fValue);
double square (double fValue);

/**
 Returns true if the argument is a finite real number.
 */
bool isFinite(double x);

/**
 Returns true if the argument is NaN (not a number).
 You can't use x == nan to test this because all
 comparisons against nan return false.
 */
bool isNaN(double x);
bool isNaN(float x);
inline bool isNaN(int x) {
    (void)x;
    return false;
}

inline bool isNaN(uint64 x) {
    (void)x;
    return false;
}

/**
 Computes x % 3.
 */
int iMod3(int x);

/**
 Uniform random number between low and hi, inclusive. [low, hi]
 @deprecated 
 @sa Random::uniform
 */
float uniformRandom(float low = 0.0f, float hi = 1.0f);

/**
 Normally distributed random number. 

 @deprecated 
 @sa Random::gaussian
 */
float gaussRandom(float mean = 0.0f, float stdev = 1.0f);


/** Returns x<sup>5</sup> */
template <class T>
inline T pow5(T x) {
    const T y = x * x;
    return y * y * x;
}


template <class T>
inline T min(const T& x, const T& y) {
    return std::min<T>(x, y);
}

template <class T>
inline T min(const T& x, const T& y, const T& z) {
    return std::min<T>(std::min<T>(x, y), z);
}

template <class T>
inline T min(const T& x, const T& y, const T& z, const T& w) {
    return std::min<T>(std::min<T>(x, y), std::min<T>(z, w));
}

template <class T>
inline T max(const T& x, const T& y) {
    return std::max<T>(x, y);
}

template <class T>
inline T max(const T& x, const T& y, const T& z) {
    return std::max<T>(std::max<T>(x, y), z);
}

template <class T>
inline T max(const T& x, const T& y, const T& z, const T& w) {
    return std::max<T>(std::max<T>(x, y), std::max<T>(z, w));
}

int iMin(int x, int y);
int iMax(int x, int y);

double square(double x);
double sumSquares(double x, double y);
double sumSquares(double x, double y, double z);
double distance(double x, double y);
double distance(double x, double y, double z);

/**
  Returnes the 0-based index of the highest 1 bit from
  the left.  -1 means the number was 0.

  @cite Based on code by jukka@liimatta.org
 */ 
int highestBit(uint32 x);

/**
 Note that fuzzyEq(a, b) && fuzzyEq(b, c) does not imply
 fuzzyEq(a, c), although that will be the case on some
 occasions.
 */
bool fuzzyEq(double a, double b);


/** True if a is definitely not equal to b.  
    Guaranteed false if a == b. 
    Possibly false when a != b.*/
bool fuzzyNe(double a, double b);

/** Is a strictly greater than b? (Guaranteed false if a <= b).
    (Possibly false if a > b) */
bool fuzzyGt(double a, double b);

/** Is a near or greater than b? */
bool fuzzyGe(double a, double b);

/** Is a strictly less than b? (Guaranteed false if a >= b)*/
bool fuzzyLt(double a, double b);

/** Is a near or less than b? */
bool fuzzyLe(double a, double b);

/**
 Computes 1 / sqrt(x).
 */
inline float rsq(float x) {
    return 1.0f / sqrtf(x);
}

/**
 Return the next power of 2 higher than the input
 If the input is already a power of 2, the output will be the same 
 as the input.
 */
int ceilPow2(unsigned int in);

/** Returns 2^x */
inline int pow2(unsigned int x) {
    return 1 << x;
}

inline double log2(double x) {
    return ::log(x) * 1.442695;
}

inline float log2(float x) {
    return ::logf(x) * 1.442695f;
}

inline double log2(int x) {
    return log2((double)x);
}


/**
 * True if num is a power of two.
 */
bool isPow2(int num);
bool isPow2(uint64 num);

bool isOdd(int num);
bool isEven(int num);

double toRadians(double deg);
double toDegrees(double rad);

/**
 Returns true if x is not exactly equal to 0.0f.
 */
inline bool any(float x) {
    return x != 0;
}

/**
 Returns true if x is not exactly equal to 0.0f.
 */
inline bool all(float x) {
    return x != 0;
}

/**
 v / v (for DirectX/Cg support)
 */
inline float normalize(float v) {
    return v / v;
}

/**
 a * b (for DirectX/Cg support)
 */
inline float dot(float a, float b) {
    return a * b;
}


/**
 a * b (for DirectX/Cg support)
 */
inline float mul(float a, float b) {
    return a * b;
}

/**
 2^x
 */
inline double exp2(double x) {
    return pow(2.0, x);
}

inline float exp2(float x) {
    return powf(2.0f, x);
}

/** @deprecated Use rsq */
inline double rsqrt(double x) {
    return 1.0 / sqrt(x);
}

/** @deprecated Use rsq */
inline float rsqrt(float x) {
    // TODO: default this to using the SSE2 instruction
    return 1.0f / sqrtf(x);
}

/**
 sin(x)/x
 */
inline double sinc(double x) {
    double r = sin(x) / x;

    if (isNaN(r)) {
        return 1.0;
    } else {
        return r;
    }
}

/**
 Computes a floating point modulo; the result is t wrapped to the range [lo, hi).
 */
inline float wrap(float t, float lo, float hi) {
    if ((t >= lo) && (t < hi)) {
        return t;
    }

    debugAssert(hi > lo);

    float interval = hi - lo;

    return t - interval * iFloor((t - lo) / interval);
}


inline double wrap(double t, double lo, double hi) {
    if ((t >= lo) && (t < hi)) {
        return t;
    }

    debugAssert(hi > lo);

    double interval = hi - lo;

    return t - interval * iFloor((t - lo) / interval);
}

inline double wrap(double t, double hi) {
    return wrap(t, 0.0, hi);
}


inline bool isFinite(double x) {
    return ! isNaN(x) && (x < G3D::inf()) && (x > -G3D::inf());
}

inline bool isFinite(float x) {
    return ! isNaN(x) && (x < G3D::finf()) && (x > -G3D::finf());
}

//----------------------------------------------------------------------------
inline int iAbs (int iValue) {
    return ( iValue >= 0 ? iValue : -iValue );
}

//----------------------------------------------------------------------------
inline int iCeil (double fValue) {
    return int(::ceil(fValue));
}

//----------------------------------------------------------------------------

inline int iClamp(int val, int low, int hi) {
    debugAssert(low <= hi);
    if (val <= low) {
        return low;
    } else if (val >= hi) {
        return hi;
    } else {
        return val;
    }
}

//----------------------------------------------------------------------------

inline int16 iClamp(int16 val, int16 low, int16 hi) {
    debugAssert(low <= hi);
    if (val <= low) {
        return low;
    } else if (val >= hi) {
        return hi;
    } else {
        return val;
    }
}

//----------------------------------------------------------------------------

inline double clamp(double val, double low, double hi) {
    debugAssert(low <= hi);
    if (val <= low) {
        return low;
    } else if (val >= hi) {
        return hi;
    } else {
        return val;
    }
}

inline float clamp(float val, float low, float hi) {
    debugAssert(low <= hi);
    if (val <= low) {
        return low;
    } else if (val >= hi) {
        return hi;
    } else {
        return val;
    }
}
//----------------------------------------------------------------------------

inline int iWrap(int val, int hi) {
    if (val < 0) {
        return ((val % hi) + hi) % hi;
    } else {
        return val % hi;
    }
}

//----------------------------------------------------------------------------
inline int iFloor (double fValue) {
    return int(::floor(fValue));
}

//----------------------------------------------------------------------------
inline int iSign (int iValue) {
    return ( iValue > 0 ? + 1 : ( iValue < 0 ? -1 : 0 ) );
}

inline int iSign (double fValue) {
    return ( fValue > 0.0 ? + 1 : ( fValue < 0.0 ? -1 : 0 ) );
}

//----------------------------------------------------------------------------
inline double abs (double fValue) {
    return double(::fabs(fValue));
}

//----------------------------------------------------------------------------
inline double aCos (double fValue) {
    if ( -1.0 < fValue ) {
        if ( fValue < 1.0 )
            return double(::acos(fValue));
        else
            return 0.0;
    } else {
        return pi();
    }
}

inline float acos (float fValue) {
    if ( -1.0f < fValue ) {
        if ( fValue < 1.0f ) {
            return ::acos(fValue);
        } else {
            return 0.0f;
        }
    } else {
        return pif();
    }
}

//----------------------------------------------------------------------------
inline double aSin (double fValue) {
    if ( -1.0 < fValue ) {
        if ( fValue < 1.0 ) {
            return double(::asin(fValue));
        } else {
            return -halfPi();
        }
    } else {
        return halfPi();
    }
}

//----------------------------------------------------------------------------
inline double aTan (double fValue) {
    return double(::atan(fValue));
}

//----------------------------------------------------------------------------
inline double aTan2 (double fY, double fX) {
    return double(::atan2(fY, fX));
}

//----------------------------------------------------------------------------
inline double sign (double fValue) {
    if (fValue > 0.0) {
        return 1.0;
    }

    if (fValue < 0.0) {
        return -1.0;
    }

    return 0.0;
}

inline float sign (float fValue) {
    if (fValue > 0.0f) {
        return 1.0f;
    }

    if (fValue < 0.0f) {
        return -1.0f;
    }

    return 0.0f;
}


inline float uniformRandom(float low, float hi) {
    return (hi - low) * float(::rand()) / float(RAND_MAX) + low;
}

inline double square(double x) {
    return x * x;
}

inline float square(float x) {
    return x * x;
}

inline int square(int x) {
    return x * x;
}

//----------------------------------------------------------------------------
inline double sumSquares(double x, double y) {
    return x*x + y*y;
}

//----------------------------------------------------------------------------
inline float sumSquares(float x, float y) {
    return x*x + y*y;
}

//----------------------------------------------------------------------------
inline double sumSquares(double x, double y, double z) {
    return x*x + y*y + z*z;
}

//----------------------------------------------------------------------------
inline float sumSquares(float x, float y, float z) {
    return x*x + y*y + z*z;
}

//----------------------------------------------------------------------------
inline double distance(double x, double y) {
    return sqrt(sumSquares(x, y));
}

//----------------------------------------------------------------------------
inline float distance(float x, float y) {
    return sqrt(sumSquares(x, y));
}

//----------------------------------------------------------------------------
inline double distance(double x, double y, double z) {
    return sqrt(sumSquares(x, y, z));
}

//----------------------------------------------------------------------------
inline float distance(float x, float y, float z) {
    return sqrt(sumSquares(x, y, z));
}

//----------------------------------------------------------------------------

/** @deprecated use G3D::min */
inline int iMin(int x, int y) {
    return (x >= y) ? y : x;
}

//----------------------------------------------------------------------------
/** @deprecated use G3D::min */
inline int iMax(int x, int y) {
    return (x >= y) ? x : y;
}

//----------------------------------------------------------------------------
inline int ceilPow2(unsigned int in) {
    in -= 1;

    in |= in >> 16;
    in |= in >> 8;
    in |= in >> 4;
    in |= in >> 2;
    in |= in >> 1;

    return in + 1;
}

inline bool isPow2(int num) {
    return ((num & -num) == num);
}

inline bool isPow2(uint64 x) {
    // See http://www.exploringbinary.com/ten-ways-to-check-if-an-integer-is-a-power-of-two-in-c/, method #9
    return ((x != 0) && !(x & (x - 1)));
}

inline bool isPow2(uint32 x) {
    // See http://www.exploringbinary.com/ten-ways-to-check-if-an-integer-is-a-power-of-two-in-c/, method #9
    return ((x != 0) && !(x & (x - 1)));
}

inline bool isOdd(int num) {
    return (num & 1) == 1;
}

inline bool isEven(int num) {
    return (num & 1) == 0;
}

inline double toRadians(double deg) {
    return deg * pi() / 180.0;
}

inline double toDegrees(double rad) {
    return rad * 180.0 / pi();
}

inline float toRadians(float deg) {
    return deg * (float)pi() / 180.0f;
}

inline float toDegrees(float rad) {
    return rad * 180.0f / (float)pi();
}

inline float toRadians(int deg) {
    return deg * (float)pi() / 180.0f;
}

inline float toDegrees(int rad) {
    return rad * 180.0f / (float)pi();
}
/**
 Computes an appropriate epsilon for comparing a and b.
 */
inline double eps(double a, double b) {
    // For a and b to be nearly equal, they must have nearly
    // the same magnitude.  This means that we can ignore b
    // since it either has the same magnitude or the comparison
    // will fail anyway.
    (void)b;
    const double aa = abs(a) + 1.0;
    if (aa == inf()) {
        return fuzzyEpsilon64;
    } else {
        return fuzzyEpsilon64 * aa;
    }
}

inline float eps(float a, float b) {
    // For a and b to be nearly equal, they must have nearly
    // the same magnitude.  This means that we can ignore b
    // since it either has the same magnitude or the comparison
    // will fail anyway.
    (void)b;
    const float aa = (float)abs(a) + 1.0f;
    if (aa == inf()) {
        return fuzzyEpsilon32;
    } else {
        return fuzzyEpsilon32 * aa;
    }
}


inline bool fuzzyEq(float a, float b) {
    return (a == b) || (abs(a - b) <= eps(a, b));
}

inline bool fuzzyEq(double a, double b) {
    return (a == b) || (abs(a - b) <= eps(a, b));
}

inline bool fuzzyNe(double a, double b) {
    return ! fuzzyEq(a, b);
}

inline bool fuzzyGt(double a, double b) {
    return a > b + eps(a, b);
}

inline bool fuzzyGe(double a, double b) {
    return a > b - eps(a, b);
}

inline bool fuzzyLt(double a, double b) {
    return a < b - eps(a, b);
}

inline bool fuzzyLe(double a, double b) {
    return a < b + eps(a, b);
}

inline int iMod3(int x) {
    return x % 3;
}

/**
 Given a 32-bit integer, returns the integer with the bytes in the opposite order.
 */
inline uint32 flipEndian32(const uint32 x) {
    return (x << 24) | ((x & 0xFF00) << 8) | 
           ((x & 0xFF0000) >> 8) | ((x & 0xFF000000) >> 24);
}

/**
 Given a 16-bit integer, returns the integer with the bytes in the opposite order.
 */
inline uint16 flipEndian16(const uint16 x) {
    return (x << 8) | ((x & 0xFF00) >> 8);
}

/** The GLSL smoothstep function */
inline float smoothstep(float edge0, float edge1, float x) {
    // Scale, bias and saturate x to 0..1 range
    x = clamp((x - edge0) / (edge1 - edge0), 0.0f, 1.0f);

    // Evaluate polynomial
    return x * x * (3 - 2 * x);
}


/** Perlin's C2 continous variation on smoothstep() */
inline float smootherstep(float edge0, float edge1, float x) {

    // Scale, and saturate x to 0..1 range
    x = clamp((x - edge0) / (edge1 - edge0), 0.0f, 1.0f);

    // Evaluate polynomial
    return x * x * x * (x * (x * 6 - 15) + 10);
}


/** Computes |b|^e * sign(b) */
inline float signedPow(float b, float e) {
    return sign(b) * powf(fabsf(b), e);
}


/** Computes |b|^e * sign(b) */
inline double signedPow(double b, double e) {
    return sign(b) * pow(abs(b), e);
}


} // namespace

namespace std {
inline int pow(int a, int b) {
    return (int)::pow(double(a), double(b));
}

}
#ifdef _MSC_VER
#   pragma warning (pop)
#endif

#endif

