// Copyright (C) 2021  Davis E. King (davis@dlib.net)
// License: Boost Software License   See LICENSE.txt for the full license.
#ifndef DLIB_INVOKE_Hh_
#define DLIB_INVOKE_Hh_

#include <functional>
#include <type_traits>
#include "utility.h"

namespace dlib
{

// ----------------------------------------------------------------------------------------

    namespace detail 
    {
        template< typename T >
        struct is_reference_wrapper : std::false_type {};
        template< typename U >
        struct is_reference_wrapper<std::reference_wrapper<U>> : std::true_type {};

        template <
            typename Base,
            typename T,
            typename Derived,
            typename... Args
        >
        constexpr auto INVOKE(
            T Base::*pmf, //pointer to member function
            Derived&& ref,
            Args&&... args
        )
        noexcept(noexcept((std::forward<Derived>(ref).*pmf)(std::forward<Args>(args)...)))
        -> typename std::enable_if<
                std::is_function<T>::value &&
                std::is_base_of<Base, typename std::decay<Derived>::type>::value,
                decltype((std::forward<Derived>(ref).*pmf)(std::forward<Args>(args)...))
           >::type
        {
            return (std::forward<Derived>(ref).*pmf)(std::forward<Args>(args)...);
        }

        template<
            typename Base,
            typename T,
            typename RefWrap,
            typename... Args
        >
        constexpr auto INVOKE(
            T Base::*pmf, //pointer to member function
            RefWrap&& ref,
            Args&&... args
        )
        noexcept(noexcept((ref.get().*pmf)(std::forward<Args>(args)...)))
        -> typename std::enable_if<
                std::is_function<T>::value &&
                is_reference_wrapper<typename std::decay<RefWrap>::type>::value,
                decltype((ref.get().*pmf)(std::forward<Args>(args)...))>::type
        {
            return (ref.get().*pmf)(std::forward<Args>(args)...);
        }

        template<
            typename Base,
            typename T,
            typename Ptr,
            typename... Args
        >
        constexpr auto INVOKE(
            T Base::*pmf, //pointer to member function
            Ptr&& ptr,
            Args&&... args
        )
        noexcept(noexcept(((*std::forward<Ptr>(ptr)).*pmf)( std::forward<Args>( args )...)))
        -> typename std::enable_if<
                std::is_function<T>::value &&
                !is_reference_wrapper<typename std::decay<Ptr>::type>::value &&
                !std::is_base_of<Base, typename std::decay<Ptr>::type>::value,
                decltype(((*std::forward<Ptr>(ptr)).*pmf)(std::forward<Args>(args)...))>::type
        {
            return ((*std::forward<Ptr>(ptr)).*pmf)( std::forward<Args>( args )...);
        }

        template<
            typename Base,
            typename T,
            typename Derived
        >
        constexpr auto INVOKE(
            T Base::*pmd, //pointer to member data
            Derived&& ref
        )
        noexcept(noexcept(std::forward<Derived>(ref).*pmd))
        -> typename std::enable_if<
                std::is_object<T>::value &&
                std::is_base_of<Base, typename std::decay<Derived>::type>::value,
                decltype(std::forward<Derived>(ref).*pmd)>::type
        {
            return std::forward<Derived>(ref).*pmd;
        }

        template<
            typename Base,
            typename T,
            typename RefWrap
        >
        constexpr auto INVOKE(
            T Base::*pmd, //pointer to member data
            RefWrap&& ref
        )
        noexcept(noexcept(ref.get().*pmd))
        -> typename std::enable_if<
                std::is_object<T>::value &&
                is_reference_wrapper<typename std::decay<RefWrap>::type>::value,
                decltype(ref.get().*pmd)>::type
        {
            return ref.get().*pmd;
        }

        template<
            typename Base,
            typename T,
            typename Ptr
        >
        constexpr auto INVOKE(
            T Base::*pmd, //pointer to member data
            Ptr&& ptr
        )
        noexcept(noexcept((*std::forward<Ptr>(ptr)).*pmd))
        -> typename std::enable_if<
                std::is_object<T>::value &&
                !is_reference_wrapper<typename std::decay<Ptr>::type>::value &&
                !std::is_base_of<Base, typename std::decay<Ptr>::type>::value,
                decltype((*std::forward<Ptr>(ptr)).*pmd)>::type
        {
            return (*std::forward<Ptr>(ptr)).*pmd;
        }

        template<
            typename F,
            typename... Args
        >
        constexpr auto INVOKE(
            F && f,
            Args&&... args
        )
        noexcept(noexcept(std::forward<F>( f )( std::forward<Args>( args )...)))
        -> typename std::enable_if<
                !std::is_member_pointer<typename std::decay<F>::type>::value,
                decltype(std::forward<F>(f)(std::forward<Args>(args)...))>::type
        {
            return std::forward<F>( f )( std::forward<Args>( args )...);
        }
    } // end namespace detail

// ----------------------------------------------------------------------------------------
    
