/// \file
// Range v3 library
//
//  Copyright <NAME> 2013-present
//  Copyright <NAME> 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_UTILITY_BOX_HPP
#define RANGES_V3_UTILITY_BOX_HPP

#include <cstdlib>
#include <type_traits>
#include <utility>

#include <meta/meta.hpp>

#include <concepts/concepts.hpp>

#include <range/v3/range_fwd.hpp>

#include <range/v3/utility/get.hpp>

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

RANGES_DIAGNOSTIC_PUSH
RANGES_DIAGNOSTIC_IGNORE_DEPRECATED_DECLARATIONS

namespace ranges
{
    /// \addtogroup group-utility Utility
    /// @{
    ///

    /// \cond
    template<typename T>
    struct RANGES_DEPRECATED("The ranges::mutable_ class template is deprecated") mutable_
    {
        mutable T value;

        CPP_member
        constexpr CPP_ctor(mutable_)()( //
            requires std::is_default_constructible<T>::value)
          : value{}
        {}
        constexpr explicit mutable_(T const & t)
          : value(t)
        {}
        constexpr explicit mutable_(T && t)
          : value(detail::move(t))
        {}
        mutable_ const & operator=(T const & t) const
        {
            value = t;
            return *this;
        }
        mutable_ const & operator=(T && t) const
        {
            value = detail::move(t);
            return *this;
        }
        constexpr operator T &() const &
        {
            return value;
        }
    };

    template<typename T, T v>
    struct RANGES_DEPRECATED("The ranges::constant class template is deprecated") constant
    {
        constant() = default;
        constexpr explicit constant(T const &)
        {}
        constant & operator=(T const &)
        {
            return *this;
        }
        constant const & operator=(T const &) const
        {
            return *this;
        }
        constexpr operator T() const
        {
            return v;
        }
        constexpr T exchange(T const &) const
        {
            return v;
        }
    };
    /// \endcond

    /// \cond
    namespace detail
    {
        // "box" has three different implementations that store a T differently:
        enum class box_compress
        {
            none, // Nothing special: get() returns a reference to a T member subobject
            ebo,  // Apply Empty Base Optimization: get() returns a reference to a T base
                  // subobject
            coalesce // Coalesce all Ts into one T: get() returns a reference to a static
                     // T singleton
        };

        // Per N4582, lambda closures are *not*:
        // - aggregates             ([expr.prim.lambda]/4)
        // - default constructible_from  ([expr.prim.lambda]/p21)
        // - copy assignable        ([expr.prim.lambda]/p21)
        template<typename Fn>
        using could_be_lambda = meta::bool_<!std::is_default_constructible<Fn>::value &&
                                            !std::is_copy_assignable<Fn>::value>;

        template<typename>
        constexpr box_compress box_compression_(...)
        {
            return box_compress::none;
        }
        template<typename T, typename = meta::if_<meta::strict_and<
                                 std::is_empty<T>,
                                 meta::not_<detail::is_final<T>>
#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ == 6 && __GNUC_MINOR__ < 2
                                 // GCC 6.0 & 6.1 find empty lambdas' implicit conversion
                                 // to function pointer when doing overload resolution
                                 // for function calls. That causes hard errors.
                                 // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=71117
                                 ,
                                 meta::not_<could_be_lambda<T>>
#endif
                                 >>>
        constexpr box_compress box_compression_(long)
        {
            return box_compress::ebo;
        }
#ifndef RANGES_WORKAROUND_MSVC_249830
        // MSVC pukes passing non-constant-expression objects to constexpr
        // functions, so do not coalesce.
        template<typename T,
                 typename =
                     meta::if_<meta::strict_and<std::is_empty<T>, detail::is_trivial<T>>>>
        constexpr box_compress box_compression_(int)
        {
            return box_compress::coalesce;
        }
#endif
        template<typename T>
        constexpr box_compress box_compression()
        {
            return box_compression_<T>(0);
        }
    } // namespace detail
    /// \endcond

    template<typename Element, typename Tag = void,
             detail::box_compress = detail::box_compression<Element>()>
    class box
    {
        Element value;

    public:
        CPP_member
        constexpr CPP_ctor(box)()(                                          //
            noexcept(std::is_nothrow_default_constructible<Element>::value) //
            requires std::is_default_constructible<Element>::value)
          : value{}
        {}
        template<typename E>
        constexpr CPP_ctor(box)(E && e)(                               //
            noexcept(std::is_nothrow_constructible<Element, E>::value) //
            requires(!defer::same_as<box, detail::decay_t<E>>) &&
            defer::constructible_from<Element, E> && defer::convertible_to<E, Element>)
          : value(static_cast<E &&>(e))
        {}
        template<typename E>
        constexpr explicit CPP_ctor(box)(E && e)(                      //
            noexcept(std::is_nothrow_constructible<Element, E>::value) //
            requires(!defer::same_as<box, detail::decay_t<E>>) &&
            defer::constructible_from<Element, E> && (!defer::convertible_to<E, Element>))
          : value(static_cast<E &&>(e))
        {}

