//
//  Copyright (c) 2018, Cem Bassoy, cem.bassoy@gmail.com
//
//  Distributed under 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)
//
//  The authors gratefully acknowledge the support of
//  Fraunhofer IOSB, Ettlingen, Germany
//

#ifndef BOOST_UBLAS_TENSOR_MULTI_INDEX_UTILITY_HPP
#define BOOST_UBLAS_TENSOR_MULTI_INDEX_UTILITY_HPP


#include <vector>
#include <tuple>
#include <type_traits>


namespace boost::numeric::ublas::detail {

template<class ... index_types>
struct has_index_impl;

template<class itype_left, class itype_right>
struct has_index_impl<itype_left, itype_right>
{
    static constexpr bool value = itype_left::value == itype_right::value;
};

template<class itype_left>
struct has_index_impl <itype_left, std::tuple<> >
{
    static constexpr bool value = false;
};

template<class itype_left, class itype_right>
struct has_index_impl <itype_left, std::tuple<itype_right> >
{
    static constexpr bool value = has_index_impl<itype_left,itype_right>::value;
};

template<class itype_left, class itype_right, class ... index_types>
struct has_index_impl <itype_left, std::tuple<itype_right, index_types...> >
{
    using next_type = has_index_impl<itype_left, std::tuple<index_types...>>;
    static constexpr bool value = has_index_impl<itype_left,itype_right>::value || next_type::value;
};
} // namespace boost::numeric::ublas::detail



namespace boost::numeric::ublas
{

/** @brief has_index is true if index occurs once or more in a multi-index
 *
 * @note a multi-index represents as tuple of single indexes of type boost::numeric::ublas::index::index_type
 *
 * @code auto has_index_value = has_index<index_type<1>, std::tuple<index_type<2>,index_type<1>> >::value; @endcode
 *
 * @tparam index_type type of index
 * @tparam tuple_type type of std::tuple representing a multi-index
*/
template<class index_type, class tuple_type>
struct has_index
{
    static constexpr bool value = detail::has_index_impl<std::decay_t<index_type>,std::decay_t<tuple_type>>::value;
};

} // namespace boost::numeric::ublas

////////////////////////////////////////////////
////////////////////////////////////////////////

namespace boost::numeric::ublas::detail {


template<class ... index_types>
struct valid_multi_index_impl;

template<>
struct valid_multi_index_impl<std::tuple<>>
{
    static constexpr bool value = true;
};

template<class itype>
struct valid_multi_index_impl<std::tuple<itype>>
{
    static constexpr bool value = true;
};


template<class itype, class ... index_types>
struct valid_multi_index_impl<std::tuple<itype,index_types...>>
{
    using ttype     = std::tuple<index_types...>;
    using has_index_type = has_index<itype, ttype>;

    static constexpr bool is_index_zero   = itype::value==0ul;
    static constexpr bool has_index_value = has_index_type::value && !is_index_zero;
    static constexpr bool value = !has_index_value && valid_multi_index_impl<ttype>::value;
};
} // namespace boost::numeric::ublas::detail

namespace boost::numeric::ublas
{

/** @brief valid_multi_index is true if indexes occur only once in a multi-index
 *
 * @note a multi-index represents as tuple of single indexes of type boost::numeric::ublas::index::index_type
 *
 * @code auto valid = valid_multi_index<  std::tuple<index_type<2>,index_type<1>>  >::value;
 * @endcode
 *
 * @tparam tuple_type type of std::tuple representing a multi-index
*/
template<class tupe_type>
struct valid_multi_index
{
    static constexpr bool value = detail::valid_multi_index_impl<std::decay_t<tupe_type>>::value;
};

} // namespace boost::numeric::ublas

////////////////////////////////////////////////
////////////////////////////////////////////////

