#include "threadedqueue.h"

#include <balancer/kernel/coro/coro_event.h>
#include <balancer/kernel/coro/coroutine.h>

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

#include <util/generic/scope.h>
#include <util/generic/strbuf.h>
#include <util/generic/yexception.h>
#include <util/network/poller.h>
#include <util/network/socket.h>
#include <util/system/compiler.h>
#include <util/system/pipe.h>
#include <util/thread/factory.h>

#include <cstdlib>

namespace NSrvKernel {

class TThreadedQueue::TImpl
    : public TSimpleRefCount<TThreadedQueue::TImpl>
    , public IDisposable
{
private:
    class TExecutor
        : public IThreadFactory::IThreadAble
        , public IDisposable
    {
    public:
        TExecutor(const TPipe& i, const TPipe& o) noexcept
            : I_(i)
            , O_(o)
        {}

        ~TExecutor() noexcept override {
            Dispose();
        }

        void DoExecute() noexcept override try {
            TSocketPoller iPoller;
            iPoller.WaitRead(I_.GetHandle(), &I_);
            TSocketPoller oPoller;
            oPoller.WaitWrite(O_.GetHandle(), &O_);

            while (IsAlive()) {
                ICallback* callback = nullptr;

                size_t read = 0;
                while (IsAlive() && read < sizeof(callback)) {
                    if (iPoller.WaitT(TDuration::MilliSeconds(100))) {
                        size_t readNext = I_.Read(reinterpret_cast<char*>(&callback) + read, sizeof(callback) - read);
                        if (!readNext) {
                            break;
                        }
                        read += readNext;
                    }
                }

                if (read < sizeof(callback)) {
                    break;
                }

                SafeRun(callback);

                size_t written = 0;
                while (IsAlive() && written < sizeof(callback)) {
                    if (oPoller.WaitT(TDuration::MilliSeconds(100))) {
                        size_t writtenNext = O_.Write(reinterpret_cast<char*>(&callback) + written, sizeof(callback) - written);
                        if (!writtenNext) {
                            break;
                        }
                        written += writtenNext;
                    }
                }
            }

            I_.Close();
            O_.Close();
        } catch (...) {}

    private:
        TPipe I_;
        TPipe O_;
    };

public:
    TImpl(TContExecutor* executor, IThreadFactory* pool)
        : Executor_(executor)
        , ThreadPool_(pool)
    {
        TPipe li, lo, ri, ro;

        TPipe::Pipe(li, ro);
        TPipe::Pipe(ri, lo);

        SetNonBlock(li.GetHandle());
        SetNonBlock(lo.GetHandle());
        SetNonBlock(ri.GetHandle());
        SetNonBlock(ro.GetHandle());

        DispatchTask_ = TCoroutine{ECoroType::Service, "threaded_queue_dispatcher", Executor_, [this, exec = Executor_, li] {
            auto* const cont = exec->Running();
            while (true) {
                ICallback* callback = nullptr;
                size_t read = 0;

                do {
                    const TContIOStatus ret = NCoro::ReadI(cont, li.GetHandle(), reinterpret_cast<char*>(&callback) + read, sizeof(callback) - read);
                    if (ret.Status() || !ret.Processed()) {
                        break;
                    }
                    read += ret.Processed();
                } while (read < sizeof(callback));

                if (read < sizeof(callback)) {
                    break;
                }

                // TODO: bad, but safe for now, since TImpl outlives TDispatcher
                auto* event = AtomicGet(callback->Event_);
                if (event) {
                    event->notify();
                } else {
                    delete callback;
                }
                --InFly_;
            }
        }};

        ThreadExecutor_ = MakeHolder<TExecutor>(ri, ro);
        ExecutorThread_ = ThreadPool_->Run(ThreadExecutor_.Get());

        O_ = lo;
    }

    ~TImpl() noexcept override {
        Dispose();
        Join();
    }

    ICallback* Run(
        ICallback* const callback, TInstant enqueueDeadline, TInstant waitingDeadline) noexcept
    {
        if (!IsAlive()) {
            delete callback;
            return nullptr;
        }

        TCoroManualEvent event;

        AtomicSet(callback->Event_, &event);

        ++InFly_;
        const TContIOStatus ret = NCoro::WriteD(Executor_->Running(), O_.GetHandle(), &callback, sizeof(callback), enqueueDeadline);

        if (ret.Status() || ret.Processed() < sizeof(callback)) {
            --InFly_;
            delete callback;
            return nullptr;
        }

        int status = ETIMEDOUT;

        if (waitingDeadline != TInstant::Zero()) {
            status = event.wait_until(Executor_, waitingDeadline);
        }

        AtomicSet(callback->Event_, nullptr);
        if (status == EWAKEDUP) {
            return callback;
        }
        return nullptr;
    }

    void Join() noexcept {
        ExecutorThread_->Join();
        DispatchTask_.Join();
    }

private:
    void DoDispose() noexcept override {
        if (TCont* current = Executor_->Running()) {
            while (InFly_ > 0 && !current->Cancelled() && DispatchTask_.Running()) {
                current->SleepT(TDuration::MilliSeconds(10));
            }
        }

        ThreadExecutor_->Dispose();
    }

private:
    int InFly_ = 0;
    TPipe O_;
    TContExecutor* const Executor_ = nullptr;

    THolder<TExecutor> ThreadExecutor_;
    THolder<IThreadFactory::IThread> ExecutorThread_;
    IThreadFactory* const ThreadPool_ = nullptr;
    TCoroutine DispatchTask_;
};

TThreadedQueue::TThreadedQueue(TContExecutor* executor, IThreadFactory* pool) noexcept
    : Executor_(executor)
    , ThreadPool_(pool)
{}

TThreadedQueue::~TThreadedQueue() noexcept {
    Dispose();
    Join();
}

void TThreadedQueue::Construct() {
    Impl_.Reset(new TImpl(Executor_, ThreadPool_));
}

void TThreadedQueue::Join() noexcept {
    if (!!Impl_) {
        Impl_->Join();
    }
}

void TThreadedQueue::DoDispose() noexcept {
    if (!!Impl_) {
        Impl_->Dispose();
    }
}

THolder<TThreadedQueue::ICallback> TThreadedQueue::Run(
    TThreadedQueue::ICallback* callback, TInstant enqueueDeadline, TInstant waitingDeadline) noexcept
{
    if (Impl_ == nullptr) {
        try {
            Construct(); // Construct() may fail on reaching ulimit -n
        } catch (TSystemError& e) {
            if (e.Status() == EMFILE || e.Status() == ENFILE) {
                Cerr << "too many open files, consider raising ulimit -n: " << e.what() << Endl;
                abort();
            } else {
                throw;
            }
        } catch (...) {
            Cerr << "unknown error: " << CurrentExceptionMessage() << Endl;
            abort();
        }
    }
    if (Impl_ != nullptr) {
        return THolder<TThreadedQueue::ICallback>(Impl_->Run(callback, enqueueDeadline, waitingDeadline));
    }
    return {};
}

void TThreadedQueueList::Construct() {
    for (auto& queue: *this) {
        queue.Construct();
    }
}

TThreadedQueue* TThreadedQueueList::FindOrCreate(TContExecutor* executor, IThreadFactory* pool,
                                                 const TString& name) noexcept
{
    for (auto& queue: *this) {
        if (queue.N_ == name) {
            return &queue;
        }
    }

    PushBack(new TThreadedQueueListItem(executor, pool, name));

    return Back();
}

}  // namespace NSrvKernel
