#pragma once

#include <spider/runtime/common.hpp>
#include <spider/runtime/native/machine.hpp>

#if __cplusplus >= 202002L
    #include <bit>
#endif

#if defined(SPIDER_COMPILER_MSVC)
    #include <cstdlib>
#endif

#include <cstring>

namespace spider {

    /*
     * This file contains cross platform type juggling.
     * Optimized for each case.
     *
     * General assumtions is that unsigned and signed
     * integers are the exact same bit representation.
     *
     * Floats to and from integers is the focus when
     * juggling.
     *
     * Additionally, provides help selecting a specific
     * type from raw bytes.
    */

    // Utilities //

#if __cplusplus >= 202002L
    using std::bit_cast;
#else
    template<typename To, typename From>
    inline To bit_cast(const From& src) {
        static_assert(sizeof(To) == sizeof(From), "bit_cast size mismatch");
        To dst;
        std::memcpy(&dst, &src, sizeof(To));
        return dst;
    }
#endif

    template<typename T>
    inline T byteswap(T v) {
        static_assert(std::is_integral<T>::value, "byteswap requires integral type");

        using U = std::make_unsigned_t<T>;
        U u = static_cast<U>(v);

        if constexpr (sizeof(T) == 1) {
            return v;
        } else if constexpr (sizeof(T) == 2) {

#if defined(SPIDER_COMPILER_MSVC)
            u = _byteswap_ushort(u);
#elif defined(SPIDER_COMPILER_GCC_LIKE)
            u = __builtin_bswap16(u);
#else
            u = (u >> 8) | (u << 8);
#endif

        } else if constexpr (sizeof(T) == 4) {

#if defined(SPIDER_COMPILER_MSVC)
            u = _byteswap_ulong(u);
#elif defined(SPIDER_COMPILER_GCC_LIKE)
            u = __builtin_bswap32(u);
#else
            u =
                ((u & 0x000000FFu) << 24) |
                ((u & 0x0000FF00u) << 8) |
                ((u & 0x00FF0000u) >> 8) |
                ((u & 0xFF000000u) >> 24);
#endif

        } else if constexpr (sizeof(T) == 8) {

#if defined(SPIDER_COMPILER_MSVC)
            u = _byteswap_uint64(u);
#elif defined(SPIDER_COMPILER_GCC_LIKE)
            u = __builtin_bswap64(u);
#else
            u =
                ((u & 0x00000000000000FFull) << 56) |
                ((u & 0x000000000000FF00ull) << 40) |
                ((u & 0x0000000000FF0000ull) << 24) |
                ((u & 0x00000000FF000000ull) << 8) |
                ((u & 0x000000FF00000000ull) >> 8) |
                ((u & 0x0000FF0000000000ull) >> 24) |
                ((u & 0x00FF000000000000ull) >> 40) |
                ((u & 0xFF00000000000000ull) >> 56);
#endif

        } else {
            // Generic fallback (rare: non 1/2/4/8-byte types)
            U result = 0;
            for (size_t i = 0; i < sizeof(T); ++i) {
                result |= ((u >> (i * 8)) & 0xFF) << ((sizeof(T) - 1 - i) * 8);
            }
            u = result;
        }

        return static_cast<T>(u);
    }

    // Store Big Endian //

    template<typename T>
    inline void storeBE(T n, u8* bytes) {
        static_assert(std::is_trivially_copyable<T>::value);
#if SPIDER_BIG_ENDIAN
        std::memcpy(bytes, &n, sizeof(T));
#endif
#if SPIDER_LITTLE_ENDIAN
        T tmp = byteswap(n);
        std::memcpy(bytes, &tmp, sizeof(T));
#endif
#if !SPIDER_BIG_ENDIAN && !SPIDER_LITTLE_ENDIAN
        for (size_t i = 0; i < sizeof(T); ++i) {
            bytes[i] = static_cast<u8>(
                (static_cast<std::make_unsigned_t<T>>(n) >> ((sizeof(T) - 1 - i) * 8)) & 0xFF
            );
        }
#endif
    }