namespace boost::numeric::ublas::detail
{

template<class ... index_types >
struct number_equal_indexes_impl;

template<class ... itypes_right >
struct number_equal_indexes_impl < std::tuple<>, std::tuple<itypes_right...>>
{
    static constexpr unsigned value  = 0;
};

template<class itype, class ... itypes_left, class ... itypes_right>
struct number_equal_indexes_impl < std::tuple<itype,itypes_left...>, std::tuple<itypes_right...>>
{
    using tuple_right = std::tuple<itypes_right...>;
    using has_index_type = has_index<itype, tuple_right>;

    static constexpr bool is_index_zero   = itype::value==0ul;
    static constexpr bool has_index_value = has_index_type::value && !is_index_zero;

    using next_type = number_equal_indexes_impl< std::tuple<itypes_left...>, tuple_right >;
    static constexpr unsigned v = has_index_value ? 1 : 0;
    static constexpr unsigned value  = v + next_type::value;
};
} // namespace boost::numeric::ublas::detail


namespace boost::numeric::ublas {

/** @brief number_equal_indexes contains the number of equal indexes of two multi-indexes
 *
 * @note a multi-index represents as tuple of single indexes of type boost::numeric::ublas::index::index_type
 *
 *
 * @code auto num = number_equal_indexes<
 *                        std::tuple<index_type<2>,index_type<1>>,
 *                        std::tuple<index_type<1>,index_type<3>>  >::value;
 * @endcode
 *
 * @tparam tuple_type_left  type of left std::tuple representing a multi-index
 * @tparam tuple_type_right type of right std::tuple representing a multi-index
*/
template<class tuple_left, class tuple_right>
struct number_equal_indexes
{
    static constexpr unsigned value  =
        detail::number_equal_indexes_impl< std::decay_t<tuple_left>, std::decay_t<tuple_right>>::value;
};

} // namespace boost::numeric::ublas


////////////////////////////////////////////////
////////////////////////////////////////////////

namespace boost::numeric::ublas::detail
{


template<std::size_t r, std::size_t m, class itype, class ttype>
struct index_position_impl
{
    static constexpr auto is_same = std::is_same< std::decay_t<itype>, std::decay_t<std::tuple_element_t<r,ttype>> >::value;
    static constexpr auto value   = is_same ? r : index_position_impl<r+1,m,itype,ttype>::value;
};



template<std::size_t m, class itype, class ttype>
struct index_position_impl < m, m, itype, ttype>
{
    static constexpr auto value = std::tuple_size<ttype>::value;
};

} // namespace boost::numeric::ublas::detail


namespace boost::numeric::ublas
{
/** @brief index_position contains the zero-based index position of an index type within a multi-index
 *
 * @note a multi-index represents as tuple of single indexes of type boost::numeric::ublas::index::index_type
 *
 * @code auto num = index_position<
 *                       index_type<1>,
 *                       std::tuple<index_type<2>,index_type<1>>  >::value;
 * @endcode
 *
 * @returns value returns 0 and N-1 if index_type is found, N otherwise where N is tuple_size_v<tuple_type>.
 *
 * @tparam index_type type of index
 * @tparam tuple_type type of std::tuple that is searched for index
*/
template<class index_type, class tuple_type>
struct index_position
{
    static constexpr auto value  = detail::index_position_impl<0ul,std::tuple_size<tuple_type>::value,std::decay_t<index_type>,std::decay_t<tuple_type>>::value;
};

} // namespace boost::numeric::ublas

////////////////////////////////////////////////
////////////////////////////////////////////////


