#pragma once

#include "va_args.h"

#include <util/system/defaults.h>

#include <iterator>
#include <type_traits>
#include <stlfwd>

#if _LIBCPP_STD_VER >= 17
template <bool B>
using TBoolConstant = std::bool_constant<B>;
#else
template <bool B>
struct TBoolConstant: std::integral_constant<bool, B> {};
#endif

#if _LIBCPP_STD_VER >= 17
template <class B>
using TNegation = std::negation<B>;
#else
template <class B>
struct TNegation: ::TBoolConstant<!bool(B::value)> {};
#endif

namespace NPrivate {
    template <class... Bs>
    constexpr bool ConjunctionImpl() {
        bool bs[] = {(bool)Bs::value...};
        for (auto b : bs) {
            if (!b) {
                return false;
            }
        }
        return true;
    }

    template <class... Bs>
    constexpr bool DisjunctionImpl() {
        bool bs[] = {(bool)Bs::value...};
        for (auto b : bs) {
            if (b) {
                return true;
            }
        }
        return false;
    }
}

#if _LIBCPP_STD_VER >= 17 && !defined(_MSC_VER)
// Disable std::conjunction for MSVC by analogy with std::disjunction.
template <class... Bs>
using TConjunction = std::conjunction<Bs...>;
#else
template <class... Bs>
struct TConjunction: ::TBoolConstant<::NPrivate::ConjunctionImpl<Bs...>()> {};
#endif

#if _LIBCPP_STD_VER >= 17 && !defined(_MSC_VER)
// Disable std::disjunction for MSVC.
// It reduces build time (500 -> 20 seconds) and memory consumption (20 GB -> less than 1 GB)
// for some files (notably search/dssm_boosting/dssm_boosting_calcer.cpp).
template <class... Bs>
using TDisjunction = std::disjunction<Bs...>;
#else
template <class... Bs>
struct TDisjunction: ::TBoolConstant<::NPrivate::DisjunctionImpl<Bs...>()> {};
#endif

#if _LIBCPP_STD_VER >= 17
template <class... Bs>
using TVoidT = std::void_t<Bs...>;
#else
template <class...>
using TVoidT = void;
#endif

template <class T>
struct TPodTraits {
    enum {
        IsPod = false
    };
};

template <class T>
class TTypeTraitsBase {
public:
    static constexpr bool IsPod = (TPodTraits<std::remove_cv_t<T>>::IsPod || std::is_scalar<std::remove_all_extents_t<T>>::value ||
                                   TPodTraits<std::remove_cv_t<std::remove_all_extents_t<T>>>::IsPod);
};

namespace NPrivate {
    template <class T>
    struct TIsSmall: std::integral_constant<bool, (sizeof(T) <= sizeof(void*))> {};
}

template <class T>
class TTypeTraits: public TTypeTraitsBase<T> {
    using TBase = TTypeTraitsBase<T>;

    /*
     * can be effectively passed to function as value
     */
    static constexpr bool IsValueType = std::is_scalar<T>::value ||
                                        std::is_array<T>::value ||
                                        std::is_reference<T>::value ||
                                        (TBase::IsPod &&
                                         std::conditional_t<
                                             std::is_function<T>::value,
                                             std::false_type,
                                             ::NPrivate::TIsSmall<T>>::value);

public:
    /*
     * can be used in function templates for effective parameters passing
     */
    using TFuncParam = std::conditional_t<IsValueType, T, const std::remove_reference_t<T>&>;
};

template <>
class TTypeTraits<void>: public TTypeTraitsBase<void> {};

#define Y_DECLARE_PODTYPE(type) \
    template <>                 \
    struct TPodTraits<type> {   \
        enum { IsPod = true };  \
    }

