/// \file
// Range v3 library
//
//  Copyright Eric Niebler 2013-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_VIEW_SLICE_HPP
#define RANGES_V3_VIEW_SLICE_HPP

#include <type_traits>

#include <meta/meta.hpp>

#include <range/v3/range_fwd.hpp>

#include <range/v3/functional/bind_back.hpp>
#include <range/v3/iterator/counted_iterator.hpp>
#include <range/v3/iterator/default_sentinel.hpp>
#include <range/v3/iterator/operations.hpp>
#include <range/v3/iterator/traits.hpp>
#include <range/v3/range/concepts.hpp>
#include <range/v3/range/traits.hpp>
#include <range/v3/utility/optional.hpp>
#include <range/v3/utility/static_const.hpp>
#include <range/v3/view/all.hpp>
#include <range/v3/view/drop_exactly.hpp>
#include <range/v3/view/facade.hpp>
#include <range/v3/view/subrange.hpp>
#include <range/v3/view/view.hpp>

namespace ranges
{
    /// \cond
    namespace detail
    {
        template<typename Rng, typename Int>
        iterator_t<Rng> pos_at_(Rng && rng, Int i, input_range_tag, std::true_type)
        {
            RANGES_EXPECT(0 <= i);
            return next(ranges::begin(rng), i);
        }

        template<typename Rng, typename Int>
        iterator_t<Rng> pos_at_(Rng && rng, Int i, bidirectional_range_tag,
                                std::false_type)
        {
            if(0 > i)
            {
                // If it's not common and we know the size, faster to count from the front
                if(RANGES_CONSTEXPR_IF(sized_range<Rng> && !common_range<Rng>))
                    return next(ranges::begin(rng), distance(rng) + i);
                // Otherwise, probably faster to count from the back.
                return next(ranges::next(ranges::begin(rng), ranges::end(rng)), i);
            }
            return next(ranges::begin(rng), i);
        }

        template<typename Rng, typename Int>
        iterator_t<Rng> pos_at_(Rng && rng, Int i, input_range_tag, std::false_type)
        {
            RANGES_EXPECT(i >= 0 || (bool)sized_range<Rng> || (bool)forward_range<Rng>);
            if(0 > i)
                return next(ranges::begin(rng), distance(rng) + i);
            return next(ranges::begin(rng), i);
        }

        template<typename Rng, bool IsRandomAccess>
        struct slice_view_ : view_facade<slice_view<Rng>, finite>
        {
        private:
            friend range_access;
            Rng rng_;
            range_difference_t<Rng> from_, count_;
            detail::non_propagating_cache<iterator_t<Rng>> begin_;

            iterator_t<Rng> get_begin_()
            {
                if(!begin_)
                    begin_ = detail::pos_at_(
                        rng_, from_, range_tag_of<Rng>{}, is_infinite<Rng>{});
                return *begin_;
            }

        public:
            slice_view_() = default;
            constexpr slice_view_(Rng rng, range_difference_t<Rng> from,
                                  range_difference_t<Rng> count)
              : rng_(std::move(rng))
              , from_(from)
              , count_(count)
            {}
            counted_iterator<iterator_t<Rng>> begin()
            {
                return make_counted_iterator(get_begin_(), count_);
            }
            default_sentinel_t end()
            {
                return {};
            }
            auto size() const
            {
                return static_cast<detail::iter_size_t<iterator_t<Rng>>>(count_);
            }
            Rng base() const
            {
                return rng_;
            }
        };

        template<typename Rng>
        struct slice_view_<Rng, true> : view_interface<slice_view<Rng>, finite>
        {
        private:
            Rng rng_;
            range_difference_t<Rng> from_, count_;

        public:
            slice_view_() = default;
            constexpr slice_view_(Rng rng, range_difference_t<Rng> from,
                                  range_difference_t<Rng> count)
              : rng_(std::move(rng))
              , from_(from)
              , count_(count)
            {
                RANGES_EXPECT(0 <= count_);
            }
            iterator_t<Rng> begin()
            {
                return detail::pos_at_(
                    rng_, from_, range_tag_of<Rng>{}, is_infinite<Rng>{});
            }
            iterator_t<Rng> end()
            {
                return detail::pos_at_(
                           rng_, from_, range_tag_of<Rng>{}, is_infinite<Rng>{}) +
                       count_;
            }
            template<typename BaseRng = Rng>
            auto begin() const -> CPP_ret(iterator_t<BaseRng const>)( //
                requires range<BaseRng const>)
            {
                return detail::pos_at_(
                    rng_, from_, range_tag_of<Rng>{}, is_infinite<Rng>{});
            }
            template<typename BaseRng = Rng>
            auto end() const -> CPP_ret(iterator_t<BaseRng const>)( //
                requires range<BaseRng const>)
            {
                return detail::pos_at_(
                           rng_, from_, range_tag_of<Rng>{}, is_infinite<Rng>{}) +
                       count_;
            }
            auto size() const
            {
                return static_cast<detail::iter_size_t<iterator_t<Rng>>>(count_);
            }
            Rng base() const
            {
                return rng_;
            }
        };
    } // namespace detail
    /// \endcond

    /// \addtogroup group-views
    /// @{
    template<typename Rng>
    struct slice_view : detail::slice_view_<Rng, (bool)random_access_range<Rng>>
    {
        using detail::slice_view_<Rng, (bool)random_access_range<Rng>>::slice_view_;
    };

#if RANGES_CXX_DEDUCTION_GUIDES >= RANGES_CXX_DEDUCTION_GUIDES_17
    template<typename Rng>
    slice_view(Rng &&, range_difference_t<Rng>, range_difference_t<Rng>)
        ->slice_view<views::all_t<Rng>>;
#endif

