/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Copyright 2015-2018 Danny Robson <danny@nerdcruft.net>
 */

#pragma once

#include "../types.hpp"

#include "../types/traits.hpp"

#include <cstddef>
#include <tuple>
#include <functional>


namespace cruft::tuple::type {
    ///////////////////////////////////////////////////////////////////////////
    /// Invoke a provided FunctorT with the provided arguments and a
    /// type_tag<T> for each type in the tuple-like type TupleT.
    ///
    /// The order of invocation is low-index to high-index (left to right).
    ///
    /// \tparam TupleT      A tuple-like type
    /// \tparam FunctorT    An invokable type
    /// \tparam S           The index to be invoked; this is private.
    /// \tparam ArgsT       User supplied arguments
    template<
        typename TupleT,
        typename FunctorT,
        size_t S = 0,
        typename ...ArgsT
    >
    void
    each (FunctorT &&func, ArgsT &&...args)
    {
        /// Get a handle on the type we need to forward.
        static_assert (S < std::tuple_size_v<TupleT>);
        using value_type = typename std::tuple_element<S,TupleT>::type;

        /// Call the user's invokable.
        std::invoke (func, args..., cruft::type_tag<value_type> {});

        /// Recurse into ourselves with a higher index if we haven't reached
        /// the end.
        if constexpr (S + 1 < std::tuple_size_v<TupleT>) {
            each<TupleT,FunctorT,S+1> (
                std::forward<FunctorT> (func),
                std::forward<ArgsT> (args)...
            );
        }
    }


    ///////////////////////////////////////////////////////////////////////////////
    /// Convert a tuple-type holding an initial set of types into a tuple-type
    /// holding a different set of types using the supplied transform:
    ///     `FieldT<...>`.
    ///
    /// \tparam TupleT  The initial tuple-type
    /// \tparam FieldT  The type-transform type.
    ///                 The conversion uses FieldT<...>::type to perform the
    ///                 conversions.
    template <
        typename TupleT,
        template <
            typename
        > class FieldT,
        typename Indices = std::make_index_sequence<
            std::tuple_size<TupleT>::value
        >
    >
    struct map;


    //-----------------------------------------------------------------------------
    template <
        typename TupleT,
        template <
            typename
        > class FieldT,
        size_t ...Indices
    >
    struct map<
        TupleT,
        FieldT,
        std::index_sequence<Indices...>
    > {
        typedef std::tuple<
            typename FieldT<
                typename std::tuple_element<Indices, TupleT>::type
            >::type...
        > type;
    };


    ///////////////////////////////////////////////////////////////////////////
    /// Query the index of the first occurrence of type `T' in the tuple-like
    /// type `TupleT'.
    ///
    /// If the query type does not occur in the tuple type a compiler error
    /// should be generated.
    ///
    /// \tparam TupleT  The tuple-type to be queried
    /// \tparam ValueT  The value-type we are searching for
    ///
    /// The index will be defined as the size_t `::value` if it has been found.
    template<
        typename TupleT,
        typename ValueT,
        typename = std::make_index_sequence<
            std::tuple_size_v<TupleT>
        >
    >
    struct index { };


    //-------------------------------------------------------------------------
    // specialisation for tuples with matching first elements. the index is 0.
    template<
        typename ValueT,
        typename ...TailT,
        size_t ...Indices
    >
    struct index<
        std::tuple<ValueT, TailT...>,
        ValueT,
        std::index_sequence<Indices...>
    > {
        static constexpr std::size_t value = 0;
    };


    //-------------------------------------------------------------------------
    // specialisation for tuples with non-matching first elements.
    // increment and recurse.
    template<
        typename ValueT,
        typename HeadT,
        typename ...TailT,
        size_t ...Indices
    >
    struct index<
        std::tuple<HeadT, TailT...>,
        ValueT,
        std::index_sequence<Indices...>
    > {
        static constexpr std::size_t value = 1u + index<std::tuple<TailT...>,ValueT>::value;
    };


    //-------------------------------------------------------------------------
    // convert the tuple type (which is not a tuple or it would have matched
    // the tuple specialisation, into the equivalent std::tuple and requery
    // against that.
    template <
        typename TupleT,
        typename ValueT,
        size_t ...Indices
    >  struct index<
        TupleT,
        ValueT,
        std::index_sequence<Indices...>
    > {
        static constexpr std::size_t value = index<
            std::tuple<std::tuple_element_t<Indices,TupleT>...>,
            ValueT,
            std::index_sequence<Indices...>
        >::value;
    };


