//
// experimental/impl/coro.hpp
// ~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2021-2023 Klemens D. Morgenstern
//                         (klemens dot morgenstern at gmx dot net)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

//
#ifndef ASIO_EXPERIMENTAL_IMPL_CORO_HPP
#define ASIO_EXPERIMENTAL_IMPL_CORO_HPP

#if defined(_MSC_VER) && (_MSC_VER >= 1200)
# pragma once
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)

#include "asio/detail/config.hpp"
#include "asio/append.hpp"
#include "asio/associated_cancellation_slot.hpp"
#include "asio/bind_allocator.hpp"
#include "asio/deferred.hpp"
#include "asio/experimental/detail/coro_completion_handler.hpp"
#include "asio/detail/push_options.hpp"

namespace asio {
namespace experimental {

template <typename Yield, typename Return,
    typename Executor, typename Allocator>
struct coro;

namespace detail {

struct coro_cancellation_source
{
  cancellation_slot slot;
  cancellation_state state;
  bool throw_if_cancelled_ = true;

  void reset_cancellation_state()
  {
    state = cancellation_state(slot);
  }

  template <typename Filter>
  void reset_cancellation_state(ASIO_MOVE_ARG(Filter) filter)
  {
    state = cancellation_state(slot, ASIO_MOVE_CAST(Filter)(filter));
  }

  template <typename InFilter, typename OutFilter>
  void reset_cancellation_state(ASIO_MOVE_ARG(InFilter) in_filter,
      ASIO_MOVE_ARG(OutFilter) out_filter)
  {
    state = cancellation_state(slot,
        ASIO_MOVE_CAST(InFilter)(in_filter),
        ASIO_MOVE_CAST(OutFilter)(out_filter));
  }

  bool throw_if_cancelled() const
  {
    return throw_if_cancelled_;
  }

  void throw_if_cancelled(bool value)
  {
    throw_if_cancelled_ = value;
  }
};

template <typename Signature, typename Return,
    typename Executor, typename Allocator>
struct coro_promise;

template <typename T>
struct is_noexcept : std::false_type
{
};

template <typename Return, typename... Args>
struct is_noexcept<Return(Args...)> : std::false_type
{
};

template <typename Return, typename... Args>
struct is_noexcept<Return(Args...) noexcept> : std::true_type
{
};

template <typename T>
constexpr bool is_noexcept_v = is_noexcept<T>::value;

template <typename T>
struct coro_error;

template <>
struct coro_error<asio::error_code>
{
  static asio::error_code invalid()
  {
    return asio::error::fault;
  }

  static asio::error_code cancelled()
  {
    return asio::error::operation_aborted;
  }

  static asio::error_code interrupted()
  {
    return asio::error::interrupted;
  }

  static asio::error_code done()
  {
    return asio::error::broken_pipe;
  }
};

template <>
struct coro_error<std::exception_ptr>
{
  static std::exception_ptr invalid()
  {
    return std::make_exception_ptr(
        asio::system_error(
          coro_error<asio::error_code>::invalid()));
  }

  static std::exception_ptr cancelled()
  {
    return std::make_exception_ptr(
        asio::system_error(
          coro_error<asio::error_code>::cancelled()));
  }

  static std::exception_ptr interrupted()
  {
    return std::make_exception_ptr(
        asio::system_error(
          coro_error<asio::error_code>::interrupted()));
  }

  static std::exception_ptr done()
  {
    return std::make_exception_ptr(
        asio::system_error(
          coro_error<asio::error_code>::done()));
  }
};

template <typename T, typename Coroutine >
struct coro_with_arg
{
  using coro_t = Coroutine;
  T value;
  coro_t& coro;

  struct awaitable_t
  {
    T value;
    coro_t& coro;

    constexpr static bool await_ready() { return false; }

