// 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_CONCEPTS_HPP
#define BOOST_COBALT_CONCEPTS_HPP

#include <coroutine>
#include <concepts>
#include <utility>

#include <boost/asio/error.hpp>
#include <boost/asio/is_executor.hpp>
#include <boost/asio/execution/executor.hpp>
#include <boost/system/system_error.hpp>
#include <boost/throw_exception.hpp>

namespace boost::cobalt
{

// tag::outline[]
template<typename Awaitable, typename Promise = void>
concept awaitable_type = requires (Awaitable aw, std::coroutine_handle<Promise> h)
{
    {aw.await_ready()} -> std::convertible_to<bool>;
    {aw.await_suspend(h)};
    {aw.await_resume()};
};

template<typename Awaitable, typename Promise = void>
concept awaitable =
        awaitable_type<Awaitable, Promise>
    || requires (Awaitable && aw) { {std::forward<Awaitable>(aw).operator co_await()} -> awaitable_type<Promise>;}
    || requires (Awaitable && aw) { {operator co_await(std::forward<Awaitable>(aw))} -> awaitable_type<Promise>;};
//end::outline[]

struct promise_throw_if_cancelled_base;
template<typename Promise = void>
struct enable_awaitables
{
    template<awaitable<Promise> Aw>
    Aw && await_transform(Aw && aw,
                          const boost::source_location & loc = BOOST_CURRENT_LOCATION)
    {
        if constexpr (std::derived_from<Promise, promise_throw_if_cancelled_base>)
        {
          auto p = static_cast<Promise*>(this);
          // a promise inheriting promise_throw_if_cancelled_base needs to also have a .cancelled() function
          if (!!p->cancelled() && p->throw_if_cancelled())
          {
            constexpr boost::source_location here{BOOST_CURRENT_LOCATION};
            boost::throw_exception(system::system_error(
                {asio::error::operation_aborted, &here},
                "throw_if_cancelled"), loc);
          }

        }
        return static_cast<Aw&&>(aw);
    }
};

template <typename T>
concept with_get_executor = requires (T& t)
{
  {t.get_executor()} -> asio::execution::executor;
};


}

#endif //BOOST_COBALT_CONCEPTS_HPP