/* Copyright (C) 2010, 2011 and 2014 Chris Vine

The library comprised in this file or of which this file is part is
distributed by Chris Vine under the GNU Lesser General Public
License as follows:

   This library 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 2.1 of
   the License, or (at your option) any later version.

   This library 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, version 2.1, for more details.

   You should have received a copy of the GNU Lesser General Public
   License, version 2.1, along with this library (see the file LGPL.TXT
   which came with this source code package in the c++-gtk-utils
   sub-directory); if not, write to the Free Software Foundation, Inc.,
   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

However, it is not intended that the object code of a program whose
source code instantiates a template from this file or uses macros or
inline functions (of any length) should by reason only of that
instantiation or use be subject to the restrictions of use in the GNU
Lesser General Public License.  With that in mind, the words "and
macros, inline functions and instantiations of templates (of any
length)" shall be treated as substituted for the words "and small
macros and small inline functions (ten lines or less in length)" in
the fourth paragraph of section 5 of that licence.  This does not
affect any other reason why object code may be subject to the
restrictions in that licence (nor for the avoidance of doubt does it
affect the application of section 2 of that licence to modifications
of the source code in this file).

*/

#ifndef CGU_PARAM_H
#define CGU_PARAM_H

#include <c++-gtk-utils/cgu_config.h>

#ifdef CGU_USE_TUPLE
#include <tuple>
#include <cstddef>     // for std::size_t
#include <utility>     // for std::forward and std::move
#include <type_traits> // for std::result_of
#endif

