#ifndef INCLUDE_GUARD_BIT_H
#define INCLUDE_GUARD_BIT_H

#include <cstddef>

// Polyfill for `<bit>`, it compiled with proper standard this falls back to
// the STL implementation. Only provides functionality if not supported.

// TODO: extend to others than gcc (and maybe clang?)
// Inject polyfill for `std::endian` (since C++20)
namespace std {
    enum class endian {
        little = __ORDER_LITTLE_ENDIAN__,
        big    = __ORDER_BIG_ENDIAN__,
        native = __BYTE_ORDER__
    };

    // Polyfill `std::byteswap` (since C++23)
    template <typename T>
    constexpr T byteswap(T x) noexcept {
#ifdef __GNUC__
        if constexpr (sizeof(T) == 8) {
            uint64_t swapped = __builtin_bswap64(*reinterpret_cast<uint64_t*>(&x));
            return *reinterpret_cast<double*>(&swapped);
        } else if constexpr (sizeof(T) == 4) {
            uint32_t swapped = __builtin_bswap32(*reinterpret_cast<uint32_t*>(&x));
            return *reinterpret_cast<double*>(&swapped);
        } else if constexpr (sizeof(T) == 2) {
            uint16_t swapped = __builtin_bswap16(*reinterpret_cast<uint16_t*>(&x));
            return *reinterpret_cast<double*>(&swapped);
        } else {
            static_assert(sizeof(T) == 1);
            return x;
        }
#else
        if constexpr (sizeof(T) == 8) {
            uint64_t swapped = *reinterpret_cast<uint64_t*>(&x);
            swapped = (((swapped & static_cast<uint64_t>(0xFF00FF00FF00FF00ULL)) >>  8)
                     | ((swapped & static_cast<uint64_t>(0x00FF00FF00FF00FFULL)) <<  8));
            swapped = (((swapped & static_cast<uint64_t>(0xFFFF0000FFFF0000ULL)) >> 16)
                     | ((swapped & static_cast<uint64_t>(0x0000FFFF0000FFFFULL)) << 16));
            swapped = ((swapped >> 32) | (swapped << 32));
            return *reinterpret_cast<T*>(&swapped);
        } else if constexpr (sizeof(T) == 4) {
            uint32_t swapped = *reinterpret_cast<uint32_t*>(&x);
            swapped = (((swapped & static_cast<uint32_t>(0xFF00FF00)) >>  8)
                     | ((swapped & static_cast<uint32_t>(0x00FF00FF)) <<  8));
            swapped = ((swapped >> 16) | (swapped << 16));
            return *reinterpret_cast<T*>(&swapped);
        } else if constexpr (sizeof(T) == 2) {
            uint16_t swapped = *reinterpret_cast<uint16_t*>(&x);
            swapped = ((swapped << 8) | (swapped >> 8));
            return *reinterpret_cast<T*>(&swapped);
        } else {
            static_assert(sizeof(T) == 1);
            return x;
        }
#endif
    }

} /* namespace std */

#endif /* INCLUDE_GUARD_BIT_H */
