/* Copyright (C) 2012 to 2015 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_TASK_MANAGER_TPP
#define CGU_TASK_MANAGER_TPP

namespace Cgu {

namespace Thread {

// because the side effects of some helper functions changed in
// version 2.2.5, this is now the TaskManagerHelper2 namespace so
// that no ODR issues arise on library linking with old binaries
namespace TaskManagerHelper2 {

// the sole purpose of this struct is to enable a callback object to
// be constructed with Callback::make_ref() which takes an argument
// which can be mutated when the callback is executed.  Normally this
// would be unsafe: however in this particular use it is fine as the
// callback is only ever executed once, via a TaskManager object.
struct UniqueCbWrapperArg {
  mutable std::unique_ptr<const Callback::Callback> ptr;
};

// the sole purpose of this struct is to enable a callback object to
// be constructed with Callback::make_ref() from WhenPackagedWrapper::
// do task()/do_task_rel() which takes an argument which can be
// mutated when the callback is executed.  Normally this would be
// unsafe: however in this particular use it is fine as the callback
// is only ever executed once.
template <class Ret>
struct FutureWrapperArg {
  mutable std::future<Ret> fut;
};

template <class Ret, class Func>
struct MemfunResultWrapperArgs {
  SharedLockPtr<AsyncResult<Ret>> ret;
  Func func;
};

// We pass Func as a template type so we do not have to duplicate code
// for const and non-const object methods (this is instead represented
// by the Func type)
template <class Ret, class T, class Func, class... Params>
struct MemfunResultWrapper {
  static void exec(const MemfunResultWrapperArgs<Ret, Func>& wrapper_args,
                   T* obj,
                   const Params&... params) {
    wrapper_args.ret->set((obj->*wrapper_args.func)(params...));
  }
  static void do_fail(const SharedLockPtr<AsyncResult<Ret>>& ret) {
    ret->set_error(); // won't throw
  }
};

// We pass Func as a template type so we do not have to duplicate code
// for const and non-const object methods (this is instead represented
// by the Func type).  The 'when' data member is marked mutable so
// that it can be mutated when the callback is executed.  Normally
// this would be unsafe: however in this particular use it is fine as
// the callback is only ever executed once, via a TaskManager task.
template <class Ret, class Func>
struct MemfunWhenWrapperArgs {
  mutable std::unique_ptr<const Callback::CallbackArg<const Ret&>> when;
  gint priority;
  GMainContext* context;
  Func func;
};

// We pass Func as a template type so we do not have to duplicate code
// for const and non-const object methods (this is instead represented
// by the Func type).  The 'when' data member is marked mutable so
// that it can be mutated when the callback is executed.  Normally
// this would be unsafe: however in this particular use it is fine as
// the callback is only ever executed once, via a TaskManager task.
template <class Ret, class Func>
struct MemfunWhenWrapperArgsRel {
  mutable std::unique_ptr<SafeEmitterArg<const Ret&>> when;
  gint priority;
  GMainContext* context;
  Func func;
};

// We pass Func as a template type so we do not have to duplicate code
// for const and non-const object methods (this is instead represented
// by the Func type)
template <class Ret, class T, class Func, class... Params>
struct MemfunWhenWrapper {
  static void do_task(const MemfunWhenWrapperArgs<Ret, Func>& wrapper_args,
		      T* obj,
		      const Params&... params) {
    Callback::post(Callback::make_ref(&do_when,
				      std::move(wrapper_args.when),
				      (obj->*wrapper_args.func)(params...)),
		   wrapper_args.priority,
		   wrapper_args.context);
  }
  static void do_when(const std::unique_ptr<const Callback::CallbackArg<const Ret&>>& when,
		      const Ret& val) {
    when->dispatch(val);
  }
  static void do_task_rel(const MemfunWhenWrapperArgsRel<Ret, Func>& wrapper_args,
			  T* obj,
			  const Params&... params) {
    Callback::post(Callback::make_ref(&do_when_rel,
				      std::move(wrapper_args.when),
				      (obj->*wrapper_args.func)(params...)),
		   wrapper_args.priority,
		   wrapper_args.context);
  }
  static void do_when_rel(const std::unique_ptr<SafeEmitterArg<const Ret&>>& when_emitter,
			  const Ret& val) {
    when_emitter->emit(val);
  }
  static void post_fail(const UniqueCbWrapperArg& do_fail_wrapper,
			gint priority,
			GMainContext* context) {
    // this function does not throw
    Callback::post(do_fail_wrapper.ptr.release(), priority, context);
  }
  static void do_fail_rel(const std::unique_ptr<SafeEmitter>& fail_emitter) {
    fail_emitter->emit(); // might throw std::bad_alloc
  }
};

template <class Ret, class... Params>
struct FunResultWrapperArgs {
  SharedLockPtr<AsyncResult<Ret>> ret;
  Ret (*func)(Params...);
};

template <class Ret, class... Params>
struct FunResultWrapper {
  static void exec(const FunResultWrapperArgs<Ret, Params...>& wrapper_args,
		   const Params&... args) {
    wrapper_args.ret->set(wrapper_args.func(args...));
  }
  static void do_fail(const SharedLockPtr<AsyncResult<Ret>>& ret) {
    ret->set_error(); // won't throw
  }
};

// the 'when' data member is marked mutable so that it can be mutated
// when the callback is executed.  Normally this would be unsafe:
// however in this particular use it is fine as the callback is only
// ever executed once, via a TaskManager task.
template <class Ret, class... Params>
struct FunWhenWrapperArgs {
  typedef Ret (*Func)(Params...);
  mutable std::unique_ptr<const Callback::CallbackArg<const Ret&>> when;
  gint priority;
  GMainContext* context;
  Func func;
};

// the 'when' data member is marked mutable so that it can be mutated
// when the callback is executed.  Normally this would be unsafe:
// however in this particular use it is fine as the callback is only
// ever executed once, via a TaskManager task.
template <class Ret, class... Params>
struct FunWhenWrapperArgsRel {
  typedef Ret (*Func)(Params...);
  mutable std::unique_ptr<SafeEmitterArg<const Ret&>> when;
  gint priority;
  GMainContext* context;
  Func func;
};

template <class Ret, class... Params>
struct FunWhenWrapper {
  static void do_task(const FunWhenWrapperArgs<Ret, Params...>& wrapper_args,
		      const Params&... params) {
    Callback::post(Callback::make_ref(&do_when,
				      std::move(wrapper_args.when),
				      wrapper_args.func(params...)),
		   wrapper_args.priority,
		   wrapper_args.context);
  }
  static void do_when(const std::unique_ptr<const Callback::CallbackArg<const Ret&>>& when,
		      const Ret& val) {
    when->dispatch(val);
  }
  static void do_task_rel(const FunWhenWrapperArgsRel<Ret, Params...>& wrapper_args,
			  const Params&... params) {
    Callback::post(Callback::make_ref(&do_when_rel,
				      std::move(wrapper_args.when),
				      wrapper_args.func(params...)),
		   wrapper_args.priority,
		   wrapper_args.context);
  }
  static void do_when_rel(const std::unique_ptr<SafeEmitterArg<const Ret&>>& when_emitter,
			  const Ret& val) {
    when_emitter->emit(val);
  }
  static void post_fail(const UniqueCbWrapperArg& do_fail_wrapper,
			gint priority,
			GMainContext* context) {
    // this function does not throw
    Callback::post(do_fail_wrapper.ptr.release(), priority, context);
  }
  static void do_fail_rel(const std::unique_ptr<SafeEmitter>& fail_emitter) {
    fail_emitter->emit(); // might throw std::bad_alloc
  }
};

template <class Ret, class FType>
struct FunctorWhenWrapper {
  static void do_task(FType& f,
		      std::unique_ptr<const Cgu::Callback::CallbackArg<const Ret&>>& when,
		      gint priority,
		      GMainContext* context) {
    Callback::post(Callback::make_ref(&do_when,
				      std::move(when),
				      f()),
		   priority,
		   context);
  }
  static void do_when(const std::unique_ptr<const Callback::CallbackArg<const Ret&>>& when,
		      const Ret& val) {
    when->dispatch(val);
  }
  static void do_task_rel(FType& f,
			  std::unique_ptr<SafeEmitterArg<const Ret&>>& when,
			  gint priority,
			  GMainContext* context) {
    Callback::post(Callback::make_ref(&do_when_rel,
				      std::move(when),
				      f()),
		   priority,
		   context);
  }
  static void do_when_rel(const std::unique_ptr<SafeEmitterArg<const Ret&>>& when_emitter,
			  const Ret& val) {
    when_emitter->emit(val);
  }
  static void post_fail(const UniqueCbWrapperArg& do_fail_wrapper,
			gint priority,
			GMainContext* context) {
    // this function does not throw
    Callback::post(do_fail_wrapper.ptr.release(), priority, context);
  }
  static void do_fail_rel(const std::unique_ptr<SafeEmitter>& fail_emitter) {
    fail_emitter->emit(); // might throw std::bad_alloc
  }
};

/* 
 * The FunctorWhenExec class is a specialised class which is necessary
 * because the 'functor' and 'when' members need to be declared
 * mutable so that they can bind to the reference to non-const
 * arguments of FunctorWhenWrapper::do_task() and
 * FunctorWhenWrapper::do_task_rel(), and thus so that a mutable
 * lambda can be executed by that function and a 'when' callback can
 * be passed by std::unique_ptr.  Because it is so specialised, it is
 * not suitable for inclusion in the generic interfaces provided in
 * callback.h.  (Except in this specialised usage, it can also be
 * dangerous, as it allows members of the callback object to be
 * mutated: normally this would be undesirable.)  An alternative would
 * have been to put the 'functor' and 'when' members in a wrapper
 * struct like MemfunWhenWrapperArgs or FunWhenWrapperArgs, but if
 * 'functor' were an lvalue that would mean it being copied twice.
 * This is the most efficient implementation.  It is safe to hold
 * 'do_fail_cb' by pointer, because that callback always needs to
 * execute if something else fails by throwing and the implementation
 * always deletes it when it has been finished with or has not been
 * used.
 */
