// Copyright (c) 2024, Eugene Gershnik
// SPDX-License-Identifier: BSD-3-Clause

#ifndef HEADER_MODERN_UUID_COMMON_H_INCLUDED
#define HEADER_MODERN_UUID_COMMON_H_INCLUDED

// Config macros:
//
// The following macros must be defined identically when using and building this library:
//
// MUUID_MULTITHREADED - auto-detected. Set to 1 to force multi-threaded build and 0 to force single threaded 1
// MUUID_USE_EXCEPTIONS - auto-detected. Set to 1 to force usage of exceptions and 0 to force not using them
// MUUID_SHARED - set to 1 when using/building a shared library version of the library
//
// The following macro should be set when building the library itself but not when using it:
//
// MUUID_BUILDING_MUUID - set 1 if building the library itself. 

#include <cstdint>
#include <cstring>
#include <cstdio>
#include <climits>

#include <concepts>
#include <compare>
#include <array>
#include <span>
#include <string_view>
#include <optional>
#include <limits>
#include <chrono>
#include <numeric>
#include <algorithm>
#include <istream>
#include <ostream>

#if !defined(MUUID_MULTITHREADED)
    #if defined(_MSC_VER) && !defined(_MT)
        #define MUUID_MULTITHREADED 0
    #elif defined(_LIBCPP_VERSION) && (defined(_LIBCPP_HAS_NO_THREADS) || defined(_LIBCPP_HAS_THREADS) && !_LIBCPP_HAS_THREADS)
        #define MUUID_MULTITHREADED 0
    #elif defined(__GLIBCXX__) && !_GLIBCXX_HAS_GTHREADS
        #define MUUID_MULTITHREADED 0
    #elif !__has_include(<thread>) || !__has_include(<mutex>) || !__has_include(<atomic>)
        #define MUUID_MULTITHREADED 0
    #else
        #define MUUID_MULTITHREADED 1
    #endif
#endif

#if !defined(MUUID_USE_EXCEPTIONS)
    #if defined(__GNUC__) && !defined(__EXCEPTIONS)
        #define MUUID_USE_EXCEPTIONS 0
    #elif defined(__clang__) && !defined(__cpp_exceptions)
        #define MUUID_USE_EXCEPTIONS 0
    #elif defined(_MSC_VER) && !_HAS_EXCEPTIONS 
        #define MUUID_USE_EXCEPTIONS 0
    #else
        #define MUUID_USE_EXCEPTIONS 1
    #endif
#endif

#if MUUID_SHARED
    #if defined(_WIN32) || defined(_WIN64)
        #if MUUID_BUILDING_MUUID
            #define MUUID_EXPORTED __declspec(dllexport)
        #else
            #define MUUID_EXPORTED __declspec(dllimport)
        #endif
    #elif defined(__GNUC__)
        #define MUUID_EXPORTED [[gnu::visibility("default")]]
    #else
        #define MUUID_EXPORTED
    #endif
#else
    #define MUUID_EXPORTED
#endif


//See https://github.com/llvm/llvm-project/issues/77773 for the sad story of how feature test
//macros are useless with libc++
#if (__cpp_lib_format >= 201907L || (defined(_LIBCPP_VERSION) && _LIBCPP_VERSION >= 170000)) && __has_include(<format>)

    #define MUUID_SUPPORTS_STD_FORMAT 1

#endif

#if defined(FMT_VERSION) && FMT_VERSION >= 60000 && defined(FMT_THROW)

    #define MUUID_SUPPORTS_FMT_FORMAT 1

#endif

#if MUUID_USE_FMT && !MUUID_SUPPORTS_FMT_FORMAT

    #error "MUUID_USE_FMT is requested but fmt library (of version >= 6.0) is not detected. Did you forget to include <fmt/format.h> before this header?"

#endif

#if MUUID_SUPPORTS_STD_FORMAT
    #include <format>
#endif

#if defined(__cpp_lib_bit_cast)
    #define MUUID_BIT_CAST(x, y) std::bit_cast<T>(y)
#elif defined(__has_builtin)
    #if __has_builtin(__builtin_bit_cast)
        #define MUUID_BIT_CAST(x, y) __builtin_bit_cast(T, y)
    #endif
#endif

#if defined(_MSC_VER) && !defined(__clang__)

    #if defined(_M_ARM64) || defined(_M_ARM) || defined(_M_ARM64EC)
        #include <intrin.h>
        #define MUUID_THREAD_YIELD __yield()
    #elif defined(_M_X64) || defined(_M_IX86)
        #include <emmintrin.h>
        #define MUUID_THREAD_YIELD _mm_pause()
    #endif

