// Copyright 2014 Jarod42 aka Joris Dauphin

#include <cstdint>
#include <tuple>
#include <type_traits>

#if 1 // multiple dispatch

// sequence of size_t // not in C++11
template <std::size_t ...> struct index_sequence {};

// Create index_sequence<0, >
template <std::size_t N, std::size_t ...Is>
struct make_index_sequence : make_index_sequence < N - 1, N - 1, Is... > {};

template <std::size_t ... Is>
struct make_index_sequence<0, Is...> : index_sequence<Is...> {};

// Generic IVisitor
// Do: using MyIVisitor = IVisitorTs<Child1, Child2, ...>
template <typename ... Ts> class IVisitorTs;

template <typename T, typename ... Ts>
class IVisitorTs<T, Ts...> : public IVisitorTs<Ts...>
{
public:
    using tuple_type = std::tuple<T, Ts...>;
    using IVisitorTs<Ts...>::visit;

    virtual void visit(const T &t) = 0;
};

template <typename T> class IVisitorTs<T>
{
public:
    using tuple_type = std::tuple<T>;

    virtual void visit(const T &t) = 0;
};

namespace detail
{

// retrieve the index of T in Ts...
template <typename T, typename ... Ts> struct get_index;

template <typename T, typename ... Ts>
struct get_index<T, T, Ts...> : std::integral_constant<std::size_t, 0> {};

template <typename T, typename Tail,  typename ... Ts>
struct get_index<T, Tail, Ts...> :
        std::integral_constant < std::size_t, 1 + get_index<T, Ts...>::value > {};

// retrieve the index of T in Tuple<Ts...>
template <typename T, typename Tuple> struct get_index_in_tuple;

template <typename T, template <typename...> class C, typename ... Ts>
struct get_index_in_tuple<T, C<Ts...>> : get_index<T, Ts...> {};

// tuple_contain

template <typename T, typename Tuple> struct tuple_contain;

template <typename T, typename ... Ts>
struct tuple_contain<T, std::tuple<T, Ts...>> : std::true_type {};

template <typename T>
struct tuple_contain<T, std::tuple<>> : std::false_type {};

template <typename T, typename T1, typename ... Ts>
struct tuple_contain<T, std::tuple<T1, Ts...>> :
    tuple_contain<T, std::tuple<Ts...>> {};

// get element of a multiarray
template <std::size_t I>
struct multi_array_getter {
    template <typename T, std::size_t N>
    static constexpr auto get(const T &a, const std::array<std::size_t, N> &indices)
    -> decltype(multi_array_getter<I - 1>::get(a[indices[N - I]], indices))
    {
        return multi_array_getter<I - 1>::get(a[indices[N - I]], indices);
    }
};

template <>
struct multi_array_getter<0> {
    template <typename T, std::size_t N>
    static constexpr auto get(const T &a, const std::array<std::size_t, N> &)
    -> decltype(a)
    {
        return a;
    }
};

// Provide an implementation of visitor
// by forwarding to C implementation (which may be non virtual)
template <typename IVisitor, typename C, typename...Ts> struct IVisitorImpl;

template <typename IVisitor, typename C, typename T, typename...Ts>
struct IVisitorImpl<IVisitor, C, T, Ts...> : IVisitorImpl<IVisitor, C, Ts...> {
    virtual void visit(const T &t) override { C::visit(t); }
};

template <typename IVisitor, typename C, typename T>
struct IVisitorImpl<IVisitor, C, T> : IVisitor, C {
    virtual void visit(const T &t) override { C::visit(t); }
};

// helper to expand child type to IVisitorImpl
template <typename IVisitor, typename C>
struct IVisitorImplType;

template <typename ... Ts, typename C>
struct IVisitorImplType<IVisitorTs<Ts...>, C> {
    using type = IVisitorImpl<IVisitorTs<Ts...>, C, Ts...>;
};

template <template <typename> class F, typename Tuple>
struct to_index_sequence;

template <template <typename> class F, typename... Ts>
struct to_index_sequence<F, std::tuple<Ts...>>
{
    using type = index_sequence<F<Ts>::value...>;
};

template <typename Tuple, typename SeqFilter, typename...Res>
struct filter_tuple;

template <typename...Res>
struct filter_tuple<std::tuple<>, index_sequence<>, Res...>
{
    using type = std::tuple<Res...>;
};

template <typename T, typename... Ts, std::size_t...Is, typename...Res>
struct filter_tuple<std::tuple<T, Ts...>, index_sequence<0, Is...>, Res...>
{
    using type = typename filter_tuple<std::tuple<Ts...>,
                                       index_sequence<Is...>,
                                       Res...>::type;
};

template <typename T, typename... Ts, std::size_t I,  std::size_t...Is, typename...Res>
struct filter_tuple<std::tuple<T, Ts...>, index_sequence<I, Is...>, Res...>
{
    using type = typename filter_tuple<std::tuple<Ts...>,
                                       index_sequence<Is...>,
                                       Res..., T>::type;
};

template <typename T> struct BUG; // TODO : REMOVE

// Create an multi array of pointer of function
// (with all combinaisons of overload).
template <typename Ret, typename F, typename Arg>
class GetAllOverload
{
private:
    template<std::size_t N, typename Tuple>
    struct RestrictedTuple
    {
    private:
        constexpr static std::size_t ArgSize = std::tuple_size<Arg>::value;
        using baseArg = typename std::decay<typename std::tuple_element<ArgSize - N, Arg>::type>::type;
        template <typename T>
        using FilterDerivedOf = std::is_base_of<baseArg, T>;
        using FilterIndex = typename to_index_sequence<FilterDerivedOf, Tuple>::type;
    public:
        using type = typename filter_tuple<Tuple, FilterIndex>::type;
    };