    template <typename Y, typename R, typename E, typename A>
    auto await_suspend(coroutine_handle<coro_promise<Y, R, E, A>> h)
      -> coroutine_handle<>
    {
      auto& hp = h.promise();

      if constexpr (!coro_promise<Y, R, E, A>::is_noexcept)
      {
        if ((hp.cancel->state.cancelled() != cancellation_type::none)
            && hp.cancel->throw_if_cancelled_)
        {
          asio::detail::throw_error(
              asio::error::operation_aborted, "coro-cancelled");
        }
      }

      if (hp.get_executor() == coro.get_executor())
      {
        coro.coro_->awaited_from = h;
        coro.coro_->reset_error();
        coro.coro_->input_ = std::move(value);
        coro.coro_->cancel = hp.cancel;
        return coro.coro_->get_handle();
      }
      else
      {
        coro.coro_->awaited_from =
          dispatch_coroutine(
              asio::prefer(hp.get_executor(),
                execution::outstanding_work.tracked),
                [h]() mutable { h.resume(); }).handle;

        coro.coro_->reset_error();
        coro.coro_->input_ = std::move(value);

        struct cancel_handler
        {
          using src = std::pair<cancellation_signal,
                detail::coro_cancellation_source>;

          std::shared_ptr<src> st = std::make_shared<src>();

          cancel_handler(E e, coro_t& coro) : e(e), coro_(coro.coro_)
          {
            st->second.state =
              cancellation_state(st->second.slot = st->first.slot());
          }

          E e;
          typename coro_t::promise_type* coro_;

          void operator()(cancellation_type ct)
          {
            asio::dispatch(e, [ct, st = st]() mutable
            {
              auto & [sig, state] = *st;
              sig.emit(ct);
            });
          }
        };

        if (hp.cancel->state.slot().is_connected())
        {
          hp.cancel->state.slot().template emplace<cancel_handler>(
              coro.get_executor(), coro);
        }

        auto hh = detail::coroutine_handle<
          typename coro_t::promise_type>::from_promise(*coro.coro_);

        return dispatch_coroutine(
            coro.coro_->get_executor(), [hh]() mutable { hh.resume(); }).handle;
      }
    }

    auto await_resume() -> typename coro_t::result_type
    {
      coro.coro_->cancel = nullptr;
      coro.coro_->rethrow_if();
      return std::move(coro.coro_->result_);
    }
  };

  template <typename CompletionToken>
  auto async_resume(CompletionToken&& token) &&
  {
    return coro.async_resume(std::move(value),
        std::forward<CompletionToken>(token));
  }

  auto operator co_await() &&
  {
    return awaitable_t{std::move(value), coro};
  }
};

template <bool IsNoexcept>
struct coro_promise_error;

template <>
struct coro_promise_error<false>
{
  std::exception_ptr error_;

  void reset_error()
  {
    error_ = std::exception_ptr{};
  }

  void unhandled_exception()
  {
    error_ = std::current_exception();
  }

  void rethrow_if()
  {
    if (error_)
      std::rethrow_exception(error_);
  }
};

#if defined(__GNUC__)
# pragma GCC diagnostic push
# if defined(__clang__)
#  pragma GCC diagnostic ignored "-Wexceptions"
# else
#  pragma GCC diagnostic ignored "-Wterminate"
# endif
#elif defined(_MSC_VER)
# pragma warning(push)
# pragma warning (disable:4297)
#endif

template <>
struct coro_promise_error<true>
{
  void reset_error()
  {
  }

  void unhandled_exception() noexcept
  {
    throw;
  }

  void rethrow_if()
  {
  }
};

#if defined(__GNUC__)
# pragma GCC diagnostic pop
#elif defined(_MSC_VER)
# pragma warning(pop)
#endif

template <typename T = void>
struct yield_input
{
  T& value;
  coroutine_handle<> awaited_from{noop_coroutine()};

  bool await_ready() const noexcept
  {
    return false;
  }

  template <typename U>
  coroutine_handle<> await_suspend(coroutine_handle<U>) noexcept
  {
    return std::exchange(awaited_from, noop_coroutine());
  }

  T await_resume() const noexcept
  {
    return std::move(value);
  }
};

template <>
struct yield_input<void>
{
  coroutine_handle<> awaited_from{noop_coroutine()};

  bool await_ready() const noexcept
  {
    return false;
  }

  auto await_suspend(coroutine_handle<>) noexcept
  {
    return std::exchange(awaited_from, noop_coroutine());
  }

  constexpr void await_resume() const noexcept
  {
  }
};

struct coro_awaited_from
{
  coroutine_handle<> awaited_from{noop_coroutine()};

  auto final_suspend() noexcept
  {
    struct suspendor
    {
      coroutine_handle<> awaited_from;

      constexpr static bool await_ready() noexcept
      {
        return false;
      }

      auto await_suspend(coroutine_handle<>) noexcept
      {
        return std::exchange(awaited_from, noop_coroutine());
      }

      constexpr static void await_resume() noexcept
      {
      }
    };

    return suspendor{std::exchange(awaited_from, noop_coroutine())};
  }

