/*!
 * \file    invoke.h
 * \brief   Template meta-programming utilities for callables
 *
 * Copyright (C) 2018 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_meta_invoke_h__
#define __utl_meta_invoke_h__

#include <utl/core/impl.h>
#include <utl/meta/integral.h>
#include <utl/meta/detection.h>
#include <utl/meta/operations.h>

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

   /*!
    * \name meta::invoke
    *
    * A meta-programming invoke() analogous. A \c meta::invocable shall contain a nested
    * template type named \b apply which is bind to actual invocable meta-function.
    *
    *  - We can use \c wrap<> or even better \c quote<> in order to wrap a metafunction to a type (metafunction class)
    *  - We can pass these wrapped types to other metafunctions
    *  - We can \c invoke<> the inner \c apply from a wrapped metafunction class.
    */
   //! @{

   /*!
    * identity, identity_t.
    */
   //! @{
   template <typename _Tp>
   struct identity {
   #if defined (UTL_WORKAROUND_CWG_1558)
      // redirect unused Ts... via void_t
      template <typename... Ts>
      using apply = first_of<_Tp, void_t<Ts...>>;   //!< identity is invokable, must also have apply
   #else
      template <typename...>
      using apply = _Tp;   //!< identity is invokable, must also have apply
   #endif
      using type = _Tp;    //!< identity
   };

   //! identity type alias
   template <typename _Tp>
   using identity_t = eval<identity<_Tp>>;
   //! @}

   /*!
    * invoke, invoke_t
    */
   //! @{
   /*!
    * Invoke the nested apply meta-function from \c Fn with the arguments \c Args.
    * \note
    *    This is an analogous to the std::invoke()
    */
   template <typename Fn, typename... Args>
   using invoke = typename Fn::template apply<Args...>;

   /*!
    * Evaluate the invocation of the nested apply metafunction from \p Fn
    * with the arguments \p Args.
    */
   template <typename Fn, typename... Args>
   using invoke_t = eval< invoke <Fn, Args...>>;
   //! @}

   //! wrap
   //! @{
   /*!
    * wrap is a higher-order primitive that wraps an n-ary Metafunction
    * to create a corresponding Metafunction Class (Invocable). This way
    * we can pass Metafunctions as types to other metafunctions and let
    * them \c invoke the inner templated apply
    */
   template <template <typename...> class F>
   struct wrap {
      template <typename... Args>
      using apply = F<Args...>;
   };

   //! Wrap a template \p F taking literal constants of type \p T into an Invokable
   template <typename T, template <T...> class F>
   struct wrap_i {
      // requires meta::Integral
      template <typename... Ts>
      using apply = F<Ts::type::value...>;
   };
   //! @}

   //! Is applicable trait
   //! @{
   namespace detail {

      template<template<typename...> class F, typename... T>
      struct is_applicable_ {
         template<template<typename...> class G, typename = G<T...>>
            static true_ check (int);  //< T.. can be passed to G
         template<template<typename...> class>
            static false_ check (...); //< all other combinations

         using type = decltype(check<F>(0));
      };

      template<typename F, typename... T>
      struct is_applicable_q_ {
         template<typename G, typename Ret = invoke_t<G, T...>>
            static Ret check (int);    //< T.. can be passed to G
         template<typename...>
            static nil_ check (...);   //< all other combinations

         using type = if_ <
            not_same_<
               nil_,
               decltype(check<F>(0))
            >, true_, false_
         >;
         //!\note
         //! check doesn't return \c true_ or \c false_. The reason is that the \p F is
         //! probably quoted/deferred. This implies an embedded is_applicable<> check
         //! inside defer<> and so its guaranteed to be well formed.
         //! Thus we return the actual evaluated type and make the check for nil_
      };

      template<typename T, template <T...> class F, T... Is>
      struct is_applicable_i_ {
         template<typename TT, template<TT...> class G, typename = G<Is...>>
            static true_ check (int);  //< Is... can be passed to G
         template<typename TT, template<TT...> class G>
            static false_ check (...); //< all other combinations

         using type = decltype(check<T, F>(0));
      };
   }

   //! check if we can instantiate \p F with parameters \p T
   template<template<typename...> class F, typename... T>
   using is_applicable_t = eval<
      detail::is_applicable_<F, T...>
   >;
   //! check if we can invoke \p Q with parameters \p T
   template<typename Q, typename... T>
   using is_applicable_qt = eval <
      detail::is_applicable_q_ <Q, T...>
   >;

   //! check if we can instantiate \p F with parameters \p Is of type \p T
   template <typename T, template<T...> class F, T... Is>
   using is_applicable_it = eval<
      detail::is_applicable_i_<T, F, Is...>
   >;

   //! @}

   //! defer
   //! @{
   namespace detail {
      //! @{
      template<template<typename...> class F, typename... Ts>
      struct defer_ {
         using type = F<Ts...>;
      };

      template<typename T, template<T...> class F, T... Is>
      struct defer_i_ {
         using type = F<Is...>;
      };

      //! \note
      //! We use struct instead of:
      //! template<template<typename...> class F, typename... Ts>
      //! using defer_ =  F<Ts...>;
      //!
      //! The use of struct here is due to Core issue 1430 [\ref link1 1] and is used
      //! as suggested by Roy Crihfield in [\ref link2 2].
      //! In short, this is due to language's inability to expand Ts... into
      //! a fixed parameter list of an alias template.
      //!
      //! \anchor link1 [1]: https://wg21.link/cwg1430
      //! \anchor link2 [2]: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59498
      //! @}
   }

   //! defer alias template for F<Ts...>
   template<template<class...> class F, class... Ts>
   using defer = if_<
      detail::is_applicable_<F, Ts...>,
      detail::defer_<F, Ts...>,
      nil_  //!< Safe, nil_ is dereferencable
   >;

   //! defer_i alias template for F<T, Is...>
   template <typename T, template<T...> class F, T... Is>
   using defer_i = if_ <
      detail::is_applicable_i_<T, F, Is...>,
      detail::defer_i_<T, F, Is...>,
      nil_  //!< Safe, nil_ is dereferencable
   >;
   //! @}

   //! quote
   //! @{
   /*!
    * quote deferred is a higher-order primitive that wraps an n-ary Metafunction
    * to create a corresponding Metafunction Class (Invocable) using defer<> to
    * postpone the evaluation of Metafunction. This is a safe version of \c wrap<>.
    * Again this way we can pass Metafunctions as types to other metafunctions and let
    * them \c invoke the inner templated apply
    */
   template <template <typename...> class F>
   struct quote {
      template <typename... Args>
      using apply = eval<
         defer<F, Args...>    //!< defer here to avoid DR1430
      >;
   };

   //! Wrap a template \p F taking literal constants of type \p T into an Invokable
   template <typename T, template <T...> class F>
   struct quote_i {
      // requires meta::Integral
      template <typename... Ts>
      using apply = eval<
         defer_i<T, F, Ts::type::value...>  //!< defer here to avoid DR1430
      >;
   };
   //! @}

   //! compose
   //! @{
   namespace detail {
      template <template <typename...> class... Fns> struct compose_f_ {};

      // recursive call to all invokes
      template <template <typename...> class Fn0,
                template <typename...> class... Fns>
      struct compose_f_<Fn0, Fns...> {
         template <typename... Args>
         using apply = invoke<
            quote<Fn0>,
            invoke<compose_f_<Fns...>, Args...>
         >;
      };
      // Termination specialization, finally pass the arguments
      template <template <typename...> class Fn0>
      struct compose_f_<Fn0> {
         template <typename ...Args>
         using apply = invoke<quote<Fn0>, Args...>;
      };


      template<typename ...Fns> struct compose_ {};

      // recursive call to all invokes
      template<typename Fn0, typename ...Fns>
      struct compose_<Fn0, Fns...> {
         template <typename ...Args>
         using apply = invoke<
               Fn0,
               invoke<compose_<Fns...>, Args...>
         >;
      };
      // Termination specialization, finally pass the arguments
      template<typename Fn0>
      struct compose_<Fn0> {
         template <typename... Args>
         using apply = invoke<Fn0, Args...>;
      };
   }

   /*!
    * Create an invocable from other invocables(quoted metafunctions) by composition.
    * \note
    *    This implies from N invocables in \p Fns the first N-1 has to be unary.
    *    Thats because of the "return" type of metafunction. They can only return one
    *    type. So for n-ary invocables in the N-1 places the typelist<> is the solution.
    * \example
    * \code
    *    static_assert( std::is_same<
    *       invoke<compose<quote<F1>, quote<F2>, quote<F3>>, int>, F1<F2<F3<int>>>
    *    >, "");
    * \endcode
    */
   template <typename... Fns>
   using compose = detail::compose_<Fns...>;

   /*!
    * Create an invocable from other metafunctions by composition.
    * \note
    *    This implies from N invocables in \p Fns the first N-1 has to be unary.
    *    Thats because of the "return" type of metafunction. They can only return one
    *    type. So for n-ary invocables in the N-1 places the typelist<> is the solution.
    * \example
    * \code
    *    static_assert( std::is_same<
    *       invoke<compose_f<F1, F2, F3>, int>, F1 <F2 <F3 <int>>>
    *    >, "");
    * \endcode
    */
   template <template <typename...> class... Fns>
   using compose_f = detail::compose_f_<Fns...>;
   //! @}

   /*!
    * Applies the invocable \p Fn by binding the arguments \p Ts
    * to the front of \p Fn.
    */
   template<typename Fn, typename... Ts>
   struct bind_front {
       template<typename... Us>
       using apply = invoke<Fn, Ts..., Us...>;
   };

   /*!
    * Applies the Invocable \p Fn by binding the arguments \p Ts
    * to the back of \p Fn.
    */
   template<typename Fn, typename... Ts>
   struct bind_back {
       template<typename... Us>
       using apply = invoke<Fn, Us..., Ts...>;
   };


   /*
    * ========== meta:: predicates ============
    */
   template <typename T1>
   struct same_as {
      template <typename T2>
      struct apply : same_<T1, T2> { };
   };

   template <typename T1>
   struct not_same_as {
      template <typename T2>
      struct apply : not_same_<T1, T2> { };
   };

}}

//! @}
//! @}

#endif /* __utl_meta_invoke_h__ */
