/**
 * \file libipc/detect_plat.h
 * \author mutouyun (orz@orzz.org)
 * \brief Define platform detection related interfaces.
 */
#pragma once

/// \brief OS check.

#if defined(WINCE) || defined(_WIN32_WCE)
# define LIBIPC_OS_WINCE
#elif defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || \
     (defined(__x86_64) && defined(__MSYS__))
#define LIBIPC_OS_WIN64
#elif defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || \
      defined(__NT__) || defined(__MSYS__)
# define LIBIPC_OS_WIN32
#elif defined(__FreeBSD__)
# define LIBIPC_OS_FREEBSD
#elif defined(__QNX__) || defined(__QNXNTO__)
# define LIBIPC_OS_QNX
#elif defined(__APPLE__)
# define LIBIPC_OS_APPLE
#elif defined(ANDROID) || defined(__ANDROID__)
# define LIBIPC_OS_ANDROID
#elif defined(__linux__) || defined(__linux)
# define LIBIPC_OS_LINUX
#elif defined(_POSIX_VERSION)
# define LIBIPC_OS_POSIX
#else
# error "This OS is unsupported."
#endif

#if defined(LIBIPC_OS_WIN32) || defined(LIBIPC_OS_WIN64) || \
    defined(LIBIPC_OS_WINCE)
# define LIBIPC_OS_WIN
#endif

/// \brief Compiler check.

#if defined(_MSC_VER)
# define LIBIPC_CC_MSVC      _MSC_VER
# define LIBIPC_CC_MSVC_2015 1900
# define LIBIPC_CC_MSVC_2017 1910
# define LIBIPC_CC_MSVC_2019 1920
# define LIBIPC_CC_MSVC_2022 1930
#elif defined(__GNUC__)
# define LIBIPC_CC_GNUC __GNUC__
# if defined(__clang__)
#   define LIBIPC_CC_CLANG
#endif
#else
# error "This compiler is unsupported."
#endif

/// \brief Instruction set.
/// \see https://sourceforge.net/p/predef/wiki/Architectures/

#if defined(_M_X64) || defined(_M_AMD64) || \
    defined(__x86_64__) || defined(__x86_64) || \
    defined(__amd64__) || defined(__amd64)
# define LIBIPC_INSTR_X64
#elif defined(_M_IA64) || defined(__IA64__) || defined(_IA64) || \
      defined(__ia64__) || defined(__ia64)
# define LIBIPC_INSTR_I64
#elif defined(_M_IX86) || defined(_X86_) || defined(__i386__) || defined(__i386)
# define LIBIPC_INSTR_X86
#elif defined(_M_ARM64) || defined(__arm64__) || defined(__aarch64__)
# define LIBIPC_INSTR_ARM64
#elif defined(_M_ARM) || defined(_ARM) || defined(__arm__) || defined(__arm)
# define LIBIPC_INSTR_ARM32
#else
# error "This instruction set is unsupported."
#endif

#if defined(LIBIPC_INSTR_X86) || defined(LIBIPC_INSTR_X64)
# define LIBIPC_INSTR_X86_64
#elif defined(LIBIPC_INSTR_ARM32) || defined(LIBIPC_INSTR_ARM64)
# define LIBIPC_INSTR_ARM
#endif

/// \brief Byte order.

#if defined(__BYTE_ORDER__)
# define LIBIPC_ENDIAN_BIG   (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
# define LIBIPC_ENDIAN_LIT   (!LIBIPC_ENDIAN_BIG)
#else
# define LIBIPC_ENDIAN_BIG   (0)
# define LIBIPC_ENDIAN_LIT   (1)
#endif

/// \brief C++ version.

#if (__cplusplus >= 202002L) && !defined(LIBIPC_CPP_20)
# define LIBIPC_CPP_20
#endif
#if (__cplusplus >= 201703L) && !defined(LIBIPC_CPP_17)
# define LIBIPC_CPP_17
#endif
#if /*(__cplusplus >= 201402L) &&*/ !defined(LIBIPC_CPP_14)
# define LIBIPC_CPP_14
#endif

#if !defined(LIBIPC_CPP_20) && \
    !defined(LIBIPC_CPP_17) && \
    !defined(LIBIPC_CPP_14)
# error "This C++ version is unsupported."
#endif

/// \brief Feature cross-platform adaptation.

#if defined(LIBIPC_CPP_17)
# define LIBIPC_INLINE_CONSTEXPR inline constexpr
#else
# define LIBIPC_INLINE_CONSTEXPR constexpr
#endif

/// \brief C++ attributes.
/// \see https://en.cppreference.com/w/cpp/language/attributes