  ~coro_awaited_from()
  {
    awaited_from.resume();
  }//must be on the right executor
};

template <typename Yield, typename Input, typename Return>
struct coro_promise_exchange : coro_awaited_from
{
  using result_type = coro_result_t<Yield, Return>;

  result_type result_;
  Input input_;

  auto yield_value(Yield&& y)
  {
    result_ = std::move(y);
    return yield_input<Input>{std::move(input_),
        std::exchange(awaited_from, noop_coroutine())};
  }

  auto yield_value(const Yield& y)
  {
    result_ = y;
    return yield_input<Input>{std::move(input_),
        std::exchange(awaited_from, noop_coroutine())};
  }

  void return_value(const Return& r)
  {
    result_ = r;
  }

  void return_value(Return&& r)
  {
    result_ = std::move(r);
  }
};

template <typename YieldReturn>
struct coro_promise_exchange<YieldReturn, void, YieldReturn> : coro_awaited_from
{
  using result_type = coro_result_t<YieldReturn, YieldReturn>;

  result_type result_;

  auto yield_value(const YieldReturn& y)
  {
    result_ = y;
    return yield_input<void>{std::exchange(awaited_from, noop_coroutine())};
  }

  auto yield_value(YieldReturn&& y)
  {
    result_ = std::move(y);
    return yield_input<void>{std::exchange(awaited_from, noop_coroutine())};
  }

  void return_value(const YieldReturn& r)
  {
    result_ = r;
  }

  void return_value(YieldReturn&& r)
  {
    result_ = std::move(r);
  }
};

template <typename Yield, typename Return>
struct coro_promise_exchange<Yield, void, Return> : coro_awaited_from
{
  using result_type = coro_result_t<Yield, Return>;

  result_type result_;

  auto yield_value(const Yield& y)
  {
    result_.template emplace<0>(y);
    return yield_input<void>{std::exchange(awaited_from, noop_coroutine())};
  }

  auto yield_value(Yield&& y)
  {
    result_.template emplace<0>(std::move(y));
    return yield_input<void>{std::exchange(awaited_from, noop_coroutine())};
  }

  void return_value(const Return& r)
  {
    result_.template emplace<1>(r);
  }

  void return_value(Return&& r)
  {
    result_.template emplace<1>(std::move(r));
  }
};

template <typename Yield, typename Input>
struct coro_promise_exchange<Yield, Input, void> : coro_awaited_from
{
  using result_type = coro_result_t<Yield, void>;

  result_type result_;
  Input input_;

  auto yield_value(Yield&& y)
  {
    result_ = std::move(y);
    return yield_input<Input>{input_,
                              std::exchange(awaited_from, noop_coroutine())};
  }

  auto yield_value(const Yield& y)
  {
    result_ = y;
    return yield_input<Input>{input_,
                              std::exchange(awaited_from, noop_coroutine())};
  }

  void return_void()
  {
    result_.reset();
  }
};

template <typename Return>
struct coro_promise_exchange<void, void, Return> : coro_awaited_from
{
  using result_type = coro_result_t<void, Return>;

  result_type result_;

  void yield_value();

  void return_value(const Return& r)
  {
    result_ = r;
  }

  void return_value(Return&& r)
  {
    result_ = std::move(r);
  }
};

template <>
struct coro_promise_exchange<void, void, void> : coro_awaited_from
{
  void return_void() {}

  void yield_value();
};

template <typename Yield>
struct coro_promise_exchange<Yield, void, void> : coro_awaited_from
{
  using result_type = coro_result_t<Yield, void>;

  result_type result_;

  auto yield_value(const Yield& y)
  {
    result_ = y;
    return yield_input<void>{std::exchange(awaited_from, noop_coroutine())};
  }

  auto yield_value(Yield&& y)
  {
    result_ = std::move(y);
    return yield_input<void>{std::exchange(awaited_from, noop_coroutine())};
  }

