/// \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_ACTION_INSERT_HPP
#define RANGES_V3_ACTION_INSERT_HPP

#include <initializer_list>
#include <utility>

#include <range/v3/range_fwd.hpp>

#include <range/v3/action/concepts.hpp>
#include <range/v3/algorithm/max.hpp>
#include <range/v3/iterator/common_iterator.hpp>
#include <range/v3/range/traits.hpp>
#include <range/v3/utility/static_const.hpp>

namespace ranges
{
    /// \cond
    namespace adl_insert_detail
    {
        template<typename Cont, typename... Args>
        using insert_result_t = decltype(
            unwrap_reference(std::declval<Cont>()).insert(std::declval<Args>()...));

        template<typename Cont, typename T>
        auto insert(Cont && cont, T && t) -> CPP_ret(insert_result_t<Cont &, T>)( //
            requires lvalue_container_like<Cont> &&
            (!range<T> && constructible_from<range_value_t<Cont>, T>))
        {
            return unwrap_reference(cont).insert(static_cast<T &&>(t));
        }

        template<typename Cont, typename I, typename S>
        auto insert(Cont && cont, I i, S j)
            -> CPP_ret(insert_result_t<Cont &, detail::cpp17_iterator_t<I, S>,
                                       detail::cpp17_iterator_t<I, S>>)( //
                requires lvalue_container_like<Cont> && sentinel_for<S, I> && (!range<S>))
        {
            return unwrap_reference(cont).insert(detail::cpp17_iterator_t<I, S>{i},
                                                 detail::cpp17_iterator_t<I, S>{j});
        }

        template<typename Cont, typename Rng>
        auto insert(Cont && cont, Rng && rng)
            -> CPP_ret(insert_result_t<Cont &, detail::range_cpp17_iterator_t<Rng>,
                                       detail::range_cpp17_iterator_t<Rng>>)( //
                requires lvalue_container_like<Cont> && range<Rng>)
        {
            return unwrap_reference(cont).insert(
                detail::range_cpp17_iterator_t<Rng>{ranges::begin(rng)},
                detail::range_cpp17_iterator_t<Rng>{ranges::end(rng)});
        }

        template<typename Cont, typename I, typename T>
        auto insert(Cont && cont, I p, T && t)
            -> CPP_ret(insert_result_t<Cont &, I, T>)( //
                requires lvalue_container_like<Cont> && input_iterator<I> &&
                (!range<T> && constructible_from<range_value_t<Cont>, T>))
        {
            return unwrap_reference(cont).insert(p, static_cast<T &&>(t));
        }

        template<typename Cont, typename I, typename N, typename T>
        auto insert(Cont && cont, I p, N n, T && t)
            -> CPP_ret(insert_result_t<Cont &, I, N, T>)( //
                requires lvalue_container_like<Cont> && input_iterator<I> &&
                    integral<N> && constructible_from<range_value_t<Cont>, T>)
        {
            return unwrap_reference(cont).insert(p, n, static_cast<T &&>(t));
        }

        /// \cond
        namespace detail
        {
            using ranges::detail::cpp17_iterator_t;
            using ranges::detail::range_cpp17_iterator_t;

            template<typename Cont, typename P>
            auto insert_reserve_helper(Cont & cont, P const p,
                                       range_size_t<Cont> const delta)
                -> CPP_ret(iterator_t<Cont>)( //
                    requires container<Cont> && input_iterator<P> &&
                        random_access_reservable<Cont>)
            {
                auto const old_size = ranges::size(cont);
                auto const max_size = cont.max_size();
                RANGES_EXPECT(delta <= max_size - old_size);
                auto const new_size = old_size + delta;
                auto const old_capacity = cont.capacity();
                auto const index = p - ranges::begin(cont);
                if(old_capacity < new_size)
                {
                    auto const new_capacity =
                        (old_capacity <= max_size / 3 * 2)
                            ? ranges::max(old_capacity + old_capacity / 2, new_size)
                            : max_size;
                    cont.reserve(new_capacity);
                }
                return ranges::begin(cont) + index;
            }