#define Y_HAS_MEMBER_IMPL_2(method, name)                                                 \
    template <class T>                                                                    \
    struct TClassHas##name {                                                              \
        struct TBase {                                                                    \
            void method();                                                                \
        };                                                                                \
        class THelper: public T, public TBase {                                           \
        public:                                                                           \
            template <class T1>                                                           \
            inline THelper(const T1& = T1()) {                                            \
            }                                                                             \
        };                                                                                \
        template <class T1, T1 val>                                                       \
        class TChecker {};                                                                \
        struct TNo {                                                                      \
            char ch;                                                                      \
        };                                                                                \
        struct TYes {                                                                     \
            char arr[2];                                                                  \
        };                                                                                \
        template <class T1>                                                               \
        static TNo CheckMember(T1*, TChecker<void (TBase::*)(), &T1::method>* = nullptr); \
        static TYes CheckMember(...);                                                     \
        static constexpr bool value =                                                     \
            (sizeof(TYes) == sizeof(CheckMember((THelper*)nullptr)));                     \
    };                                                                                    \
    template <class T, bool isClassType>                                                  \
    struct TBaseHas##name: std::false_type {};                                            \
    template <class T>                                                                    \
    struct TBaseHas##name<T, true>                                                        \
        : std::integral_constant<bool, TClassHas##name<T>::value> {};                     \
    template <class T>                                                                    \
    struct THas##name                                                                     \
        : TBaseHas##name<T, std::is_class<T>::value || std::is_union<T>::value> {}

#define Y_HAS_MEMBER_IMPL_1(name) Y_HAS_MEMBER_IMPL_2(name, name)

/* @def Y_HAS_MEMBER
 *
 * This macro should be used to define compile-time introspection helper classes for template
 * metaprogramming.
 *
 * Macro accept one or two parameters, when used with two parameters e.g. `Y_HAS_MEMBER(xyz, ABC)`
 * will define class `THasABC` with static member `value` of type bool. Usage with one parameter
 * e.g. `Y_HAS_MEMBER(xyz)` will produce the same result as `Y_HAS_MEMBER(xyz, xyz)`.
 *
 * @code
 * #include <type_traits>
 *
 * Y_HAS_MEMBER(push_front, PushFront);
 *
 * template <typename T, typename U>
 * std::enable_if_t<THasPushFront<T>::value, void>
 * PushFront(T& container, const U value) {
 *     container.push_front(x);
 * }
 *
 * template <typename T, typename U>
 * std::enable_if_t<!THasPushFront<T>::value, void>
 * PushFront(T& container, const U value) {
 *     container.insert(container.begin(), x);
 * }
 * @endcode
 */
#define Y_HAS_MEMBER(...) Y_PASS_VA_ARGS(Y_MACRO_IMPL_DISPATCHER_2(__VA_ARGS__, Y_HAS_MEMBER_IMPL_2, Y_HAS_MEMBER_IMPL_1)(__VA_ARGS__))

#define Y_HAS_SUBTYPE_IMPL_2(subtype, name) \
    template <class T, class = void>        \
    struct THas##name: std::false_type {};  \
    template <class T>                      \
    struct THas##name<T, ::TVoidT<typename T::subtype>>: std::true_type {}

#define Y_HAS_SUBTYPE_IMPL_1(name) Y_HAS_SUBTYPE_IMPL_2(name, name)

/* @def Y_HAS_SUBTYPE
 *
 * This macro should be used to define compile-time introspection helper classes for template
 * metaprogramming.
 *
 * Macro accept one or two parameters, when used with two parameters e.g. `Y_HAS_SUBTYPE(xyz, ABC)`
 * will define class `THasABC` with static member `value` of type bool. Usage with one parameter
 * e.g. `Y_HAS_SUBTYPE(xyz)` will produce the same result as `Y_HAS_SUBTYPE(xyz, xyz)`.
 *
 * @code
 * Y_HAS_MEMBER(find, FindMethod);
 * Y_HAS_SUBTYPE(const_iterator, ConstIterator);
 * Y_HAS_SUBTYPE(key_type, KeyType);
 *
 * template <typename T>
 * using TIsAssocCont = std::conditional_t<
 *     THasFindMethod<T>::value && THasConstIterator<T>::value && THasKeyType<T>::value,
 *     std::true_type,
 *     std::false_type,
 * >;
 *
 * static_assert(TIsAssocCont<TVector<int>>::value == false, "");
 * static_assert(TIsAssocCont<THashMap<int>>::value == true, "");
 * @endcode
 */
