// Boost.TypeErasure library
//
// Copyright 2011 Steven Watanabe
//
// 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)
//
// $Id$

#if !defined(BOOST_PP_IS_ITERATING)

#ifndef BOOST_TYPE_ERASURE_DETAIL_VTABLE_HPP_INCLUDED
#define BOOST_TYPE_ERASURE_DETAIL_VTABLE_HPP_INCLUDED

#include <boost/config.hpp>
#include <boost/mpl/at.hpp>
#include <boost/mpl/size.hpp>
#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/iteration/iterate.hpp>
#include <boost/preprocessor/repetition/enum.hpp>
#include <boost/preprocessor/repetition/enum_params.hpp>
#include <boost/preprocessor/repetition/enum_trailing.hpp>
#include <boost/preprocessor/repetition/enum_trailing_params.hpp>
#include <boost/preprocessor/repetition/repeat.hpp>
#include <boost/type_erasure/detail/rebind_placeholders.hpp>
#include <boost/type_erasure/config.hpp>

namespace boost {
namespace type_erasure {
namespace detail {

#if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) && !defined(BOOST_NO_CXX11_CONSTEXPR) && !defined(BOOST_NO_CXX11_DEFAULTED_FUNCTIONS)

template<class... T>
struct stored_arg_pack;

template<class It, class End, class... T>
struct make_arg_pack_impl
{
    typedef typename make_arg_pack_impl<
        typename ::boost::mpl::next<It>::type,
        End,
        T...,
        typename ::boost::mpl::deref<It>::type
    >::type type;
};

template<class End, class... T>
struct make_arg_pack_impl<End, End, T...>
{
    typedef stored_arg_pack<T...> type;
};

template<class Seq>
struct make_arg_pack
{
    typedef typename make_arg_pack_impl<
        typename ::boost::mpl::begin<Seq>::type,
        typename ::boost::mpl::end<Seq>::type
    >::type type;
};

template<class Args>
struct make_vtable_impl;

template<class Seq>
struct make_vtable
{
    typedef typename ::boost::type_erasure::detail::make_vtable_impl<
        typename ::boost::type_erasure::detail::make_arg_pack<Seq>::type
    >::type type;
};

template<class Table, class Args>
struct make_vtable_init_impl;

template<class Seq, class Table>
struct make_vtable_init
{
    typedef typename make_vtable_init_impl<
        Table,
        typename ::boost::type_erasure::detail::make_arg_pack<Seq>::type
    >::type type;
};

template<class T>
struct vtable_entry
{
    typename T::type value;
    vtable_entry() = default;
    constexpr vtable_entry(typename T::type arg) : value(arg) {}
};

template<class... T>
struct compare_vtable;

template<>
struct compare_vtable<> {
    template<class S>
    static bool apply(const S& s1, const S& s2)
    {
        return true;
    }
};

template<class T0, class... T>
struct compare_vtable<T0, T...> {
    template<class S>
    static bool apply(const S& s1, const S& s2)
    {
        return static_cast<const vtable_entry<T0>&>(s1).value ==
            static_cast<const vtable_entry<T0>&>(s2).value &&
            compare_vtable<T...>::apply(s1, s2);
    }
};

template<class... T>
struct vtable_storage : vtable_entry<T>...
{
    vtable_storage() = default;

    constexpr vtable_storage(typename T::type... arg)
        : vtable_entry<T>(arg)... {}

    template<class Bindings, class Src>
    void convert_from(const Src& src)
    {
        *this = vtable_storage(
            src.lookup(
                (typename ::boost::type_erasure::detail::rebind_placeholders<
                T, Bindings
            >::type*)0)...);
    }

    bool operator==(const vtable_storage& other) const
    { return compare_vtable<T...>::apply(*this, other); }

    template<class U>
    typename U::type lookup(U*) const
    {
        return static_cast<const vtable_entry<U>*>(this)->value;
    }
};

// Provide this specialization manually.
// gcc 4.7.2 fails to instantiate the primary template.
template<>
struct vtable_storage<>
{
    vtable_storage() = default;

    template<class Bindings, class Src>
    void convert_from(const Src& src) {}

