#pragma once

#include "typetraits.h"
#include "yexception.h"

#include <util/system/compat.h>
#include <util/system/type_name.h>
#include <util/system/unaligned_mem.h>
#include <util/system/yassert.h>

#include <cstdlib>

template <class T, class F>
static inline T VerifyDynamicCast(F f) {
    if (!f) {
        return nullptr;
    }

    T ret = dynamic_cast<T>(f);

    Y_VERIFY(ret, "verify cast failed");

    return ret;
}

#if !defined(NDEBUG)
    #define USE_DEBUG_CHECKED_CAST
#endif

namespace NPrivate {
    template <typename T, typename F>
    static T DynamicCast(F f) {
        return dynamic_cast<T>(f);
    }
}

/*
 * replacement for dynamic_cast(dynamic_cast in debug mode, else static_cast)
 */
template <class T, class F>
static inline T CheckedCast(F f) {
#if defined(USE_DEBUG_CHECKED_CAST)
    return VerifyDynamicCast<T>(f);
#else
    /* Make sure F is polymorphic.
     * Without this cast, CheckedCast with non-polymorphic F
     * incorrectly compiled without error in release mode.
     */
    {
        auto&& x = &::NPrivate::DynamicCast<T, F>;

        (void)x;
    }

    return static_cast<T>(f);
#endif // USE_DEBUG_CHECKED_CAST
}

/*
 * be polite
 */
#undef USE_DEBUG_CHECKED_CAST

template <bool isUnsigned>
class TInteger;

template <>
class TInteger<true> {
public:
    template <class TUnsigned>
    static constexpr bool IsNegative(TUnsigned) noexcept {
        return false;
    }
};

template <>
class TInteger<false> {
public:
    template <class TSigned>
    static constexpr bool IsNegative(const TSigned value) noexcept {
        return value < 0;
    }
};

template <class TType>
constexpr bool IsNegative(const TType value) noexcept {
    return TInteger<std::is_unsigned<TType>::value>::IsNegative(value);
}

namespace NPrivate {
    template <class T>
    using TUnderlyingTypeOrSelf = typename std::conditional<
        std::is_enum<T>::value,
        std::underlying_type<T>, // Lazy evaluatuion: do not call ::type here, because underlying_type<T> is undefined if T is not an enum.
        std::enable_if<true, T>  // Wrapping T in a class, that has member ::type typedef.
        >::type::type;           // Left ::type is for std::conditional, right ::type is for underlying_type/enable_if

    template <class TSmall, class TLarge>
    struct TSafelyConvertible {
        using TSmallInt = TUnderlyingTypeOrSelf<TSmall>;
        using TLargeInt = TUnderlyingTypeOrSelf<TLarge>;

        static constexpr bool Result = std::is_integral<TSmallInt>::value && std::is_integral<TLargeInt>::value &&
                                       ((std::is_signed<TSmallInt>::value == std::is_signed<TLargeInt>::value && sizeof(TSmallInt) >= sizeof(TLargeInt)) ||
                                        (std::is_signed<TSmallInt>::value && sizeof(TSmallInt) > sizeof(TLargeInt)));
    };
}

template <class TSmallInt, class TLargeInt>
constexpr std::enable_if_t<::NPrivate::TSafelyConvertible<TSmallInt, TLargeInt>::Result, TSmallInt> SafeIntegerCast(TLargeInt largeInt) noexcept {
    return static_cast<TSmallInt>(largeInt);
}

template <class TSmall, class TLarge>
inline std::enable_if_t<!::NPrivate::TSafelyConvertible<TSmall, TLarge>::Result, TSmall> SafeIntegerCast(TLarge largeInt) {
    using TSmallInt = ::NPrivate::TUnderlyingTypeOrSelf<TSmall>;
    using TLargeInt = ::NPrivate::TUnderlyingTypeOrSelf<TLarge>;

    if (std::is_unsigned<TSmallInt>::value && std::is_signed<TLargeInt>::value) {
        if (IsNegative(largeInt)) {
            ythrow TBadCastException() << "Conversion '" << TypeName<TLarge>() << '{' << TLargeInt(largeInt) << "}' to '"
                                       << TypeName<TSmallInt>()
                                       << "', negative value converted to unsigned";
        }
    }

    TSmallInt smallInt = TSmallInt(largeInt);

    if (std::is_signed<TSmallInt>::value && std::is_unsigned<TLargeInt>::value) {
        if (IsNegative(smallInt)) {
            ythrow TBadCastException() << "Conversion '" << TypeName<TLarge>() << '{' << TLargeInt(largeInt) << "}' to '"
                                       << TypeName<TSmallInt>()
                                       << "', positive value converted to negative";
        }
    }

    if (TLargeInt(smallInt) != largeInt) {
        ythrow TBadCastException() << "Conversion '" << TypeName<TLarge>() << '{' << TLargeInt(largeInt) << "}' to '"
                                   << TypeName<TSmallInt>() << "', loss of data";
    }

    return static_cast<TSmall>(smallInt);
}

template <class TSmallInt, class TLargeInt>
inline TSmallInt IntegerCast(TLargeInt largeInt) noexcept {
    try {
        return SafeIntegerCast<TSmallInt>(largeInt);
    } catch (const yexception& exc) {
        Y_FAIL("IntegerCast: %s", exc.what());
    }
}

/* Convert given enum value to its underlying type. This is just a shortcut for
 * `static_cast<std::underlying_type_t<EEnum>>(enum_)`.
 */
template <typename T>
constexpr std::underlying_type_t<T> ToUnderlying(const T enum_) noexcept {
    return static_cast<std::underlying_type_t<T>>(enum_);
}

// std::bit_cast from c++20
template <class TTarget, class TSource>
TTarget BitCast(const TSource& source) {
    static_assert(sizeof(TSource) == sizeof(TTarget), "Size mismatch");
    static_assert(std::is_trivially_copyable<TSource>::value, "TSource is not trivially copyable");
    static_assert(std::is_trivial<TTarget>::value, "TTarget is not trivial");

    // Support volatile qualifiers.
    // ReadUnaligned does not work with volatile pointers, so cast away
    // volatileness beforehand.
    using TNonvolatileSource = std::remove_volatile_t<TSource>;
    using TNonvolatileTarget = std::remove_volatile_t<TTarget>;

    return ReadUnaligned<TNonvolatileTarget>(&const_cast<const TNonvolatileSource&>(source));
}