template <class FType, class When>
class FunctorWhenExec: public Cgu::Callback::Callback {
public:
  typedef void (*Func)(FType&, When&, gint, GMainContext*);
private:
  Func func;
  mutable FType functor;
  // 'When' is either a std::unique_ptr<const Cgu::Callback::CallbackArg<const Ret&>>
  //  or a std::unique_ptr<SafeEmitterArg<const Ret&>> type
  mutable When when;
  gint priority;
  GMainContext* context;
public:
  void dispatch() const {func(functor, when, priority, context);}
  template <class FunctorArg>
  FunctorWhenExec(Func func_, FunctorArg&& functor_,
		  When&& when_, gint priority_,
		  GMainContext* context_): func(func_),
					   functor(std::forward<FunctorArg>(functor_)),
					   when(std::move(when_)), priority(priority_),
					   context(context_) {}
};

// The 'task' and 'do_when_cb' data members are marked mutable so that
// they can be mutated when the callback is executed.  Normally this
// would be unsafe: however in this particular use it is fine as the
// callback is only ever executed once, via a TaskManager task.
template <class Ret>
struct WhenPackagedWrapperArgs {
  mutable std::packaged_task<Ret()> task;
  mutable std::unique_ptr<const Callback::Callback> do_when_cb;
  gint priority;
  GMainContext* context;
};

