// Copyright (c) 2022 Klemens D. Morgenstern
//
// 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 BOOST_COBALT_WRAPPER_HPP
#define BOOST_COBALT_WRAPPER_HPP

#include <boost/cobalt/this_coro.hpp>
#include <boost/cobalt/concepts.hpp>
#include <boost/cobalt/detail/util.hpp>

#include <boost/asio/bind_executor.hpp>
#include <boost/asio/executor.hpp>
#include <boost/asio/post.hpp>
#include <boost/config.hpp>

#include <coroutine>
#include <utility>
#if BOOST_COBALT_NO_SELF_DELETE
#include <boost/asio/consign.hpp>
#endif
namespace boost::cobalt::detail
{

template<typename Allocator>
struct partial_promise_base
{
    template<typename CompletionToken>
    void * operator new(const std::size_t size, CompletionToken & token)
    {
      // gcc: 168 40
      // clang: 144 40
      return allocate_coroutine(size, asio::get_associated_allocator(token));
    }

    template<typename Executor, typename CompletionToken>
    void * operator new(const std::size_t size, Executor &, CompletionToken & token)
    {
      // gcc: 120 8 16
      // clang: 96 8 16
      return allocate_coroutine(size, asio::get_associated_allocator(token));
    }
#if defined(__cpp_sized_deallocation)
    void operator delete(void * raw, const std::size_t size)
    {
      deallocate_coroutine<Allocator>(raw, size);
    }
#else
  void operator delete(void * raw)
  {
    deallocate_coroutine<Allocator>(raw);
  }

#endif

};

template<>
struct partial_promise_base<std::allocator<void>> {};


template<>           struct partial_promise_base<void> {};
template<typename T> struct partial_promise_base<std::allocator<T>> {};

// alloc options are two: allocator or aligned storage
template<typename Allocator = void>
struct partial_promise : partial_promise_base<Allocator>
{
    auto initial_suspend() noexcept
    {
        return std::suspend_always();
    }

    auto final_suspend() noexcept
    {
        return std::suspend_never();
    }

    void return_void() {}
};

template<typename Allocator = void>
struct post_coroutine_promise : partial_promise<Allocator>
{
    template<typename CompletionToken>
    auto yield_value(CompletionToken cpl)
    {
        struct awaitable_t
        {
            CompletionToken cpl;
            constexpr bool await_ready() noexcept { return false; }
            BOOST_NOINLINE
            auto await_suspend(std::coroutine_handle<void> h) noexcept
            {
                auto c = std::move(cpl);
                if (this_thread::has_executor())
                  detail::self_destroy(h, asio::get_associated_executor(c, this_thread::get_executor()));
                else
                  detail::self_destroy(h, asio::get_associated_executor(c));

                asio::post(std::move(c));
            }

            constexpr void await_resume() noexcept {}
        };
        return awaitable_t{std::move(cpl)};
    }

    std::coroutine_handle<post_coroutine_promise<Allocator>> get_return_object()
    {
        return std::coroutine_handle<post_coroutine_promise<Allocator>>::from_promise(*this);
    }

    void unhandled_exception()
    {
        detail::self_destroy(std::coroutine_handle<post_coroutine_promise<Allocator>>::from_promise(*this));
        throw;
    }
};


}

namespace std
{

template <typename T, typename ... Args>
struct coroutine_traits<coroutine_handle<boost::cobalt::detail::post_coroutine_promise<T>>, Args...>
{
    using promise_type = boost::cobalt::detail::post_coroutine_promise<T>;
};

} // namespace std


namespace boost::cobalt::detail
{


template <typename CompletionToken>
auto post_coroutine(CompletionToken token)
    -> std::coroutine_handle<post_coroutine_promise<asio::associated_allocator_t<CompletionToken>>>
{
    co_yield std::move(token);
}

template <asio::execution::executor Executor, typename CompletionToken>
auto post_coroutine(Executor exec, CompletionToken token)
    -> std::coroutine_handle<post_coroutine_promise<asio::associated_allocator_t<CompletionToken>>>
{
    co_yield asio::bind_executor(exec, std::move(token));
}

template <with_get_executor Context, typename CompletionToken>
auto post_coroutine(Context &ctx, CompletionToken token)
    -> std::coroutine_handle<post_coroutine_promise<asio::associated_allocator_t<CompletionToken>>>
{
    co_yield asio::bind_executor(ctx.get_executor(), std::move(token));
}

}

#endif //BOOST_COBALT_WRAPPER_HPP
