// Range v3 library
//
//  Copyright Eric Niebler 2018-present
//
//  Use, modification and distribution is subject to the
//  Boost Software License, Version 1.0. (See accompanying
//  file LICENSE_1_0.txt or copy at
//  http://www.boost.org/LICENSE_1_0.txt)
//
// Project home: https://github.com/ericniebler/range-v3
//

#ifndef RANGES_V3_STD_ITERATOR
#define RANGES_V3_STD_ITERATOR

#if defined(__GNUC__)
#include_next <iterator>
#elif defined(_MSC_VER)
#include <../include/iterator> // HACKHACK
#else
#error "Cannot use range-v3 STL deep integration on this platform."
#endif

#if RANGES_DEEP_STL_INTEGRATION

#include <range/v3/detail/config.hpp>
#include <std/detail/associated_types.hpp>

namespace ranges
{
    template<typename I>
    struct incrementable_traits;

    template<typename I>
    struct readable_traits;

    /// \cond
    namespace detail
    {
        template<typename I>
        typename incrementable_traits<I>::difference_type cpp17_difference_type_(int);
        template<typename I>
        void cpp17_difference_type_(long);

        template<typename I>
        typename I::pointer cpp17_pointer_type_(int);
        template<typename I>
        auto cpp17_pointer_type_(long, I *pi = nullptr) -> decltype(pi->operator->());
        template<typename I>
        void cpp17_pointer_type_(...);

        template<typename I>
        typename I::reference cpp17_reference_type_(int);
        template<typename I>
        auto cpp17_reference_type_(long, I *pi = nullptr) -> decltype(**pi);

        template<typename I>
        auto cpp17_iterator_category_4_(long)
        {
            return std::bidirectional_iterator_tag{};
        }
        // Satisfies Cpp17RandomAccessIterator?
        template<typename I>
        auto cpp17_iterator_category_4_(
            int,
            I *pi = nullptr,
            typename incrementable_traits<I>::difference_type d = 0,
            always_<
                void,
                int[RANGES_IS_SAME(decltype(*pi += d), I &)],
                int[RANGES_IS_SAME(decltype(*pi -= d), I &)],
                int[RANGES_IS_SAME(decltype(*pi + d), I)],
                int[RANGES_IS_SAME(decltype(*pi - d), I)],
                int[RANGES_IS_SAME(decltype(d + *pi), I)],
                int[RANGES_IS_SAME(decltype(*pi - *pi), decltype(d))],
                int[RANGES_IS_SAME(decltype((*pi)[d]), decltype(**pi))],
                decltype(*pi < *pi ? true : false),
                decltype(*pi > *pi ? true : false),
                decltype(*pi <= *pi ? true : false),
                decltype(*pi >= *pi ? true : false)
            > * = nullptr)
        {
            return std::random_access_iterator_tag{};
        }

        template<typename I>
        auto cpp17_iterator_category_3_(long)
        {
            return std::forward_iterator_tag{};
        }
        // Satisfies Cpp17BidirectionalIterator?
        template<typename I>
        auto cpp17_iterator_category_3_(
            int,
            I *pi = nullptr,
            void (*fn)(I const &) = nullptr,
            always_<
                void,
                decltype(fn((*pi)--)), // i-- convertible to I const &
                int[RANGES_IS_SAME(decltype(--*pi), I &)], // --i has type I &
                // *i has the same type as *i--
                int[RANGES_IS_SAME(decltype(**pi), decltype(*(*pi)--))]
            > * = nullptr)
        {
            return cpp17_iterator_category_4_<I>(0);
        }

        template<typename I>
        auto cpp17_iterator_category_2_(long)
        {
            return std::input_iterator_tag{};
        }
        // Satisfies Cpp17ForwardIterator?
        template<typename I>
        auto cpp17_iterator_category_2_(
            int,
            I *pi = nullptr,
            void (*fn)(I const &) = nullptr,
            typename readable_traits<I>::value_type *pv = nullptr,
            typename readable_traits<I>::value_type const *pcv = nullptr,
            always_<
                void,
                decltype(I{}), // Default constructible
                decltype(fn((*pi)++)), // i++ convertible to I const &
                // *i has the same type as *i++
                int[RANGES_IS_SAME(decltype(**pi), decltype(*(*pi)++))],
                // *i is a real reference to value_type
#ifdef RANGES_WORKAROUND_MSVC_793042
                enable_if_t<RANGES_IS_SAME(decltype(**pi), decltype(*pv)) ||
                            RANGES_IS_SAME(decltype(**pi), decltype(*pcv)) ||
                            RANGES_IS_SAME(decltype(**pi), typename readable_traits<I>::value_type &&) ||
                            RANGES_IS_SAME(decltype(**pi), typename readable_traits<I>::value_type const &&)>
#else // ^^^ workaround / no workaround vvv
                int[RANGES_IS_SAME(decltype(**pi), decltype(*pv)) ||
                    RANGES_IS_SAME(decltype(**pi), decltype(*pcv)) ||
                    RANGES_IS_SAME(decltype(**pi), typename readable_traits<I>::value_type &&) ||
                    RANGES_IS_SAME(decltype(**pi), typename readable_traits<I>::value_type const &&)]
#endif // RANGES_WORKAROUND_MSVC_793042
            > * = nullptr)
        {
            return cpp17_iterator_category_3_<I>(0);
        }

