#pragma once
#include <concurrencpp/concurrencpp.h>
#include <drogon/drogon.h>
#include <trantor/net/EventLoop.h>
#include <coroutine>
#include <exception>
#include <memory>
#include <optional>
#include <utility>

namespace Ecap_Coro {
template <typename T>
class Concurrencpp_Drogon_Awaiter {
public:
    explicit Concurrencpp_Drogon_Awaiter(concurrencpp::result<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();
        }
        state_->running_task.emplace(run(state_, std::move(task_)));
    }
    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{};
        std::optional<concurrencpp::result<void>> running_task{};
    };
    static concurrencpp::result<void> run(std::shared_ptr<State> state, concurrencpp::result<T> task)
    {
        try {
            state->value.emplace(co_await task);
        } catch (...) {
            state->exception = std::current_exception();
        }
        resume(std::move(state));
        co_return;
    }
    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_;
    concurrencpp::result<T> task_;
};
template <>
class Concurrencpp_Drogon_Awaiter<void> {
public:
    explicit Concurrencpp_Drogon_Awaiter(concurrencpp::result<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();
        }
        state_->running_task.emplace(run(state_, std::move(task_)));
    }
    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{};
        std::optional<concurrencpp::result<void>> running_task{};
    };
    static concurrencpp::result<void> run(std::shared_ptr<State> state, concurrencpp::result<void> task)
    {
        try {
            co_await task;
        } catch (...) {
            state->exception = std::current_exception();
        }
        resume(std::move(state));
        co_return;
    }
    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_;
    concurrencpp::result<void> task_;
};
template <typename T>
auto to_drogon(concurrencpp::result<T>&& task)
{
    return Concurrencpp_Drogon_Awaiter<T>{std::move(task)};
}
template <typename T>
drogon::Task<T> to_drogon_task(concurrencpp::result<T>&& task)
{
    co_return co_await to_drogon(std::move(task));
}
inline drogon::Task<> to_drogon_task(concurrencpp::result<void>&& task)
{
    co_await to_drogon(std::move(task));
    co_return;
}
}
