#ifndef SLANG_CORE_MATH_H
#define SLANG_CORE_MATH_H

#include "slang.h"

#include <cmath>

namespace Slang
{
// Some handy constants

// The largest positive (or negative) number
#define SLANG_HALF_MAX 65504.0f
// Smallest (denormalized) value. 1 / 2^24
#define SLANG_HALF_SUB_NORMAL_MIN (1.0f / 16777216.0f)

class Math
{
public:
    // Use to fix type punning issues with strict aliasing
    union FloatIntUnion
    {
        float fvalue;
        int ivalue;

        SLANG_FORCE_INLINE static FloatIntUnion makeFromInt(int i)
        {
            FloatIntUnion cast;
            cast.ivalue = i;
            return cast;
        }
        SLANG_FORCE_INLINE static FloatIntUnion makeFromFloat(float f)
        {
            FloatIntUnion cast;
            cast.fvalue = f;
            return cast;
        }
    };
    union DoubleInt64Union
    {
        double dvalue;
        int64_t ivalue;
        SLANG_FORCE_INLINE static DoubleInt64Union makeFromInt64(int64_t i)
        {
            DoubleInt64Union cast;
            cast.ivalue = i;
            return cast;
        }
        SLANG_FORCE_INLINE static DoubleInt64Union makeFromDouble(double d)
        {
            DoubleInt64Union cast;
            cast.dvalue = d;
            return cast;
        }
    };

    static const float Pi;

    template<typename T>
    static T Abs(T a)
    {
        return (a < 0) ? -a : a;
    }

    template<typename T>
    static T Min(const T& v1, const T& v2)
    {
        return v1 < v2 ? v1 : v2;
    }
    template<typename T>
    static T Max(const T& v1, const T& v2)
    {
        return v1 > v2 ? v1 : v2;
    }
    template<typename T>
    static T Min(const T& v1, const T& v2, const T& v3)
    {
        return Min(v1, Min(v2, v3));
    }
    template<typename T>
    static T Max(const T& v1, const T& v2, const T& v3)
    {
        return Max(v1, Max(v2, v3));
    }
    template<typename T>
    static T Clamp(const T& val, const T& vmin, const T& vmax)
    {
        if (val < vmin)
            return vmin;
        else if (val > vmax)
            return vmax;
        else
            return val;
    }

    static inline int FastFloor(float x)
    {
        int i = (int)x;
        return i - (i > x);
    }

    static inline int FastFloor(double x)
    {
        int i = (int)x;
        return i - (i > x);
    }

    static inline int IsNaN(float x) { return std::isnan(x); }
    static inline int IsNaN(double x) { return std::isnan(x); }

    static inline int IsInf(float x) { return std::isinf(x); }
    static inline int IsInf(double x) { return std::isinf(x); }

    static inline unsigned int Ones32(unsigned int x)
    {
        /* 32-bit recursive reduction using SWAR...
            but first step is mapping 2-bit values
            into sum of 2 1-bit values in sneaky way
        */
        x -= ((x >> 1) & 0x55555555);
        x = (((x >> 2) & 0x33333333) + (x & 0x33333333));
        x = (((x >> 4) + x) & 0x0f0f0f0f);
        x += (x >> 8);
        x += (x >> 16);
        return (x & 0x0000003f);
    }

    static inline unsigned int Log2Floor(unsigned int x)
    {
        x |= (x >> 1);
        x |= (x >> 2);
        x |= (x >> 4);
        x |= (x >> 8);
        x |= (x >> 16);
        return (Ones32(x >> 1));
    }

    static inline unsigned int Log2Ceil(unsigned int x)
    {
        int y = (x & (x - 1));
        y |= -y;
        y >>= (32 - 1);
        x |= (x >> 1);
        x |= (x >> 2);
        x |= (x >> 4);
        x |= (x >> 8);
        x |= (x >> 16);
        return (Ones32(x >> 1) - y);
    }
    /*
    static inline int Log2(float x)
    {
        unsigned int ix = (unsigned int&)x;
        unsigned int exp = (ix >> 23) & 0xFF;
        int log2 = (unsigned int)(exp) - 127;

        return log2;
    }
    */

