/// \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_SPLIT_WHEN_HPP
#define RANGES_V3_VIEW_SPLIT_WHEN_HPP

#include <type_traits>
#include <utility>

#include <meta/meta.hpp>

#include <range/v3/range_fwd.hpp>

#include <range/v3/algorithm/find_if_not.hpp>
#include <range/v3/functional/bind_back.hpp>
#include <range/v3/functional/invoke.hpp>
#include <range/v3/iterator/default_sentinel.hpp>
#include <range/v3/iterator/operations.hpp>
#include <range/v3/range/access.hpp>
#include <range/v3/range/concepts.hpp>
#include <range/v3/range/traits.hpp>
#include <range/v3/utility/static_const.hpp>
#include <range/v3/view/all.hpp>
#include <range/v3/view/facade.hpp>
#include <range/v3/view/indirect.hpp>
#include <range/v3/view/iota.hpp>
#include <range/v3/view/take_while.hpp>
#include <range/v3/view/view.hpp>

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

    template<typename Rng, typename Fun>
    struct split_when_view
      : view_facade<split_when_view<Rng, Fun>,
                    is_finite<Rng>::value ? finite : range_cardinality<Rng>::value>
    {
    private:
        friend range_access;
        Rng rng_;
        semiregular_box_t<Fun> fun_;

        template<bool IsConst>
        struct cursor
        {
        private:
            friend range_access;
            friend split_when_view;
            friend struct cursor<!IsConst>;
            bool zero_;
            using CRng = meta::const_if_c<IsConst, Rng>;
            iterator_t<CRng> cur_;
            sentinel_t<CRng> last_;
            using fun_ref_t = semiregular_box_ref_or_val_t<Fun, IsConst>;
            fun_ref_t fun_;

            struct search_pred
            {
                bool zero_;
                iterator_t<CRng> first_;
                sentinel_t<CRng> last_;
                fun_ref_t fun_;
                bool operator()(iterator_t<CRng> cur) const
                {
                    return (zero_ && cur == first_) ||
                           (cur != last_ && !invoke(fun_, cur, last_).first);
                }
            };
            using reference_ =
                indirect_view<take_while_view<iota_view<iterator_t<CRng>>, search_pred>>;
            reference_ read() const
            {
                return reference_{{views::iota(cur_), {zero_, cur_, last_, fun_}}};
            }
            void next()
            {
                RANGES_EXPECT(cur_ != last_);
                // If the last match consumed zero elements, bump the position.
                if(zero_)
                {
                    zero_ = false;
                    ++cur_;
                }
                for(; cur_ != last_; ++cur_)
                {
                    auto p = invoke(fun_, cur_, last_);
                    if(p.first)
                    {
                        zero_ = (cur_ == p.second);
                        cur_ = p.second;
                        return;
                    }
                }
            }
            bool equal(default_sentinel_t) const
            {
                return cur_ == last_;
            }
            bool equal(cursor const & that) const
            {
                return cur_ == that.cur_;
            }
            cursor(fun_ref_t fun, iterator_t<CRng> first, sentinel_t<CRng> last)
              : cur_(first)
              , last_(last)
              , fun_(fun)
            {
                // For skipping an initial zero-length match
                auto p = invoke(fun, first, last);
                zero_ = p.first && first == p.second;
            }

        public:
            cursor() = default;
            CPP_template(bool Other)( //
                requires IsConst && (!Other)) cursor(cursor<Other> that)
              : cursor{std::move(that.cur_), std::move(that.last_), std::move(that.fun_)}
            {}
        };
        cursor<false> begin_cursor()
        {
            return {fun_, ranges::begin(rng_), ranges::end(rng_)};
        }
        template<bool Const = true>
        auto begin_cursor() const -> CPP_ret(cursor<Const>)( //
            requires Const && range<meta::const_if_c<Const, Rng>> &&
                invocable<Fun const &, iterator_t<meta::const_if_c<Const, Rng>>,
                          sentinel_t<meta::const_if_c<Const, Rng>>>)
        {
            return {fun_, ranges::begin(rng_), ranges::end(rng_)};
        }

    public:
        split_when_view() = default;
        split_when_view(Rng rng, Fun fun)
          : rng_(std::move(rng))
          , fun_(std::move(fun))
        {}
    };

#if RANGES_CXX_DEDUCTION_GUIDES >= RANGES_CXX_DEDUCTION_GUIDES_17
    CPP_template(typename Rng, typename Fun)(requires copy_constructible<Fun>)
        split_when_view(Rng &&, Fun)
            ->split_when_view<views::all_t<Rng>, Fun>;
#endif

    namespace views
    {
        struct split_when_fn
        {
        private:
            friend view_access;
            template<typename T>
            static auto bind(split_when_fn split_when, T && t)
            {
                return make_pipeable(bind_back(split_when, static_cast<T &&>(t)));
            }
            template<typename Pred>
            struct predicate_pred
            {
                semiregular_box_t<Pred> pred_;

                template<typename I, typename S>
                auto operator()(I cur, S last) const -> CPP_ret(std::pair<bool, I>)( //
                    requires sentinel_for<S, I>)
                {
                    auto where = ranges::find_if_not(cur, last, std::ref(pred_));
                    return {cur != where, where};
                }
            };

        public:
            template<typename Rng, typename Fun>
            auto operator()(Rng && rng, Fun fun) const -> CPP_ret(
                split_when_view<all_t<Rng>, Fun>)( //
                requires viewable_range<Rng> && forward_range<Rng> &&
                    invocable<Fun &, iterator_t<Rng>, sentinel_t<Rng>> &&
                        invocable<Fun &, iterator_t<Rng>, iterator_t<Rng>> &&
                            copy_constructible<Fun> && convertible_to<
                                invoke_result_t<Fun &, iterator_t<Rng>, sentinel_t<Rng>>,
                                std::pair<bool, iterator_t<Rng>>>)
            {
                return {all(static_cast<Rng &&>(rng)), std::move(fun)};
            }
            template<typename Rng, typename Fun>
            auto operator()(Rng && rng, Fun fun) const
                -> CPP_ret(split_when_view<all_t<Rng>, predicate_pred<Fun>>)( //
                    requires viewable_range<Rng> && forward_range<Rng> && predicate<
                        Fun const &, range_reference_t<Rng>> && copy_constructible<Fun>)
            {
                return {all(static_cast<Rng &&>(rng)),
                        predicate_pred<Fun>{std::move(fun)}};
            }
        };

        /// \relates split_when_fn
        /// \ingroup group-views
        RANGES_INLINE_VARIABLE(view<split_when_fn>, split_when)
    } // namespace views
    /// @}
} // namespace ranges

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

#endif