template <class Ret>
struct WhenPackagedWrapper {
  static void do_task(const WhenPackagedWrapperArgs<Ret>& wrapper_args) {
    // this will not throw
    wrapper_args.task();
    // this will not throw
    Callback::post(wrapper_args.do_when_cb.release(),
		   wrapper_args.priority,
		   wrapper_args.context);
  }
  static void do_when(const std::unique_ptr<const Callback::CallbackArg<std::future<Ret>&>>& when,
		      const FutureWrapperArg<Ret>& f) {
    when->dispatch(f.fut);
  }
  static void do_when_rel(const std::unique_ptr<SafeEmitterArg<std::future<Ret>&>>& when_emitter,
			  const FutureWrapperArg<Ret>& f) {
    when_emitter->emit(f.fut); // might throw std::bad_alloc
  }
};

} // namespace TaskManagerHelper2

/*** templated task wrappers for functions returning a value ***/

// for non-const non-static member functions

template <class Ret, class... Params, class... Args, class T>
Cgu::SharedLockPtr<Cgu::AsyncResult<Ret>> TaskManager::make_task_result(T& t,
									Ret (T::*func)(Params...),
									Args&&... args) {
  static_assert(sizeof...(Args) < 4,
		"No greater than three bound arguments can be passed to "
		"TaskManager::make_task_result() taking a member function.");

  typedef Ret (T::*Func)(Params...);

  SharedLockPtr<AsyncResult<Ret>> ret{new AsyncResult<Ret>};

  typedef std::unique_ptr<const Callback::Callback> CbPtr;

  CbPtr exec_cb(Callback::make_ref(&TaskManagerHelper2::MemfunResultWrapper<Ret, T, Func, Params...>::exec,
				   TaskManagerHelper2::MemfunResultWrapperArgs<Ret, Func>{ret, func},
				   &t,
				   std::forward<Args>(args)...));
  CbPtr do_fail_cb(Callback::make_ref(&TaskManagerHelper2::MemfunResultWrapper<Ret, T, Func, Params...>::do_fail,
				      ret));
  add_task(std::move(exec_cb), std::move(do_fail_cb));

  return ret;
}