#elif defined(__clang__) || defined(__GNUC__)

    #if defined(__x86_64__) || defined(__i386__)
        #define MUUID_THREAD_YIELD __builtin_ia32_pause()
    #elif defined(__aarch64__) || defined(__arm__)
        #define MUUID_THREAD_YIELD  asm volatile("yield" ::: "memory")
    #endif

#endif

#if defined(_MSC_VER) && !defined(__clang__)
    #define MUUID_IGNORE_UNREACHABLE_BEGIN \
        __pragma(warning(push)) __pragma(warning(disable: 4702))
    #define MUUID_IGNORE_UNREACHABLE_END \
        __pragma(warning(pop))
#else
    #define MUUID_IGNORE_UNREACHABLE_BEGIN
    #define MUUID_IGNORE_UNREACHABLE_END
#endif

namespace muuid
{
    namespace impl {
        template<class T, size_t Extent>
        std::true_type is_span_helper(std::span<T, Extent> * x);

        std::false_type is_span_helper(...);

        template<class T>
        constexpr bool is_span = decltype(is_span_helper((T *)nullptr))::value;

        template<class T>
        concept byte_like = std::is_standard_layout_v<T> && 
                            sizeof(T) == sizeof(uint8_t) && 
        requires {
            static_cast<T>(uint8_t{});
            static_cast<uint8_t>(T{});
        };

        static_assert(byte_like<char>);
        static_assert(byte_like<unsigned char>);
        static_assert(byte_like<signed char>);
        static_assert(byte_like<std::byte>);

        template<class T>
        concept char_like = std::is_same_v<T, char> || std::is_same_v<T, wchar_t> ||
                            std::is_same_v<T, char8_t> || std::is_same_v<T, char16_t> || std::is_same_v<T, char32_t>;

        void invalid_constexpr_call(const char *);

        #if MUUID_USE_EXCEPTIONS
            #define MUUID_THROW(x) throw x
        #else
            [[noreturn]] inline void fail(const char* message) {
                fprintf(stderr, "muuid: fatal error: %s", message);
                abort();
            }
            #define MUUID_THROW(x) ::muuid::impl::fail((x).what())
        #endif

        template<size_t Val>
        requires(Val > 0 && Val <= 128)
        struct ct_log2 {
            static constexpr size_t value = []() constexpr {
                if constexpr (Val == 1) return 0;
                else if constexpr (Val < 4) return 1;
                else if constexpr (Val < 8) return 2;
                else if constexpr (Val < 16) return 3;
                else if constexpr (Val < 32) return 4;
                else if constexpr (Val < 64) return 5;
                else if constexpr (Val < 128) return 6;
                return 7;
            }();
        };

        template<class T, size_t N>
        struct ct_string {
            T chars[N];

            constexpr auto size() const -> size_t { return N - 1; }

            constexpr ct_string(const T (&src)[N]) {
                std::copy(src, src + N, this->chars);
            }

            constexpr ct_string(const T el) {
                std::fill(this->chars, this->chars + N - 1, el);
                this->chars[N - 1] = 0;
            }
        };
        template<class T, size_t N>
        ct_string(const T (&src)[N]) -> ct_string<T, N>;

        template<std::same_as<size_t> S>
        constexpr size_t hash_combine(S prev, S next) {
            constexpr auto digits = std::numeric_limits<S>::digits;
            static_assert(digits == 64 || digits == 32);
            
            if constexpr (digits == 64) {
                S x = prev + 0x9e3779b9 + next;
                const S m = 0xe9846af9b1a615d;
                x ^= x >> 32;
                x *= m;
                x ^= x >> 32;
                x *= m;
                x ^= x >> 28;
                return x;
            } else {
                S x = prev + 0x9e3779b9 + next;
                const S m1 = 0x21f0aaad;
                const S m2 = 0x735a2d97;
                x ^= x >> 16;
                x *= m1;
                x ^= x >> 15;
                x *= m2;
                x ^= x >> 15;
                return x;
            }
        }

        template<impl::byte_like Byte, class T>
        constexpr const Byte * read_bytes(const Byte * bytes, T & val) noexcept {
            T tmp = uint8_t(*bytes++);
            for(unsigned i = 0; i < sizeof(T) - 1; ++i)
                tmp = (tmp << 8) | uint8_t(*bytes++);
            val = tmp;
            return bytes;
        }