            template<typename Cont, typename P, typename I, typename S>
            auto insert_impl(Cont && cont, P p, I i, S j, std::false_type)
                -> CPP_ret(decltype(unwrap_reference(cont).insert(
                    p, cpp17_iterator_t<I, S>{i}, cpp17_iterator_t<I, S>{j})))( //
                    requires sentinel_for<S, I> && (!range<S>))
            {
                using C = cpp17_iterator_t<I, S>;
                return unwrap_reference(cont).insert(p, C{i}, C{j});
            }

            template<typename Cont, typename P, typename I, typename S>
            auto insert_impl(Cont && cont_, P p, I i, S j, std::true_type)
                -> CPP_ret(decltype(unwrap_reference(cont_).insert(
                    ranges::begin(unwrap_reference(cont_)), cpp17_iterator_t<I, S>{i},
                    cpp17_iterator_t<I, S>{j})))( //
                    requires sized_sentinel_for<S, I> && random_access_reservable<Cont> &&
                    (!range<S>))
            {
                using C = cpp17_iterator_t<I, S>;
                auto && cont = unwrap_reference(cont_);
                auto const delta = static_cast<range_size_t<Cont>>(j - i);
                auto pos = insert_reserve_helper(cont, std::move(p), delta);
                return cont.insert(pos, C{std::move(i)}, C{std::move(j)});
            }

            template<typename Cont, typename I, typename Rng>
            auto insert_impl(Cont && cont, I p, Rng && rng, std::false_type)
                -> CPP_ret(decltype(unwrap_reference(cont).insert(
                    p, range_cpp17_iterator_t<Rng>{ranges::begin(rng)},
                    range_cpp17_iterator_t<Rng>{ranges::end(rng)})))( //
                    requires range<Rng>)
            {
                using C = range_cpp17_iterator_t<Rng>;
                return unwrap_reference(cont).insert(
                    p, C{ranges::begin(rng)}, C{ranges::end(rng)});
            }

            template<typename Cont, typename I, typename Rng>
            auto insert_impl(Cont && cont_, I p, Rng && rng, std::true_type)
                -> CPP_ret(decltype(unwrap_reference(cont_).insert(
                    begin(unwrap_reference(cont_)),
                    range_cpp17_iterator_t<Rng>{ranges::begin(rng)},
                    range_cpp17_iterator_t<Rng>{ranges::end(rng)})))( //
                    requires random_access_reservable<Cont> && sized_range<Rng>)
            {
                using C = range_cpp17_iterator_t<Rng>;
                auto && cont = unwrap_reference(cont_);
                auto const delta = static_cast<range_size_t<Cont>>(ranges::size(rng));
                auto pos = insert_reserve_helper(cont, std::move(p), delta);
                return cont.insert(pos, C{ranges::begin(rng)}, C{ranges::end(rng)});
            }
        } // namespace detail
        /// \endcond

        template<typename Cont, typename P, typename I, typename S>
        auto insert(Cont && cont, P p, I i, S j) -> CPP_ret(decltype(detail::insert_impl(
            static_cast<Cont &&>(cont), std::move(p), std::move(i), std::move(j),
            meta::bool_<random_access_reservable<Cont> &&
                        sized_sentinel_for<S, I>>{})))( //
            requires lvalue_container_like<Cont> && input_iterator<P> &&
                sentinel_for<S, I> &&
            (!range<S>))
        {
            return detail::insert_impl(static_cast<Cont &&>(cont),
                                       std::move(p),
                                       std::move(i),
                                       std::move(j),
                                       meta::bool_ < random_access_reservable<Cont> &&
                                           sized_sentinel_for<S, I>> {});
        }

        template<typename Cont, typename I, typename Rng>
        auto insert(Cont && cont, I p, Rng && rng)
            -> CPP_ret(decltype(detail::insert_impl(
                static_cast<Cont &&>(cont), std::move(p), static_cast<Rng &&>(rng),
                meta::bool_<random_access_reservable<Cont> && sized_range<Rng>>{})))( //
                requires lvalue_container_like<Cont> && input_iterator<I> && range<Rng>)
        {
            return detail::insert_impl(static_cast<Cont &&>(cont),
                                       std::move(p),
                                       static_cast<Rng &&>(rng),
                                       meta::bool_ < random_access_reservable<Cont> &&
                                           sized_range<Rng>> {});
        }