template <class Ret, class... Params, class... Args, class T>
void TaskManager::make_task_when_full(std::unique_ptr<const Cgu::Callback::CallbackArg<const Ret&>> when,
				      Cgu::Releaser* when_releaser,
				      std::unique_ptr<const Cgu::Callback::Callback> fail,
				      Cgu::Releaser* fail_releaser,
				      gint priority,
				      GMainContext* context,
				      T& t,
				      Ret (T::*func)(Params...),
				      Args&&... args) {

  static_assert(sizeof...(Args) < 4,
		"No greater than three bound arguments can be passed to "
		"TaskManager::make_task_when_full() taking a member function.");

  typedef Ret (T::*Func)(Params...);
  typedef std::unique_ptr<const Callback::Callback> CbPtr;

  // we construct the callback for MemfunWhenWrapper::do_fail() here
  // rather than in MemfunWhenWrapper::post_fail(), so that
  // MemfunWhenWrapper::post_fail() cannot throw
  CbPtr post_fail_cb;
  if (fail) {
    CbPtr do_fail_cb;
    if (fail_releaser) {
      std::unique_ptr<SafeEmitter> fail_emitter(new SafeEmitter);
      fail_emitter->connect(fail.release(), *fail_releaser);
      do_fail_cb.reset(Callback::make_ref(&TaskManagerHelper2::MemfunWhenWrapper<Ret, T, Func, Params...>::do_fail_rel,
					  std::move(fail_emitter)));
    }
    else {
      do_fail_cb = std::move(fail);
    }
    post_fail_cb.reset(Callback::make_ref(&TaskManagerHelper2::MemfunWhenWrapper<Ret, T, Func, Params...>::post_fail,
					  TaskManagerHelper2::UniqueCbWrapperArg{std::move(do_fail_cb)},
					  priority,
					  context));
  }

  CbPtr do_task_cb;
  if (when_releaser) {
    std::unique_ptr<SafeEmitterArg<const Ret&>> when_emitter(new SafeEmitterArg<const Ret&>);
    when_emitter->connect(when.release(), *when_releaser);
    do_task_cb.reset(Callback::make_ref(&TaskManagerHelper2::MemfunWhenWrapper<Ret, T, Func, Params...>::do_task_rel,
					TaskManagerHelper2::MemfunWhenWrapperArgsRel<Ret, Func>{
					  std::move(when_emitter),
					  priority,
					  context,
					  func
					},
					&t,
					std::forward<Args>(args)...));
  }
  else {
    do_task_cb.reset(Callback::make_ref(&TaskManagerHelper2::MemfunWhenWrapper<Ret, T, Func, Params...>::do_task,
					TaskManagerHelper2::MemfunWhenWrapperArgs<Ret, Func>{
					  std::move(when),
					  priority,
					  context,
					  func
					},
					&t,
					std::forward<Args>(args)...));
  }
  add_task(std::move(do_task_cb), std::move(post_fail_cb));
}