    template<>
    inline void storeBE<f32>(f32 n, u8* bytes) {
        u32 tmp = bit_cast<u32>(n);
        storeBE(tmp, bytes);
    }

    template<>
    inline void storeBE<f64>(f64 n, u8* bytes) {
        u64 tmp = bit_cast<u64>(n);
        storeBE(tmp, bytes);
    }

    // Load Big Endian //

    template<typename T>
    inline void loadBE(T* n, const u8* bytes) {
        static_assert(std::is_trivially_copyable<T>::value);
#if SPIDER_BIG_ENDIAN
        std::memcpy(n, bytes, sizeof(T));
#endif
#if SPIDER_LITTLE_ENDIAN
        T tmp;
        std::memcpy(&tmp, bytes, sizeof(T));
        *n = byteswap(tmp);
#endif
#if !SPIDER_BIG_ENDIAN && !SPIDER_LITTLE_ENDIAN
        using U = std::make_unsigned_t<T>;
        U result = 0;
        for (size_t i = 0; i < sizeof(T); ++i) {
            result |= static_cast<U>(bytes[i]) << ((sizeof(T) - 1 - i) * 8);
        }
        *n = static_cast<T>(result);
#endif
    }

    template<>
    inline void loadBE<f32>(f32* n, const u8* bytes) {
        u32 tmp;
        loadBE(&tmp, bytes);
        *n = bit_cast<f32>(tmp);
    }

    template<>
    inline void loadBE<f64>(f64* n, const u8* bytes) {
        u64 tmp;
        loadBE(&tmp, bytes);
        *n = bit_cast<f64>(tmp);
    }

    // Store Little Endian //

    template<typename T>
    inline void storeLE(T n, u8* bytes) {
        static_assert(std::is_trivially_copyable<T>::value);
#if SPIDER_BIG_ENDIAN
        T tmp = byteswap(n);
        std::memcpy(bytes, &tmp, sizeof(T));
#endif
#if SPIDER_LITTLE_ENDIAN
        std::memcpy(bytes, &n, sizeof(T));
#endif
#if !SPIDER_BIG_ENDIAN && !SPIDER_LITTLE_ENDIAN
        for (size_t i = 0; i < sizeof(T); ++i) {
            bytes[i] = static_cast<u8>(
                (static_cast<std::make_unsigned_t<T>>(n) >> (i * 8)) & 0xFF
            );
        }
#endif
    }

    template<>
    inline void storeLE<f32>(f32 n, u8* bytes) {
        u32 tmp = bit_cast<u32>(n);
        storeLE(tmp, bytes);
    }

    template<>
    inline void storeLE<f64>(f64 n, u8* bytes) {
        u64 tmp = bit_cast<u64>(n);
        storeLE(tmp, bytes);
    }

    // Load Little Endian //

    template<typename T>
    inline void loadLE(T* n, const u8* bytes) {
        static_assert(std::is_trivially_copyable<T>::value);
#if SPIDER_BIG_ENDIAN
        T tmp;
        std::memcpy(&tmp, bytes, sizeof(T));
        *n = byteswap(tmp);
#endif
#if SPIDER_LITTLE_ENDIAN
        std::memcpy(n, bytes, sizeof(T));
#endif
#if !SPIDER_BIG_ENDIAN && !SPIDER_LITTLE_ENDIAN
        using U = std::make_unsigned_t<T>;
        U result = 0;
        for (size_t i = 0; i < sizeof(T); ++i) {
            result |= static_cast<U>(bytes[i]) << (i * 8);
        }
        *n = static_cast<T>(result);
#endif
    }

    template<>
    inline void loadLE<f32>(f32* n, const u8* bytes) {
        u32 tmp;
        loadLE(&tmp, bytes);
        *n = bit_cast<f32>(tmp);
    }

    template<>
    inline void loadLE<f64>(f64* n, const u8* bytes) {
        u64 tmp;
        loadLE(&tmp, bytes);
        *n = bit_cast<f64>(tmp);
    }

}