        template<impl::byte_like Byte, class T>
        constexpr Byte * write_bytes(T val, Byte * bytes) noexcept {
            bytes[sizeof(T) - 1] = Byte(static_cast<uint8_t>(val));
            if constexpr (sizeof(T) > 1) {
                for(unsigned i = 1; i != sizeof(T); ++i) {
                    val >>= 8;
                    bytes[sizeof(T) - i - 1] = Byte(static_cast<uint8_t>(val));
                }
            }
            return bytes + sizeof(T);
        }

        template<impl::byte_like Byte, std::integral T>
        constexpr const Byte * reinterpret_bytes(const Byte * bytes, T & val) noexcept {
            if (!std::is_constant_evaluated()) {
                memcpy(&val, bytes, sizeof(T));
            } else {
            #if MUUID_HAVE_BIT_CAST
                    Byte cpp_is_dumb[sizeof(T)] = {};
                    for(size_t i = 0; i < sizeof(T); ++i)
                        cpp_is_dumb[i] = bytes[i];
                    val = MUUID_BIT_CAST(T, cpp_is_dumb);
            #elif defined(__BYTE_ORDER__) || defined(_MSC_VER)
                    val = 0;
                    #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ || defined(_MSC_VER)
                        for(size_t i = sizeof(T); i > 0; --i)
                            val = (val << 8) | T(uint8_t(bytes[i - 1]));
                    #else
                        for(size_t i = 0; i < sizeof(T); ++i)
                            val = (val << 8) | T(uint8_t(bytes[i]));
                    #endif
            #else
                    #error "Your compiler and library are too ancient, please upgrade"
            #endif
            }

            return bytes + sizeof(T);
        }

        template<size_t Offset, size_t Size, impl::byte_like Byte, std::integral T>
        constexpr const Byte * reinterpret_bytes_partial(const Byte * bytes, T & val) noexcept {
            static_assert(Offset < sizeof(T), "Offset must be less than size of T");
            static_assert(Size <= sizeof(T) - Offset, "Offset+Size must be less or equal to size of T");

            if (!std::is_constant_evaluated()) {
                val = 0;
                memcpy((uint8_t *)&val + Offset, bytes, Size);
            } else {
                #if defined(MUUID_BIT_CAST)
                        Byte cpp_is_dumb[sizeof(T)] = {};
                        for(size_t i = 0; i < Size; ++i)
                            cpp_is_dumb[i + Offset] = bytes[i];
                        val = MUUID_BIT_CAST(T, cpp_is_dumb);
                #elif defined(__BYTE_ORDER__) || defined(_MSC_VER)
                        val = 0;
                        #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ || defined(_MSC_VER)
                            T shift = T(Offset * CHAR_BIT);
                            for(size_t i = 0; i < Size; ++i, shift += CHAR_BIT)
                                val |= T(uint8_t(bytes[i])) << shift;
                        #else
                            T shift = T((sizeof(T)-Offset-1) * CHAR_BIT);
                            for(size_t i = 0; i < Size; ++i, shift -= CHAR_BIT)
                                val |= T(uint8_t(bytes[i])) << shift;
                        #endif
                #else
                        #error "Your compiler and library are too ancient, please upgrade"
                #endif
            }
            
            return bytes + Size;
        }

        template<size_t BitsPerByte, size_t PackedBytes>
        requires(BitsPerByte > 0 && BitsPerByte < 8 && PackedBytes > 0)
        class bit_packer {
            static constexpr size_t bits_per_byte = BitsPerByte;
            static constexpr size_t total_bits = PackedBytes * 8;
            static constexpr auto remainder = bit_packer::total_bits % BitsPerByte;
            static constexpr size_t unpacked_bytes = (bit_packer::total_bits / BitsPerByte) + (remainder != 0);
            static constexpr size_t packed_bytes = PackedBytes;

            static constexpr size_t units = (bit_packer::total_bits / 64) + ((bit_packer::total_bits % 64) != 0);

        public:
            constexpr bit_packer() noexcept = default;

            template<impl::byte_like Byte>
            constexpr bit_packer(std::span<Byte, bit_packer::packed_bytes> src) noexcept {
                for (size_t src_idx = 0; src_idx != bit_packer::packed_bytes; ++src_idx) {
                    uint64_t carry = uint8_t(src[src_idx]);
                    for(size_t i = 0; i < bit_packer::units; ++i) {
                        auto val = this->m_units[i];
                        uint64_t next_carry = val >> 56;
                        val <<= 8;
                        val |= carry;
                        this->m_units[i] = val;
                        carry = next_carry;
                    }
                }
            }

