/// \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_GROUP_BY_HPP
#define RANGES_V3_VIEW_GROUP_BY_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/range/access.hpp>
#include <range/v3/range/concepts.hpp>
#include <range/v3/range/traits.hpp>
#include <range/v3/utility/semiregular_box.hpp>
#include <range/v3/utility/static_const.hpp>
#include <range/v3/view/facade.hpp>
#include <range/v3/view/subrange.hpp>
#include <range/v3/view/take_while.hpp>
#include <range/v3/view/view.hpp>

#include <range/v3/detail/disable_warnings.hpp>

namespace ranges
{
    // TODO group_by could support Input ranges by keeping mutable state in
    // the range itself. The group_by view would then be mutable-only and
    // Input.

    /// \addtogroup group-views
    /// @{
    template<typename Rng, typename Fun>
    struct group_by_view
      : view_facade<group_by_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 struct cursor<!IsConst>;
            friend range_access;
            friend group_by_view;
            using CRng = meta::const_if_c<IsConst, Rng>;
            iterator_t<CRng> cur_;
            sentinel_t<CRng> last_;
            semiregular_box_ref_or_val_t<Fun, IsConst> fun_;

            struct mixin : basic_mixin<cursor>
            {
                mixin() = default;
                using basic_mixin<cursor>::basic_mixin;
                iterator_t<CRng> base() const
                {
                    return this->get().cur_;
                }
            };

            struct pred
            {
                iterator_t<CRng> first_;
                semiregular_box_ref_or_val_t<Fun, IsConst> fun_;
                bool operator()(range_reference_t<CRng> r) const
                {
                    return invoke(fun_, *first_, r);
                }
            };
#ifdef RANGES_WORKAROUND_MSVC_787074
            template<bool Const = IsConst>
            auto read() const
                -> take_while_view<subrange<iterator_t<meta::const_if_c<Const, Rng>>,
                                            sentinel_t<meta::const_if_c<Const, Rng>>>,
                                   pred>
#else  // ^^^ workaround / no workaround vvv
            auto read() const
                -> take_while_view<subrange<iterator_t<CRng>, sentinel_t<CRng>>, pred>
#endif // RANGES_WORKAROUND_MSVC_787074
            {
                return {{cur_, last_}, {cur_, fun_}};
            }
            void next()
            {
                cur_ = find_if_not(cur_, last_, pred{cur_, fun_});
            }
            bool equal(default_sentinel_t) const
            {
                return cur_ == last_;
            }
            bool equal(cursor const & that) const
            {
                return cur_ == that.cur_;
            }
            cursor(semiregular_box_ref_or_val_t<Fun, IsConst> fun, iterator_t<CRng> first,
                   sentinel_t<CRng> last)
              : cur_(first)
              , last_(last)
              , fun_(fun)
            {}

        public:
            cursor() = default;
            CPP_template(bool Other)(         //
                requires IsConst && (!Other)) //
                cursor(cursor<Other> that)
              : cur_(std::move(that.cur_))
              , last_(std::move(last_))
              , fun_(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 &, range_common_reference_t<meta::const_if_c<Const, Rng>>,
                range_common_reference_t<meta::const_if_c<Const, Rng>>>)
        {
            return {fun_, ranges::begin(rng_), ranges::end(rng_)};
        }

    public:
        group_by_view() = default;
        constexpr group_by_view(Rng rng, Fun fun)
          : rng_(std::move(rng))
          , fun_(std::move(fun))
        {}
        Rng base() const
        {
            return rng_;
        }
    };

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

    namespace views
    {
        struct group_by_base_fn
        {
            template<typename Rng, typename Fun>
            constexpr auto operator()(Rng && rng, Fun fun) const
                -> CPP_ret(group_by_view<all_t<Rng>, Fun>)( //
                    requires viewable_range<Rng> && forward_range<Rng> &&
                        indirect_relation<Fun, iterator_t<Rng>>)
            {
                return {all(static_cast<Rng &&>(rng)), std::move(fun)};
            }
        };

        struct group_by_fn : group_by_base_fn
        {
            using group_by_base_fn::operator();

            template<typename Fun>
            constexpr auto operator()(Fun fun) const
            {
                return make_view_closure(bind_back(group_by_base_fn{}, std::move(fun)));
            }
        };

        /// \relates group_by_fn
        /// \ingroup group-views
        RANGES_INLINE_VARIABLE(group_by_fn, group_by)
    } // namespace views
    /// @}
} // namespace ranges

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

#endif