// for const non-static member functions

template <class Ret, class... Params, class... Args, class T>
Cgu::SharedLockPtr<Cgu::AsyncResult<Ret>> TaskManager::make_task_result(const T& t,
									Ret (T::*func)(Params...) const,
									Args&&... args) {
  static_assert(sizeof...(Args) < 4,
		"No greater than three bound arguments can be passed to "
		"TaskManager::make_task_result() taking a member function.");

  typedef Ret (T::*Func)(Params...) const;

  SharedLockPtr<AsyncResult<Ret>> ret{new AsyncResult<Ret>};

  typedef std::unique_ptr<const Callback::Callback> CbPtr;

  CbPtr exec_cb(Callback::make_ref(&TaskManagerHelper2::MemfunResultWrapper<Ret, const T, Func, Params...>::exec,
				   TaskManagerHelper2::MemfunResultWrapperArgs<Ret, Func>{ret, func},
				   &t,
				   std::forward<Args>(args)...));
  CbPtr do_fail_cb(Callback::make_ref(&TaskManagerHelper2::MemfunResultWrapper<Ret, const T, Func, Params...>::do_fail,
				      ret));
  add_task(std::move(exec_cb), std::move(do_fail_cb));

  return ret;
}

template <class Ret, class... Params, class... Args, class T>
void TaskManager::make_task_when_full(std::unique_ptr<const Cgu::Callback::CallbackArg<const Ret&>> when,
				      Cgu::Releaser* when_releaser,
				      std::unique_ptr<const Cgu::Callback::Callback> fail,
				      Cgu::Releaser* fail_releaser,
				      gint priority,
				      GMainContext* context,
				      const T& t,
				      Ret (T::*func)(Params...) const,
				      Args&&... args) {

  static_assert(sizeof...(Args) < 4,
		"No greater than three bound arguments can be passed to "
		"TaskManager::make_task_when_full() taking a member function.");

  typedef Ret (T::*Func)(Params...) const;
  typedef std::unique_ptr<const Callback::Callback> CbPtr;

  // we construct the callback for MemfunWhenWrapper::do_fail() here
  // rather than in MemfunWhenWrapper::post_fail(), so that
  // MemfunWhenWrapper::post_fail() cannot throw
  CbPtr post_fail_cb;
  if (fail) {
    CbPtr do_fail_cb;
    if (fail_releaser) {
      std::unique_ptr<SafeEmitter> fail_emitter(new SafeEmitter);
      fail_emitter->connect(fail.release(), *fail_releaser);
      do_fail_cb.reset(Callback::make_ref(&TaskManagerHelper2::MemfunWhenWrapper<Ret, const T, Func, Params...>::do_fail_rel,
					  std::move(fail_emitter)));
    }
    else {
      do_fail_cb = std::move(fail);
    }
    post_fail_cb.reset(Callback::make_ref(&TaskManagerHelper2::MemfunWhenWrapper<Ret, const T, Func, Params...>::post_fail,
					  TaskManagerHelper2::UniqueCbWrapperArg{std::move(do_fail_cb)},
					  priority,
					  context));
  }

  CbPtr do_task_cb;
  if (when_releaser) {
    std::unique_ptr<SafeEmitterArg<const Ret&>> when_emitter(new SafeEmitterArg<const Ret&>);
    when_emitter->connect(when.release(), *when_releaser);
    do_task_cb.reset(Callback::make_ref(&TaskManagerHelper2::MemfunWhenWrapper<Ret, const T, Func, Params...>::do_task_rel,
					TaskManagerHelper2::MemfunWhenWrapperArgsRel<Ret, Func>{
					  std::move(when_emitter),
					  priority,
					  context,
					  func
					},
					&t,
					std::forward<Args>(args)...));
  }
  else {
    do_task_cb.reset(Callback::make_ref(&TaskManagerHelper2::MemfunWhenWrapper<Ret, const T, Func, Params...>::do_task,
					TaskManagerHelper2::MemfunWhenWrapperArgs<Ret, Func>{
					  std::move(when),
					  priority,
					  context,
					  func
					},
					&t,
					std::forward<Args>(args)...));
  }
  add_task(std::move(do_task_cb), std::move(post_fail_cb));
}