#define Y_HAS_SUBTYPE(...) Y_PASS_VA_ARGS(Y_MACRO_IMPL_DISPATCHER_2(__VA_ARGS__, Y_HAS_SUBTYPE_IMPL_2, Y_HAS_SUBTYPE_IMPL_1)(__VA_ARGS__))

template <class T1, class T2>
struct TPodTraits<std::pair<T1, T2>> {
    enum {
        IsPod = TTypeTraits<T1>::IsPod && TTypeTraits<T2>::IsPod
    };
};

template <class T>
struct TIsPointerToConstMemberFunction: std::false_type {
};

template <class R, class T, class... Args>
struct TIsPointerToConstMemberFunction<R (T::*)(Args...) const>: std::true_type {
};

template <class R, class T, class... Args>
struct TIsPointerToConstMemberFunction<R (T::*)(Args...) const&>: std::true_type {
};

template <class R, class T, class... Args>
struct TIsPointerToConstMemberFunction<R (T::*)(Args...) const&&>: std::true_type {
};

template <class R, class T, class... Args>
struct TIsPointerToConstMemberFunction<R (T::*)(Args..., ...) const>: std::true_type {
};

template <class R, class T, class... Args>
struct TIsPointerToConstMemberFunction<R (T::*)(Args..., ...) const&>: std::true_type {
};

template <class R, class T, class... Args>
struct TIsPointerToConstMemberFunction<R (T::*)(Args..., ...) const&&>: std::true_type {
};

namespace NPrivate {
    template <template <typename...> class TBase, class TDerived>
    struct TIsBaseOfTemplateHelper {
        template <typename... Ts>
        static constexpr std::true_type Check(const TBase<Ts...>*);

        static constexpr std::false_type Check(...);

        using TType = decltype(Check(std::declval<TDerived*>()));
    };
}

template <template <class...> class T, class U>
struct TIsSpecializationOf: std::false_type {};

template <template <class...> class T, class... Ts>
struct TIsSpecializationOf<T, T<Ts...>>: std::true_type {};

template <template <typename...> class TBase, class TDerived>
using TIsTemplateBaseOf = typename ::NPrivate::TIsBaseOfTemplateHelper<TBase, TDerived>::TType;

/*
 * TDependentFalse is a constant dependent on a template parameter.
 * Use it in static_assert in a false branch of if constexpr to produce a compile error.
 * See an example with dependent_false at https://en.cppreference.com/w/cpp/language/if
 *
 * if constexpr (std::is_same<T, someType1>) {
 * } else if constexpr (std::is_same<T, someType2>) {
 * } else {
 *     static_assert(TDependentFalse<T>, "unknown type");
 * }
 */
template <typename... T>
constexpr bool TDependentFalse = false;

// FIXME: neither nvcc10 nor nvcc11 support using auto in this context
#if defined(__NVCC__)
template <size_t Value>
constexpr bool TValueDependentFalse = false;
#else
template <auto... Values>
constexpr bool TValueDependentFalse = false;
#endif

/*
 * shortcut for std::enable_if_t<...> which checks that T is std::tuple or std::pair
 */
template <class T, class R = void>
using TEnableIfTuple = std::enable_if_t<::TDisjunction<::TIsSpecializationOf<std::tuple, std::decay_t<T>>,
                                                       ::TIsSpecializationOf<std::pair, std::decay_t<T>>>::value,
                                        R>;

namespace NPrivate {
    // To allow ADL with custom begin/end
    using std::begin;
    using std::end;

    template <typename T>
    auto IsIterableImpl(int) -> decltype(
        begin(std::declval<T&>()) != end(std::declval<T&>()),   // begin/end and operator !=
        ++std::declval<decltype(begin(std::declval<T&>()))&>(), // operator ++
        *begin(std::declval<T&>()),                             // operator*
        std::true_type{});

    template <typename T>
    std::false_type IsIterableImpl(...);
}

template <typename T>
using TIsIterable = decltype(NPrivate::IsIterableImpl<T>(0));