// -*- C++ -*-
// -----------------------------------------------------------------------------------------------------
// Copyright (c) 2006-2021, Knut Reinert & Freie Universität Berlin
// Copyright (c) 2016-2021, Knut Reinert & MPI für molekulare Genetik
// This file may be used, modified and/or redistributed under the terms of the 3-clause BSD-License
// shipped with this file and also available at: https://github.com/seqan/seqan3/blob/master/LICENSE.md
// -----------------------------------------------------------------------------------------------------

/*!\file
 * \brief The [\<bit\> header](https://en.cppreference.com/w/cpp/header/bit) from C++20's standard library.
 * \author Enrico Seiler <enrico.seiler AT fu-berlin.de>
 * \author Marcel Ehrhardt <marcel.ehrhardt AT fu-berlin.de>
 */

#pragma once

#if __has_include(<bit>)
#include <bit>
#endif // __has_include(<bit>)
#include <climits>
#include <type_traits>

/*!\defgroup std_bit bit
 * \ingroup std
 * \brief The [\<bit\> header](https://en.cppreference.com/w/cpp/header/bit) from C++20's standard library.
 */

/*!\brief A workaround for __cpp_lib_bitops for gcc version 9.x (in C++20 mode).
 *        Those versions implemented std::countl_zero, etc, but did not define that feature detection macro.
 * \ingroup std_bit
 */
#ifdef SEQAN3_DOXYGEN_ONLY // needed as seqan3/core/platform.hpp might not be included
#if SEQAN3_DOXYGEN_ONLY(1)0
// documentation-only
#define SEQAN3_CPP_LIB_BITOPS IMPLEMENTATION_DEFINED
#undef SEQAN3_CPP_LIB_BITOPS
#endif // SEQAN3_DOXYGEN_ONLY(1)0
#endif // SEQAN3_DOXYGEN_ONLY

#ifndef SEQAN3_CPP_LIB_BITOPS
#   if defined(__cpp_lib_bitops) && __cpp_lib_bitops >= 201907L
#       define SEQAN3_CPP_LIB_BITOPS 1
#   elif defined(__GNUC__) && (__GNUC__ == 9) && __cplusplus > 201703L
#       define SEQAN3_CPP_LIB_BITOPS 1
#   endif
#endif

/*!\brief This defines __cpp_lib_int_pow2.
 * \ingroup std_bit
 * Note: g++-9.3 -std=c++2a on ubuntu 20.04 did `#define __cpp_lib_int_pow2 201806L`, which is an older implementation
 * of the __cpp_lib_int_pow2 proposal.
 *
 * \sa http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0556r3.html (__cpp_lib_int_pow2 >= 201806L)
 * \sa http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1956r1.pdf (__cpp_lib_int_pow2 >= 202002L)
 */
#ifdef SEQAN3_DOXYGEN_ONLY // needed as seqan3/core/platform.hpp might not be included
#if SEQAN3_DOXYGEN_ONLY(1)0
// documentation-only
#define SEQAN3_CPP_LIB_INT_POW2 IMPLEMENTATION_DEFINED
#undef SEQAN3_CPP_LIB_INT_POW2
#endif // SEQAN3_DOXYGEN_ONLY(1)0
#endif // SEQAN3_DOXYGEN_ONLY

#ifndef SEQAN3_CPP_LIB_INT_POW2
#   if defined(__cpp_lib_int_pow2) && __cpp_lib_int_pow2 >= 202002L
#       define SEQAN3_CPP_LIB_INT_POW2 1
#   endif
#endif

/*!\brief A workaround for __cpp_lib_endian for gcc version 8.x (in C++20 mode).
 *        Those versions implemented std::endian, but did not define that feature detection macro.
 * \ingroup std_bit
 */
#ifdef SEQAN3_DOXYGEN_ONLY // needed as seqan3/core/platform.hpp might not be included
#if SEQAN3_DOXYGEN_ONLY(1)0
// documentation-only
#define SEQAN3_CPP_LIB_ENDIAN IMPLEMENTATION_DEFINED
#undef SEQAN3_CPP_LIB_ENDIAN
#endif // SEQAN3_DOXYGEN_ONLY(1)0
#endif // SEQAN3_DOXYGEN_ONLY

#ifndef SEQAN3_CPP_LIB_ENDIAN
#   if defined(__cpp_lib_endian)
#       define SEQAN3_CPP_LIB_ENDIAN 1
#   elif defined(__GNUC__) && __GNUC__ == 8 && __cplusplus > 201703L
#       define SEQAN3_CPP_LIB_ENDIAN 1
#   endif
#endif

#ifndef SEQAN3_CPP_LIB_ENDIAN

#include <cstddef>

namespace std
{

/*!\brief Indicates the endianness of all scalar types.
 * \sa https://en.cppreference.com/w/cpp/types/endian
 * \ingroup std_bit
 */
enum class endian
{
#ifdef _WIN32
    little = 0,                       //!< implementation-defined
    big    = 1,                       //!< implementation-defined
    native = little                   //!< implementation-defined
#else
    little = __ORDER_LITTLE_ENDIAN__, //!< implementation-defined
    big    = __ORDER_BIG_ENDIAN__,    //!< implementation-defined
    native = __BYTE_ORDER__           //!< implementation-defined
#endif
};

} //namespace std

