/***************************************************************************
* Copyright (c) Johan Mabille, Sylvain Corlay and Wolf Vollprecht          *
* Copyright (c) QuantStack                                                 *
*                                                                          *
* Distributed under the terms of the BSD 3-Clause License.                 *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#ifndef XTL_SEQUENCE_HPP
#define XTL_SEQUENCE_HPP

#include <array>
#include <cstddef>
#include <type_traits>
#include <utility>
#include <vector>

#include "xtl_config.hpp"
#include "xmeta_utils.hpp"

namespace xtl
{
    template <class S>
    S make_sequence(typename S::size_type size);

    template <class S>
    S make_sequence(typename S::size_type size, typename S::value_type v);

    template <class S>
    S make_sequence(std::initializer_list<typename S::value_type> init);

    template <class R, class A>
    decltype(auto) forward_sequence(A&& s);

    // equivalent to std::size(c) in c++17
    template <class C>
    constexpr auto sequence_size(const C& c) -> decltype(c.size());

    // equivalent to std::size(a) in c++17
    template <class T, std::size_t N>
    constexpr std::size_t sequence_size(const T (&a)[N]);

    /********************************
     * make_sequence implementation *
     ********************************/

    namespace detail
    {
        template <class S>
        struct sequence_builder
        {
            using value_type = typename S::value_type;
            using size_type = typename S::size_type;

            inline static S make(size_type size)
            {
                return S(size);
            }

            inline static S make(size_type size, value_type v)
            {
                return S(size, v);
            }

            inline static S make(std::initializer_list<value_type> init)
            {
                return S(init);
            }
        };

        template <class T, std::size_t N>
        struct sequence_builder<std::array<T, N>>
        {
            using sequence_type = std::array<T, N>;
            using value_type = typename sequence_type::value_type;
            using size_type = typename sequence_type::size_type;

            inline static sequence_type make(size_type /*size*/)
            {
                return sequence_type();
            }

            inline static sequence_type make(size_type /*size*/, value_type v)
            {
                sequence_type s;
                s.fill(v);
                return s;
            }

            inline static sequence_type make(std::initializer_list<value_type> init)
            {
                sequence_type s;
                std::copy(init.begin(), init.end(), s.begin());
                return s;
            }
        };
    }

    template <class S>
    inline S make_sequence(typename S::size_type size)
    {
        return detail::sequence_builder<S>::make(size);
    }

    template <class S>
    inline S make_sequence(typename S::size_type size, typename S::value_type v)
    {
        return detail::sequence_builder<S>::make(size, v);
    }

    template <class S>
    inline S make_sequence(std::initializer_list<typename S::value_type> init)
    {
        return detail::sequence_builder<S>::make(init);
    }

    /***********************************
     * forward_sequence implementation *
     ***********************************/

    namespace detail
    {
        template <class R, class A, class E = void>
        struct sequence_forwarder_impl
        {
            template <class T>
            static inline R forward(const T& r)
            {
                R ret;
                std::copy(std::begin(r), std::end(r), std::begin(ret));
                return ret;
            }
        };

        template <class R, class A>
        struct sequence_forwarder_impl<R, A, void_t<decltype(std::declval<R>().resize(
              std::declval<std::size_t>()))>>
        {
            template <class T>
            static inline auto forward(const T& r)
            {
                return R(std::begin(r), std::end(r));
            }
        };

        template <class R, class A>
        struct sequence_forwarder
            : sequence_forwarder_impl<R, A>
        {
        };

        template <class R>
        struct sequence_forwarder<R, R>
        {
            template <class T>
            static inline T&& forward(T&& t) noexcept
            {
                return std::forward<T>(t);
            }
        };

        template <class R, class A>
        using forwarder_type = detail::sequence_forwarder<
            std::decay_t<R>,
            std::remove_cv_t<std::remove_reference_t<A>>
        >;
    }

    template <class R, class A>
    inline decltype(auto) forward_sequence(typename std::remove_reference<A>::type& s)
    {
        using forwarder = detail::forwarder_type<R, A>;
        return forwarder::forward(std::forward<A>(s));
    }

    template <class R, class A>
    inline decltype(auto) forward_sequence(typename std::remove_reference<A>::type&& s)
    {
        using forwarder = detail::forwarder_type<R, A>;
        static_assert(!std::is_lvalue_reference<A>::value,
                      "Can not forward an rvalue as an lvalue.");
        return forwarder::forward(std::move(s));
    }

    /********************************
     * sequence_size implementation *
     ********************************/

    // equivalent to std::size(c) in c++17
    template <class C>
    constexpr auto sequence_size(const C& c) -> decltype(c.size())
    {
        return c.size();
    }

    // equivalent to std::size(a) in c++17
    template <class T, std::size_t N>
    constexpr std::size_t sequence_size(const T (&)[N])
    {
        return N;
    }

    /****************************
     * are_equivalent_sequences *
     ****************************/

    template <class E1, class E2>
    inline bool are_equivalent_sequences(const E1& e1, const E2& e2)
    {
        return std::equal(e1.cbegin(), e1.cend(), e2.cbegin(), e2.cend());
    }
}

#endif
