#include "waked.h"

#include <balancer/kernel/testing/cont_executor.h>

#include <library/cpp/testing/unittest/registar.h>
#include <util/thread/pool.h>

using namespace NSrvKernel;

Y_UNIT_TEST_SUITE(WakedEvent) {
    Y_UNIT_TEST(ThreadLocalWaker) {
        NTesting::TTestContExecutor executor;

        bool checked = false;
        TCoroutine checkWaker("checkWaker", &executor, [&checked]() {
            auto tlsWaker = ThreadLocalEventWaker();
            UNIT_ASSERT(tlsWaker);
#ifdef _linux_
            UNIT_ASSERT(tlsWaker->EdgeTriggered);
#endif

            auto tlsWaker2 = ThreadLocalEventWaker();
            UNIT_ASSERT(tlsWaker2);
            UNIT_ASSERT(tlsWaker.Get() == tlsWaker2.Get());

            checked = true;
        });

        UNIT_ASSERT(!ThreadLocalEventWaker());
        executor.Execute();
        UNIT_ASSERT(checked);
        UNIT_ASSERT(!ThreadLocalEventWaker());
    }

    Y_UNIT_TEST(DestroyEventWaker) {
        TWakedEvent event;

        TAtomic finished = 0;

        TThreadPool threadPool;
        threadPool.Start(1, 0);
        UNIT_ASSERT(threadPool.AddFunc([&]() {
            while (!AtomicGet(finished)) {
                event.Signal();
            }
        }));

        NTesting::TTestContExecutor executor;

        TCoroutine wait("wait", &executor, [&]() {
            for (size_t i = 0; i < 10000; ++i) {
                TEventWaker waker(&executor);
                auto v = event.WaitD(Now(), executor.Running(), &waker);
                Y_UNUSED(v);
            }

            AtomicSet(finished, 1);
        });

        executor.Execute();
        threadPool.Stop();
    }

    Y_UNIT_TEST(WakedEventMultiThread) {
        static const int N = 5;

        TVector<TWakedEvent> events(N);
        TAtomic finished = 0;


        NTesting::TTestContExecutor executor;
        TEventWaker waker(&executor);

        TThreadPool threadPool;
#ifdef _linux_
        UNIT_ASSERT(waker.EdgeTriggered);
#endif

        threadPool.Start(N, 0);
        for (int i = 0; i < N; ++i) {
            UNIT_ASSERT(threadPool.AddFunc([&, i] {
                while (!AtomicGet(finished)) {
                    events[i].Signal();
                    Sleep(TDuration::MicroSeconds(100));
                }
            }));
        }

        TVector<TCoroutine> recvConts(Reserve(N));
        for (int i = 0; i < N; ++i) {
            recvConts.emplace_back("recv", &executor, [&, i]() {
                TCont* cont = executor.Running();
                size_t succ = 0;
                size_t err = 0;

                while (succ < 50000 || err < 5000) {
                    if (!events[i].WaitD(Now() + TDuration::MicroSeconds(50), cont, &waker)) {
                        ++succ;
                    } else {
                        ++err;
                    }
                }
            });
        }

        TCoroutine wait("wait", &executor, [&]() {
            for (int i = 0; i < N; ++i) {
                recvConts[i].Join();
            }
            AtomicSet(finished, 1);
            executor.Abort();
        });

        executor.Execute();
    }
}
