#pragma once

#include "channel.h"

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

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

#include <util/system/spinlock.h>

namespace NSrvKernel {
    namespace NPrivate {
        struct TEventWakerState;

        using TEventWakerStatePtr = TIntrusivePtr<TEventWakerState>;
    };

    struct TContWaiter
        : public TContEvent
        , TIntrusiveListItem<TContWaiter>
    {
    public:
        TContWaiter(TCont* cont, NPrivate::TEventWakerStatePtr& waker)
            : TContEvent(cont)
            , Waker(waker)
        {
        }
    public:
        NPrivate::TEventWakerStatePtr Waker = nullptr;
    };

    struct TLockedContList {
        TIntrusiveList<TContWaiter> List;
        TAdaptiveLock Lock;
    };

    namespace NPrivate {
        struct TEventWakerState
            : public TAtomicRefCount<TEventWakerState>
        {
        public:
            void Wake(TContWaiter* cont) {
                with_lock (ToWake.Lock) {
                    ToWake.List.PushBack(cont);
                }

                if (AtomicCas(&Signalled, 1, 0)) {
                    Event.Signal();
                }
            }

            void Unlink(TContWaiter& cont) {
                with_lock (ToWake.Lock) {
                    cont.Unlink();
                }
            }
        public:
            TLockedContList ToWake;
            TCoroutineEventImpl Event;
            TAtomic Signalled = 0;
        };
    }

    class TEventWaker {
    public:
        const bool EdgeTriggered;
    public:
        TEventWaker(TContExecutor* executor)
            : EdgeTriggered(decltype(State_->Event)::MaySkipRead && executor->Poller()->PollEngine() == EContPoller::Epoll)
            , Executor_(executor)
            , PollEvent_(State_->Event.ReceiveFd(), CONT_POLL_READ | (EdgeTriggered ? CONT_POLL_EDGE_TRIGGERED : 0),
                [this](int /*status*/) {
                    AtomicSet(State_->Signalled, 0);

                    if (!EdgeTriggered) {
                        decltype(State_->Event)::TBaseType tmp;
                        const int readResult = read(State_->Event.ReceiveFd(), &tmp, sizeof tmp);
                        if (readResult < 0) {
                            const int err = LastSystemError();
                            if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) {
                                Y_VERIFY(false, "unexpected read error %d", err);
                            }
                        }
                    }

                    for (;;) {
                        TContWaiter* waiter = nullptr;
                        with_lock (State_->ToWake.Lock) {
                            if (!State_->ToWake.List.Empty()) {
                                waiter = State_->ToWake.List.PopFront();
                            }
                        }
                        if (waiter) {
                            waiter->Wake();
                        } else {
                            break;
                        }
                    }
                })
        {
            Executor_->Poller()->Schedule(&PollEvent_);
        }

        ~TEventWaker() {
            Executor_->Poller()->Remove(&PollEvent_);
        }

        TContExecutor* Executor() {
            return Executor_;
        }

        NPrivate::TEventWakerStatePtr& GetState() {
            return State_;
        }
    private:
        NPrivate::TEventWakerStatePtr State_ = new NPrivate::TEventWakerState();
        TContExecutor* Executor_ = nullptr;

        struct TPollEvent
            : public NCoro::IPollEvent
        {
        public:
            TPollEvent(SOCKET fd, ui16 what, std::function<void(int)> callback)
                : NCoro::IPollEvent(fd, what)
                , Callback_(callback)
            {
            }
            void OnPollEvent(int status) noexcept final {
                Callback_(status);
            }
        private:
            std::function<void(int)> Callback_;
        } PollEvent_;
    };

    class TThreadLocalEventWaker
        : public TEventWaker
        , public TSimpleRefCount<TThreadLocalEventWaker>
    {
    public:
        TThreadLocalEventWaker(TContExecutor* executor);
        ~TThreadLocalEventWaker();
    };

    using TThreadLocalEventWakerRef = TIntrusivePtr<TThreadLocalEventWaker>;

    TThreadLocalEventWakerRef ThreadLocalEventWaker(TCont* cont);
    TThreadLocalEventWakerRef ThreadLocalEventWaker();

    class TWakedEvent {
    public:
        void Signal() {
            with_lock (WaitList_.Lock) {
                AtomicSet(Signalled_, 1);
                if (!WaitList_.List.Empty()) {
                    TContWaiter* waiter = WaitList_.List.PopFront();
                    {
                        NPrivate::TEventWakerStatePtr ref = waiter->Waker;
                        ref->Wake(waiter);
                    }
                }
            }
        }

        void SignalFromExecutorThread() {
            with_lock (WaitList_.Lock) {
                AtomicSet(Signalled_, 1);
                if (!WaitList_.List.Empty()) {
                    TContWaiter* waiter = WaitList_.List.PopFront();
                    waiter->Wake();
                }
            }
        }

        [[nodiscard]] int WaitD(TInstant deadline, TCont* cont, TEventWaker* waker) {
            TContWaiter waiter(cont, waker->GetState());

            with_lock (WaitList_.Lock) {
                if (AtomicGet(Signalled_)) {
                    AtomicSet(Signalled_, 0);
                    return 0;
                }
                WaitList_.List.PushBack(&waiter);
            }

            const auto res = waiter.WaitD(deadline);

            if (EWAKEDUP == res) { // No need to lock anything here, because WaitD will return EWAKEDUP only if waiter already unlinked from WaitList and ToWake
                AtomicSet(Signalled_, 0);
                return 0;
            }

            with_lock (WaitList_.Lock) {
                waiter.Waker->Unlink(waiter);
            }

            return res;
        }
    private:
        TAtomic Signalled_ = 0;
        TLockedContList WaitList_;
    };

    template <typename T>
    using TT2WChannel = TChannel<T, NChannel::TThreadEvent, TWakedEvent>;

    template <typename T>
    using TW2TChannel = TChannel<T, TWakedEvent, NChannel::TThreadEvent>;

    template <typename T>
    using TW2WChannel = TChannel<T, TWakedEvent, TWakedEvent>;

    class TUnitedEvent {
    public:
        void Signal() {
            WakedEvent_.Signal();
            ThreadEvent_.Signal();
        }

        [[nodiscard]] EChannelStatus WaitD(TInstant deadline) {
            if (TCont* runningCont = RunningCont()) {
                auto waker = ThreadLocalEventWaker(runningCont);
                return WaitEventD(WakedEvent_, deadline, runningCont, waker.Get());
            } else {
                return WaitEventD(ThreadEvent_, deadline);
            }
        }
    private:
        TWakedEvent WakedEvent_;
        NChannel::TThreadEvent ThreadEvent_;
    };

    inline
    EChannelStatus WaitEventD(TUnitedEvent& event, TInstant deadline) {
        return event.WaitD(deadline);
    }

    template <typename T>
    using TT2UChannel = TChannel<T, NChannel::TThreadEvent, TUnitedEvent>;

    template <typename T>
    using TU2TChannel = TChannel<T, TUnitedEvent, NChannel::TThreadEvent>;

    template <typename T>
    using TW2UChannel = TChannel<T, TWakedEvent, TUnitedEvent>;

    template <typename T>
    using TU2WChannel = TChannel<T, TUnitedEvent, TWakedEvent>;

    template <typename T>
    using TU2UChannel = TChannel<T, TUnitedEvent, TUnitedEvent>;
}