  void return_void()
  {
    result_.reset();
  }
};

template <typename Yield, typename Return,
    typename Executor, typename Allocator>
struct coro_promise final :
  coro_promise_allocator<Allocator>,
  coro_promise_error<coro_traits<Yield, Return, Executor>::is_noexcept>,
  coro_promise_exchange<
      typename coro_traits<Yield, Return, Executor>::yield_type,
      typename coro_traits<Yield, Return, Executor>::input_type,
      typename coro_traits<Yield, Return, Executor>::return_type>
{
  using coro_type = coro<Yield, Return, Executor, Allocator>;

  auto handle()
  {
    return coroutine_handle<coro_promise>::from_promise(this);
  }

  using executor_type = Executor;

  executor_type executor_;

  std::optional<coro_cancellation_source> cancel_source;
  coro_cancellation_source * cancel;

  using cancellation_slot_type = asio::cancellation_slot;

  cancellation_slot_type get_cancellation_slot() const noexcept
  {
    return cancel ? cancel->slot : cancellation_slot_type{};
  }

  using allocator_type =
    typename std::allocator_traits<associated_allocator_t<Executor>>::
      template rebind_alloc<std::byte>;
  using traits = coro_traits<Yield, Return, Executor>;

  using input_type = typename traits::input_type;
  using yield_type = typename traits::yield_type;
  using return_type = typename traits::return_type;
  using error_type = typename traits::error_type;
  using result_type = typename traits::result_type;
  constexpr static bool is_noexcept = traits::is_noexcept;

  auto get_executor() const -> Executor
  {
    return executor_;
  }

  auto get_handle()
  {
    return coroutine_handle<coro_promise>::from_promise(*this);
  }

  template <typename... Args>
  coro_promise(Executor executor, Args&&... args) noexcept
    : coro_promise_allocator<Allocator>(
        executor, std::forward<Args>(args)...),
      executor_(std::move(executor))
  {
  }

  template <typename First, typename... Args>
  coro_promise(First&& f, Executor executor, Args&&... args) noexcept
    : coro_promise_allocator<Allocator>(
        f, executor, std::forward<Args>(args)...),
      executor_(std::move(executor))
  {
  }

  template <typename First, detail::execution_context Context, typename... Args>
  coro_promise(First&& f, Context&& ctx, Args&&... args) noexcept
    : coro_promise_allocator<Allocator>(
        f, ctx, std::forward<Args>(args)...),
      executor_(ctx.get_executor())
  {
  }

  template <detail::execution_context Context, typename... Args>
  coro_promise(Context&& ctx, Args&&... args) noexcept
    : coro_promise_allocator<Allocator>(
        ctx, std::forward<Args>(args)...),
      executor_(ctx.get_executor())
  {
  }

  auto get_return_object()
  {
    return coro<Yield, Return, Executor, Allocator>{this};
  }

  auto initial_suspend() noexcept
  {
    return suspend_always{};
  }

  using coro_promise_exchange<
      typename coro_traits<Yield, Return, Executor>::yield_type,
      typename coro_traits<Yield, Return, Executor>::input_type,
      typename coro_traits<Yield, Return, Executor>::return_type>::yield_value;

  auto await_transform(this_coro::executor_t) const
  {
    struct exec_helper
    {
      const executor_type& value;

      constexpr static bool await_ready() noexcept
      {
        return true;
      }

      constexpr static void await_suspend(coroutine_handle<>) noexcept
      {
      }

      executor_type await_resume() const noexcept
      {
        return value;
      }
    };

    return exec_helper{executor_};
  }

  auto await_transform(this_coro::cancellation_state_t) const
  {
    struct exec_helper
    {
      const asio::cancellation_state& value;

      constexpr static bool await_ready() noexcept
      {
        return true;
      }

      constexpr static void await_suspend(coroutine_handle<>) noexcept
      {
      }

      asio::cancellation_state await_resume() const noexcept
      {
        return value;
      }
    };
    assert(cancel);
    return exec_helper{cancel->state};
  }

  // This await transformation resets the associated cancellation state.
  auto await_transform(this_coro::reset_cancellation_state_0_t) noexcept
  {
    struct result
    {
      detail::coro_cancellation_source * src_;

      bool await_ready() const noexcept
      {
        return true;
      }

      void await_suspend(coroutine_handle<void>) noexcept
      {
      }

      auto await_resume() const
      {
        return src_->reset_cancellation_state();
      }
    };

    return result{cancel};
  }

  // This await transformation resets the associated cancellation state.
  template <typename Filter>
  auto await_transform(
      this_coro::reset_cancellation_state_1_t<Filter> reset) noexcept
  {
    struct result
    {
      detail::coro_cancellation_source* src_;
      Filter filter_;

      bool await_ready() const noexcept
      {
        return true;
      }

      void await_suspend(coroutine_handle<void>) noexcept
      {
      }

      auto await_resume()
      {
        return src_->reset_cancellation_state(
            ASIO_MOVE_CAST(Filter)(filter_));
      }
    };

    return result{cancel, ASIO_MOVE_CAST(Filter)(reset.filter)};
  }

  // This await transformation resets the associated cancellation state.
  template <typename InFilter, typename OutFilter>
  auto await_transform(
      this_coro::reset_cancellation_state_2_t<InFilter, OutFilter> reset)
  noexcept
  {
    struct result
    {
      detail::coro_cancellation_source* src_;
      InFilter in_filter_;
      OutFilter out_filter_;

      bool await_ready() const noexcept
      {
        return true;
      }

      void await_suspend(coroutine_handle<void>) noexcept
      {
      }

      auto await_resume()
      {
        return src_->reset_cancellation_state(
            ASIO_MOVE_CAST(InFilter)(in_filter_),
            ASIO_MOVE_CAST(OutFilter)(out_filter_));
      }
    };

    return result{cancel,
        ASIO_MOVE_CAST(InFilter)(reset.in_filter),
        ASIO_MOVE_CAST(OutFilter)(reset.out_filter)};
  }

  // This await transformation determines whether cancellation is propagated as
  // an exception.
  auto await_transform(this_coro::throw_if_cancelled_0_t) noexcept
    requires (!is_noexcept)
  {
    struct result
    {
      detail::coro_cancellation_source* src_;

      bool await_ready() const noexcept
      {
        return true;
      }

      void await_suspend(coroutine_handle<void>) noexcept
      {
      }

      auto await_resume()
      {
        return src_->throw_if_cancelled();
      }
    };

    return result{cancel};
  }

  // This await transformation sets whether cancellation is propagated as an
  // exception.
  auto await_transform(
      this_coro::throw_if_cancelled_1_t throw_if_cancelled) noexcept
    requires (!is_noexcept)
  {
    struct result
    {
      detail::coro_cancellation_source* src_;
      bool value_;

      bool await_ready() const noexcept
      {
        return true;
      }

      void await_suspend(coroutine_handle<void>) noexcept
      {
      }

      auto await_resume()
      {
        src_->throw_if_cancelled(value_);
      }
    };

    return result{cancel, throw_if_cancelled.value};
  }

  template <typename Yield_, typename Return_,
      typename Executor_, typename Allocator_>
  auto await_transform(coro<Yield_, Return_, Executor_, Allocator_>& kr)
    -> decltype(auto)
  {
    return kr;
  }

  template <typename Yield_, typename Return_,
      typename Executor_, typename Allocator_>
  auto await_transform(coro<Yield_, Return_, Executor_, Allocator_>&& kr)
  {
    return std::move(kr);
  }

  template <typename T_, typename Coroutine >
  auto await_transform(coro_with_arg<T_, Coroutine>&& kr) -> decltype(auto)
  {
    return std::move(kr);
  }

  template <typename T_>
    requires requires(T_ t) {{ t.async_wait(deferred) }; }
  auto await_transform(T_& t) -> decltype(auto)
  {
    return await_transform(t.async_wait(deferred));
  }

  template <typename Op>
  auto await_transform(Op&& op,
      typename constraint<is_async_operation<Op>::value>::type = 0)
  {
    if ((cancel->state.cancelled() != cancellation_type::none)
        && cancel->throw_if_cancelled_)
    {
      asio::detail::throw_error(
          asio::error::operation_aborted, "coro-cancelled");
    }
    using signature = typename completion_signature_of<Op>::type;
    using result_type = detail::coro_completion_handler_type_t<signature>;
    using handler_type =
      typename detail::coro_completion_handler_type<signature>::template
        completion_handler<coro_promise>;

    struct aw_t
    {
      Op op;
      std::optional<result_type> result;

      constexpr static bool await_ready()
      {
        return false;
      }

      void await_suspend(coroutine_handle<coro_promise> h)
      {
        std::move(op)(handler_type{h, result});
      }

      auto await_resume()
      {
        if constexpr (is_noexcept)
        {
          if constexpr (std::tuple_size_v<result_type> == 0u)
            return;
          else if constexpr (std::tuple_size_v<result_type> == 1u)
            return std::get<0>(std::move(result).value());
          else
            return std::move(result).value();
        }
        else
          return detail::coro_interpret_result(std::move(result).value());
      }
    };

    return aw_t{std::move(op), {}};
  }
};

} // namespace detail

template <typename Yield, typename Return,
    typename Executor, typename Allocator>
struct coro<Yield, Return, Executor, Allocator>::awaitable_t
{
  coro& coro_;

