#ifndef MPL_IS_CALLABLE_HPP
#define MPL_IS_CALLABLE_HPP

#include <type_traits>

namespace mpl
{
    namespace // implementation detail
    {
        // build R (*)(Args...) from R (Args...)
        // compile error if signature is not a valid function signature
        template <typename, typename>
        struct build_free_function;

        template <typename F, typename R, typename ... Args>
        struct build_free_function<F, R (Args...)>
        { using type = R (*)(Args...); };

        // build R (C::*)(Args...) from R (Args...)
        //       R (C::*)(Args...) const from R (Args...) const
        //       R (C::*)(Args...) volatile from R (Args...) volatile
        // compile error if signature is not a valid member function signature
        template <typename, typename>
        struct build_class_function;

        template <typename C, typename R, typename ... Args>
        struct build_class_function<C, R (Args...)>
        { using type = R (C::*)(Args...); };

        template <typename C, typename R, typename ... Args>
        struct build_class_function<C, R (Args...) const>
        { using type = R (C::*)(Args...) const; };

        template <typename C, typename R, typename ... Args>
        struct build_class_function<C, R (Args...) volatile>
        { using type = R (C::*)(Args...) volatile; };

        // determine whether a class C has an operator() with signature S
        template <typename C, typename S>
        struct is_functor_with_signature
        {
            typedef char (& yes)[1];
            typedef char (& no)[2];

            // helper struct to determine that C::operator() does indeed have
            // the desired signature; &C::operator() is only of type 
            // R (C::*)(Args...) if this is true
            template <typename T, T> struct check;

            // T is needed to enable SFINAE
            template <typename T> static yes deduce(check<
                typename build_class_function<C, S>::type, &T::operator()> *);
            // fallback if check helper could not be built
            template <typename> static no deduce(...);

            static bool constexpr value = sizeof(deduce<C>(0)) == sizeof(yes);
        };

        // determine whether a free function pointer F has signature S
        template <typename F, typename S>
        struct is_function_with_signature
        {
            // check whether F and the function pointer of S are of the same
            // type
            static bool constexpr value = std::is_same<
                F, typename build_free_function<F, S>::type
            >::value;
        };

        // C is a class, delegate to is_functor_with_signature
        template <typename C, typename S, bool>
        struct is_callable_impl
            : std::integral_constant<
                bool, is_functor_with_signature<C, S>::value
              >
        {};

        // F is not a class, delegate to is_function_with_signature
        template <typename F, typename S>
        struct is_callable_impl<F, S, false>
            : std::integral_constant<
                bool, is_function_with_signature<F, S>::value
              >
        {};
    }

    // Determine whether type Callable is callable with signature Signature.
    // Compliant with functors, i.e. classes that declare operator(); and free
    // function pointers: R (*)(Args...), but not R (Args...)!
    template <typename Callable, typename Signature>
    struct is_callable
        : is_callable_impl<
            Callable, Signature,
            std::is_class<Callable>::value
          >
    {};

    namespace // tests
    {
        struct A { void operator()(); };
        struct B {};
        struct C { 
            int operator()(int &, void **) const; 
            int operator()(double);
        };
        void a();
        int b;
        int c(int &, void **);
        int c(double);

#define MPL_IS_CALLABLE_POSITIVE "should be recognized as callable"
#define MPL_IS_CALLABLE_NEGATIVE "should not be recognized as callable"
        static_assert(is_callable<A, void ()>::value, MPL_IS_CALLABLE_POSITIVE);
        static_assert(!is_callable<B, void ()>::value, MPL_IS_CALLABLE_NEGATIVE);
        static_assert(is_callable<C, int (int &, void **) const>::value, MPL_IS_CALLABLE_POSITIVE);
        static_assert(is_callable<C, int (double)>::value, MPL_IS_CALLABLE_POSITIVE);
        static_assert(is_callable<decltype(&a), void ()>::value, MPL_IS_CALLABLE_POSITIVE);
        static_assert(!is_callable<decltype(&b), void ()>::value, MPL_IS_CALLABLE_NEGATIVE);
        static_assert(is_callable<decltype(static_cast<int (*)(int &, void **)>(&c)), int (int &, void **)>::value, MPL_IS_CALLABLE_POSITIVE);
        static_assert(is_callable<decltype(static_cast<int (*)(double)>(&c)), int (double)>::value, MPL_IS_CALLABLE_POSITIVE);
#undef MPL_IS_CALLABLE_POSITIVE
#undef MPL_IS_CALLABLE_NEGATIVE
    }
}

#endif // MPL_IS_CALLABLE_HPP