/*
 *  Copyright (C) GridGain Systems. All Rights Reserved.
 *  _________        _____ __________________        _____
 *  __  ____/___________(_)______  /__  ____/______ ____(_)_______
 *  _  / __  __  ___/__  / _  __  / _  / __  _  __ `/__  / __  __ \
 *  / /_/ /  _  /    _  /  / /_/ /  / /_/ /  / /_/ / _  /  _  / / /
 *  \____/   /_/     /_/   \_,__/   \____/   \__,_/  /_/   /_/ /_/
 */

#pragma once

#include "ignite/common/detail/config.h"

#include <array>
#include <cassert>
#include <climits>
#include <cstdint>
#include <type_traits>

/**
 * Maximum number of digits in std::uint64_t number.
 */
const std::int32_t UINT64_MAX_PRECISION = 20;

#if defined(__cpp_lib_bitops) && __cpp_lib_bitops >= 201907L
# define IGNITE_STD_BITOPS 1
#endif
#if defined(__cpp_lib_int_pow2) && __cpp_lib_int_pow2 >= 202002L
# define IGNITE_STD_INT_POW2 1
#endif

#if IGNITE_STD_BITOPS || IGNITE_STD_INT_POW2
# include <bit>
#endif

#if !IGNITE_STD_BITOPS || !IGNITE_STD_INT_POW2
# include <limits>
#endif

#if !IGNITE_STD_BITOPS && defined(_MSC_VER)
# include <intrin.h>
#endif