    bool operator==(const vtable_storage& other) const
    { return true; }
};

template<class... T>
struct make_vtable_impl<stored_arg_pack<T...> >
{
    typedef vtable_storage<T...> type;
};

template<class Table, class... T>
struct vtable_init
{
    static constexpr Table value = Table(T::value...);
};

template<class Table, class... T>
constexpr Table vtable_init<Table, T...>::value;

template<class Table, class... T>
struct make_vtable_init_impl<Table, stored_arg_pack<T...> >
{
    typedef vtable_init<Table, T...> type;
};

#else

template<int N>
struct make_vtable_impl;

template<class Seq>
struct make_vtable
{
    typedef typename make_vtable_impl<
        (::boost::mpl::size<Seq>::value)>::template apply<Seq>::type type;
};

template<int N>
struct make_vtable_init_impl;

template<class Seq, class Table>
struct make_vtable_init
{
    typedef typename make_vtable_init_impl<
        (::boost::mpl::size<Seq>::value)>::template apply<Seq, Table>::type type;
};

#define BOOST_PP_FILENAME_1 <boost/type_erasure/detail/vtable.hpp>
#define BOOST_PP_ITERATION_LIMITS (0, BOOST_TYPE_ERASURE_MAX_FUNCTIONS)
#include BOOST_PP_ITERATE()

#endif

}
}
}

#endif

#else

#define N BOOST_PP_ITERATION()

#define BOOST_TYPE_ERASURE_VTABLE_ENTRY(z, n, data)                     \
    typename BOOST_PP_CAT(T, n)::type BOOST_PP_CAT(t, n);               \
    typename BOOST_PP_CAT(T, n)::type lookup(BOOST_PP_CAT(T, n)*) const \
    {                                                                   \
        return BOOST_PP_CAT(t, n);                                      \
    }

#define BOOST_TYPE_ERASURE_VTABLE_COMPARE(z, n, data)                   \
    && BOOST_PP_CAT(t, n) == other.BOOST_PP_CAT(t, n)

#define BOOST_TYPE_ERASURE_VTABLE_INIT(z, n, data)  \
    BOOST_PP_CAT(data, n)::value

#define BOOST_TYPE_ERASURE_AT(z, n, data)       \
    typename ::boost::mpl::at_c<data, n>::type

#define BOOST_TYPE_ERASURE_CONVERT_ELEMENT(z, n, data)                  \
    BOOST_PP_CAT(t, n) = src.lookup(                                    \
        (typename ::boost::type_erasure::detail::rebind_placeholders<   \
            BOOST_PP_CAT(T, n), Bindings                                \
        >::type*)0                                                      \
    );

#if N != 0
template<BOOST_PP_ENUM_PARAMS(N, class T)>
#else
template<class D = void>
#endif
struct BOOST_PP_CAT(vtable_storage, N)
{
    BOOST_PP_REPEAT(N, BOOST_TYPE_ERASURE_VTABLE_ENTRY, ~)

    template<class Bindings, class Src>
    void convert_from(const Src& src)
    {
        BOOST_PP_REPEAT(N, BOOST_TYPE_ERASURE_CONVERT_ELEMENT, ~)
    }

    bool operator==(const BOOST_PP_CAT(vtable_storage, N)& other) const
    { return true BOOST_PP_REPEAT(N, BOOST_TYPE_ERASURE_VTABLE_COMPARE, ~); }
};

template<>
struct make_vtable_impl<N>
{
    template<class Seq>
    struct apply
    {
        typedef ::boost::type_erasure::detail::BOOST_PP_CAT(vtable_storage, N)<
            BOOST_PP_ENUM(N, BOOST_TYPE_ERASURE_AT, Seq)
        > type;
    };
};
template<class Table BOOST_PP_ENUM_TRAILING_PARAMS(N, class T)>
struct BOOST_PP_CAT(vtable_init, N)
{
    static const Table value;
};

template<class Table BOOST_PP_ENUM_TRAILING_PARAMS(N, class T)>
const Table BOOST_PP_CAT(vtable_init, N)<
    Table BOOST_PP_ENUM_TRAILING_PARAMS(N, T)>::value =
{
    BOOST_PP_ENUM(N, BOOST_TYPE_ERASURE_VTABLE_INIT, T)
};

template<>
struct make_vtable_init_impl<N>
{
    template<class Seq, class Table>
    struct apply
    {
        typedef ::boost::type_erasure::detail::BOOST_PP_CAT(vtable_init, N)<
            Table
            BOOST_PP_ENUM_TRAILING(N, BOOST_TYPE_ERASURE_AT, Seq)
        > type;
    };
};

#undef BOOST_TYPE_ERASURE_CONVERT_ELEMENT
#undef BOOST_TYPE_ERASURE_AT
#undef BOOST_TYPE_ERASURE_VTABLE_INIT
#undef BOOST_TYPE_ERASURE_VTABLE_COMPARE
#undef BOOST_TYPE_ERASURE_VTABLE_ENTRY
#undef N

#endif
