// Boost.Geometry

// Copyright (c) 2020, Oracle and/or its affiliates.

// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle

// Licensed under the Boost Software License version 1.0.
// http://www.boost.org/users/license.html

#ifndef BOOST_GEOMETRY_UTIL_SEQUENCE_HPP
#define BOOST_GEOMETRY_UTIL_SEQUENCE_HPP


#include <type_traits>


namespace boost { namespace geometry
{

namespace util
{


// An alternative would be to use std:tuple and std::pair
//   but it would add dependency.


template <typename ...Ts>
struct type_sequence {};


// true if T is a sequence
template <typename T>
struct is_sequence : std::false_type {};

template <typename ...Ts>
struct is_sequence<type_sequence<Ts...>> : std::true_type {};

template <typename T, T ...Is>
struct is_sequence<std::integer_sequence<T, Is...>> : std::true_type {};


// number of elements in a sequence
template <typename Sequence>
struct sequence_size {};

template <typename ...Ts>
struct sequence_size<type_sequence<Ts...>>
    : std::integral_constant<std::size_t, sizeof...(Ts)>
{};

template <typename T, T ...Is>
struct sequence_size<std::integer_sequence<T, Is...>>
    : std::integral_constant<std::size_t, sizeof...(Is)>
{};


// element of a sequence
template <std::size_t I, typename Sequence>
struct sequence_element {};

template <std::size_t I, typename T, typename ...Ts>
struct sequence_element<I, type_sequence<T, Ts...>>
{
    typedef typename sequence_element<I - 1, type_sequence<Ts...>>::type type;
};

template <typename T, typename ...Ts>
struct sequence_element<0, type_sequence<T, Ts...>>
{
    typedef T type;
};

template <std::size_t I, typename T, T J, T ...Js>
struct sequence_element<I, std::integer_sequence<T, J, Js...>>
    : std::integral_constant
        <
            T,
            sequence_element<I - 1, std::integer_sequence<T, Js...>>::value
        >
{};

template <typename T, T J, T ...Js>
struct sequence_element<0, std::integer_sequence<T, J, Js...>>
    : std::integral_constant<T, J>
{};


template <typename Sequence>
struct sequence_front
    : sequence_element<0, Sequence>
{
    static_assert(sequence_size<Sequence>::value > 0, "Sequence can not be empty.");
};


template <typename Sequence>
struct sequence_back
    : sequence_element<sequence_size<Sequence>::value - 1, Sequence>
{
    static_assert(sequence_size<Sequence>::value > 0, "Sequence can not be empty.");
};


template <typename Sequence>
struct sequence_empty
    : std::integral_constant
        <
            bool,
            sequence_size<Sequence>::value == 0
        >
{};


// merge<type_sequence<A, B>, type_sequence<C, D>>::type is
//   type_sequence<A, B, C, D>
// merge<integer_sequence<A, B>, integer_sequence<C, D>>::type is
//   integer_sequence<A, B, C, D>
template <typename ...Sequences>
struct merge;

template <typename S>
struct merge<S>
{
    typedef S type;
};

template <typename ...T1s, typename ...T2s>
struct merge<type_sequence<T1s...>, type_sequence<T2s...>>
{
    typedef type_sequence<T1s..., T2s...> type;
};

template <typename T, T ...I1s, T ...I2s>
struct merge<std::integer_sequence<T, I1s...>, std::integer_sequence<T, I2s...>>
{
    typedef std::integer_sequence<T, I1s..., I2s...> type;
};

template <typename S1, typename S2, typename ...Sequences>
struct merge<S1, S2, Sequences...>
{
    typedef typename merge
        <
            typename merge<S1, S2>::type,
            typename merge<Sequences...>::type
        >::type type;
};


// combine<type_sequence<A, B>, type_sequence<C, D>>::type is
//   type_sequence<type_sequence<A, C>, type_sequence<A, D>,
//                 type_sequence<B, C>, type_sequence<B, D>>
template <typename Sequence1, typename Sequence2>
struct combine;

template <typename ...T1s, typename ...T2s>
struct combine<type_sequence<T1s...>, type_sequence<T2s...>>
{
    template <typename U1, typename ...U2s>
    using type_sequence_t = type_sequence<type_sequence<U1, U2s>...>;

    typedef typename merge
        <
            type_sequence_t<T1s, T2s...>...
        >::type type;
};

// combine<integer_sequence<T, 1, 2>, integer_sequence<T, 3, 4>>::type is
//   type_sequence<integer_sequence<T, 1, 3>, integer_sequence<T, 1, 4>,
//                 integer_sequence<T, 2, 3>, integer_sequence<T, 2, 4>>
template <typename T, T ...I1s, T ...I2s>
struct combine<std::integer_sequence<T, I1s...>, std::integer_sequence<T, I2s...>>
{
    template <T J1, T ...J2s>
    using type_sequence_t = type_sequence<std::integer_sequence<T, J1, J2s>...>;

    typedef typename merge
        <
            type_sequence_t<I1s, I2s...>...
        >::type type;
};


// Selects least element from a parameter pack based on
// LessPred<T1, T2>::value comparison, similar to std::min_element
template
<
    template <typename, typename> class LessPred,
    typename ...Ts
>
struct select_pack_element;

template
<
    template <typename, typename> class LessPred,
    typename T
>
struct select_pack_element<LessPred, T>
{
    typedef T type;
};

template
<
    template <typename, typename> class LessPred,
    typename T1, typename T2
>
struct select_pack_element<LessPred, T1, T2>
{
    typedef std::conditional_t<LessPred<T1, T2>::value, T1, T2> type;
};

template
<
    template <typename, typename> class LessPred,
    typename T1, typename T2, typename ...Ts
>
struct select_pack_element<LessPred, T1, T2, Ts...>
{
    typedef typename select_pack_element
        <
            LessPred,
            typename select_pack_element<LessPred, T1, T2>::type,
            typename select_pack_element<LessPred, Ts...>::type
        >::type type;
};


// Selects least element from a sequence based on
// LessPred<T1, T2>::value comparison, similar to std::min_element
template
<
    typename Sequence,
    template <typename, typename> class LessPred    
>
struct select_element;

template
<
    typename ...Ts,
    template <typename, typename> class LessPred
>
struct select_element<type_sequence<Ts...>, LessPred>
{
    typedef typename select_pack_element<LessPred, Ts...>::type type;
};


// Selects least pair sequence of elements from a parameter pack based on
// LessPred<xxx_sequence<T11, T12>, xxx_sequence<T21, T22>>::value comparison
template
<
    typename Sequence1,
    typename Sequence2,
    template <typename, typename> class LessPred
>
struct select_combination_element
{
    typedef typename select_element
        <
            typename combine<Sequence1, Sequence2>::type,
            LessPred
        >::type type;
};


} // namespace util


}} // namespace boost::geometry


#endif // BOOST_GEOMETRY_UTIL_SEQUENCE_HPP
