#pragma once

#include <balancer/kernel/helpers/errors.h>
#include <library/cpp/coroutine/engine/events.h>
#include <balancer/kernel/coro/coroutine.h>

#include <functional>

namespace NBalancerClient {
bool IsCancelledError(const NSrvKernel::TError& error);

class TPinger;
class TPingContext: public TIntrusiveListItem<TPingContext> {
    friend class TPinger;
  public:
    enum class EState {
        NotStarted,
        LimitReached,
        IdleTimer,
        PingTimer,
        PingFailed,
        Cancelled
    };

    struct TOptions {
        typedef std::function<NSrvKernel::TError (TInstant deadline)> TPingHandler;
        typedef std::function<void (NSrvKernel::TError)> TCancelHandler;
        typedef std::function<void (EState)> TStateChangeHandler;

        TPinger& Pinger;
        TContExecutor* ContExecutor = nullptr;
        TPingHandler PingHandler;
        TCancelHandler CancelHandler;
        TStateChangeHandler StateChangeHandler;
        TDuration PingTimeout = TDuration::Seconds(1);
        size_t MaxSuccessivePings = 2;
    };

    TPingContext(TOptions options);
    void Cancel();

    void DataReceived();
    void PingAck();
    void ResetSuccessivePings();
    EState GetState() const;
    size_t GetSuccessivePings() const;
  private:
    void Ping();
    void SetState(EState state);

    const TOptions Options_;
    EState State_ = EState::NotStarted;
    TInstant LastDataReceived_ = TInstant::Zero();
    size_t SuccessivePings_ = 0;
    TContSimpleEvent PingAckReceivedEvent_;
    NSrvKernel::TCoroutine Coroutine_;
};
}