namespace ignite::detail {

/**
 * Returns the number of consecutive 0 bits in the value of x, starting from the least significant bit ("right").
 */
template<typename T>
int countr_zero(T value) noexcept {
    static_assert(std::is_unsigned_v<T>, "countr_zero() doesn't support this type");
    static_assert(sizeof(T) <= sizeof(unsigned long long), "countr_zero() doesn't support this type size");

#if IGNITE_STD_BITOPS
    return std::countr_zero(value);
#elif defined(__GNUC__) || defined(__clang__)
    if (value == 0) {
        return std::numeric_limits<T>::digits;
    }

    if constexpr (sizeof(T) <= sizeof(unsigned int)) {
        return __builtin_ctz(value);
    } else if constexpr (sizeof(T) <= sizeof(unsigned long)) {
        return __builtin_ctzl(value);
    } else {
        return __builtin_ctzll(value);
    }
#elif defined(_MSC_VER)
    unsigned long index;
    if constexpr (sizeof(T) <= sizeof(unsigned long)) {
        return _BitScanForward(&index, value) ? index : std::numeric_limits<T>::digits;
    } else {
        return _BitScanForward64(&index, value) ? index : std::numeric_limits<T>::digits;
    }
#else
# error "TODO: implement countr_zero() for other compilers"
#endif
}

/**
 * Returns the number of consecutive 0 bits in the value of x, starting from the most significant bit ("left").
 */
template<typename T>
int countl_zero(T value) noexcept {
    static_assert(std::is_unsigned_v<T>, "countl_zero() doesn't support this type");
    static_assert(sizeof(T) <= sizeof(unsigned long long), "countl_zero() doesn't support this type size");

#if IGNITE_STD_BITOPS
    return std::countl_zero(value);
#elif defined(__GNUC__) || defined(__clang__)
    if (value == 0) {
        return std::numeric_limits<T>::digits;
    }

    if constexpr (sizeof(T) <= sizeof(unsigned int)) {
        constexpr auto extraBits = std::numeric_limits<unsigned int>::digits - std::numeric_limits<T>::digits;
        return __builtin_clz(value) - extraBits;
    } else if constexpr (sizeof(T) <= sizeof(unsigned long)) {
        constexpr auto extraBits = std::numeric_limits<unsigned long>::digits - std::numeric_limits<T>::digits;
        return __builtin_clzl(value) - extraBits;
    } else {
        constexpr auto extraBits = std::numeric_limits<unsigned long long>::digits - std::numeric_limits<T>::digits;
        return __builtin_clzll(value) - extraBits;
    }
#elif defined(_MSC_VER)
    unsigned long index;
    constexpr auto bit_size = std::numeric_limits<T>::digits;
    if constexpr (sizeof(T) <= sizeof(unsigned long)) {
        return _BitScanReverse(&index, value) ? bit_size - index - 1 : bit_size;
    } else {
        return _BitScanReverse64(&index, value) ? bit_size - index - 1 : bit_size;
    }
#else
# error "TODO: implement countl_zero() for other compilers"
#endif
}

/**
 * If x is not zero, calculates the number of bits needed to store the value x, that is, 1 + ⌊log2(x)⌋.
 * If x is zero, returns zero.
 */
template<typename T>
int bit_width(T value) noexcept {
    static_assert(std::is_unsigned_v<T>, "bit_width() doesn't support this type");

#if IGNITE_STD_INT_POW2
    return std::bit_width(value);
#else
    return std::numeric_limits<T>::digits - countl_zero(value);
#endif
}

/**
 * If value is not zero, calculates the largest integral power of two that is not greater than value.
 * If value is zero, returns zero.
 */
template<typename T>
T bit_floor(T value) noexcept {
    static_assert(std::is_unsigned_v<T>, "bit_floor() doesn't support this type");

#if IGNITE_STD_INT_POW2
    return std::bit_floor(value);
#else
    return value == 0 ? 0u : T(1u) << (bit_width(value) - 1);
#endif
}

/**
 * Calculates the smallest integral power of two that is not smaller than a given value.
 */
template<typename T>
T bit_ceil(T value) noexcept {
    static_assert(std::is_unsigned_v<T>, "bit_ceil() doesn't support this type");

#if IGNITE_STD_INT_POW2
    return std::bit_ceil(value);
#else
    // TODO: If bit_width(value - 1) is equal to the number of bits in T then this should
    // be UB e.g. reported by the UB sanitizer. But because of the integer promotion in
    // the expression below this might be hidden. A solution is actually provided in the
    // "Possible implementation" section here:
    // https://en.cppreference.com/w/cpp/numeric/bit_ceil
    // But do we really need this complication in our code? We can use somewhat relaxed
    // rules as compared to the standard library.
    return value <= 1u ? 1u : T(1u) << bit_width(T(value - 1u));
#endif
}

/**
 * Get a number of bytes needed to store a specified number of bits of information.
 *
 * @param bits_num Number of bits to store.
 * @return Required bytes number.
 */
[[nodiscard]] inline std::size_t bytes_for_bits(std::size_t bits_num) {
    return (bits_num + CHAR_BIT - 1) / CHAR_BIT;
}

/**
 * Get n-th power of ten.
 *
 * @param n Power. Should be in range [0, UINT64_MAX_PRECISION]
 * @return 10 pow n, if n is in range [0, UINT64_MAX_PRECISION].
 *     Otherwise, behaviour is undefined.
 */
inline std::uint64_t ten_power_u64(std::int32_t n) {
    static const std::array<uint64_t, UINT64_MAX_PRECISION> TEN_POWERS_TABLE{
        1U, // 0  / 10^0
        10U, // 1  / 10^1
        100U, // 2  / 10^2
        1000U, // 3  / 10^3
        10000U, // 4  / 10^4
        100000U, // 5  / 10^5
        1000000U, // 6  / 10^6
        10000000U, // 7  / 10^7
        100000000U, // 8  / 10^8
        1000000000U, // 9  / 10^9
        10000000000U, // 10 / 10^10
        100000000000U, // 11 / 10^11
        1000000000000U, // 12 / 10^12
        10000000000000U, // 13 / 10^13
        100000000000000U, // 14 / 10^14
        1000000000000000U, // 15 / 10^15
        10000000000000000U, // 16 / 10^16
        100000000000000000U, // 17 / 10^17
        1000000000000000000U, // 18 / 10^18
        10000000000000000000U // 19 / 10^19
    };

    assert(n >= 0 && n < UINT64_MAX_PRECISION);

    return TEN_POWERS_TABLE[n];
}

/**
 * Get the number of decimal digits of the integer value.
 *
 * @param value The value.
 * @return The number of decimal digits of the integer value.
 */
[[nodiscard]] inline std::int32_t digit_length(std::uint64_t value) {
    // See http://graphics.stanford.edu/~seander/bithacks.html
    // for the details on the algorithm.

    if (value < 10)
        return 1;

    std::int32_t r = ((64 - countl_zero(value) + 1) * 1233) >> 12;

    assert(r <= UINT64_MAX_PRECISION);

    return (r == UINT64_MAX_PRECISION || value < ten_power_u64(r)) ? r : r + 1;
}

} // namespace ignite::detail
