/// \file mingw.invoke.h
/// \brief Lightweight `invoke` implementation, for C++11 and C++14.
///
/// (c) 2018-2019 by Nathaniel J. McClatchey, San Jose, CA, United States
/// \author Nathaniel J. McClatchey, PhD
///
/// \copyright Simplified (2-clause) BSD License.
///
/// \note This file may become part of the mingw-w64 runtime package. If/when
/// this happens, the appropriate license will be added, i.e. this code will
/// become dual-licensed, and the current BSD 2-clause license will stay.

#ifndef MINGW_INVOKE_H_
#define MINGW_INVOKE_H_

#include <type_traits>  //  For std::result_of, etc.
#include <utility>      //  For std::forward
#include <functional>   //  For std::reference_wrapper

namespace mingw_stdthread
{
namespace detail
{
//  For compatibility, implement std::invoke for C++11 and C++14
#if __cplusplus < 201703L
  template<bool PMemFunc, bool PMemData>
  struct Invoker
  {
    template<class F, class... Args>
    inline static typename std::result_of<F(Args...)>::type invoke (F&& f, Args&&... args)
    {
      return std::forward<F>(f)(std::forward<Args>(args)...);
    }
  };
  template<bool>
  struct InvokerHelper;

  template<>
  struct InvokerHelper<false>
  {
    template<class T1>
    inline static auto get (T1&& t1) -> decltype(*std::forward<T1>(t1))
    {
      return *std::forward<T1>(t1);
    }

    template<class T1>
    inline static auto get (const std::reference_wrapper<T1>& t1) -> decltype(t1.get())
    {
      return t1.get();
    }
  };

  template<>
  struct InvokerHelper<true>
  {
    template<class T1>
    inline static auto get (T1&& t1) -> decltype(std::forward<T1>(t1))
    {
      return std::forward<T1>(t1);
    }
  };

  template<>
  struct Invoker<true, false>
  {
    template<class T, class F, class T1, class... Args>
    inline static auto invoke (F T::* f, T1&& t1, Args&&... args) ->\
      decltype((InvokerHelper<std::is_base_of<T,typename std::decay<T1>::type>::value>::get(std::forward<T1>(t1)).*f)(std::forward<Args>(args)...))
    {
      return (InvokerHelper<std::is_base_of<T,typename std::decay<T1>::type>::value>::get(std::forward<T1>(t1)).*f)(std::forward<Args>(args)...);
    }
  };

  template<>
  struct Invoker<false, true>
  {
    template<class T, class F, class T1, class... Args>
    inline static auto invoke (F T::* f, T1&& t1, Args&&... args) ->\
      decltype(InvokerHelper<std::is_base_of<T,typename std::decay<T1>::type>::value>::get(t1).*f)
    {
      return InvokerHelper<std::is_base_of<T,typename std::decay<T1>::type>::value>::get(t1).*f;
    }
  };

  template<class F, class... Args>
  struct InvokeResult
  {
    typedef Invoker<std::is_member_function_pointer<typename std::remove_reference<F>::type>::value,
                    std::is_member_object_pointer<typename std::remove_reference<F>::type>::value &&
                    (sizeof...(Args) == 1)> invoker;
    inline static auto invoke (F&& f, Args&&... args) -> decltype(invoker::invoke(std::forward<F>(f), std::forward<Args>(args)...))
    {
      return invoker::invoke(std::forward<F>(f), std::forward<Args>(args)...);
    }
  };

  template<class F, class...Args>
  auto invoke (F&& f, Args&&... args) -> decltype(InvokeResult<F, Args...>::invoke(std::forward<F>(f), std::forward<Args>(args)...))
  {
    return InvokeResult<F, Args...>::invoke(std::forward<F>(f), std::forward<Args>(args)...);
  }
#else
    using std::invoke;
#endif
} //  Namespace "detail"
} //  Namespace "mingw_stdthread"

#endif