        template<typename I>
        using cpp17_readable_iterator_category_t =
            decltype(detail::cpp17_iterator_category_2_<I>(0));

        template<typename I>
        auto cpp17_iterator_category_(long)
        {
            return cpp17_iterator_category_2_<I>(0);
        }
        // Explicitly declares its category?
        template<typename I>
        typename I::iterator_category cpp17_iterator_category_(int)
        {
            return {};
        }

        template<typename I>
        auto std_iterator_traits_impl_2_(long)
        {
            return std_output_iterator_traits<
                decltype(detail::cpp17_difference_type_<I>(0))>{};
        }
        // Satisfies Cpp17InputIterator?
        template<typename I>
        auto std_iterator_traits_impl_2_(
            int,
            I *pi = nullptr,
            typename incrementable_traits<I>::difference_type d = 0,
            typename readable_traits<I>::value_type const *pcv = nullptr,
            always_<
                void,
                int[decltype(d)(-1) < decltype(d)(0)], // signed difference type
                decltype(decltype(*pcv)(**pi)),        // sensible reference/value type
                decltype(decltype(*pcv)(*(*pi)++)),    // sensible post-increment result
                decltype(*pi == *pi ? true : false),   // equality comparable
                decltype(*pi != *pi ? true : false)    //     "        "
            > * = nullptr)
        {
            using D = typename incrementable_traits<I>::difference_type;
            struct yes_traits
            {
                using difference_type = D;
                using value_type = typename readable_traits<I>::value_type;
                using reference = decltype(cpp17_reference_type_<I>(0));
                using pointer = decltype(cpp17_pointer_type_<I>(0));
                using iterator_category = decltype(cpp17_iterator_category_<I>(0));
            };
            struct no_traits
            {};
            return if_then_t<is_integral_<D>(0), yes_traits, no_traits>{};
        }

        template<typename I>
        nil_ std_iterator_traits_impl_(long)
        {
            return {};
        }
        // Satisfies Cpp17Iterator?
        template<typename I>
        auto std_iterator_traits_impl_(
            int,
            I *pi = nullptr,
            void (*nv)(...) = nullptr,
            always_<
                void,
                decltype(nv(**pi)),
                int[RANGES_IS_SAME(decltype(++*pi), I &)],
                decltype(nv(*(*pi)++))
            > * = nullptr)
        {
            return std_iterator_traits_impl_2_<I>(0);
        }

        template<typename T>
        constexpr bool has_iterator_typedefs_impl_(
            int,
            always_<
                void,
                typename T::difference_type,
                typename T::value_type,
                typename T::pointer,
                typename T::reference,
                typename T::iterator_category
            > * = nullptr)
        {
            return true;
        }
        template<typename T>
        constexpr bool has_iterator_typedefs_impl_(long)
        {
            return false;
        }
    }
    /// \endcond
}

// Hijack the primary std::iterator_traits template from each of the 3 major
// standard library implementations
RANGES_BEGIN_NAMESPACE_STD
RANGES_BEGIN_NAMESPACE_VERSION
#if defined(__GLIBCXX__)
    template<typename I>
    struct __iterator_traits<
        I,
        ::ranges::detail::enable_if_t<!::ranges::detail::has_iterator_typedefs_impl_<I>(0)>>
      : decltype(::ranges::detail::std_iterator_traits_impl_<I>(0))
    {};
#elif defined(_LIBCPP_VERSION)
    template<typename I>
    struct __iterator_traits<I, false> // doesn't have I::iterator_category
      : decltype(::ranges::detail::std_iterator_traits_impl_<I>(0))
    {};
#elif defined(_MSVC_STL_VERSION)
    template<typename I>
    struct _Iterator_traits_base<
        I,
#ifdef RANGES_WORKAROUND_MSVC_792338
        ::ranges::detail::enable_if_t<decltype(bool_constant<
            !::ranges::detail::has_iterator_typedefs_impl_<I>(0)>{})::value>>
#else // ^^^ workaround / no workaround vvv
        ::ranges::detail::enable_if_t<!::ranges::detail::has_iterator_typedefs_impl_<I>(0)>>
#endif // RANGES_WORKAROUND_MSVC_792338
      : decltype(::ranges::detail::std_iterator_traits_impl_<I>(0))
    {};
#endif
RANGES_END_NAMESPACE_VERSION
RANGES_END_NAMESPACE_STD

#endif // RANGES_DEEP_STL_INTEGRATION

#endif // RANGES_V3_STD_ITERATOR
