/// \file
// Range v3 library
//
//  Copyright Eric Niebler 2013-present
//  Copyright Casey Carter 2016
//
//  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_ITERATOR_ACCESS_HPP
#define RANGES_V3_ITERATOR_ACCESS_HPP

#include <iterator>
#include <utility>
#include <type_traits>
#include <meta/meta.hpp>
#include <concepts/concepts.hpp>
#include <range/v3/range_fwd.hpp>
#include <range/v3/utility/move.hpp>
#include <range/v3/utility/static_const.hpp>
#include <range/v3/utility/swap.hpp>
#include <std/detail/associated_types.hpp>

namespace ranges
{
    /// \addtogroup group-iterator
    /// @{

    /// \cond
    namespace detail
    {
        template<
            typename I,
#ifdef RANGES_WORKAROUND_MSVC_683388
            typename R = detail::if_then_t<
                std::is_pointer<uncvref_t<I>>::value &&
                    std::is_array<std::remove_pointer_t<uncvref_t<I>>>::value,
                std::add_lvalue_reference_t<std::remove_pointer_t<uncvref_t<I>>>,
                decltype(*std::declval<I &>())>,
#else
            typename R = decltype(*std::declval<I &>()),
#endif
            typename = R &>
        using iter_reference_t_ = R;
    } // namespace detail
    /// \endcond

    template<typename R>
    using iter_reference_t = detail::iter_reference_t_<R>;

#if defined(RANGES_DEEP_STL_INTEGRATION) && RANGES_DEEP_STL_INTEGRATION && \
    !defined(RANGES_DOXYGEN_INVOKED)
    template<typename T>
    using iter_value_t =
        typename detail::if_then_t<
            detail::is_std_iterator_traits_specialized_v<T>,
            std::iterator_traits<T>,
            readable_traits<T>>::value_type;
#else
    template<typename T>
    using iter_value_t =
        typename readable_traits<T>::value_type;
#endif

    /// \cond
    namespace _iter_move_
    {
#if RANGES_BROKEN_CPO_LOOKUP
        void iter_move(); // unqualified name lookup block
#endif

        template<typename T>
        decltype(iter_move(std::declval<T>())) try_adl_iter_move_(int);

        template<typename T>
        void try_adl_iter_move_(long);

        template<typename T>
        RANGES_INLINE_VAR constexpr bool is_adl_indirectly_movable_v =
            !RANGES_IS_SAME(void, decltype(_iter_move_::try_adl_iter_move_<T>(42)));

        struct fn
        {
            template<
                typename I,
                typename = detail::enable_if_t<is_adl_indirectly_movable_v<I &>>>
#ifndef RANGES_WORKAROUND_CLANG_23135
            constexpr
#endif // RANGES_WORKAROUND_CLANG_23135
            auto CPP_auto_fun(operator())(I &&i) (const)
            (
                return iter_move(i)
            )

            template<
                typename I,
                typename = detail::enable_if_t<!is_adl_indirectly_movable_v<I &>>,
                typename R = iter_reference_t<I>>
#ifndef RANGES_WORKAROUND_CLANG_23135
            constexpr
#endif // RANGES_WORKAROUND_CLANG_23135
            auto CPP_auto_fun(operator())(I &&i) (const)
            (
                return static_cast<aux::move_t<R>>(aux::move(*i))
            )
        };
    } // _iter_move_
    /// \endcond

    RANGES_DEFINE_CPO(_iter_move_::fn, iter_move)

    /// \cond
    namespace detail
    {
        template<typename I, typename O>
        auto is_indirectly_movable_(I &(*i)(), O &(*o)(), iter_value_t<I> *v = nullptr) ->
            always_<
                std::true_type,
                decltype(iter_value_t<I>(iter_move(i()))),
                decltype(*v = iter_move(i())),
                decltype(*o() = (iter_value_t<I> &&) *v),
                decltype(*o() = iter_move(i()))>;
        template<typename I, typename O>
        auto is_indirectly_movable_(...) ->
            std::false_type;

        template<typename I, typename O>
        auto is_nothrow_indirectly_movable_(iter_value_t<I> *v) ->
            meta::bool_<
                noexcept(iter_value_t<I>(iter_move(std::declval<I &>()))) &&
                noexcept(*v = iter_move(std::declval<I &>())) &&
                noexcept(*std::declval<O &>() = (iter_value_t<I> &&) *v) &&
                noexcept(*std::declval<O &>() = iter_move(std::declval<I &>()))>;
        template<typename I, typename O>
        auto is_nothrow_indirectly_movable_(...) ->
            std::false_type;
    }
    /// \endcond

    template<typename I, typename O>
    RANGES_INLINE_VAR constexpr bool is_indirectly_movable_v =
        decltype(detail::is_indirectly_movable_<I, O>(nullptr, nullptr))::value;

    template<typename I, typename O>
    RANGES_INLINE_VAR constexpr bool is_nothrow_indirectly_movable_v =
        decltype(detail::is_nothrow_indirectly_movable_<I, O>(nullptr))::value;

    template<typename I, typename O>
    struct is_indirectly_movable
      : meta::bool_<is_indirectly_movable_v<I, O>>
    {};

    template<typename I, typename O>
    struct is_nothrow_indirectly_movable
      : meta::bool_<is_nothrow_indirectly_movable_v<I, O>>
    {};

    /// \cond
    namespace _iter_swap_
    {
        struct nope
        {};