        constexpr Element & get() & noexcept
        {
            return value;
        }
        constexpr Element const & get() const & noexcept
        {
            return value;
        }
        constexpr Element && get() && noexcept
        {
            return detail::move(value);
        }
        constexpr Element const && get() const && noexcept
        {
            return detail::move(value);
        }
    };

    template<typename Element, typename Tag>
    class box<Element, Tag, detail::box_compress::ebo> : Element
    {
    public:
        CPP_member
        constexpr CPP_ctor(box)()(                                          //
            noexcept(std::is_nothrow_default_constructible<Element>::value) //
            requires std::is_default_constructible<Element>::value)
          : Element{}
        {}
        template<typename E>
        constexpr CPP_ctor(box)(E && e)(                               //
            noexcept(std::is_nothrow_constructible<Element, E>::value) //
            requires(!defer::same_as<box, detail::decay_t<E>>) &&
            defer::constructible_from<Element, E> && defer::convertible_to<E, Element>)
          : Element(static_cast<E &&>(e))
        {}
        template<typename E>
        constexpr explicit CPP_ctor(box)(E && e)(                      //
            noexcept(std::is_nothrow_constructible<Element, E>::value) //
            requires(!defer::same_as<box, detail::decay_t<E>>) &&
            defer::constructible_from<Element, E> && (!defer::convertible_to<E, Element>))
          : Element(static_cast<E &&>(e))
        {}

        constexpr Element & get() & noexcept
        {
            return *this;
        }
        constexpr Element const & get() const & noexcept
        {
            return *this;
        }
        constexpr Element && get() && noexcept
        {
            return detail::move(*this);
        }
        constexpr Element const && get() const && noexcept
        {
            return detail::move(*this);
        }
    };

    template<typename Element, typename Tag>
    class box<Element, Tag, detail::box_compress::coalesce>
    {
        static Element value;

    public:
        constexpr box() noexcept = default;
        template<typename E>
        constexpr CPP_ctor(box)(E &&)( //
            noexcept(true)             //
            requires(!defer::same_as<box, detail::decay_t<E>>) &&
            defer::constructible_from<Element, E> && defer::convertible_to<E, Element>)
        {}
        template<typename E>
        constexpr explicit CPP_ctor(box)(E &&)( //
            noexcept(true)                      //
            requires(!defer::same_as<box, detail::decay_t<E>>) &&
            defer::constructible_from<Element, E> && (!defer::convertible_to<E, Element>))
        {}

        constexpr Element & get() & noexcept
        {
            return value;
        }
        constexpr Element const & get() const & noexcept
        {
            return value;
        }
        constexpr Element && get() && noexcept
        {
            return detail::move(value);
        }
        constexpr Element const && get() const && noexcept
        {
            return detail::move(value);
        }
    };

    template<typename Element, typename Tag>
    Element box<Element, Tag, detail::box_compress::coalesce>::value{};

    /// \cond
    namespace _get_
    {
        /// \endcond
        // Get by tag type
        template<typename Tag, typename Element, detail::box_compress BC>
        constexpr Element & get(box<Element, Tag, BC> & b) noexcept
        {
            return b.get();
        }
        template<typename Tag, typename Element, detail::box_compress BC>
        constexpr Element const & get(box<Element, Tag, BC> const & b) noexcept
        {
            return b.get();
        }
        template<typename Tag, typename Element, detail::box_compress BC>
        constexpr Element && get(box<Element, Tag, BC> && b) noexcept
        {
            return detail::move(b).get();
        }
        // Get by index
        template<std::size_t I, typename Element, detail::box_compress BC>
        constexpr Element & get(box<Element, meta::size_t<I>, BC> & b) noexcept
        {
            return b.get();
        }
        template<std::size_t I, typename Element, detail::box_compress BC>
        constexpr Element const & get(
            box<Element, meta::size_t<I>, BC> const & b) noexcept
        {
            return b.get();
        }
        template<std::size_t I, typename Element, detail::box_compress BC>
        constexpr Element && get(box<Element, meta::size_t<I>, BC> && b) noexcept
        {
            return detail::move(b).get();
        }
        /// \cond
    } // namespace _get_
    /// \endcond
    /// @}
} // namespace ranges

RANGES_DIAGNOSTIC_POP

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

#endif
