/*!
 * \file    utl/utility/invoke.h
 * \brief   invoke() and invoke traits implementation
 *
 * Copyright (C) 2018-2019 Christos Choutouridis
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more detail.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#ifndef __utl_utility_invoke_h__
#define __utl_utility_invoke_h__

#include <utl/core/impl.h>
#include <utl/meta/meta.h>

#include <type_traits>
#include <functional>
#include <utility>

/*!
 * \ingroup utility
 * \defgroup invoke
 */
//! @{
namespace utl {

   namespace detail {

      template <class T>
      struct is_ref_wrapper : meta::false_ {};
      template <class U>
      struct is_ref_wrapper<std::reference_wrapper<U>> : meta::true_ {};

      // 1
      template <class T, class Type, class T1, class... Args,
         meta::enable_if_t<
            std::is_member_function_pointer<std::decay_t<Type T::*>>::value &&
            std::is_base_of<T, std::decay_t<T1>>::value,
            int> =0
      >
      decltype(auto) invoke_impl_(Type T::* f, T1&& t1, Args&&... args) {
         return (std::forward<T1>(t1).*f)(std::forward<Args>(args)...);
      }

      // 2
      template <class T, class Type, class T1, class... Args,
         meta::enable_if_t<
            std::is_member_function_pointer<std::decay_t<Type T::*>>::value &&
            is_ref_wrapper<std::decay_t<T1>>::value,
            int> =0
      >
      decltype(auto) invoke_impl_(Type T::* f, T1&& t1, Args&&... args) {
         return (t1.get().*f)(std::forward<Args>(args)...);
      }

      // 3
      template <class T, class Type, class T1, class... Args,
         meta::enable_if_t<
            std::is_member_function_pointer<std::decay_t<Type T::*>>::value &&
            !std::is_base_of<T, std::decay_t<T1>>::value &&
            !is_ref_wrapper<std::decay_t<T1>>::value,
            int> =0
      >
      decltype(auto) invoke_impl_(Type T::* f, T1&& t1, Args&&... args) {
         return ((*std::forward<T1>(t1)).*f)(std::forward<Args>(args)...);
      }

      // 4
      template <class T, class Type, class T1, class... Args,
         meta::enable_if_t<
            std::is_member_object_pointer<std::decay_t<Type T::*>>::value &&
            std::is_base_of<T, std::decay_t<T1>>::value,
            int> =0
      >
      decltype(auto) invoke_impl_(Type T::* f, T1&& t1, Args&&... args) {
         return std::forward<T1>(t1).*f;
      }

      // 5
      template <class T, class Type, class T1, class... Args,
         meta::enable_if_t<
            std::is_member_object_pointer<std::decay_t<Type T::*>>::value &&
            is_ref_wrapper<std::decay_t<T1>>::value,
            int> =0
      >
      decltype(auto) invoke_impl_(Type T::* f, T1&& t1, Args&&... args) {
         return t1.get().*f;
      }

      // 6
      template <class T, class Type, class T1, class... Args,
         meta::enable_if_t<
            std::is_member_object_pointer<std::decay_t<Type T::*>>::value &&
            !std::is_base_of<T, std::decay_t<T1>>::value &&
            !is_ref_wrapper<std::decay_t<T1>>::value,
            int> =0
      >
      decltype(auto) invoke_impl_(Type T::* f, T1&& t1, Args&&... args) {
         return (*std::forward<T1>(t1)).*f;
      }

      template <class F, class... Args>
      decltype(auto) invoke_impl_(F&& f, Args&&... args) {
         return std::forward<F>(f)(std::forward<Args>(args)...);
      }

   } // namespace detail

   //! Invoke a callable object (for C++14)
   template<typename _Callable, typename... _Args>
   inline decltype(auto) invoke(_Callable&& fn, _Args&&... args) {
      return detail::invoke_impl_(
         std::forward<_Callable>(fn), std::forward<_Args>(args)...
      );
   }
   //! @}

   //! std::is_invocable trait for C++11
   template <typename F, typename... Args>
   struct is_invocable :
      std::is_constructible<
         std::function<void(Args ...)>,
         std::reference_wrapper<typename std::remove_reference<F>::type>
      > { };

   //! std::is_invocable_r trait for C++11
   template <typename R, typename F, typename... Args>
   struct is_invocable_r :
      std::is_constructible<
         std::function<R(Args ...)>,
         std::reference_wrapper<typename std::remove_reference<F>::type>
      > { };

   /*!
    * invoke_result (SFINAE friendly)
    */
   //! @{
   namespace detail {
      template<typename _Callable, typename... _Args>
      struct try_invoke {
         using type =  decltype (
            detail::invoke_impl_(std::declval<_Callable&&>(), std::declval<_Args&&>()...)
         );
      };

      template<bool B, typename _Callable, typename... _Args>
      struct invoke_result_ {
         using type = meta::nil_;
      };

      template <typename _Callable, typename... _Args>
      struct invoke_result_ <true, _Callable, _Args...> {
         using type = meta::invoke_t<
            meta::quote<try_invoke>, _Callable, _Args...
         >;
      };
   }
   //! invoke_result (for C++14)
   template <typename _Callable, typename... _Args>
   using invoke_result = detail::invoke_result_<
      is_invocable<_Callable, _Args...>::value,
      _Callable,
      _Args...
   >;

   //! invoke_result_t (for C++14)
   template<typename _Callable, typename... _Args>
   using invoke_result_t = meta::eval <
      invoke_result<_Callable, _Args...>
   >;
}

//! @}


#endif /* __utl_utility_invoke_h__ */