    template< typename F, typename... Args>
    constexpr auto invoke(F && f, Args &&... args)
    /*!
        ensures
            - identical to std::invoke(std::forward<F>(f), std::forward<Args>(args)...)
            - works with C++11 onwards
    !*/
    noexcept(noexcept(detail::INVOKE(std::forward<F>( f ), std::forward<Args>( args )...)))
    -> decltype(detail::INVOKE(std::forward<F>( f ), std::forward<Args>( args )...))
    {
        return detail::INVOKE(std::forward<F>( f ), std::forward<Args>( args )...);
    }

// ----------------------------------------------------------------------------------------

    namespace detail
    {
        template< typename AlwaysVoid, typename, typename...>
        struct invoke_traits
        {
            static constexpr bool value = false;
        };

        template< typename F, typename... Args >
        struct invoke_traits< decltype( void(dlib::invoke(std::declval<F>(), std::declval<Args>()...)) ), F, Args...>
        {
            static constexpr bool value = true;
            using type = decltype( dlib::invoke(std::declval<F>(), std::declval<Args>()...) );
        };
    } // end namespace detail

// ----------------------------------------------------------------------------------------

    template< typename F, typename... Args >
    struct invoke_result : detail::invoke_traits< void, F, Args...> {};
    /*!
        ensures
            - identical to std::invoke_result<F, Args..>
            - works with C++11 onwards
    !*/

    template< typename F, typename... Args >
    using invoke_result_t = typename invoke_result<F, Args...>::type;
    /*!
        ensures
            - identical to std::invoke_result_t<F, Args..>
            - works with C++11 onwards
    !*/

// ----------------------------------------------------------------------------------------

    template< typename F, typename... Args >
    struct is_invocable : std::integral_constant<bool, detail::invoke_traits< void, F, Args...>::value> {};
    /*!
        ensures
            - identical to std::is_invocable<F, Args..>
            - works with C++11 onwards
    !*/

// ----------------------------------------------------------------------------------------

    template <typename R, typename F, typename... Args>
    struct is_invocable_r : std::integral_constant<bool, dlib::is_invocable<F, Args...>::value &&
                                                         std::is_convertible<invoke_result_t<F, Args...>, R>::value> {};
    /*!
        ensures
            - identical to std::is_invocable_r<R, F, Args..>
            - works with C++11 onwards
    !*/

// ----------------------------------------------------------------------------------------

    template< typename R, typename F, typename... Args>
    constexpr typename std::enable_if<dlib::is_invocable_r<R, F, Args...>::value, R>::type
    invoke_r(F && f, Args &&... args)
    /*!
        ensures
            - identical to std::invoke_r<R>(std::forward<F>(f), std::forward<Args>(args)...)
            - works with C++11 onwards
    !*/
    noexcept(noexcept(dlib::invoke(std::forward<F>( f ), std::forward<Args>( args )...)))
    {
        return dlib::invoke(std::forward<F>( f ), std::forward<Args>( args )...);
    }

// ----------------------------------------------------------------------------------------

    namespace detail
    {
        template<typename F, typename Tuple, std::size_t... I>
        constexpr auto apply_impl(F&& fn, Tuple&& tpl, index_sequence<I...>)
        noexcept(noexcept(dlib::invoke(std::forward<F>(fn),
                                       std::get<I>(std::forward<Tuple>(tpl))...)))
        -> decltype(dlib::invoke(std::forward<F>(fn),
                                 std::get<I>(std::forward<Tuple>(tpl))...))
        {
            return dlib::invoke(std::forward<F>(fn),
                                std::get<I>(std::forward<Tuple>(tpl))...);
        }
    } // end namespace detail

// ----------------------------------------------------------------------------------------

    template<typename F, typename Tuple>
    constexpr auto apply(F&& fn, Tuple&& tpl)
    /*!
        ensures
            - identical to std::apply(std::forward<F>(f), std::forward<Tuple>(tpl))
            - works with C++11 onwards
    !*/
    noexcept(noexcept(detail::apply_impl(std::forward<F>(fn),
                                         std::forward<Tuple>(tpl),
                                         make_index_sequence<std::tuple_size<typename std::remove_reference<Tuple>::type >::value>{})))
    -> decltype(detail::apply_impl(std::forward<F>(fn),
                                   std::forward<Tuple>(tpl),
                                   make_index_sequence<std::tuple_size<typename std::remove_reference<Tuple>::type >::value>{}))
    {
        return detail::apply_impl(std::forward<F>(fn),
                                  std::forward<Tuple>(tpl),
                                  make_index_sequence<std::tuple_size<typename std::remove_reference<Tuple>::type >::value>{});
    }

// ----------------------------------------------------------------------------------------

    namespace detail
    {
        template <class T, class Tuple, std::size_t... I>
        constexpr T make_from_tuple_impl( Tuple&& t, index_sequence<I...> )
        {
            return T(std::get<I>(std::forward<Tuple>(t))...);
        }
    } // end namespace detail

// ----------------------------------------------------------------------------------------

    template <class T, class Tuple>
    constexpr T make_from_tuple( Tuple&& t )
    /*!
        ensures
            - identical to std::make_from_tuple<T>(std::forward<Tuple>(t))
            - works with C++11 onwards
    !*/
    {
        return detail::make_from_tuple_impl<T>(std::forward<Tuple>(t),
                                               make_index_sequence<std::tuple_size<typename std::remove_reference<Tuple>::type >::value>{});
    }

// ----------------------------------------------------------------------------------------

}

#endif //DLIB_INVOKE_Hh_
