#pragma once

#include <util/datetime/base.h>
#include <util/generic/intrlist.h>
#include <util/generic/noncopyable.h>

class TTimerEvent;
class TContExecutor;

namespace NSrvKernel {

/**
 * Analogue of std::conditional_variable for coroutines.
 *
 * Unlike TContEvent from library/cpp/coroutine,
 * TCoroSingleCondVar doesn't bind to a coroutine object when it is created.
 * Single waiter version. If you want many waiters to wait for particular event,
 * use TCoroCondVar.
 **/
class TCoroSingleCondVar : public TNonCopyable {
public:
    /**
     * Suspends current coroutine until deadline or event occurrence.
     *
     * Behaviour is undefined if any other coroutine is waiting for this event.
     **/
    [[nodiscard]] int wait_until(TContExecutor* const exec, TInstant deadline) noexcept;

    /**
     * Suspends current coroutine for timeout or until event occurrence.
     *
     * Behaviour is undefined if any other coroutine is waiting for this event.
     **/
    [[nodiscard]] int wait_for(TContExecutor* const exec, TDuration timeout) noexcept {
        return wait_until(exec, timeout.ToDeadLine());
    }

    /**
     * Suspends current coroutine until event occurrence.
     *
     * Behaviour is undefined if any other coroutine is waiting for this event.
     **/
    [[nodiscard]] int wait(TContExecutor* const exec) noexcept {
        return wait_until(exec, TInstant::Max());
    }

    /**
     * Wakes up waiting coroutine. Does nothing if there is no waiting coroutine.
     **/
    void notify() noexcept;

private:
    TTimerEvent* Event_ = nullptr;
};

/**
 * Analogue of std::conditional_variable for coroutines.
 *
 * Unlike TContSimpleEvent from library/cpp/coroutine,
 * TCoroCondVar doesn't bind to a executor object when it is created.
 **/
class TCoroCondVar : public TNonCopyable {
private:
    struct TCondVarItem : TIntrusiveListItem<TCondVarItem> {
        TCoroSingleCondVar CV;
    };

public:
    /**
     * Suspends current coroutine until deadline or event occurrence.
     **/
    [[nodiscard]] int wait_until(TContExecutor* const exec, TInstant deadline) noexcept {
        TCondVarItem item;
        Queue_.PushBack(&item);
        int ret = item.CV.wait_until(exec, deadline);
        item.Unlink();
        return ret;
    }

    /**
     * Suspends current coroutine for timeout or until event occurrence.
     **/
    [[nodiscard]] int wait_for(TContExecutor* const exec, TDuration timeout) noexcept {
        return wait_until(exec, timeout.ToDeadLine());
    }

    /**
     * Suspends current coroutine until event occurrence.
     **/
    [[nodiscard]] int wait(TContExecutor* const exec) noexcept {
        return wait_until(exec, TInstant::Max());
    }

    /**
     * Wakes up first waiting coroutine. Does nothing if there are no waiting coroutines.
     **/
    void notify_one() noexcept {
        if (!Queue_.Empty()) {
            Queue_.Front()->CV.notify();
            Queue_.PopFront();
        }
    }

    /**
     * Wakes up all waiting coroutines. Does nothing if there are no waiting coroutines.
     **/
    void notify_all() noexcept {
        while (!Queue_.Empty()) {
            notify_one();
        }
    }

private:
    TIntrusiveList<TCondVarItem> Queue_;
};

}  // namespace NSrvKernel