    template <typename...Ts>
    struct Functor {
        // function which will be in array.
        static Ret call(F &f, const Arg &arg)
        {
            return call_helper(f, arg, make_index_sequence<sizeof...(Ts)>());
        }
    private:
        // The final dispatched function
        template <std::size_t ... Is>
        static Ret call_helper(F &f, const Arg &arg, index_sequence<Is...>)
        {
            using RetTuple = std::tuple<Ts &...>;
            return f(static_cast<typename std::tuple_element<Is, RetTuple>::type>(std::get<Is>(arg))...);
        }
    };

    // helper class to create the multi array of function pointer
    template <std::size_t N, typename Tuple, typename TupleForArg, typename...Ts>
    struct Builder;

    template <typename Tuple, typename...Ts, typename...Ts2>
    struct Builder<1, Tuple, std::tuple<Ts...>, Ts2...> {
        using RetType = std::array<Ret(*)(F &, const Arg &), sizeof...(Ts)>;

        static constexpr RetType build()
        {
            return RetType { &Functor<Ts2..., Ts>::call... };
        }
    };

    template <std::size_t N, typename Tuple, typename ...Ts, typename...Ts2>
    struct Builder<N, Tuple, std::tuple<Ts...>, Ts2...> {
        using RecTuple = typename RestrictedTuple<N - 1, Tuple>::type;
        template <typename T>
        using RecType = Builder<N - 1, Tuple, RecTuple, Ts2..., T>;
        using T0 = typename std::tuple_element<0u, std::tuple<Ts...>>::type;
        using RetType = std::array<decltype(RecType<T0>::build()), sizeof...(Ts)>;

        static constexpr RetType build()
        {
            return RetType { RecType<Ts>::build()... };
        }
    };

public:
    template <std::size_t N, typename VisitorTuple>
    static constexpr auto get()
    -> decltype(Builder<N, VisitorTuple, typename RestrictedTuple<N, VisitorTuple>::type>::build())
    {
        return Builder<N, VisitorTuple, typename RestrictedTuple<N, VisitorTuple>::type>::build();
    }
};


template <typename Ret, typename IVisitor, typename F, std::size_t N>
class dispatcher
{
private:
    std::array<std::size_t, N> index;

    template<typename Arg>
    struct visitorCallImpl {
        template <typename T>
        using FilterDerivedOf = std::is_base_of<Arg, T>;

        using Tuple = typename IVisitor::tuple_type;
        using FilterIndex = typename to_index_sequence<FilterDerivedOf, Tuple>::type;
        using FilteredTuple = typename filter_tuple<Tuple, FilterIndex>::type;

        template <typename T>
        typename std::enable_if<tuple_contain<T, FilteredTuple>::value>::type
        visit(const T &) const
        {
            *index = get_index_in_tuple<T, FilteredTuple>::value;
        }

        template <typename T>
        typename std::enable_if<!tuple_contain<T, FilteredTuple>::value>::type
        visit(const T &) const
        {
            // should not be called.
        }