// for static member functions and ordinary functions

template <class Ret, class... Params, class... Args>
Cgu::SharedLockPtr<Cgu::AsyncResult<Ret>> TaskManager::make_task_result(Ret (*func)(Params...),
									Args&&... args) {
  static_assert(sizeof...(Args) < 5,
		"No greater than four bound arguments can be passed to "
		"TaskManager::make_task_result() taking a function.");

  SharedLockPtr<AsyncResult<Ret>> ret{new AsyncResult<Ret>};

  typedef std::unique_ptr<const Callback::Callback> CbPtr;

  CbPtr exec_cb(Callback::make_ref(&TaskManagerHelper2::FunResultWrapper<Ret, Params...>::exec,
				   TaskManagerHelper2::FunResultWrapperArgs<Ret, Params...>{ret, func},
				   std::forward<Args>(args)...));
  CbPtr do_fail_cb(Callback::make_ref(&TaskManagerHelper2::FunResultWrapper<Ret, Params...>::do_fail,
				      ret));
  add_task(std::move(exec_cb), std::move(do_fail_cb));

  return ret;
}

template <class Ret, class... Params, class... Args>
void TaskManager::make_task_when_full(std::unique_ptr<const Cgu::Callback::CallbackArg<const Ret&>> when,
				      Cgu::Releaser* when_releaser,
				      std::unique_ptr<const Cgu::Callback::Callback> fail,
				      Cgu::Releaser* fail_releaser,
				      gint priority,
				      GMainContext* context,
				      Ret (*func)(Params...),
				      Args&&... args) {

  static_assert(sizeof...(Args) < 5,
		"No greater than four bound arguments can be passed to "
		"TaskManager::make_task_when_full() taking a function.");

  typedef std::unique_ptr<const Callback::Callback> CbPtr;

  // we construct the callback for FunWhenWrapper::do_fail() here
  // rather than in FunWhenWrapper::post_fail(), so that
  // FunWhenWrapper::post_fail() cannot throw
  CbPtr post_fail_cb;
  if (fail) {
    CbPtr do_fail_cb;
    if (fail_releaser) {
      std::unique_ptr<SafeEmitter> fail_emitter(new SafeEmitter);
      fail_emitter->connect(fail.release(), *fail_releaser);
      do_fail_cb.reset(Callback::make_ref(&TaskManagerHelper2::FunWhenWrapper<Ret, Params...>::do_fail_rel,
					  std::move(fail_emitter)));
    }
    else {
      do_fail_cb = std::move(fail);
    }
    post_fail_cb.reset(Callback::make_ref(&TaskManagerHelper2::FunWhenWrapper<Ret, Params...>::post_fail,
					  TaskManagerHelper2::UniqueCbWrapperArg{std::move(do_fail_cb)},
					  priority,
					  context));
  }

  CbPtr do_task_cb;
  if (when_releaser) {
    std::unique_ptr<SafeEmitterArg<const Ret&>> when_emitter(new SafeEmitterArg<const Ret&>);
    when_emitter->connect(when.release(), *when_releaser);
    do_task_cb.reset(Callback::make_ref(&TaskManagerHelper2::FunWhenWrapper<Ret, Params...>::do_task_rel,
					TaskManagerHelper2::FunWhenWrapperArgsRel<Ret, Params...>{
					  std::move(when_emitter),
					  priority,
					  context,
					  func
					},
					std::forward<Args>(args)...));
  }
  else {
    do_task_cb.reset(Callback::make_ref(&TaskManagerHelper2::FunWhenWrapper<Ret, Params...>::do_task,
					TaskManagerHelper2::FunWhenWrapperArgs<Ret, Params...>{
					  std::move(when),
					  priority,
					  context,
					  func
					},
					std::forward<Args>(args)...));
  }
  add_task(std::move(do_task_cb), std::move(post_fail_cb));
}