namespace Cgu {

/**
 * @class Param param.h c++-gtk-utils/param.h
 * @brief Struct for automatic typing of function parameter arguments
 *
 * @details This struct uses template partial specialisation in order
 * to provide automatic type mapping for function arguments.  It is
 * used by the unbound arguments of callback objects and their related
 * functors and emitter objects.  (A more detailed explanation is that
 * it is used where a virtual function is at the end of a call chain,
 * notably the dispatch() method of callback objects, with the result
 * that we cannot templatise the virtual function itself in order to
 * use std::forward for argument passing, but instead have to type-map
 * the arguments using class templates.)
 *
 * Mapping is as follows:
 *
 *  A value argument is mapped to reference to const of the value
 *  type.
 *
 *  A pointer argument is mapped to pointer argument (its original
 *  type).
 *
 *  A non-const reference argument is mapped to non-const reference
 *  (its original type).
 *
 *  A const reference argument is mapped to const reference (its
 *  original type).
 */

template<class T>
struct Param {
  typedef const T& ParamType;
};

template<class T>
struct Param<T&> {
  typedef T& ParamType;
};

template<class T>
struct Param<T*> {
  typedef T* ParamType;
};

/**
 * @class RemoveRefCond param.h c++-gtk-utils/param.h
 * @brief Struct which will conditionally convert a reference type to
 * a value type
 *
 * @details This struct is used by Callback::make() and
 * Callback::make_ref() where the std::tuple backend is not used to
 * implement the callback classes, so that where a call is made to
 * Callback::make_ref() and the target function's arguments include
 * one with a reference, that reference is removed.
 *
 * Since 2.0.0-rc3
 */
template<class T, bool unref>
struct RemoveRefCond {};

template<class T>
struct RemoveRefCond<T, true> {
  typedef T Type;
};

template<class T>
struct RemoveRefCond<T&, true> {
  typedef T Type;
};

template<class T>
struct RemoveRefCond<T, false> {
  typedef T Type;
};

// explicatory only - we could do without this specialisation
template<class T>
struct RemoveRefCond<T&, false> {
  typedef T& Type;
};

// the remainder of this file implements tuple_apply()

#if defined(DOXYGEN_PARSING) || defined(CGU_USE_TUPLE)

#ifndef DOXYGEN_PARSING

namespace TupleHelper {

/*
 MakeIndexList provides a list of tuple indices for any given
 instantiation, comprised in the type of an IndexList object.  For
 example, MakeIndexList<4>::Type is of type IndexList<0, 1, 2, 3>.
 The index values held in the type of the IndexList object are then
 unpacked into std::get().

 IndexList is the base class for MakeIndexList, from which the
 complete MakeIndexList instantiation ending recursion derives in
 order to deliver up the index list for tuple_apply_impl - its Type
 member is itself.  So the inheritance diagram for MakeIndexList<4>
 is:

   IndexList<0, 1, 2, 3>
            |
            |
 MakeIndexList<0, 0, 1, 2, 3>
            |
            |
  MakeIndexList<1, 1, 2, 3>
            |
            |
   MakeIndexList<2, 2, 3>
            |
            |
    MakeIndexList<3, 3>
            |
            |
     MakeIndexList<4>
*/

template<std::size_t... indices>
struct IndexList {
  typedef IndexList<indices...> Type;
};

template<std::size_t count, std::size_t... indices>
struct MakeIndexList: public MakeIndexList<count - 1, count - 1, indices...> {};

// ends recursion - the complete (one from base) class, inheriting
// directly from IndexList
template<std::size_t... indices>
struct MakeIndexList<0, indices...>: public IndexList<indices...> {};

template <class Obj, class Ret, class... FuncArgs, class Tuple,
	  std::size_t... indices, class... OtherArgs>
Ret tuple_apply_impl(Obj& obj,
		     Ret (Obj::* func) (FuncArgs...),
		     Tuple&& t,
		     IndexList<indices...>,
		     OtherArgs&&... args) {
  return (obj.*func)(std::get<indices>(std::forward<Tuple>(t))..., std::forward<OtherArgs>(args)...);
}

template <class Obj, class Ret, class... FuncArgs, class Tuple,
	  std::size_t... indices, class... OtherArgs>
Ret tuple_apply_impl(const Obj& obj,
		     Ret (Obj::* func) (FuncArgs...) const,
		     Tuple&& t,
		     IndexList<indices...>,
		     OtherArgs&&... args) {
  return (obj.*func)(std::get<indices>(std::forward<Tuple>(t))..., std::forward<OtherArgs>(args)...);
}

template <class Ret, class Func, class Tuple,
	  std::size_t... indices, class... OtherArgs>
Ret tuple_apply_impl(Func&& func,
		     Tuple&& t,
		     IndexList<indices...>,
		     OtherArgs&&... args) {
  return func(std::get<indices>(std::forward<Tuple>(t))..., std::forward<OtherArgs>(args)...);
}

} // namespace TupleHelper

#endif // DOXYGEN_PARSING

/**
 * @defgroup tuple tuple
 *
 * \#include <c++-gtk-utils/param.h>
 *
 * From version 2.2.10, where this library is compiled with gcc >= 4.8
 * or clang >= 3.4, the library uses a backend for relevant
 * closure/callback classes using std::tuple (unless that backend is
 * explicitly disabled with the \--without-tuple configuration option,
 * in which case the implementation used prior to version 2.2.10 is
 * retained).  The tuple backend avoids code duplication and makes for
 * a cleaner implementation, and enables Callback::make() and
 * Callback::make_ref() to take up to 10 bound arguments instead of
 * the previous 5 bound arguments.  For compilers other than gcc and
 * clang which adequately support std::tuple, the tuple backend can be
 * manually selected with the \--with-tuple configuration option.
 * Both backends are ABI compatible, because for closure/callback
 * implementation this library only exports the CallbackArg pure
 * virtual class.
 *
 * Where this library has been compiled with the tuple backend for
 * callback classes, it uses the Cgu::tuple_apply() utility function
 * to expand tuples into their component elements in order to pass
 * those elements to functions as arguments on function calls.
 * Because this utility function is of general use, it is made
 * available by the library more generally in the param.h header.
 *
 * Taking a function with the signature 'int my_func(int, double, int,
 * double)', a tuple could be expanded to apply the my_func() function
 * to it as follows:
 *
 * @code
 *   int my_func(int, double, int, double);
 *   auto tup = make_tuple(2, 3.0, 4, 5.0);
 *   int res = Cgu::tuple_apply(my_func, tup);
 * @endcode
 *
 * Trailing arguments not comprising part of the tuple can also be
 * passed to the function, so in the above example my_func() could be
 * called as follows:
 *
 * @code
 *   int my_func(int, double, int, double);
 *   auto tup = make_tuple(2, 3.0);
 *   int res = Cgu::tuple_apply(my_func, tup, 4, 5.0);
 * @endcode
 *
 * Overloads of the tuple_apply() function are provided which can
 * apply any callable object (including a normal function, a static
 * member function, the return value of std::bind() or a lambda
 * expression) to a tuple, or which can apply a const or non-const
 * non-static member function with object to the tuple.
 */

// We need three overloads of the version of this function which takes
// a callable object (two for tuple lvalues and one for tuple rvalues)
// because we cannot have the tuple as a completely deduced type in
// this context - we need to deduce TupleArgs for std::result_of.
// These overloads will work with any callable object, including a
// function pointer.
/**
 * \#include <c++-gtk-utils/param.h>
 *
 * This function expands a tuple and uses the tuple's elements as
 * arguments to a call to a callable object, such as a normal
 * function, a static member function, the return value of std::bind()
 * or a lambda expression.
 *
 * @param func The callable object.
 * @param t The std::tuple object to be expanded.
 * @param args The trailing arguments (if any) to be used in the
 * function call.
 * @return The result of applying func to the tuple and any trailing
 * arguments.
 * @note This function is not available unless either (i) this library
 * is compiled with gcc >= 4.8 or clang >= 3.4, or (ii) the library is
 * compiled by another compiler with appropriate support for C++11
 * tuples using the \--with-tuple configuration option.
 *
 * Since 2.2.10
 * @ingroup tuple
 */
template<class Func,
	 class... TupleArgs,
         class... OtherArgs>
auto tuple_apply(Func&& func,
		 const std::tuple<TupleArgs...>& t,
		 OtherArgs&&... args) -> typename std::result_of<Func(const TupleArgs&..., OtherArgs&&...)>::type {
  typedef typename std::result_of<Func(const TupleArgs&..., OtherArgs&&...)>::type Ret;
  typedef typename TupleHelper::MakeIndexList<sizeof...(TupleArgs)>::Type List;
  return TupleHelper::tuple_apply_impl<Ret>(std::forward<Func>(func),
					    t,
					    List(),
					    std::forward<OtherArgs>(args)...);
}

/**
 * \#include <c++-gtk-utils/param.h>
 *
 * This function expands a tuple and uses the tuple's elements as
 * arguments to a call to a callable object, such as a normal
 * function, a static member function, the return value of std::bind()
 * or a lambda expression.
 *
 * @param func The callable object.
 * @param t The std::tuple object to be expanded.
 * @param args The trailing arguments (if any) to be used in the
 * function call.
 * @return The result of applying func to the tuple and any trailing
 * arguments.
 * @note This function is not available unless either (i) this library
 * is compiled with gcc >= 4.8 or clang >= 3.4, or (ii) the library is
 * compiled by another compiler with appropriate support for C++11
 * tuples using the \--with-tuple configuration option.
 *
 * Since 2.2.10
 * @ingroup tuple
 */
template<class Func,
	 class... TupleArgs,
         class... OtherArgs>
auto tuple_apply(Func&& func,
		 std::tuple<TupleArgs...>& t,
		 OtherArgs&&... args) -> typename std::result_of<Func(TupleArgs&..., OtherArgs&&...)>::type {
  typedef typename std::result_of<Func(TupleArgs&..., OtherArgs&&...)>::type Ret;
  typedef typename TupleHelper::MakeIndexList<sizeof...(TupleArgs)>::Type List;
  return TupleHelper::tuple_apply_impl<Ret>(std::forward<Func>(func),
					    t,
					    List(),
					    std::forward<OtherArgs>(args)...);
}

/**
 * \#include <c++-gtk-utils/param.h>
 *
 * This function expands a tuple and uses the tuple's elements as
 * arguments to a call to a callable object, such as a normal
 * function, a static member function, the return value of std::bind()
 * or a lambda expression.
 *
 * @param func The callable object.
 * @param t The std::tuple object to be expanded.
 * @param args The trailing arguments (if any) to be used in the
 * function call.
 * @return The result of applying func to the tuple and any trailing
 * arguments.
 * @note This function is not available unless either (i) this library
 * is compiled with gcc >= 4.8 or clang >= 3.4, or (ii) the library is
 * compiled by another compiler with appropriate support for C++11
 * tuples using the \--with-tuple configuration option.
 *
 * Since 2.2.10
 * @ingroup tuple
 */
template<class Func,
	 class... TupleArgs,
         class... OtherArgs>
auto tuple_apply(Func&& func,
		 std::tuple<TupleArgs...>&& t,
		 OtherArgs&&... args) -> typename std::result_of<Func(TupleArgs&&..., OtherArgs&&...)>::type {
  typedef typename std::result_of<Func(TupleArgs&&..., OtherArgs&&...)>::type Ret;
  typedef typename TupleHelper::MakeIndexList<sizeof...(TupleArgs)>::Type List;
  return TupleHelper::tuple_apply_impl<Ret>(std::forward<Func>(func),
					    std::move(t),
					    List(),
					    std::forward<OtherArgs>(args)...);
}

/**
 * \#include <c++-gtk-utils/param.h>
 *
 * This function expands a tuple and uses the tuple's elements as
 * arguments to a function call to a non-static class member function.
 *
 * @param obj The object whose member function is to be called.
 * @param func The non-static member function to be called.
 * @param t The std::tuple object to be expanded.
 * @param args The trailing arguments (if any) to be used in the
 * function call.
 * @return The result of applying func to the tuple and any trailing
 * arguments.
 * @note This function is not available unless either (i) this library
 * is compiled with gcc >= 4.8 or clang >= 3.4, or (ii) the library is
 * compiled by another compiler with appropriate support for C++11
 * tuples using the \--with-tuple configuration option.
 *
 * Since 2.2.10
 * @ingroup tuple
 */
template<class Obj, class Ret, class... FuncArgs, class Tuple, class... OtherArgs>
Ret tuple_apply(Obj& obj,
		Ret (Obj::* func) (FuncArgs...),
		Tuple&& t,
		OtherArgs&&... args) {
  typedef typename std::remove_reference<Tuple>::type TupleType;
  static_assert(sizeof...(FuncArgs) == std::tuple_size<TupleType>::value + sizeof...(OtherArgs),
  		"The arity of the function passed to tuple_apply() and the "
  		"arguments passed to it do not match");
  typedef typename TupleHelper::MakeIndexList<std::tuple_size<TupleType>::value>::Type List;
  return TupleHelper::tuple_apply_impl(obj,
				       func,
				       std::forward<Tuple>(t),
				       List(),
				       std::forward<OtherArgs>(args)...);
}

/**
 * \#include <c++-gtk-utils/param.h>
 *
 * This function expands a tuple and uses the tuple's elements as
 * arguments to a function call to a non-static class member function.
 *
 * @param obj The object whose member function is to be called.
 * @param func The non-static member function to be called.
 * @param t The std::tuple object to be expanded.
 * @param args The trailing arguments (if any) to be used in the
 * function call.
 * @return The result of applying func to the tuple and any trailing
 * arguments.
 * @note This function is not available unless either (i) this library
 * is compiled with gcc >= 4.8 or clang >= 3.4, or (ii) the library is
 * compiled by another compiler with appropriate support for C++11
 * tuples using the \--with-tuple configuration option.
 *
 * Since 2.2.10
 * @ingroup tuple
 */
template<class Obj, class Ret, class... FuncArgs, class Tuple, class... OtherArgs>
Ret tuple_apply(const Obj& obj,
		Ret (Obj::* func) (FuncArgs...) const,
		Tuple&& t,
		OtherArgs&&... args) {
  typedef typename std::remove_reference<Tuple>::type TupleType;
  static_assert(sizeof...(FuncArgs) == std::tuple_size<TupleType>::value + sizeof...(OtherArgs),
  		"The arity of the function passed to tuple_apply() and the "
  		"arguments passed to it do not match");
  typedef typename TupleHelper::MakeIndexList<std::tuple_size<TupleType>::value>::Type List;
  return TupleHelper::tuple_apply_impl(obj,
				       func,
				       std::forward<Tuple>(t),
				       List(),
				       std::forward<OtherArgs>(args)...);
}

#endif // CGU_USE_TUPLE

} // namespace Cgu

#endif // CGU_PARAM_H