    static bool AreNearlyEqual(double a, double b, double epsilon)
    {
        // If they are equal then we are done
        if (a == b)
        {
            return true;
        }

        const double absA = Abs(a);
        const double absB = Abs(b);
        const double diff = Abs(a - b);

        // https://en.wikipedia.org/wiki/Double_precision_floating-point_format
        const double minNormal = 2.2250738585072014e-308;
        // Either a or b are very close to being zero, so doing relative comparison isn't really
        // appropriate
        if (a == 0.0 || b == 0.0 || (absA + absB < minNormal))
        {
            return diff < (epsilon * minNormal);
        }
        else
        {
            // Calculate a relative relative error
            return diff < epsilon * (absA + absB);
        }
    }

    template<typename T>
    static T getLowestBit(T val)
    {
        return val & (-val);
    }
};
inline int FloatAsInt(float val)
{
    return Math::FloatIntUnion::makeFromFloat(val).ivalue;
}
inline float IntAsFloat(int val)
{
    return Math::FloatIntUnion::makeFromInt(val).fvalue;
}

SLANG_FORCE_INLINE int64_t DoubleAsInt64(double val)
{
    return Math::DoubleInt64Union::makeFromDouble(val).ivalue;
}
SLANG_FORCE_INLINE double Int64AsDouble(int64_t value)
{
    return Math::DoubleInt64Union::makeFromInt64(value).dvalue;
}

inline unsigned short FloatToHalf(float val)
{
    const auto x = FloatAsInt(val);

    unsigned short bits = (x >> 16) & 0x8000;
    unsigned short m = (x >> 12) & 0x07ff;
    unsigned int e = (x >> 23) & 0xff;
    if (e < 103)
        return bits;
    if (e > 142)
    {
        bits |= 0x7c00u;
        bits |= e == 255 && (x & 0x007fffffu);
        return bits;
    }
    if (e < 113)
    {
        m |= 0x0800u;
        bits |= (m >> (114 - e)) + ((m >> (113 - e)) & 1);
        return bits;
    }
    bits |= ((e - 112) << 10) | (m >> 1);
    bits += m & 1;
    return bits;
}

inline float HalfToFloat(unsigned short input)
{
    static const auto magic = Math::FloatIntUnion::makeFromInt((127 + (127 - 15)) << 23);
    static const auto was_infnan = Math::FloatIntUnion::makeFromInt((127 + 16) << 23);
    Math::FloatIntUnion o;
    o.ivalue = (input & 0x7fff) << 13; // exponent/mantissa bits
    o.fvalue *= magic.fvalue;          // exponent adjust
    if (o.fvalue >= was_infnan.fvalue) // make sure Inf/NaN survive
        o.ivalue |= 255 << 23;
    o.ivalue |= (input & 0x8000) << 16; // sign bit
    return o.fvalue;
}

class Random
{
private:
    unsigned int seed;

public:
    Random(int seed) { this->seed = seed; }
    int Next() // random between 0 and RandMax (currently 0x7fff)
    {
        return ((seed = ((seed << 12) + 150889L) % 714025) & 0x7fff);
    }
    int Next(int min, int max) // inclusive min, exclusive max
    {
        unsigned int a = ((seed = ((seed << 12) + 150889L) % 714025) & 0xFFFF);
        unsigned int b = ((seed = ((seed << 12) + 150889L) % 714025) & 0xFFFF);
        unsigned int r = (a << 16) + b;
        return min + r % (max - min);
    }
    float NextFloat() { return ((Next() << 15) + Next()) / ((float)(1 << 30)); }
    float NextFloat(float valMin, float valMax) { return valMin + (valMax - valMin) * NextFloat(); }
    static int RandMax() { return 0x7fff; }
};
} // namespace Slang

#endif
