/// \file
// Range v3 library
//
//  Copyright Eric Niebler 2013-present
//  Copyright Casey Carter 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_COMPRESSED_PAIR_HPP
#define RANGES_V3_UTILITY_COMPRESSED_PAIR_HPP

#include <type_traits>
#include <utility>
#include <meta/meta.hpp>
#include <concepts/concepts.hpp>
#include <range/v3/range_fwd.hpp>
#include <range/v3/utility/box.hpp>
#include <concepts/concepts.hpp>
#include <range/v3/utility/static_const.hpp>

namespace ranges
{
    /// \cond
    namespace compressed_tuple_detail
    {
        // tagging individual elements with the complete type list disambiguates
        // base classes when composing compressed_tuples recursively.
        template<typename T, std::size_t I, typename... Ts>
        using storage = box<T, meta::list<meta::size_t<I>, Ts...>>;

        template<typename List, typename Indices> struct compressed_tuple_;
        template<typename... Ts, std::size_t... Is>
        struct RANGES_EMPTY_BASES compressed_tuple_<meta::list<Ts...>, meta::index_sequence<Is...>>
          : storage<Ts, Is, Ts...>...
        {
            static_assert(Same<meta::index_sequence<Is...>,
                meta::make_index_sequence<sizeof...(Is)>>, "What madness is this?!?");

            compressed_tuple_() = default;

            template<typename... Args,
                meta::if_<meta::strict_and<std::is_constructible<Ts, Args>...>, int> = 0>
            constexpr compressed_tuple_(Args &&... args)
                noexcept(meta::strict_and<std::is_nothrow_constructible<storage<Ts, Is, Ts...>, Args>...>::value)
              : storage<Ts, Is, Ts...>{static_cast<Args &&>(args)}...
            {}

            template<typename... Us,
                meta::if_<meta::strict_and<std::is_constructible<Us, Ts const &>...>, int> = 0>
            constexpr operator std::tuple<Us...> () const
                noexcept(meta::strict_and<std::is_nothrow_constructible<Us, Ts const &>...>::value)
            {
                return std::tuple<Us...>{get<Is>(*this)...};
            }

            template<std::size_t I, typename T = meta::at_c<meta::list<Ts...>, I>>
            friend constexpr /*c++14*/ T &
            get(compressed_tuple_ &tuple) noexcept
            {
                return static_cast<storage<T, I, Ts...> &>(tuple).get();
            }
            template<std::size_t I, typename T = meta::at_c<meta::list<Ts...>, I>>
            friend constexpr T const &
            get(compressed_tuple_ const &tuple) noexcept
            {
                return static_cast<storage<T, I, Ts...> const &>(tuple).get();
            }
            template<std::size_t I, typename T = meta::at_c<meta::list<Ts...>, I>>
            friend constexpr /*c++14*/ T &&
            get(compressed_tuple_ &&tuple) noexcept
            {
                return static_cast<storage<T, I, Ts...> &&>(tuple).get();
            }
            template<std::size_t I, typename T = meta::at_c<meta::list<Ts...>, I>>
            friend constexpr /*c++14*/ T const &&
            get(compressed_tuple_ const &&tuple) noexcept
            {
                return static_cast<storage<T, I, Ts...> const &&>(tuple).get();
            }
        };

        template<typename... Ts>
        using compressed_tuple RANGES_DEPRECATED("ranges::compressed_tuple is deprecated.") =
            compressed_tuple_<meta::list<Ts...>, meta::make_index_sequence<sizeof...(Ts)>>;
    }
    /// \endcond

    using compressed_tuple_detail::compressed_tuple;

    struct make_compressed_tuple_fn
    {
        template<typename... Args>
        constexpr auto CPP_auto_fun(operator())(Args &&... args) (const)
        (
            return compressed_tuple<bind_element_t<Args>...>{static_cast<Args &&>(args)...}
        )
    };

    /// \ingroup group-utility
    /// \sa `make_compressed_tuple_fn`
    RANGES_INLINE_VARIABLE(make_compressed_tuple_fn, make_compressed_tuple)

    template<typename First, typename Second>
    struct RANGES_EMPTY_BASES compressed_pair
      : box<First, meta::size_t<0>>
      , box<Second, meta::size_t<1>>
    {
        using first_type = First;
        using second_type = Second;

        compressed_pair() = default;

        CPP_template(typename U, typename V)(
            requires Constructible<First, U> && Constructible<Second, V>)
        constexpr compressed_pair(U &&u, V &&v)
            noexcept(noexcept(First((U &&) u)) && noexcept(Second((V &&) v)))
          : box<First, meta::size_t<0>>{(U &&) u}
          , box<Second, meta::size_t<1>>{(V &&) v}
        {}

        constexpr /*c++14*/ First &first() &
        {
            return this->box<First, meta::size_t<0>>::get();
        }
        constexpr First const &first() const &
        {
            return this->box<First, meta::size_t<0>>::get();
        }
        constexpr /*c++14*/ First &&first() &&
        {
            return static_cast<First &&>(this->box<First, meta::size_t<0>>::get());
        }

        constexpr /*c++14*/ Second &second() &
        {
            return this->box<Second, meta::size_t<1>>::get();
        }
        constexpr Second const &second() const &
        {
            return this->box<Second, meta::size_t<1>>::get();
        }
        constexpr /*c++14*/ Second &&second() &&
        {
            return static_cast<Second &&>(this->box<Second, meta::size_t<1>>::get());
        }

        CPP_template(typename F, typename S)(
            requires ConvertibleTo<First const&, F> && ConvertibleTo<Second const &, S>)
        constexpr operator std::pair<F, S> () const
        {
            return std::pair<F, S>{first(), second()};
        }
    };

    struct make_compressed_pair_fn
    {
        template<typename First, typename Second>
        constexpr auto CPP_auto_fun(operator())(First &&f, Second &&s) (const)
        (
            return compressed_pair<bind_element_t<First>, bind_element_t<Second>>{
                static_cast<First &&>(f), static_cast<Second &&>(s)
            }
        )
    };

    /// \ingroup group-utility
    /// \sa `make_compressed_pair_fn`
    RANGES_INLINE_VARIABLE(make_compressed_pair_fn, make_compressed_pair)
}

RANGES_DIAGNOSTIC_PUSH
RANGES_DIAGNOSTIC_IGNORE_MISMATCHED_TAGS
namespace std
{
    template<typename... Ts, size_t... Is>
    struct tuple_size< ::ranges::compressed_tuple_detail::compressed_tuple_<::meta::list<Ts...>, ::meta::index_sequence<Is...>>>
      : integral_constant<size_t, sizeof...(Ts)>
    {};

    template<size_t I, typename... Ts, size_t... Is>
    struct tuple_element<I, ::ranges::compressed_tuple_detail::compressed_tuple_<::meta::list<Ts...>, ::meta::index_sequence<Is...>>>
    {
        using type = ::meta::at_c<::meta::list<Ts...>, I>;
    };

    template<typename First, typename Second>
    struct tuple_size< ::ranges::compressed_pair<First, Second>>
      : integral_constant<size_t, 2>
    {};

    template<typename First, typename Second>
    struct tuple_element<0, ::ranges::compressed_pair<First, Second>>
    {
        using type = First;
    };

    template<typename First, typename Second>
    struct tuple_element<1, ::ranges::compressed_pair<First, Second>>
    {
        using type = Second;
    };
}
RANGES_DIAGNOSTIC_POP

#endif