            constexpr void drain(std::span<uint8_t, bit_packer::packed_bytes> dst) noexcept {
                for (size_t dst_idx = bit_packer::packed_bytes; dst_idx != 0; --dst_idx) {
                    uint64_t carry = 0;
                    for(size_t i = bit_packer::units; i != 0 ; --i) {
                        uint64_t val = this->m_units[i - 1];
                        uint64_t next_carry = val & 0xFF;
                        val >>= 8;
                        val |= (carry << 56);
                        this->m_units[i - 1] = val;
                        carry = next_carry;
                    }
                    dst[dst_idx - 1] = uint8_t(carry);
                }
            }

            constexpr void push(uint8_t b) noexcept {
                uint64_t carry = b;
                for(size_t i = 0; i < bit_packer::units; ++i) {
                    auto val = this->m_units[i];
                    uint64_t next_carry = val >> (64 - bit_packer::bits_per_byte);
                    val <<= bit_packer::bits_per_byte;
                    val |= carry;
                    this->m_units[i] = val;
                    carry = next_carry;
                }
            }

            constexpr uint8_t pop() noexcept {
                uint64_t carry = 0;
                for(size_t i = bit_packer::units; i != 0 ; --i) {
                    auto val = this->m_units[i - 1];
                    uint64_t next_carry = val & ((1 << bit_packer::bits_per_byte) - 1);
                    val >>= bit_packer::bits_per_byte;
                    val |= (carry << (64 - bit_packer::bits_per_byte));
                    this->m_units[i - 1] = val;
                    carry = next_carry;
                }
                return uint8_t(carry);
            }
        private:
            std::array<uint64_t, bit_packer::units> m_units{};
        };
    }


    /// How to generate node_id
    enum class node_id {
        /// Try to obtain system MAC address and fall back on random value otherwise
        detect_system,
        /// Generate random value
        generate_random
    };
    
    /**
     * Sets how to generate node id values 
     * 
     * Node ID is used in uuid::generate_time_based() and cuid2::generate() 
     * (to provide host fingerprint).
     * This call affects all subsequent calls to those functions.
     * 
     * @returns the generated node id. You can save it somewhere and then use the other overload of
     * set_node_id on subsequent runs to ensure one fixed node_id
     */
    MUUID_EXPORTED auto set_node_id(node_id type) -> std::array<uint8_t, 6>;
    /** 
     * Sets a specific node id to use for uuid::generate_time_based()
     * 
     * This call affects all subsequent calls to those functions
     */
    MUUID_EXPORTED void set_node_id(std::span<const uint8_t, 6> id);

    /// Callback interface to handle persistence of clock data
    template<class Data>
    class generic_clock_persistence {
    public:
        /// Clock persistence data
        using data = Data;

        /**
         * Per thread persistence callback.
         * 
         * All methods of this class are only accessed from a single thread
         */
        class per_thread {
        public:
            /**
             * Called when this object is no longer used. 
             * 
             * It is safe to dispose of it (e.g. do `delete this` for example) when this is called
             */
            virtual void close() noexcept = 0;
    
            /// Lock access to persistent data against other threads/processes
            virtual void lock() = 0;
            /// Unlock access to persistent data against other threads/processes
            virtual void unlock() = 0;
    
            /**
             * Load persistent data if any
             * 
             * This is called once after this object is returned from get_for_current_thread().
             * The call happens inside a lock() call.
             * 
             * @returns `true` if data was loaded, false otherwise
             */
            virtual bool load(data & d) = 0;
            
            /**
             * Save persistent data if desired
             * 
             * This can be called multiple times (whenever UUID using the clock is generated).
             * The call happens inside a lock() call.
             */
            virtual void store(const data & d) = 0;
        protected:
            per_thread() noexcept = default;
            ~per_thread() noexcept = default;
            per_thread(const per_thread &) noexcept = default;
            per_thread & operator=(const per_thread &) noexcept = default;
        };
    public:
        /**
         * Return per_thread object for the calling thread.
         * 
         * This could be either a newly allocated object or some resused one
         * depending on your implementation.
         * 
         * The return type is reference rather than pointer because you are not
         * allowed to return nullptr.
         */
        virtual per_thread & get_for_current_thread() = 0;

        /**
         * Increment the reference count for this interface.
         * 
         * The object must remain alive as long as reference count is greater than 0
         */
        virtual void add_ref() noexcept = 0;
        /**
         * Decrement the reference count for this interface.
         * 
         * The object must remain alive as long as reference count is greater than 0
         */
        virtual void sub_ref() noexcept = 0;
    protected:
        generic_clock_persistence() noexcept = default;
        ~generic_clock_persistence() noexcept = default;
        generic_clock_persistence(const generic_clock_persistence &) noexcept = default;
        generic_clock_persistence & operator=(const generic_clock_persistence &) noexcept = default;
    };
}

#endif
