// Copyright (C) 2026 Kiyotsugu Arai
// SPDX-License-Identifier: LGPL-3.0-or-later

// endian_utils.hpp
// Endian conversion utilities
//
// This file provides utility functions that support exchanging data between
// environments with different endianness.
//
// Main features:
// - Endianness detection
// - Byte-swap functions
// - Cross-platform conversion helpers

#ifndef SANGI_ENDIAN_UTILS_HPP
#define SANGI_ENDIAN_UTILS_HPP

#include <cstdint>
#include <cstring>
#include <type_traits>

// Compiler-dependent endianness detection
#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && defined(__ORDER_BIG_ENDIAN__)
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define SANGI_LITTLE_ENDIAN 1
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
#define SANGI_BIG_ENDIAN 1
#else
#error "Unknown endianness"
#endif
#elif defined(_MSC_VER) // Microsoft Visual C++ is always little-endian
#define SANGI_LITTLE_ENDIAN 1
#else
  // Runtime detection fallback
#warning "Compile-time endianness detection not available, using runtime detection"
#define SANGI_RUNTIME_ENDIAN_DETECTION 1
#endif

namespace sangi {
    namespace endian {

        /**
         * @brief Endianness enumeration
         */
        enum class Endianness {
            Little,  ///< Little-endian (least significant byte first)
            Big      ///< Big-endian (most significant byte first)
        };

        /**
         * @brief Endianness of the current platform
         */
#if defined(SANGI_LITTLE_ENDIAN)
        constexpr Endianness platform_endianness = Endianness::Little;
#elif defined(SANGI_BIG_ENDIAN)
        constexpr Endianness platform_endianness = Endianness::Big;
#else
         // Runtime detection required
        inline Endianness detect_platform_endianness() {
            const uint16_t test = 0x0102;
            const uint8_t* ptr = reinterpret_cast<const uint8_t*>(&test);
            return (ptr[0] == 0x02) ? Endianness::Little : Endianness::Big;
        }
        const Endianness platform_endianness = detect_platform_endianness();
#endif

        /**
         * @brief Reverse the byte order of a 16-bit integer
         * @param value Value to reverse
         * @return The value with its bytes reversed
         */
        inline uint16_t byte_swap(uint16_t value) {
            return ((value & 0x00FF) << 8) | ((value & 0xFF00) >> 8);
        }

        /**
         * @brief Reverse the byte order of a 32-bit integer
         * @param value Value to reverse
         * @return The value with its bytes reversed
         */
        inline uint32_t byte_swap(uint32_t value) {
            return ((value & 0x000000FF) << 24) |
                ((value & 0x0000FF00) << 8) |
                ((value & 0x00FF0000) >> 8) |
                ((value & 0xFF000000) >> 24);
        }

        /**
         * @brief Reverse the byte order of a 64-bit integer
         * @param value Value to reverse
         * @return The value with its bytes reversed
         */
        inline uint64_t byte_swap(uint64_t value) {
            return ((value & 0x00000000000000FFULL) << 56) |
                ((value & 0x000000000000FF00ULL) << 40) |
                ((value & 0x0000000000FF0000ULL) << 24) |
                ((value & 0x00000000FF000000ULL) << 8) |
                ((value & 0x000000FF00000000ULL) >> 8) |
                ((value & 0x0000FF0000000000ULL) >> 24) |
                ((value & 0x00FF000000000000ULL) >> 40) |
                ((value & 0xFF00000000000000ULL) >> 56);
        }

        /**
         * @brief Reverse the byte order of a signed integer (uses the unsigned version)
         * @param value Value to reverse
         * @return The value with its bytes reversed
         */
        template<typename T>
            requires std::is_signed_v<T>
        inline T byte_swap(T value) {
            using UT = typename std::make_unsigned<T>::type;
            return static_cast<T>(byte_swap(static_cast<UT>(value)));
        }

        /**
         * @brief Convert from a specific endianness to the current platform's endianness
         * @param value Value to convert
         * @param from Source endianness
         * @return Converted value
         */
        template<typename T>
        inline T to_native(T value, Endianness from) {
            if (from == platform_endianness) {
                return value;
            }
            return byte_swap(value);
        }

        /**
         * @brief Convert from the current platform's endianness to a specific endianness
         * @param value Value to convert
         * @param to Destination endianness
         * @return Converted value
         */
        template<typename T>
        inline T from_native(T value, Endianness to) {
            if (to == platform_endianness) {
                return value;
            }
            return byte_swap(value);
        }

        /**
         * @brief Convert from little-endian to the current platform's endianness
         * @param value Value to convert
         * @return Converted value
         */
        template<typename T>
        inline T from_little_endian(T value) {
            return to_native(value, Endianness::Little);
        }

        /**
         * @brief Convert from big-endian to the current platform's endianness
         * @param value Value to convert
         * @return Converted value
         */
        template<typename T>
        inline T from_big_endian(T value) {
            return to_native(value, Endianness::Big);
        }

        /**
         * @brief Convert from the current platform's endianness to little-endian
         * @param value Value to convert
         * @return Converted value
         */
        template<typename T>
        inline T to_little_endian(T value) {
            return from_native(value, Endianness::Little);
        }

        /**
         * @brief Convert from the current platform's endianness to big-endian
         * @param value Value to convert
         * @return Converted value
         */
        template<typename T>
        inline T to_big_endian(T value) {
            return from_native(value, Endianness::Big);
        }

        /**
         * @brief Reverse the byte order of a buffer
         * @param buffer Buffer to reverse
         * @param size Buffer size in bytes
         * @param element_size Size of each element in bytes
         */
        inline void byte_swap_buffer(void* buffer, size_t size, size_t element_size) {
            if (buffer == nullptr || size == 0 || element_size <= 1) {
                return;
            }

            uint8_t* bytes = static_cast<uint8_t*>(buffer);

            for (size_t i = 0; i < size; i += element_size) {
                for (size_t j = 0; j < element_size / 2; ++j) {
                    std::swap(bytes[i + j], bytes[i + element_size - 1 - j]);
                }
            }
        }

    } // namespace endian
} // namespace sangi

#endif // SANGI_ENDIAN_UTILS_HPP
