#pragma once

#include <type_traits>
#include <tuple>
#include <functional>

namespace vlc
{
    template <typename T>
    struct function_traits
        : public function_traits<decltype(&T::operator())>
    {};

    template<typename R, typename ...Args>
    struct function_traits<R(Args...)>
    {
        static const size_t nargs = sizeof...(Args);

        using result_type = R;
        using function_type = R(Args...);

        template <size_t i>
        struct arg
        {
            using type = typename std::tuple_element<i, std::tuple<Args...>>::type;
        };
    };

    template <typename ReturnType, typename... Args>
    struct function_traits<ReturnType(*)(Args...)>
        : public function_traits<ReturnType(Args...)>
    {};

    template <typename ClassType, typename ReturnType, typename... Args>
    struct function_traits<ReturnType(ClassType::*)(Args...)>
        : public function_traits<ReturnType(Args...)>
    {
        typedef ClassType& owner_type;
    };

    template <typename ClassType, typename ReturnType, typename... Args>
    struct function_traits<ReturnType(ClassType::*)(Args...) const>
        : public function_traits<ReturnType(Args...)>
    {
        typedef const ClassType& owner_type;
    };

    template <typename ClassType, typename ReturnType, typename... Args>
    struct function_traits<ReturnType(ClassType::*)(Args...) volatile>
        : public function_traits<ReturnType(Args...)>
    {
        typedef volatile ClassType& owner_type;
    };

    template <typename ClassType, typename ReturnType, typename... Args>
    struct function_traits<ReturnType(ClassType::*)(Args...) const volatile>
        : public function_traits<ReturnType(Args...)>
    {
        typedef const volatile ClassType& owner_type;
    };

    template <typename FunctionType>
    struct function_traits<std::function<FunctionType>>
        : public function_traits<FunctionType>
    {};
}