  constexpr static bool await_ready() { return false; }

  template <typename Y, typename R, typename E, typename A>
  auto await_suspend(
      detail::coroutine_handle<detail::coro_promise<Y, R, E, A>> h)
    -> detail::coroutine_handle<>
  {
    auto& hp = h.promise();

    if constexpr (!detail::coro_promise<Y, R, E, A>::is_noexcept)
    {
      if ((hp.cancel->state.cancelled() != cancellation_type::none)
          && hp.cancel->throw_if_cancelled_)
      {
        asio::detail::throw_error(
            asio::error::operation_aborted, "coro-cancelled");
      }
    }

    if (hp.get_executor() == coro_.get_executor())
    {
      coro_.coro_->awaited_from  = h;
      coro_.coro_->cancel = hp.cancel;
      coro_.coro_->reset_error();

      return coro_.coro_->get_handle();
    }
    else
    {
      coro_.coro_->awaited_from = detail::dispatch_coroutine(
          asio::prefer(hp.get_executor(),
            execution::outstanding_work.tracked),
          [h]() mutable
          {
            h.resume();
          }).handle;

      coro_.coro_->reset_error();

      struct cancel_handler
      {
        std::shared_ptr<std::pair<cancellation_signal,
          detail::coro_cancellation_source>> st = std::make_shared<
            std::pair<cancellation_signal, detail::coro_cancellation_source>>();

        cancel_handler(E e, coro& coro) : e(e), coro_(coro.coro_)
        {
          st->second.state = cancellation_state(
              st->second.slot = st->first.slot());
        }

        E e;
        typename coro::promise_type* coro_;

        void operator()(cancellation_type ct)
        {
          asio::dispatch(e,
              [ct, st = st]() mutable
              {
                auto & [sig, state] = *st;
                sig.emit(ct);
              });
        }
      };

      if (hp.cancel->state.slot().is_connected())
      {
        hp.cancel->state.slot().template emplace<cancel_handler>(
            coro_.get_executor(), coro_);
      }

      auto hh = detail::coroutine_handle<
        detail::coro_promise<Yield, Return, Executor, Allocator>>::from_promise(
            *coro_.coro_);

      return detail::dispatch_coroutine(
          coro_.coro_->get_executor(),
          [hh]() mutable { hh.resume(); }).handle;
    }
  }

