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

#include <functional>
#include "type_traits.h"
#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<class AlwaysVoid, class F, class... Args>
        struct invoke_traits
        {
            static constexpr bool value{false};

            template<class R>
            using is_convertible = std::false_type;
        };

        template<class F, class... Args>
        struct invoke_traits<void_t<decltype(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>()...));

            template<class R>
            using is_convertible = std::is_convertible<type, R>;
        };
    } // 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, detail::invoke_traits< void, F, Args...>::template is_convertible<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>{});
    }

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

    namespace detail
    {

        template<class Binder, std::size_t ...I, class ...Rest>
        constexpr static auto binder_run(
            Binder&& self,
            std::integral_constant<bool, false>,
            index_sequence<I...>, 
            Rest&&... rest
        ) noexcept(noexcept(dlib::invoke(std::forward<Binder>(self).func, std::get<I>(std::forward<Binder>(self).args)..., std::forward<Rest>(rest)...)))
                -> decltype(dlib::invoke(std::forward<Binder>(self).func, std::get<I>(std::forward<Binder>(self).args)..., std::forward<Rest>(rest)...))
        {
            return dlib::invoke(std::forward<Binder>(self).func, std::get<I>(std::forward<Binder>(self).args)..., std::forward<Rest>(rest)...);
        }

        template<class Binder, std::size_t ...I, class ...Rest>
        constexpr static auto binder_run(
            Binder&& self,
            std::integral_constant<bool, true>,
            index_sequence<I...>, 
            Rest&&... rest
        ) noexcept(noexcept(dlib::invoke(std::forward<Binder>(self).func, std::forward<Rest>(rest)..., std::get<I>(std::forward<Binder>(self).args)...)))
                -> decltype(dlib::invoke(std::forward<Binder>(self).func, std::forward<Rest>(rest)..., std::get<I>(std::forward<Binder>(self).args)...))
        {
            return dlib::invoke(std::forward<Binder>(self).func, std::forward<Rest>(rest)..., std::get<I>(std::forward<Binder>(self).args)...);
        }

        template<bool Back, class F, class ...Args>
        struct binder_wrapper
        {
            F                   func;
            std::tuple<Args...> args;

            template<class ...Rest>
            constexpr auto operator()(Rest&&... rest) 
                noexcept(noexcept(binder_run(std::declval<binder_wrapper&>(), std::integral_constant<bool, Back>{}, index_sequence_for<Args...>{}, std::forward<Rest>(rest)...)))
                      -> decltype(binder_run(std::declval<binder_wrapper&>(), std::integral_constant<bool, Back>{}, index_sequence_for<Args...>{}, std::forward<Rest>(rest)...))
            {
                return binder_run(*this, std::integral_constant<bool, Back>{}, index_sequence_for<Args...>{}, std::forward<Rest>(rest)...);
            }
        };

        template<bool Back, class F, class... Args>
        constexpr auto make_binder(
            F&& func, 
            Args&&... args
        )
        {
            return binder_wrapper<Back, std::decay_t<F>, std::decay_t<Args>...>{std::forward<F>(func), std::forward<Args>(args)...};
        }
    }
    
    template<class F, class ...Args>
    constexpr auto bind_front(
        F&& func,
        Args&&... args
    ) noexcept(noexcept(detail::make_binder<false>(std::forward<F>(func), std::forward<Args>(args)...)))
    /*!
        ensures
            - identical to std::bind_front()
            - works with C++11 onwards
    !*/
    {
        return detail::make_binder<false>(std::forward<F>(func), std::forward<Args>(args)...);
    }

    template<class F, class ...Args>
    constexpr auto bind_back(
        F&& func,
        Args&&... args
    ) noexcept(noexcept(detail::make_binder<true>(std::forward<F>(func), std::forward<Args>(args)...)))
    /*!
        ensures
            - identical to std::bind_back()
            - works with C++11 onwards
    !*/
    {
        return detail::make_binder<true>(std::forward<F>(func), std::forward<Args>(args)...);
    }

// ----------------------------------------------------------------------------------------
}

#endif //DLIB_FUNCTIONAL_Hh_