    namespace views
    {
        struct slice_fn
        {
        private:
            friend view_access;

            template<typename Rng>
            constexpr static slice_view<all_t<Rng>> impl_(Rng && rng,
                                                          range_difference_t<Rng> from,
                                                          range_difference_t<Rng> count,
                                                          input_range_tag, range_tag = {})
            {
                return {all(static_cast<Rng &&>(rng)), from, count};
            }
            template<typename Rng>
            static auto impl_(Rng && rng, range_difference_t<Rng> from,
                              range_difference_t<Rng> count, random_access_range_tag,
                              common_range_tag = {})
                -> CPP_ret(subrange<iterator_t<Rng>>)( //
                    requires forwarding_range_<Rng>)
            {
                auto it =
                    detail::pos_at_(rng, from, range_tag_of<Rng>{}, is_infinite<Rng>{});
                return {it, it + count};
            }

            // Overloads for the pipe syntax: rng | views::slice(from,to)
            template<typename Int>
            static constexpr auto CPP_fun(bind)(slice_fn slice, Int from, Int to)( //
                requires detail::integer_like_<Int>)
            {
                return make_pipeable(bind_back(slice, from, to));
            }
            template<typename Int>
            static constexpr auto CPP_fun(bind)(slice_fn slice, Int from,
                                                detail::from_end_<Int> to)( //
                requires detail::integer_like_<Int>)
            {
                return make_pipeable(bind_back(slice, from, to));
            }
            template<typename Int>
            static constexpr auto CPP_fun(bind)(slice_fn slice,
                                                detail::from_end_<Int> from,
                                                detail::from_end_<Int> to)( //
                requires detail::integer_like_<Int>)
            {
                return make_pipeable(bind_back(slice, from, to));
            }
            template<typename Int>
            static constexpr auto CPP_fun(bind)(slice_fn, Int from, end_fn)( //
                requires detail::integer_like_<Int>)
            {
                return make_pipeable(bind_back(ranges::views::drop_exactly, from));
            }
            template<typename Int>
            static constexpr auto CPP_fun(bind)(slice_fn slice,
                                                detail::from_end_<Int> from,
                                                end_fn to)( //
                requires detail::integer_like_<Int>)
            {
                return make_pipeable(bind_back(slice, from, to));
            }

        public:
            // slice(rng, 2, 4)
            template<typename Rng>
            constexpr auto CPP_fun(operator())( //
                Rng && rng, range_difference_t<Rng> from, range_difference_t<Rng> to)(
                const requires viewable_range<Rng> && input_range<Rng>)
            {
                RANGES_EXPECT(0 <= from && from <= to);
                return slice_fn::impl_(
                    static_cast<Rng &&>(rng), from, to - from, range_tag_of<Rng>{});
            }
            // slice(rng, 4, end-2)
            //  TODO Support Forward, non-Sized ranges by returning a range that
            //       doesn't know it's size?
            template<typename Rng>
            auto CPP_fun(operator())( //
                Rng && rng, range_difference_t<Rng> from,
                detail::from_end_of_t<Rng> to)(const requires viewable_range<Rng> &&
                                                   input_range<Rng> && sized_range<Rng>)
            {
                static_assert(!is_infinite<Rng>::value,
                              "Can't index from the end of an infinite range!");
                RANGES_EXPECT(0 <= from);
                RANGES_ASSERT(from <= distance(rng) + to.dist_);
                return slice_fn::impl_(static_cast<Rng &&>(rng),
                                       from,
                                       distance(rng) + to.dist_ - from,
                                       range_tag_of<Rng>{});
            }
            // slice(rng, end-4, end-2)
            template<typename Rng>
            auto CPP_fun(operator())( //
                Rng && rng, detail::from_end_of_t<Rng> from,
                detail::from_end_of_t<Rng> to)(const requires viewable_range<Rng> &&
                                               (forward_range<Rng> ||
                                                (input_range<Rng> && sized_range<Rng>)))
            {
                static_assert(!is_infinite<Rng>::value,
                              "Can't index from the end of an infinite range!");
                RANGES_EXPECT(from.dist_ <= to.dist_);
                return slice_fn::impl_(static_cast<Rng &&>(rng),
                                       from.dist_,
                                       to.dist_ - from.dist_,
                                       range_tag_of<Rng>{},
                                       common_range_tag_of<Rng>{});
            }
            // slice(rng, 4, end)
            template<typename Rng>
            auto CPP_fun(operator())(Rng && rng, range_difference_t<Rng> from, end_fn)(
                const requires viewable_range<Rng> && input_range<Rng>)
            {
                RANGES_EXPECT(0 <= from);
                return ranges::views::drop_exactly(static_cast<Rng &&>(rng), from);
            }
            // slice(rng, end-4, end)
            template<typename Rng>
            auto CPP_fun(operator())(Rng && rng, detail::from_end_of_t<Rng> from,
                                     end_fn)(const requires viewable_range<Rng> &&
                                             (forward_range<Rng> ||
                                              (input_range<Rng> && sized_range<Rng>)))
            {
                static_assert(!is_infinite<Rng>::value,
                              "Can't index from the end of an infinite range!");
                return slice_fn::impl_(static_cast<Rng &&>(rng),
                                       from.dist_,
                                       -from.dist_,
                                       range_tag_of<Rng>{},
                                       common_range_tag_of<Rng>{});
            }
        };

        /// \relates _slice_fn
        /// \ingroup group-views
        RANGES_INLINE_VARIABLE(view<slice_fn>, slice)
    } // namespace views
    /// @}
} // namespace ranges

#include <range/v3/detail/satisfy_boost_range.hpp>
RANGES_SATISFY_BOOST_RANGE(::ranges::slice_view)

#endif