    ///////////////////////////////////////////////////////////////////////////
    /// Transform a tuple-like type (ie, something that responds to
    /// tuple_element) into a tuple
    template <
        typename TupleT,
        typename Indices = std::make_index_sequence<
            std::tuple_size_v<TupleT>
        >
    > struct entuple;


    //-------------------------------------------------------------------------
    template <
        typename TupleT,
        size_t ...Indices
    > struct entuple<
        TupleT,
        std::index_sequence<Indices...>
    > {
        using type = std::tuple<
            std::tuple_element_t<Indices,TupleT>...
        >;
    };


    //-------------------------------------------------------------------------
    template <typename T>
    using entuple_t = typename entuple<T>::type;


    ///////////////////////////////////////////////////////////////////////////
    /// Concatenate a variadic collection of tuple-like types
    template <typename ...Args>
    struct cat;


    //-------------------------------------------------------------------------
    template <>
    struct cat<> { using type = std::tuple<>; };


    //-------------------------------------------------------------------------
    template <typename HeadT, typename ...TailT>
    struct cat<HeadT,TailT...> {
        using type = decltype(
            std::tuple_cat (
                std::declval<
                    entuple_t<HeadT>
                > (),
                std::declval<
                    typename cat<TailT...>::type
                > ()
            )
        );
    };


    //-------------------------------------------------------------------------
    template <typename ...TupleT>
    using cat_t = typename cat<TupleT...>::type;


    ///////////////////////////////////////////////////////////////////////////
    /// Remove a type from a tuple-like type
    template <
        typename DoomedT,
        typename TupleT
    >
    struct remove;


    //-------------------------------------------------------------------------
    template <
        typename DoomedT
    > struct remove<
        DoomedT,
        std::tuple<>
    > {
        using type = std::tuple<>;
    };


    //-------------------------------------------------------------------------
    template <
        typename DoomedT,
        typename HeadT,
        typename ...TailT
    >
    struct remove<
        DoomedT,
        std::tuple<HeadT,TailT...>
    > {
        using type = cat_t<
            std::conditional_t<
                std::is_same_v<DoomedT,HeadT>,
                std::tuple<     >,
                std::tuple<HeadT>
            >,
            typename remove<DoomedT,std::tuple<TailT...>>::type
        >;
    };


    //-------------------------------------------------------------------------
    template <typename DoomedT, typename TupleT>
    using remove_t = typename remove<DoomedT,TupleT>::type;


    ///////////////////////////////////////////////////////////////////////////
    /// Remove duplicate types from the list of held types in a tuple
    template <typename TupleT>
    struct unique;


    //-------------------------------------------------------------------------
    template <>
    struct unique<std::tuple<>> { using type = std::tuple<>; };


    //-------------------------------------------------------------------------
    template <typename HeadT, typename ...TailT>
    struct unique<
        std::tuple<HeadT, TailT...>
    > {
        using type = cat_t<
            std::tuple<HeadT>,
            typename unique<
                typename remove<
                    HeadT,
                    std::tuple<TailT...>
                >::type
            >::type
        >;
    };


    //-------------------------------------------------------------------------
    template <typename T>
    using unique_t = typename unique<T>::type;


    ///////////////////////////////////////////////////////////////////////////
    /// Convenience accessor for the 'nth' type of a tuple.
    template <typename TupleT, std::size_t IndexV>
    struct nth;


    //-------------------------------------------------------------------------
    template <typename HeadT, typename ...TailT>
    struct nth<std::tuple<HeadT,TailT...>,0> {
        using type = HeadT;
    };


    //-------------------------------------------------------------------------
    template <typename HeadT, typename ...TailT, std::size_t IndexV>
    struct nth<std::tuple<HeadT,TailT...>,IndexV> : nth<std::tuple<TailT...>,IndexV-1> { };


    //-------------------------------------------------------------------------
    template <typename TupleT, std::size_t IndexV>
    using nth_t = typename nth<TupleT, IndexV>::type;


    ///////////////////////////////////////////////////////////////////////////
    /// Extract the first CountV types of the tuple-like-object and store them
    /// in a tuple definition.
    template <
        std::size_t CountV,
        typename TupleT,
        typename Indices = std::make_index_sequence<CountV>
    >
    struct prefix;


    //-------------------------------------------------------------------------
    template <
        std::size_t CountV,
        typename TupleT,
        std::size_t ...IndexV
    >
    struct prefix<
        CountV,
        TupleT,
        std::index_sequence<IndexV...>
    > {
        using type = std::tuple<
            std::tuple_element_t<IndexV, TupleT>...
        >;
    };


    //-------------------------------------------------------------------------
    template <std::size_t CountV, typename TupleT>
    using prefix_t = typename prefix<CountV, TupleT>::type;
};