#pragma once
#include <util/system/defaults.h>

#include <cmath>
#include <limits>

namespace NYql {
/*

double

visual c++

fff8000000000000 0.0/0.0
7ff8000000000001 snan
7ff8000000000000 qnan

gcc/clang

7ff8000000000000 0.0/0.0
7ff4000000000000 snan
7ff8000000000000 qnan

float

visual c++

ffc00000 0.0f/0.0f
7fc00001 snan
7fc00000 qnan

gcc/clang

7fc00000 0.0f/0.0f
7fa00000 snan
7fc00000 qnan

*/

template <typename T>
struct TFpTraits {
    static constexpr bool Supported = false;
};

template <>
struct TFpTraits<float> {
    static constexpr bool Supported = std::numeric_limits<float>::is_iec559;
    using TIntegral = ui32;
    static constexpr TIntegral SignMask = (1u << 31);
    static constexpr TIntegral Mantissa = 23;
    static constexpr TIntegral Exp = 8;
    static constexpr TIntegral MaxMantissa = (1u << Mantissa) - 1;
    static constexpr TIntegral MaxExp = (1u << Exp) - 1;
    static constexpr TIntegral QNan = 0x7fc00000u;
};

template <>
struct TFpTraits<double> {
    static constexpr bool Supported = std::numeric_limits<double>::is_iec559;
    using TIntegral = ui64;
    static constexpr TIntegral SignMask = (1ull << 63);
    static constexpr TIntegral Mantissa = 52;
    static constexpr TIntegral Exp = 11;
    static constexpr TIntegral MaxMantissa = (1ull << Mantissa) - 1;
    static constexpr TIntegral MaxExp = (1ull << Exp) - 1;
    static constexpr TIntegral QNan = 0x7ff8000000000000ull;
};

template <typename T, bool>
struct TCanonizeFpBitsImpl {
};

template <typename T>
struct TCanonizeFpBitsImpl<T, true> {
    static void Do(void* buffer) {
        using TTraits = TFpTraits<T>;
        using TIntegral = typename TTraits::TIntegral;
        const TIntegral value = *(TIntegral*)(buffer);
        if (value == TTraits::SignMask) {
            *(TIntegral*)buffer = 0;
            return;
        }

        const TIntegral exp = (value >> TTraits::Mantissa) & TTraits::MaxExp;
        // inf or nan
        if (exp == TTraits::MaxExp) {
            if (value & TTraits::MaxMantissa) {
                // quiet nan
                *(TIntegral*)buffer = TTraits::QNan;
            }
        }
    }
};

template <typename T>
struct TCanonizeFpBitsImpl<T, false> {
    static void Do(void* buffer) {
        using TNumTraits = std::numeric_limits<T>;
        const T value = *(T*)buffer;
        switch (std::fpclassify(value)) {
        case FP_NAN:
            static_assert(TNumTraits::has_quiet_NaN, "no QNAN");
            *(T*)buffer = TNumTraits::quiet_NaN();
            break;
        case FP_ZERO:
            *(T*)buffer = T(0);
            break;
        }
    }
};

/*
    Canonize floating point number bits.
    Converts minus zero to zero and all NaNs to quiet NaN.
    @param buffer[in/out] - aligned buffer with floating point number
*/
template <typename T>
void CanonizeFpBits(void* buffer) {
    return TCanonizeFpBitsImpl<T, TFpTraits<T>::Supported>::Do(buffer);
}

}