#pragma once

#include <balancer/kernel/coro/channel.h>

#include <library/cpp/coroutine/engine/events.h>
#include <library/cpp/coroutine/engine/impl.h>
#include <library/cpp/threading/future/core/future.h>

#include <util/thread/singleton.h>
#include <util/system/thread.h>
#include <util/generic/size_literals.h>

#include <functional>
#include <semaphore>

namespace NHttp::NRequesters {
class TWorker {
  public:
    class TCoroutine {
        friend class TWorker;
      public:
        TCoroutine(const TCoroutine&) = delete;
        TCoroutine(TCoroutine&&) = delete;
        TCoroutine& operator=(const TCoroutine&) = delete;
        TCoroutine& operator=(TCoroutine&&) = delete;

        template<typename F>
        TCoroutine(TWorker& worker, F&& func, const char* name): Worker_{worker} {
            Worker_.Execute([&]() {
                Cont_ = Worker_.Executor().CreateOwned([this, func = std::forward<F>(func)](TCont*) mutable {
                    func();
                    Cont_ = nullptr;
                }, name);
            });
        }

        ~TCoroutine();

        bool IsRunning() const;

        void Cancel();

      private:
        TWorker& Worker_;
        std::atomic<TCont*> Cont_;
    };

    TWorker(const TString& name = "NHttp::TWorker");

    virtual ~TWorker();

    template<typename F>
    typename std::enable_if<std::is_same<typename std::invoke_result<F>::type, void>::value, void>::type Execute(F&& func) {
        if (Current() == this) {
            return func();
        }

        std::binary_semaphore semaphore{0};
        Y_VERIFY(Channel_.Send([&]() {
            func();
            semaphore.release();
        }, TInstant::Zero()) == NSrvKernel::EChannelStatus::Success);

        semaphore.acquire();
    }

    template<typename F>
    typename std::enable_if<!std::is_same<typename std::invoke_result<F>::type, void>::value, typename std::invoke_result<F>::type>::type Execute(F&& func) {
        TMaybe<typename std::invoke_result<F>::type> value;

        Execute([&]() {
            value = func();
        });

        return std::move(value.GetRef());
    }

    template<typename F>
    std::unique_ptr<TCoroutine> Create(F&& func, const char* name) {
        return std::make_unique<TCoroutine>(*this, std::forward<F>(func), name);
    }

    TContExecutor& Executor();

    static TWorker* Current();

  private:
    static void* Run(void* data);
    void Loop(TCont* cont);

    TContExecutor Executor_;
    NSrvKernel::TT2CChannel<std::function<void ()>> Channel_;
    TThread Thread_;
};
}
