#pragma once

#include <util/system/yassert.h>
#include <util/system/defaults.h>

#include <cmath>
#include <cfloat>
#include <cstdlib>

#include "typetraits.h"
#include "utility.h"

constexpr double PI = M_PI;
constexpr double M_LOG2_10 = 3.32192809488736234787; // log2(10)
constexpr double M_LN2_INV = M_LOG2E;                // 1 / ln(2) == log2(e)

/**
 * \returns                             Absolute value of the provided argument.
 */
template <class T>
constexpr T Abs(T value) {
    return std::abs(value);
}

/**
 * @returns                             Base 2 logarithm of the provided double
 *                                      precision floating point value.
 */
inline double Log2(double value) {
    return log(value) * M_LN2_INV;
}

/**
 * @returns                             Base 2 logarithm of the provided
 *                                      floating point value.
 */
inline float Log2(float value) {
    return logf(value) * static_cast<float>(M_LN2_INV);
}

/**
 * @returns                             Base 2 logarithm of the provided integral value.
 */
template <class T>
inline std::enable_if_t<std::is_integral<T>::value, double>
Log2(T value) {
    return Log2(static_cast<double>(value));
}

/** Returns 2^x */
double Exp2(double);
float Exp2f(float);

template <class T>
static constexpr T Sqr(const T t) noexcept {
    return t * t;
}

inline double Sigmoid(double x) {
    return 1.0 / (1.0 + std::exp(-x));
}

inline float Sigmoid(float x) {
    return 1.0f / (1.0f + std::exp(-x));
}

static inline bool IsFinite(double f) {
#if defined(isfinite)
    return isfinite(f);
#elif defined(_win_)
    return _finite(f) != 0;
#elif defined(_darwin_)
    return isfinite(f);
#elif defined(__GNUC__)
    return __builtin_isfinite(f);
#elif defined(_STLP_VENDOR_STD)
    return _STLP_VENDOR_STD::isfinite(f);
#else
    return std::isfinite(f);
#endif
}

static inline bool IsNan(double f) {
#if defined(_win_)
    return _isnan(f) != 0;
#else
    return std::isnan(f);
#endif
}

inline bool IsValidFloat(double f) {
    return IsFinite(f) && !IsNan(f);
}

#ifdef _MSC_VER
double Erf(double x);
#else
inline double Erf(double x) {
    return erf(x);
}
#endif

/**
 * @returns                             Natural logarithm of the absolute value
 *                                      of the gamma function of provided argument.
 */
inline double LogGamma(double x) noexcept {
#if defined(_glibc_)
    int sign;

    (void)sign;

    return lgamma_r(x, &sign);
#elif defined(__GNUC__)
    return __builtin_lgamma(x);
#elif defined(_unix_)
    return lgamma(x);
#else
    extern double LogGammaImpl(double);
    return LogGammaImpl(x);
#endif
}

/**
 * @returns                             x^n for integer n, n >= 0.
 */
template <class T, class Int>
T Power(T x, Int n) {
    static_assert(std::is_integral<Int>::value, "only integer powers are supported");
    Y_ASSERT(n >= 0);
    if (n == 0) {
        return T(1);
    }
    while ((n & 1) == 0) {
        x = x * x;
        n >>= 1;
    }
    T result = x;
    n >>= 1;
    while (n > 0) {
        x = x * x;
        if (n & 1) {
            result = result * x;
        }
        n >>= 1;
    }
    return result;
};

/**
 * Compares two floating point values and returns true if they are considered equal.
 * The two numbers are compared in a relative way, where the exactness is stronger
 * the smaller the numbers are.
 *
 * Note that comparing values where either one is 0.0 will not work.
 * The solution to this is to compare against values greater than or equal to 1.0.
 *
 * @code
 * // Instead of comparing with 0.0
 * FuzzyEquals(0.0, 1.0e-200); // This will return false
 * // Compare adding 1 to both values will fix the problem
 * FuzzyEquals(1 + 0.0, 1 + 1.0e-200); // This will return true
 * @endcode
 */
inline bool FuzzyEquals(double p1, double p2, double eps = 1.0e-13) {
    return (Abs(p1 - p2) <= eps * Min(Abs(p1), Abs(p2)));
}

/**
 * @see FuzzyEquals(double, double, double)
 */
inline bool FuzzyEquals(float p1, float p2, float eps = 1.0e-6) {
    return (Abs(p1 - p2) <= eps * Min(Abs(p1), Abs(p2)));
}

namespace NUtilMathPrivate {
    template <bool IsSigned>
    struct TCeilDivImpl {};

    template <>
    struct TCeilDivImpl<true> {
        template <class T>
        static inline T Do(T x, T y) noexcept {
            return x / y + (((x < 0) ^ (y > 0)) && (x % y));
        }
    };

    template <>
    struct TCeilDivImpl<false> {
        template <class T>
        static inline T Do(T x, T y) noexcept {
            auto quot = x / y;
            return (x % y) ? (quot + 1) : quot;
        }
    };
}

/**
 * @returns Equivalent to ceil((double) x / (double) y) but using only integer arithmetic operations
 */
template <class T>
inline T CeilDiv(T x, T y) noexcept {
    static_assert(std::is_integral<T>::value, "Integral type required.");
    Y_ASSERT(y != 0);
    return ::NUtilMathPrivate::TCeilDivImpl<std::is_signed<T>::value>::Do(x, y);
}