        void setIndexPtr(std::size_t &index) { this->index = &index; }
    private:
        std::size_t *index = nullptr;
    };

    template <std::size_t I, typename Tuple>
    void set_index(const Tuple &t)
    {
        using ElementType = typename std::decay<typename std::tuple_element<I, Tuple>::type>::type;
        using VisitorType = typename IVisitorImplType<IVisitor, visitorCallImpl<ElementType>>::type;
        VisitorType visitor;
        visitor.setIndexPtr(index[I]);

        std::get<I>(t).accept(visitor);
    }
public:
    template <typename Tuple, std::size_t ... Is>
    Ret operator()(F &&f, const Tuple &t, index_sequence<Is...>)
    {
        const int dummy[] = {(set_index<Is>(t), 0)...};
        static_cast<void>(dummy); // silent the warning unused varaible
        constexpr auto a = GetAllOverload < Ret, F &&, Tuple >::
                           template get<sizeof...(Is), typename IVisitor::tuple_type>();
        auto func = multi_array_getter<N>::get(a, index);
        return (*func)(f, t);
    }
};

} // namespace detail

template <typename Ret, typename Visitor, typename F, typename ... Ts>
Ret dispatch(F &&f, Ts &...args)
{
    constexpr std::size_t size = sizeof...(Ts);
    detail::dispatcher<Ret, Visitor, F&&, size> d;
    return d(std::forward<F>(f), std::tie(args...), make_index_sequence<size>());
}

#endif // multiple dispatch

#if 1 // multiple dispatch usage
struct Square;
struct Rect;
struct Circle;

using IShapeVisitor = IVisitorTs<Square, Rect, Circle>;

struct IShape {
    virtual ~IShape() = default;
    virtual void accept(IShapeVisitor &) const = 0;
};
struct Rect : IShape {
    virtual void accept(IShapeVisitor &v) const override { v.visit(*this); }
};

struct Square : Rect {
    virtual void accept(IShapeVisitor &v) const override { v.visit(*this); }
};
struct Circle : IShape {
    virtual void accept(IShapeVisitor &v) const override { v.visit(*this); }
};

#include <iostream>
class ShapePrinter : public IShapeVisitor
{
public:
    void visit(const Rect &s) override { std::cout << "Rect"; }
    void visit(const Square &s) override { std::cout << "Square"; }
    void visit(const Circle &s) override { std::cout << "Circle"; }
};

struct Printer
{
    void operator() (const Rect &s) const { std::cout << "Rect"; }
    void operator() (const Square &s) const { std::cout << "Square"; }
    void operator() (const Circle &s) const { std::cout << "Circle"; }
};

struct P3
{
    template <typename S1, typename S2, typename S3>
    void operator() (const S1& s1, const S2& s2, const S3& s3)
    {
        Printer()(s1); std::cout << " ";
        Printer()(s2); std::cout << " ";
        Printer()(s3); std::cout << std::endl;
    }
};


struct IsEqual {
    bool operator()(IShape &s1, IShape &s2) const
    {
        ShapePrinter printer;
        s1.accept(printer);
        std::cout << " != ";
        s2.accept(printer);
        std::cout << std::endl;
        return false;
    }

    template <typename S>
    bool operator()(S &s1, S &s2) const
    {
        ShapePrinter printer;
        s1.accept(printer);
        std::cout << " == ";
        s2.accept(printer);
        std::cout << std::endl;
        return true;
    }
};

int main(int argc, char *argv[])
{
    Rect rect;
    Square sq;
    Circle c;
    IShape *shapes[] = { &rect, &sq, &c };

    for (auto shape1 : shapes) {
        for (auto shape2 : shapes) {
            dispatch<bool, IShapeVisitor>(IsEqual(), *shape1, *shape2);
        }
    }
    dispatch<void, IShapeVisitor>(Printer(), sq);
    dispatch<void, IShapeVisitor>(Printer(), *shapes[0]);

    dispatch<bool, IShapeVisitor>(IsEqual(), rect, *shapes[0]);
    dispatch<bool, IShapeVisitor>(IsEqual(), *shapes[0], rect);

    dispatch<bool, IShapeVisitor>(IsEqual(), rect, sq);

    dispatch<void, IShapeVisitor>(P3(), c, rect, sq);

    return 0;
}

#endif // multiple dispatch usage