        struct insert_fn
        {
            template<typename Rng, typename... Args>
            using insert_result_t =
                decltype(insert(std::declval<Rng>(), std::declval<Args>()...));

            template<typename Rng, typename T>
            auto operator()(Rng && rng, T && t) const
                -> CPP_ret(insert_result_t<Rng, T>)( //
                    requires range<Rng> &&
                    (!range<T>)&&constructible_from<range_value_t<Rng>, T>)
            {
                return insert(static_cast<Rng &&>(rng), static_cast<T &&>(t));
            }

            template<typename Rng, typename Rng2>
            auto operator()(Rng && rng, Rng2 && rng2) const
                -> CPP_ret(insert_result_t<Rng, Rng2>)( //
                    requires range<Rng> && range<Rng2>)
            {
                static_assert(!is_infinite<Rng>::value,
                              "Attempting to insert an infinite range into a container");
                return insert(static_cast<Rng &&>(rng), static_cast<Rng2 &&>(rng2));
            }

            template<typename Rng, typename T>
            auto operator()(Rng && rng, std::initializer_list<T> rng2) const
                -> CPP_ret(insert_result_t<Rng, std::initializer_list<T> &>)( //
                    requires range<Rng>)
            {
                return insert(static_cast<Rng &&>(rng), rng2);
            }

            template<typename Rng, typename I, typename S>
            auto operator()(Rng && rng, I i, S j) const
                -> CPP_ret(insert_result_t<Rng, I, S>)( //
                    requires range<Rng> && sentinel_for<S, I> && (!range<S>))
            {
                return insert(static_cast<Rng &&>(rng), std::move(i), std::move(j));
            }

            template<typename Rng, typename I, typename T>
            auto operator()(Rng && rng, I p, T && t) const
                -> CPP_ret(insert_result_t<Rng, I, T>)( //
                    requires range<Rng> && input_iterator<I> &&
                    (!range<T>)&&constructible_from<range_value_t<Rng>, T>)
            {
                return insert(
                    static_cast<Rng &&>(rng), std::move(p), static_cast<T &&>(t));
            }

            template<typename Rng, typename I, typename Rng2>
            auto operator()(Rng && rng, I p, Rng2 && rng2) const
                -> CPP_ret(insert_result_t<Rng, I, Rng2>)( //
                    requires range<Rng> && input_iterator<I> && range<Rng2>)
            {
                static_assert(!is_infinite<Rng>::value,
                              "Attempting to insert an infinite range into a container");
                return insert(
                    static_cast<Rng &&>(rng), std::move(p), static_cast<Rng2 &&>(rng2));
            }

            template<typename Rng, typename I, typename T>
            auto operator()(Rng && rng, I p, std::initializer_list<T> rng2) const
                -> CPP_ret(insert_result_t<Rng, I, std::initializer_list<T> &>)( //
                    requires range<Rng> && input_iterator<I>)
            {
                return insert(static_cast<Rng &&>(rng), std::move(p), rng2);
            }

            template<typename Rng, typename I, typename N, typename T>
            auto operator()(Rng && rng, I p, N n, T && t) const
                -> CPP_ret(insert_result_t<Rng, I, N, T>)( //
                    requires range<Rng> && input_iterator<I> && integral<N> &&
                    (!range<T>)&&constructible_from<range_value_t<Rng>, T>)
            {
                return insert(
                    static_cast<Rng &&>(rng), std::move(p), n, static_cast<T &&>(t));
            }

            template<typename Rng, typename P, typename I, typename S>
            auto operator()(Rng && rng, P p, I i, S j) const
                -> CPP_ret(insert_result_t<Rng, P, I, S>)( //
                    requires range<Rng> && input_iterator<P> && sentinel_for<S, I> &&
                    (!range<S>))
            {
                return insert(
                    static_cast<Rng &&>(rng), std::move(p), std::move(i), std::move(j));
            }
        };
    } // namespace adl_insert_detail
    /// \endcond

    /// \ingroup group-actions
    RANGES_INLINE_VARIABLE(adl_insert_detail::insert_fn, insert)

    namespace actions
    {
        using ranges::insert;
    }
} // namespace ranges

#endif