#endif // SEQAN3_CPP_LIB_ENDIAN

#if !defined(SEQAN3_CPP_LIB_INT_POW2) || !defined(SEQAN3_CPP_LIB_BITOPS)

namespace std::detail
{
/*!\brief How many bits has a type?
 * \ingroup std_bit
 * \tparam type_t The type to determine the number of bits.
 */
template <typename type_t>
constexpr auto bits_of = CHAR_BIT * sizeof(type_t);
} // namespace std::detail

#endif // !defined(SEQAN3_CPP_LIB_INT_POW2) || !defined(SEQAN3_CPP_LIB_BITOPS)

#ifndef SEQAN3_CPP_LIB_INT_POW2

namespace std
{

#ifndef SEQAN3_CPP_LIB_BITOPS
// forward declare
template<class T> constexpr int countl_zero(T x) noexcept;
#endif // SEQAN3_CPP_LIB_BITOPS

// // bit_­cast
// template<class To, class From> constexpr To bit_cast(const From& from) noexcept;
//
// // integral powers of 2

/*!\brief Checks if x is an integral power of two.
 * \sa https://en.cppreference.com/w/cpp/numeric/has_single_bit
 * \ingroup std_bit
 */
template<class T> constexpr bool has_single_bit(T x) noexcept
{
    static_assert(std::is_unsigned_v<T>, "Input should be unsigned.");

    return x > 0 && (x & (x-1)) == 0;
}

/*!\brief Calculates the smallest integral power of two that is not smaller than x.
 * \sa https://en.cppreference.com/w/cpp/numeric/bit_ceil
 * \ingroup std_bit
 */
template<class T> constexpr T bit_ceil(T x) noexcept
{
    static_assert(std::is_unsigned_v<T>, "Input should be unsigned.");

    if (x == 0)
        return 1;

    --x;
    for (size_t shift = 1; !has_single_bit(x + 1); shift <<= 1)
        x |= x >> shift;

    return x + 1;
}

// template<class T> constexpr T bit_floor(T x) noexcept;

/*!\brief If x is not zero, calculates the number of bits needed to store the value x, that is,
 *        1 + floor(log2(x)). If x is zero, returns zero.
 * \sa https://en.cppreference.com/w/cpp/numeric/bit_width
 * \ingroup std_bit
 */
template<class T> constexpr T bit_width(T x) noexcept
{
    static_assert(std::is_unsigned_v<T>, "Input should be unsigned.");

    return detail::bits_of<T> - countl_zero(x);
}

} // namespace std

#endif // SEQAN3_CPP_LIB_INT_POW2

#ifndef SEQAN3_CPP_LIB_BITOPS

namespace std
{

//
// // rotating
// template<class T> [[nodiscard]] constexpr T rotl(T x, int s) noexcept;
// template<class T> [[nodiscard]] constexpr T rotr(T x, int s) noexcept;
//
// // counting

/*!\brief Returns the number of consecutive 0 bits in the value of x, starting from the most significant bit ("left").
 * \sa https://en.cppreference.com/w/cpp/numeric/countl_zero
 * \ingroup std_bit
 */
template<class T> constexpr int countl_zero(T x) noexcept
{
    static_assert(std::is_unsigned_v<T>, "Input should be unsigned.");

    if constexpr (sizeof(T) == sizeof(unsigned long long))
        return x == 0u ? detail::bits_of<T> : __builtin_clzll(x);
    else if constexpr (sizeof(T) == sizeof(unsigned long))
        return x == 0u ? detail::bits_of<T> : __builtin_clzl(x);
    else
        return detail::bits_of<T> + (x == 0u ? 0u : __builtin_clz(x) - detail::bits_of<unsigned int>);
}

// template<class T> constexpr int countl_one(T x) noexcept;

/*!\brief Returns the number of consecutive 0 bits in the value of x, starting from the least significant bit ("right").
 * \sa https://en.cppreference.com/w/cpp/numeric/countr_zero
 * \ingroup std_bit
 */
template<class T> constexpr int countr_zero(T x) noexcept
{
    static_assert(std::is_unsigned_v<T>, "Input should be unsigned.");

    if constexpr (sizeof(T) == sizeof(unsigned long long))
        return x == 0u ? detail::bits_of<T> : __builtin_ctzll(x);
    else if constexpr (sizeof(T) == sizeof(unsigned long))
        return x == 0u ? detail::bits_of<T> : __builtin_ctzl(x);
    else
        return x == 0u ? detail::bits_of<T> : __builtin_ctz(x);
}
// template<class T> constexpr int countr_one(T x) noexcept;

/*!\brief Returns the number of 1 bits in the value of x.
 * \sa https://en.cppreference.com/w/cpp/numeric/popcount
 * \ingroup std_bit
 */
template<class T> constexpr int popcount(T x) noexcept
{
    static_assert(std::is_unsigned_v<T>, "Input should be unsigned.");

    if constexpr (sizeof(T) == sizeof(unsigned long long))
        return __builtin_popcountll(x);
    else if constexpr (sizeof(T) == sizeof(unsigned long))
        return __builtin_popcountl(x);
    else
        return __builtin_popcount(x);
}

} // namespace std

#endif // SEQAN3_CPP_LIB_BITOPS