        // Q: Should std::reference_wrapper be considered a proxy wrt swapping rvalues?
        // A: No. Its operator= is currently defined to reseat the references, so
        //    std::swap(ra, rb) already means something when ra and rb are (lvalue)
        //    reference_wrappers. That reseats the reference wrappers but leaves the
        //    referents unmodified. Treating rvalue reference_wrappers differently would
        //    be confusing.

        // Q: Then why is it OK to "re"-define swap for pairs and tuples of references?
        // A: Because as defined above, swapping an rvalue tuple of references has the same
        //    semantics as swapping an lvalue tuple of references. Rather than reseat the
        //    references, assignment happens *through* the references.

        // Q: But I have an iterator whose operator* returns an rvalue
        //    std::reference_wrapper<T>. How do I make it model IndirectlySwappable?
        // A: With an overload of iter_swap.

        // Intentionally create an ambiguity with std::iter_swap, which is
        // unconstrained.
        template<typename T, typename U>
        nope iter_swap(T, U) = delete;

#ifdef RANGES_WORKAROUND_MSVC_620035
        nope iter_swap();
#endif

        template<typename T, typename U>
        decltype(iter_swap(std::declval<T>(), std::declval<U>())) try_adl_iter_swap_(int);

        template<typename T, typename U>
        nope try_adl_iter_swap_(long);

        // Test whether an overload of iter_swap for a T and a U can be found
        // via ADL with the iter_swap overload above participating in the
        // overload set. This depends on user-defined iter_swap overloads
        // being a better match than the overload in namespace std.
        template<typename T, typename U>
        RANGES_INLINE_VAR constexpr bool is_adl_indirectly_swappable_v =
            !RANGES_IS_SAME(nope, decltype(_iter_swap_::try_adl_iter_swap_<T, U>(42)));

        struct fn
        {
            // *If* a user-defined iter_swap is found via ADL, call that:
            template<typename T, typename U>
            constexpr /*c++14*/
            detail::enable_if_t<is_adl_indirectly_swappable_v<T, U>>
            operator()(T &&t, U &&u) const
                noexcept(noexcept(iter_swap((T &&) t, (U &&) u)))
            {
                (void) iter_swap((T &&) t, (U &&) u);
            }

            // *Otherwise*, for Readable types with swappable reference
            // types, call ranges::swap(*a, *b)
            template<typename I0, typename I1>
            constexpr /*c++14*/
            detail::enable_if_t<
                !is_adl_indirectly_swappable_v<I0, I1> &&
                is_swappable_with<iter_reference_t<I0>, iter_reference_t<I1>>::value>
            operator()(I0 &&a, I1 &&b) const
                noexcept(noexcept(ranges::swap(*a, *b)))
            {
                ranges::swap(*a, *b);
            }

            // *Otherwise*, for Readable types that are mutually
            // IndirectlyMovableStorable, implement as:
            //      iter_value_t<T0> tmp = iter_move(a);
            //      *a = iter_move(b);
            //      *b = std::move(tmp);
            template<typename I0, typename I1>
            constexpr /*c++14*/
            detail::enable_if_t<
                !is_adl_indirectly_swappable_v<I0, I1> &&
                !is_swappable_with<iter_reference_t<I0>, iter_reference_t<I1>>::value &&
                is_indirectly_movable_v<I0, I1> &&
                is_indirectly_movable_v<I1, I0>>
            operator()(I0 &&a, I1 &&b) const
                noexcept(
                    is_nothrow_indirectly_movable_v<I0, I1> &&
                    is_nothrow_indirectly_movable_v<I1, I0>)
            {
                iter_value_t<I0> v0 = iter_move(a);
                *a = iter_move(b);
                *b = detail::move(v0);
            }
        };
    } // namespace _iter_swap_
    /// \endcond

    /// \relates _iter_swap_::fn
    RANGES_DEFINE_CPO(_iter_swap_::fn, iter_swap)

    /// \cond
    namespace detail
    {
        template<typename T, typename U>
        auto is_indirectly_swappable_(T &(*t)(), U &(*u)()) ->
            detail::always_<
                std::true_type,
                decltype(iter_swap(t(), u()))>;
        template<typename T, typename U>
        auto is_indirectly_swappable_(...) ->
            std::false_type;

        template<typename T, typename U>
        auto is_nothrow_indirectly_swappable_(int) ->
            meta::bool_<noexcept(iter_swap(std::declval<T &>(), std::declval<U &>()))>;
        template<typename T, typename U>
        auto is_nothrow_indirectly_swappable_(long) ->
            std::false_type;
    } // namespace detail
    /// \endcond

    template<typename T, typename U>
    RANGES_INLINE_VAR constexpr bool is_indirectly_swappable_v =
        decltype(detail::is_indirectly_swappable_<T, U>(nullptr, nullptr))::value;

    template<typename T, typename U>
    RANGES_INLINE_VAR constexpr bool is_nothrow_indirectly_swappable_v =
        decltype(detail::is_nothrow_indirectly_swappable_<T, U>(0))::value;

    template<typename T, typename U>
    struct is_indirectly_swappable
      : meta::bool_<is_indirectly_swappable_v<T, U>>
    {};

    template<typename T, typename U>
    struct is_nothrow_indirectly_swappable
      : meta::bool_<is_nothrow_indirectly_swappable_v<T, U>>
    {};

    namespace cpp20
    {
        using ranges::iter_value_t;
        using ranges::iter_reference_t;
        using ranges::iter_move;
        using ranges::iter_swap;
    }
    /// @}
} // namespace ranges

#endif // RANGES_V3_ITERATOR_ACCESS_HPP