#if defined(__has_cpp_attribute)
# if __has_cpp_attribute(fallthrough)
#   define LIBIPC_FALLTHROUGH [[fallthrough]]
# endif
# if __has_cpp_attribute(maybe_unused)
#   define LIBIPC_UNUSED [[maybe_unused]]
# endif
# if __has_cpp_attribute(likely)
#   define LIBIPC_LIKELY(...) (__VA_ARGS__) [[likely]]
# endif
# if __has_cpp_attribute(unlikely)
#   define LIBIPC_UNLIKELY(...) (__VA_ARGS__) [[unlikely]]
# endif
# if __has_cpp_attribute(nodiscard)
#   define LIBIPC_NODISCARD [[nodiscard]]
# endif
# if __has_cpp_attribute(assume)
#   define LIBIPC_ASSUME(...) [[assume(__VA_ARGS__)]]
# endif
#endif

#if !defined(LIBIPC_FALLTHROUGH)
# if defined(LIBIPC_CC_GNUC)
#   define LIBIPC_FALLTHROUGH __attribute__((__fallthrough__))
# else
#   define LIBIPC_FALLTHROUGH
# endif
#endif

#if !defined(LIBIPC_UNUSED)
# if defined(LIBIPC_CC_GNUC)
#   define LIBIPC_UNUSED __attribute__((__unused__))
# elif defined(LIBIPC_CC_MSVC)
#   define LIBIPC_UNUSED __pragma(warning(suppress: 4100 4101 4189))
# else
#   define LIBIPC_UNUSED
# endif
#endif

#if !defined(LIBIPC_LIKELY)
# if defined(__has_builtin)
#   if __has_builtin(__builtin_expect)
#     define LIBIPC_LIKELY(...) (__builtin_expect(!!(__VA_ARGS__), 1))
#   endif
# endif
#endif

#if !defined(LIBIPC_LIKELY)
# define LIBIPC_LIKELY(...) (__VA_ARGS__)
#endif

#if !defined(LIBIPC_UNLIKELY)
# if defined(__has_builtin)
#   if __has_builtin(__builtin_expect)
#     define LIBIPC_UNLIKELY(...) (__builtin_expect(!!(__VA_ARGS__), 0))
#   endif
# endif
#endif

#if !defined(LIBIPC_UNLIKELY)
# define LIBIPC_UNLIKELY(...) (__VA_ARGS__)
#endif

#if !defined(LIBIPC_NODISCARD)
/// \see https://stackoverflow.com/questions/4226308/msvc-equivalent-of-attribute-warn-unused-result
# if defined(LIBIPC_CC_GNUC) && (LIBIPC_CC_GNUC >= 4)
#   define LIBIPC_NODISCARD __attribute__((warn_unused_result))
# elif defined(LIBIPC_CC_MSVC) && (LIBIPC_CC_MSVC >= 1700)
#   define LIBIPC_NODISCARD _Check_return_
# else
#   define LIBIPC_NODISCARD
# endif
#endif

#if !defined(LIBIPC_ASSUME)
# if defined(__has_builtin)
#   if __has_builtin(__builtin_assume)
      /// \see https://clang.llvm.org/docs/LanguageExtensions.html#langext-builtin-assume
#     define LIBIPC_ASSUME(...) __builtin_assume(__VA_ARGS__)
#   elif __has_builtin(__builtin_unreachable)
      /// \see https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html#index-_005f_005fbuiltin_005funreachable
#     define LIBIPC_ASSUME(...) do { if (!(__VA_ARGS__)) __builtin_unreachable(); } while (false)
#   endif
# endif
#endif

#if !defined(LIBIPC_ASSUME)
# if defined(LIBIPC_CC_MSVC)
    /// \see https://learn.microsoft.com/en-us/cpp/intrinsics/assume?view=msvc-140
#   define LIBIPC_ASSUME(...) __assume(__VA_ARGS__)
# else
#   define LIBIPC_ASSUME(...)
# endif
#endif

/// \see https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_exceptions.html
///      https://learn.microsoft.com/en-us/cpp/preprocessor/predefined-macros
///      https://stackoverflow.com/questions/6487013/programmatically-determine-whether-exceptions-are-enabled
#if defined(__cpp_exceptions) && __cpp_exceptions || \
    defined(__EXCEPTIONS) || defined(_CPPUNWIND)
# define LIBIPC_TRY                    try
# define LIBIPC_CATCH(...)             catch (__VA_ARGS__)
# define LIBIPC_THROW($EXCEPTION, ...) throw $EXCEPTION
#else
# define LIBIPC_TRY                    if (true)
# define LIBIPC_CATCH(...)             else if (false)
# define LIBIPC_THROW($EXCEPTION, ...) return __VA_ARGS__
#endif
