#pragma once

#include "universal_sleep.h"

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

#include <util/random/random.h>
#include <util/thread/lfqueue.h>
#include <util/system/event.h>
#include <util/system/pipe.h>
#include <util/generic/variant.h>

#ifdef _linux_
    #include <sys/eventfd.h>
#endif

namespace NSrvKernel {

    template <typename T>
    class TEventImplWrapper {
    public:
        void Signal() {
            if (AtomicCas(&Signalled_, 1, 0)) {
                Impl_.Signal();
            }
        }

        [[nodiscard]] int WaitD(TInstant deadline, TCont* cont) {
            typename T::TBaseType tmp;
            const int res = NCoro::ReadD(cont, Impl_.ReceiveFd(), &tmp, sizeof tmp, deadline).Status();
            AtomicSet(Signalled_, 0);
            return res;
        }
    private:
        T Impl_;
        TAtomic Signalled_ = 0;
    };

    class TSimplePipeEventImpl {
    public:
        TSimplePipeEventImpl();

        int ReceiveFd() const {
            return ReceivePipe_;
        }

        void Signal();

        using TBaseType = char;
        static constexpr bool MaySkipRead = false;
    private:
        TPipeHandle SendPipe_;
        TPipeHandle ReceivePipe_;
    };

    using TSimplePipeEvent = TEventImplWrapper<TSimplePipeEventImpl>;
#ifdef _linux_
    class TEventFdEventImpl {
    public:
        TEventFdEventImpl();
        ~TEventFdEventImpl();

        int ReceiveFd() const {
            return Fd_;
        }

        void Signal();

        using TBaseType = eventfd_t;
        static constexpr bool MaySkipRead = true;
    private:
        int Fd_ = 0;
    };
    using TEventFdEvent = TEventImplWrapper<TEventFdEventImpl>;
    using TCoroutineEventImpl = TEventFdEventImpl;
#else
    using TCoroutineEventImpl = TSimplePipeEventImpl;
#endif

    namespace NChannel {
        using TThreadEvent = TAutoEvent;
        using TCoroutineEvent = TEventImplWrapper<TCoroutineEventImpl>;

        struct TNoWait {};
    }

    enum class [[nodiscard]] EChannelStatus {
        Canceled,
        TimedOut,
        Success,
    };

    enum class [[nodiscard]] EChannelOverflowMode {
        Signal,
        Poll,
    };


    template <typename TEvent, typename... Args>
    EChannelStatus WaitEventD(TEvent& event, TInstant deadline, TCont* cont, Args... waitArgs) {
        int result = event.WaitD(deadline, cont, std::forward<Args>(waitArgs)...);
        switch (result) {
            case 0:
                return EChannelStatus::Success;
            case ECANCELED:
                return EChannelStatus::Canceled;
            case ETIMEDOUT:
                return EChannelStatus::TimedOut;
            default:
                ythrow TSystemError(result) << "can't read from eventfd";
        }
    }

    inline
    EChannelStatus WaitEventD(NChannel::TThreadEvent& event, TInstant deadline) {
        if (event.WaitD(deadline)) {
            return EChannelStatus::Success;
        }

        return EChannelStatus::TimedOut;
    }

    template <typename E>
    EChannelStatus WaitEventD(E&, TInstant deadline, NChannel::TNoWait) {
        Y_VERIFY(!deadline);
        return EChannelStatus::TimedOut;
    }

    template <typename T, typename TREvent, typename TSEvent>
    class TChannel {
    public:
        using TReceiveEvent = TREvent;
        using TSendEvent = TSEvent;

        explicit TChannel(size_t limit, EChannelOverflowMode overflowMode = EChannelOverflowMode::Poll)
            : InFlyLimit_(limit)
        {
            if (overflowMode == EChannelOverflowMode::Signal) {
                ReceiveEvent_ = &ReceiveEventHolder_.template emplace<TReceiveEvent>();
            }
        }

        template <typename U, typename... Args>
        EChannelStatus Send(U&& t, TInstant deadline, Args... waitArgs) {
            auto res = CanEnqueue(deadline, std::forward<Args>(waitArgs)...);
            if (res != EChannelStatus::Success) {
                return res;
            }

            Queue_.Enqueue(std::forward<U>(t));
            SendEvent_.Signal();
            return EChannelStatus::Success;
        }

        template <typename U>
        [[nodiscard]] bool TrySend(U&& t) {
            return Send(std::forward<U>(t), TInstant::Zero(), NChannel::TNoWait{}) == EChannelStatus::Success;
        }

        template <typename... Args>
        EChannelStatus Receive(T& t, TInstant deadline, Args... waitArgs) {
            while (!Queue_.Dequeue(&t)) {
                auto res = WaitEventD(SendEvent_, deadline, std::forward<Args>(waitArgs)...);
                if (res != EChannelStatus::Success) {
                    return res;
                }
            }

            AtomicDecrement(InFly_);

            if (ReceiveEvent_) {
                ReceiveEvent_->Signal();
            }

            return EChannelStatus::Success;
        }

        [[nodiscard]] bool TryReceive(T& t) {
            return Receive(t, TInstant::Zero(), NChannel::TNoWait{}) == EChannelStatus::Success;
        }

        size_t InFly() const {
            return AtomicGet(InFly_);
        }

    private:
        template <typename... Args>
        EChannelStatus CanEnqueue(TInstant deadline, Args... waitArgs) {
            TDuration pollWait = TDuration::MicroSeconds(100);
            for (;;) {
                const size_t inFly = InFly();
                const size_t next = inFly + 1;
                if (next > InFlyLimit_) {

                    if (ReceiveEvent_) {
                        auto res = WaitEventD(*ReceiveEvent_, deadline, std::forward<Args>(waitArgs)...);
                        if (res != EChannelStatus::Success) {
                            return res;
                        }
                    } else {
                        const TInstant now = Now();
                        if (now >= deadline) {
                            return EChannelStatus::TimedOut;
                        }

                        const TInstant nextTry = Min(now + pollWait, deadline);
                        pollWait *= (1.5 + RandomNumber<double>());

                        auto res = UniversalSleepUntil(nextTry);
                        if (res == ECANCELED) {
                            return EChannelStatus::Canceled;
                        }
                    }
                } else if (AtomicCas(&InFly_, next, inFly)) {
                    return EChannelStatus::Success;
                }
            }
        }

    private:
        const size_t InFlyLimit_;
        TAtomic InFly_ = 0;

        TLockFreeQueue<T> Queue_;

        TSendEvent SendEvent_;
        std::variant<std::monostate, TReceiveEvent> ReceiveEventHolder_;
        TReceiveEvent* ReceiveEvent_ = nullptr;
    };

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

    template <typename T>
    using TT2CChannel = TChannel<T, NChannel::TThreadEvent, NChannel::TCoroutineEvent>;

    template <typename T>
    using TC2TChannel = TChannel<T, NChannel::TCoroutineEvent, NChannel::TThreadEvent>;

    template <typename T>
    using TC2CChannel = TChannel<T, NChannel::TCoroutineEvent, NChannel::TCoroutineEvent>;
}