// for callable objects

template <class Ret, class Func>
Cgu::SharedLockPtr<Cgu::AsyncResult<Ret>> TaskManager::make_task_result(Func&& f) {

  // there are two types related to the functor to be executed by the
  // task.  'Func' is the transient type provided by argument
  // deduction for forwarding, and will vary depending on whether the
  // functor object is a lvalue (which will deduce it as a reference
  // type) or rvalue (which will not).  'FType' is the type to be held
  // by the callback object generated in this function, and is never a
  // reference type.  It is also never const, because the FType member
  // is marked mutable in the callback object so that it can execute
  // mutable lambdas (or other functors with a non-const operator()()
  // method).
  typedef typename std::remove_const<typename std::remove_reference<Func>::type>::type FType;
  typedef std::unique_ptr<const Callback::Callback> CbPtr;

  SharedLockPtr<AsyncResult<Ret>> ret{new AsyncResult<Ret>};
  CbPtr exec_cb(new TaskManagerHelper2::FunctorResultExec<Ret, FType>(std::forward<Func>(f), ret));
  CbPtr do_fail_cb(Callback::make_ref(&TaskManagerHelper2::FunctorResultWrapper<Ret, FType>::do_fail,
				      ret));
  add_task(std::move(exec_cb), std::move(do_fail_cb));

  return ret;
}

template <class Ret, class Func>
void TaskManager::make_task_when_full(std::unique_ptr<const Cgu::Callback::CallbackArg<const Ret&>> when,
				      Cgu::Releaser* when_releaser,
				      std::unique_ptr<const Cgu::Callback::Callback> fail,
				      Cgu::Releaser* fail_releaser,
				      gint priority,
				      GMainContext* context,
				      Func&& f) {

  // there are two types related to the functor to be executed by the
  // task.  'Func' is the transient type provided by argument
  // deduction for forwarding, and will vary depending on whether the
  // functor object is a lvalue (which will deduce it as a reference
  // type) or rvalue (which will not).  'FType' is the type to be held
  // by the callback object generated in this function, and is never a
  // reference type.  It is also never const, because the FType member
  // is marked mutable in the callback object so that it can execute
  // mutable lambdas (or other functors with a non-const operator()()
  // method).
  typedef typename std::remove_const<typename std::remove_reference<Func>::type>::type FType;
  typedef std::unique_ptr<const Callback::Callback> CbPtr;

  // we construct the callback for FunctorWhenWrapper::do_fail() here
  // rather than in FunctorWhenWrapper::post_fail(), so that
  // FunctorWhenWrapper::post_fail() cannot throw
  CbPtr post_fail_cb;
  if (fail) {
    CbPtr do_fail_cb;
    if (fail_releaser) {
      std::unique_ptr<SafeEmitter> fail_emitter(new SafeEmitter);
      fail_emitter->connect(fail.release(), *fail_releaser);
      do_fail_cb.reset(Callback::make_ref(&TaskManagerHelper2::FunctorWhenWrapper<Ret, FType>::do_fail_rel,
					  std::move(fail_emitter)));
    }
    else {
      do_fail_cb = std::move(fail);
    }
    post_fail_cb.reset(Callback::make_ref(&TaskManagerHelper2::FunctorWhenWrapper<Ret, FType>::post_fail,
					  TaskManagerHelper2::UniqueCbWrapperArg{std::move(do_fail_cb)},
					  priority,
					  context));
  }

  CbPtr do_task_cb;
  if (when_releaser) {
    typedef std::unique_ptr<SafeEmitterArg<const Ret&>> When;
    When when_emitter(new SafeEmitterArg<const Ret&>);
    when_emitter->connect(when.release(), *when_releaser);
    do_task_cb.reset(new TaskManagerHelper2::FunctorWhenExec<FType, When>{&TaskManagerHelper2::FunctorWhenWrapper<Ret, FType>::do_task_rel,
	                                                                  std::forward<Func>(f),
	                                                                  std::move(when_emitter),
								          priority,
	                                                                  context});
  }
  else {
    typedef std::unique_ptr<const Cgu::Callback::CallbackArg<const Ret&>> When;
    do_task_cb.reset(new TaskManagerHelper2::FunctorWhenExec<FType, When>{&TaskManagerHelper2::FunctorWhenWrapper<Ret, FType>::do_task,
	                                                                  std::forward<Func>(f),
								          std::move(when),
								          priority,
	                                                                  context});
  }
  add_task(std::move(do_task_cb), std::move(post_fail_cb));
}

