#pragma once
#include <asio/awaitable.hpp>
#include <asio/co_spawn.hpp>
#include <asio/thread_pool.hpp>
#include <coroutine>
#include <drogon/drogon.h>
#include <exception>
#include <memory>
#include <optional>
#include <trantor/net/EventLoop.h>
#include <utility>
namespace Ecap_Coro {
inline asio::thread_pool& asio_drogon_pool() {
	static asio::thread_pool pool(1);
	return pool;
}
template <typename T> class Asio_Drogon_Awaiter {
public:
	explicit Asio_Drogon_Awaiter(asio::awaitable<T>&& task)
		: state_(std::make_shared<State>()), task_(std::move(task)) {}
	bool await_ready() const noexcept { return false; }
	void await_suspend(std::coroutine_handle<> continuation) {
		state_->continuation = continuation;
		state_->loop = trantor::EventLoop::getEventLoopOfCurrentThread();
		if (state_->loop == nullptr) {
			state_->loop = drogon::app().getLoop();
		}
		asio::co_spawn(asio_drogon_pool(), std::move(task_),
			[state = state_](std::exception_ptr exception, T value) mutable {
				state->exception = exception;
				if (!exception) {
					state->value.emplace(std::move(value));
				}
				resume(std::move(state));
			});
	}
	T await_resume() {
		if (state_->exception) {
			std::rethrow_exception(state_->exception);
		}
		return std::move(*state_->value);
	}
private:
	struct State {
		trantor::EventLoop* loop{};
		std::coroutine_handle<> continuation{};
		std::optional<T> value{};
		std::exception_ptr exception{};
	};
	static void resume(std::shared_ptr<State> state) {
		auto resume_fn = [state] { state->continuation.resume(); };
		if (state->loop != nullptr) {
			state->loop->queueInLoop(std::move(resume_fn));
		}
		else {
			resume_fn();
		}
	}
	std::shared_ptr<State> state_;
	asio::awaitable<T> task_;
};
template <> class Asio_Drogon_Awaiter<void> {
public:
	explicit Asio_Drogon_Awaiter(asio::awaitable<void>&& task)
		: state_(std::make_shared<State>()), task_(std::move(task)) {}
	bool await_ready() const noexcept { return false; }
	void await_suspend(std::coroutine_handle<> continuation) {
		state_->continuation = continuation;
		state_->loop = trantor::EventLoop::getEventLoopOfCurrentThread();
		if (state_->loop == nullptr) {
			state_->loop = drogon::app().getLoop();
		}
		asio::co_spawn(asio_drogon_pool(), std::move(task_),
			[state = state_](std::exception_ptr exception) mutable {
				state->exception = exception;
				resume(std::move(state));
			});
	}
	void await_resume() {
		if (state_->exception) {
			std::rethrow_exception(state_->exception);
		}
	}
private:
	struct State {
		trantor::EventLoop* loop{};
		std::coroutine_handle<> continuation{};
		std::exception_ptr exception{};
	};
	static void resume(std::shared_ptr<State> state) {
		auto resume_fn = [state] { state->continuation.resume(); };
		if (state->loop != nullptr) {
			state->loop->queueInLoop(std::move(resume_fn));
		}
		else {
			resume_fn();
		}
	}
	std::shared_ptr<State> state_;
	asio::awaitable<void> task_;
};
template <typename T> auto to_drogon(asio::awaitable<T>&& task) {
	return Asio_Drogon_Awaiter<T>{std::move(task)};
}
template <typename T>
drogon::Task<T> to_drogon_task(asio::awaitable<T>&& task) {
	co_return co_await to_drogon(std::move(task));
}
inline drogon::Task<> to_drogon_task(asio::awaitable<void>&& task) {
	co_await to_drogon(std::move(task));
	co_return;
}
}