  auto await_resume() -> result_type
  {
    coro_.coro_->cancel = nullptr;
    coro_.coro_->rethrow_if();
    if constexpr (!std::is_void_v<result_type>)
      return std::move(coro_.coro_->result_);
  }
};

template <typename Yield, typename Return,
    typename Executor, typename Allocator>
struct coro<Yield, Return, Executor, Allocator>::initiate_async_resume
{
  typedef Executor executor_type;
  typedef Allocator allocator_type;
  typedef asio::cancellation_slot cancellation_slot_type;

  explicit initiate_async_resume(coro* self)
    : coro_(self->coro_)
  {
  }

  executor_type get_executor() const noexcept
  {
    return coro_->get_executor();
  }

  allocator_type get_allocator() const noexcept
  {
    return coro_->get_allocator();
  }

  template <typename E, typename WaitHandler>
  auto handle(E exec, WaitHandler&& handler,
      std::true_type /* error is noexcept */,
      std::true_type /* result is void */)  //noexcept
  {
    return [this, the_coro = coro_,
        h = std::forward<WaitHandler>(handler),
        exec = std::move(exec)]() mutable
    {
      assert(the_coro);

      auto ch = detail::coroutine_handle<promise_type>::from_promise(*the_coro);
      assert(ch && !ch.done());

      the_coro->awaited_from = post_coroutine(std::move(exec), std::move(h));
      the_coro->reset_error();
      ch.resume();
    };
  }