namespace boost::numeric::ublas::detail
{

template<std::size_t r, std::size_t m>
struct index_position_pairs_impl
{
    template<class array_type, class tuple_left, class tuple_right>
    static constexpr void run(array_type& out, tuple_left const& lhs, tuple_right const& rhs, std::size_t p)
    {
        using index_type     = std::tuple_element_t<r-1,tuple_left>;
        using has_index_type = has_index<index_type, tuple_right>;
        using get_index_type = index_position<index_type,tuple_right>;
        using next_type      = index_position_pairs_impl<r+1,m>;
        if constexpr ( has_index_type::value && index_type::value != 0)
            out[p++] = std::make_pair(r-1,get_index_type::value);
        next_type::run( out, lhs, rhs, p );
    }
};

template<std::size_t m>
struct index_position_pairs_impl<m,m>
{
    template<class array_type, class tuple_left, class tuple_right>
    static constexpr void run( 
        [[maybe_unused]] array_type& out, 
        [[maybe_unused]] tuple_left const& t1 , 
        [[maybe_unused]] tuple_right const& t2, 
        [[maybe_unused]] std::size_t p
    ){
        using index_type     = std::tuple_element_t<m-1,tuple_left>;
        using has_index_type = has_index<index_type, tuple_right>;
        using get_index_type = index_position<index_type, tuple_right>;
        if constexpr ( has_index_type::value && index_type::value != 0 )
            out[p] = std::make_pair(m-1,get_index_type::value);
    }
};

template<std::size_t r>
struct index_position_pairs_impl<r,0>
{
    template<class array_type, class tuple_left, class tuple_right>
    static constexpr void run(
        [[maybe_unused]] array_type& a, 
        [[maybe_unused]] tuple_left const& t1, 
        [[maybe_unused]] tuple_right const& t2, 
        [[maybe_unused]] std::size_t sz
    ){}
};


} // namespace boost::numeric::ublas::detail

namespace boost::numeric::ublas
{
/** @brief index_position_pairs returns zero-based index positions of matching indexes of two multi-indexes
 *
 * @note a multi-index represents as tuple of single indexes of type boost::numeric::ublas::index::index_type
 *
 * @code auto pairs = index_position_pairs(std::make_tuple(_a,_b), std::make_tuple(_b,_c));
 * @endcode
 *
 * @returns a std::array instance containing index position pairs of type std::pair<std::size_t, std::size_t>.
 *
 * @param lhs left std::tuple instance representing a multi-index
 * @param rhs right std::tuple instance representing a multi-index
*/
template<class tuple_left, class tuple_right>
auto index_position_pairs(tuple_left const& lhs, tuple_right const& rhs)
{
    using pair_type = std::pair<std::size_t,std::size_t>;
    constexpr auto m = std::tuple_size<tuple_left >::value;
    constexpr auto p = number_equal_indexes<tuple_left, tuple_right>::value;
    auto array = std::array<pair_type,p>{};
    detail::index_position_pairs_impl<1,m>::run(array, lhs, rhs,0);
    return array;
}

} // namespace boost::numeric::ublas

////////////////////////////
////////////////////////////
////////////////////////////
////////////////////////////


//namespace boost::numeric::ublas::detail
//{

//template<class array_type, std::size_t ... R>
//constexpr auto array_to_vector_impl( array_type const& array, std::index_sequence<R...> /*unused*/)
//{
//    return std::make_pair(
//          std::vector<std::size_t>{std::get<R>(array).first  +1 ...} ,
//          std::vector<std::size_t>{std::get<R>(array).second +1 ...} );
//}

//} // namespace boost::numeric::ublas::detail


//namespace boost::numeric::ublas
//{
///** @brief array_to_vector converts a std::array of zero-based index position pairs into two std::vector of one-based index positions
// *
// * @code auto two_vectors = array_to_vector(std::make_array ( std::make_pair(1,2), std::make_pair(3,4) ) ) ;
// * @endcode
// *
// * @returns two std::vector of one-based index positions
// *
// * @param array std::array of zero-based index position pairs
//*/
//template<class pair_type, std::size_t N>
//constexpr auto array_to_vector( std::array<pair_type,N> const& array)
//{
//    constexpr auto sequence = std::make_index_sequence<N>{};
//    return detail::array_to_vector_impl( array, sequence );
//}


//} // namespace boost::numeric::ublas


#endif // BOOST_UBLAS_TENSOR_MULTI_INDEX_UTILITY_HPP