template <class When, class Func>
void TaskManager::make_task_packaged_when(When&& when,
					  Cgu::Releaser* when_releaser,
					  gint priority,
					  GMainContext* context,
					  Func&& func) {

  // this method will fail to compile if Ret is a reference type: that
  // is a feature, not a bug, as a function returning a reference
  // lacks referential transparency, is unlikely to be thread-safe and
  // is unsuitable for use as a task function
  typedef decltype(func()) Ret;

  std::packaged_task<Ret()> task{std::forward<Func>(func)};
  std::future<Ret> fut{task.get_future()};

  // we either pass when_ptr for direct execution, or we put it in an
  // EmitterArg object constructed on freestore and pass that for
  // execution
  typedef std::unique_ptr<const Callback::CallbackArg<std::future<Ret>&>> WhenPtr;
  WhenPtr when_ptr(Callback::lambda<std::future<Ret>&>(std::forward<When>(when)));

  std::unique_ptr<const Callback::Callback> do_task_cb;
  if (when_releaser) {
    typedef std::unique_ptr<SafeEmitterArg<std::future<Ret>&>> WhenEmitter;
    WhenEmitter when_emitter(new SafeEmitterArg<std::future<Ret>&>);
    when_emitter->connect(when_ptr.release(), *when_releaser);
    // we construct the callback for
    // WhenPackagedWrapper::do_when_rel() here rather than in
    // WhenPackagedWrapper::do_task(), so that
    // WhenPackagedWrapper::do_task() cannot throw
    std::unique_ptr<const Callback::Callback> do_when_cb
      (
       Callback::make_ref(&TaskManagerHelper2::WhenPackagedWrapper<Ret>::do_when_rel,
			  std::move(when_emitter),
			  TaskManagerHelper2::FutureWrapperArg<Ret>{std::move(fut)})
       );
    do_task_cb.reset(Callback::make_ref(&TaskManagerHelper2::WhenPackagedWrapper<Ret>::do_task,
					TaskManagerHelper2::WhenPackagedWrapperArgs<Ret>{
					  std::move(task),
					  std::move(do_when_cb),
					  priority,
					  context
					}));
  }
  else {
    // we construct the callback for WhenPackagedWrapper::do_when()
    // here rather than in WhenPackagedWrapper::do_task(), so that
    // WhenPackagedWrapper::do_task() cannot throw
    std::unique_ptr<const Callback::Callback> do_when_cb
      (
       Callback::make_ref(&TaskManagerHelper2::WhenPackagedWrapper<Ret>::do_when,
			  std::move(when_ptr),
			  TaskManagerHelper2::FutureWrapperArg<Ret>{std::move(fut)})
       );
    do_task_cb.reset(Callback::make_ref(&TaskManagerHelper2::WhenPackagedWrapper<Ret>::do_task,
					TaskManagerHelper2::WhenPackagedWrapperArgs<Ret>{
					  std::move(task),
					  std::move(do_when_cb),
					  priority,
					  context
					}));
  }
  add_task(std::move(do_task_cb), std::unique_ptr<const Callback::Callback>());
}

} // namespace Thread

} // namespace Cgu

#endif // CGU_TASK_MANAGER_TPP