  template <typename E, typename WaitHandler>
  requires (!std::is_void_v<result_type>)
  auto handle(E exec, WaitHandler&& handler,
      std::true_type /* error is noexcept */,
      std::false_type  /* result is void */)  //noexcept
  {
    return [the_coro = coro_,
        h = std::forward<WaitHandler>(handler),
        exec = std::move(exec)]() mutable
    {
      assert(the_coro);

      auto ch = detail::coroutine_handle<promise_type>::from_promise(*the_coro);
      assert(ch && !ch.done());

      the_coro->awaited_from = detail::post_coroutine(
          exec, std::move(h), the_coro->result_).handle;
      the_coro->reset_error();
      ch.resume();
    };
  }

  template <typename E, typename WaitHandler>
  auto handle(E exec, WaitHandler&& handler,
      std::false_type /* error is noexcept */,
      std::true_type /* result is void */)
  {
    return [the_coro = coro_,
        h = std::forward<WaitHandler>(handler),
        exec = std::move(exec)]() mutable
    {
      if (!the_coro)
        return asio::post(exec,
            asio::append(std::move(h),
              detail::coro_error<error_type>::invalid()));

      auto ch = detail::coroutine_handle<promise_type>::from_promise(*the_coro);
      if (!ch)
        return asio::post(exec,
            asio::append(std::move(h),
              detail::coro_error<error_type>::invalid()));
      else if (ch.done())
        return asio::post(exec,
            asio::append(std::move(h),
              detail::coro_error<error_type>::done()));
      else
      {
        the_coro->awaited_from = detail::post_coroutine(
            exec, std::move(h), the_coro->error_).handle;
        the_coro->reset_error();
        ch.resume();
      }
    };
  }

  template <typename E, typename WaitHandler>
  auto handle(E exec, WaitHandler&& handler,
      std::false_type /* error is noexcept */,
      std::false_type  /* result is void */)
  {
    return [the_coro = coro_,
        h = std::forward<WaitHandler>(handler),
        exec = std::move(exec)]() mutable
    {
      if (!the_coro)
        return asio::post(exec,
            asio::append(std::move(h),
              detail::coro_error<error_type>::invalid(), result_type{}));

      auto ch =
        detail::coroutine_handle<promise_type>::from_promise(*the_coro);
      if (!ch)
        return asio::post(exec,
            asio::append(std::move(h),
              detail::coro_error<error_type>::invalid(), result_type{}));
      else if (ch.done())
        return asio::post(exec,
            asio::append(std::move(h),
              detail::coro_error<error_type>::done(), result_type{}));
      else
      {
        the_coro->awaited_from = detail::post_coroutine(
            exec, std::move(h), the_coro->error_, the_coro->result_).handle;
        the_coro->reset_error();
        ch.resume();
      }
    };
  }

  template <typename WaitHandler>
  void operator()(WaitHandler&& handler)
  {
    const auto exec = asio::prefer(
        get_associated_executor(handler, get_executor()),
        execution::outstanding_work.tracked);

    coro_->cancel = &coro_->cancel_source.emplace();
    coro_->cancel->state = cancellation_state(
        coro_->cancel->slot = get_associated_cancellation_slot(handler));
    asio::dispatch(get_executor(),
        handle(exec, std::forward<WaitHandler>(handler),
          std::integral_constant<bool, is_noexcept>{},
          std::is_void<result_type>{}));
  }

  template <typename WaitHandler, typename Input>
  void operator()(WaitHandler&& handler, Input&& input)
  {
    const auto exec = asio::prefer(
        get_associated_executor(handler, get_executor()),
        execution::outstanding_work.tracked);

    coro_->cancel = &coro_->cancel_source.emplace();
    coro_->cancel->state = cancellation_state(
        coro_->cancel->slot = get_associated_cancellation_slot(handler));
    asio::dispatch(get_executor(),
        [h = handle(exec, std::forward<WaitHandler>(handler),
            std::integral_constant<bool, is_noexcept>{},
            std::is_void<result_type>{}),
            in = std::forward<Input>(input), the_coro = coro_]() mutable
        {
          the_coro->input_ = std::move(in);
          std::move(h)();
        });
  }

private:
  typename coro::promise_type* coro_;
};

} // namespace experimental
} // namespace asio

#include "asio/detail/pop_options.hpp"

#endif // ASIO_EXPERIMENTAL_IMPL_CORO_HPP
