#pragma once

#include <library/cpp/coroutine/engine/impl.h>

#include <util/generic/scope.h>

namespace NSrvKernel {

enum class ECoroType {
    Service,
    Common
};

/**
 * TCoroutine class represents some concurrently working coroutine.
 *
 * It is analogue of std::thread class for coroutines.
 **/
class TCoroutine {
public:
    TCoroutine() noexcept = default;

    TCoroutine(TCoroutine&& rhs) noexcept = default;

    /**
     * Creates coroutine with specified callable.
     *
     * Callable must have next signature:
     *
     * void(Args&&...)
     *
     * Example:
     *
     * struct TWidget {
     *     void Foo();
     * };
     * ...
     * TWidget widget;
     * TCoroutine task(cont->Executor(), &TWidget::Foo, &widget);
     * task.Join();
     *
     * Notice that arguments will be decayed to values. If you want to send references,
     * use reference wrappers (std::ref, std::cref).
     **/
    template <class F, class... Args>
    TCoroutine(ECoroType coroutineType, const char* name, TContExecutor* e, F&& f, Args&&... args)
        : Hook_(MakeHolder<THook>())
    {
        Y_ASSERT(e != nullptr);
        TMaybe<ui32> stackSize = Nothing();
        if (coroutineType == ECoroType::Service) {
            stackSize = 16 << 10;
        }

        using TArgsTuple = std::tuple<std::decay_t<Args>...>;
        using TInvokeContext = std::tuple<THook*, std::decay_t<F>, TArgsTuple>;

        // Allocating invoke context with control block pointer, callable and arguments.
        auto p = MakeHolder<TInvokeContext>(Hook_.Get(), std::forward<F>(f),
                                            std::forward_as_tuple(std::forward<Args>(args)...));

        // Final configuration of control block.
        Hook_->Cont = e->Create(&Trampoline<TInvokeContext>, p.Release(), name, stackSize);
    }

    template <class F, class... Args>
    TCoroutine(const char* name, TContExecutor* e, F&& f, Args&&... args)
        : TCoroutine(ECoroType::Common, name, e , std::forward<F>(f), std::forward<Args>(args)...)
    {}

    ~TCoroutine() {
        Cancel();
        Join();
    }

    TCoroutine& operator=(TCoroutine&& rhs) noexcept {
        TCoroutine tmp = std::move(rhs);
        Swap(tmp);
        return *this;
    }

    void Swap(TCoroutine& rhs) noexcept {
        DoSwap(Hook_, rhs.Hook_);
    }

    /**
     * Blocks the current coroutine until *this coroutine finishes its execution.
     *
     * Does nothing if Running() == false before this call.
     * Running() == false after this call.
     **/
    void Join(TInstant deadline = TInstant::Max()) noexcept {
        if (Running()) {
            Hook_->Cont->Executor()->Running()->Join(Hook_->Cont, deadline);
        }
    }

    /**
     * Part of coroutine interface. Marks running coroutine as canceled.
     **/
    void Cancel() noexcept {
        if (Running()) {
            Hook_->Cont->Cancel();
        }
    }

    /**
     * Checks if the coroutine object identifies an active coroutine.
     *
     * Default constructed TCoroutine is not running.
     */
    bool Running() const noexcept {
        return Hook_ && Hook_->Cont != nullptr;
    }

private:
    // InvokeContext == std::tuple<NPrivate::TCoroContext*, F, std::tuple<Args...>>
    template <class InvokeContext>
    static void Trampoline(TCont*, void* ctx) {
        THolder<InvokeContext> p(static_cast<InvokeContext*>(ctx));

        Y_DEFER {
            std::get<0>(*p)->Cont = nullptr;
        };

        std::apply([&](auto&&... args) {
            using TCallable = decltype(std::get<1>(*p));
            std::invoke(std::forward<TCallable>(std::get<1>(*p)),
                        std::forward<decltype(args)>(args)...);
        }, std::move(std::get<2>(*p)));
    }

private:
    struct THook {
        TCont* Cont = nullptr;
    };
    THolder<THook> Hook_;
};

}  // namespace NSrvKernel
