#pragma once

#include "http2_base.h"
#include "http2_flow.h"
#include "http2_frame.h"
#include "http2_stats.h"

#include <balancer/kernel/coro/coro_cond_var.h>
#include <balancer/kernel/http2/server/common/http2_common.h>
#include <balancer/kernel/http2/server/hpack/hpack.h>
#include <balancer/kernel/http2/server/utils/http2_log.h>
#include <balancer/kernel/http2/server/utils/http2_queue.h>

namespace NSrvKernel::NHTTP2 {

    class TConnOutput final : public IFlowCallback {
        using TStreamQueuePtr = THolder<IQueue<IStreamOutput>>;

    public:
        enum class EState {
            Open,
            Closed
        };

        enum class EBuffer {
            Normal,
            Ctrl,
            OOBCtrl
        };

        TConnOutput(
            IConnection& conn,
            IIoOutput& clientOut,
            TContExecutor& executor,
            TStats& stats,
            TLogger& logger,
            const TClientSettings& clientSettings,
            const TAuxServerSettings& auxServerSettings
        ) noexcept;

        void PrintTo(IOutputStream& out) const;

        [[nodiscard]] bool IsOpen() const noexcept;

        void Close() noexcept;

        // Called from streams

        TError SendHeaders(ui32 streamId, THeadersList, bool eos) noexcept;

        TError SendDataAsync(IStreamOutput::TPrioHandle& streamHandle) noexcept;

        TError SendDataEndStream(ui32 streamId) noexcept;

        TError SendStreamWindowUpdate(ui32 streamId, ui32 windowUpdate) noexcept;

        // Called from streams and connection

        TError SendRstStream(ui32 stream, EErrorCode errorCode, TStreamErrorReason reason) noexcept;

        // Called from connection

        void SendSettings(TServerSettings& serverSettings) noexcept;

        TError SendSettingsAck() noexcept;

        void SendPing(TPing ping) noexcept;

        TError SendPingAck(TChunkPtr ping) noexcept;

        void SendGoAwayGraceful(TGoAway goAway, TConnErrorReason reason) noexcept;

        void SendGoAwayUrgent(TGoAway goAway, TConnErrorReason reason) noexcept;

        void SendShutdownWarning() noexcept;

        TError SendConnWindowUpdate(ui32 windowUpdate) noexcept;

        [[nodiscard]] TConnSendFlow& GetFlow() noexcept {
            return Flow_;
        }

        [[nodiscard]] THPackEncoder& GetEncoder() noexcept {
            return Encoder_;
        }

        void SaveStreamQueue() noexcept;

        void RestoreStreamQueue() noexcept;

        void Start() noexcept;

        [[nodiscard]] bool Running() const noexcept {
            return ConnTask_.Running();
        }

        void Cancel() noexcept;

        void Join() noexcept;

    private:
        void DoSendGoAway(TGoAway, TConnErrorReason reason, EBuffer) noexcept;

        TError DoWaitBuffer(EBuffer buffer) noexcept;

        void DoSerializeFrame(TFrameHeading heading, TChunkPtr payload, EBuffer buffer) noexcept;

        void DoSerializeFrame(TFrameHeading heading, TChunkList payload, EBuffer buffer) noexcept;

        [[nodiscard]] ui32 GetBufferFreeSpace() const noexcept;

        [[nodiscard]] ui32 GetNextCtrlFrameSize() const noexcept;

        [[nodiscard]] ui32 GetNextFrameSize() const noexcept;

        [[nodiscard]] ui32 GetNextDataFrameSize() const noexcept;

        void OnFlowBlockedForever() noexcept override;

        TError OnFlowUnblocked() noexcept override;

        static TStreamQueuePtr CreateStreamQueue(EStreamsPrioQueueType) noexcept;

    private:
        IConnection& Conn_;
        IIoOutput& ClientOut_;
        TContExecutor& Executor_;
        TStats& Stats_;
        TLogger& Logger_;
        const TClientSettings& ClientSettings_;
        const TAuxServerSettings& AuxServerSettings_;

        THPackEncoder Encoder_;

        TCoroCondVar BusyBufferCV_;
        TCoroSingleCondVar EmptyBufferCV_;
        TConnSendFlow Flow_;

        TChunkList FrameBuffer_;

        TStreamQueuePtr StreamQueue_ = MakeHolder<TRoundRobinQueue<IStreamOutput>>();
        // A place to backup the stream queue contents while the priorities are rebuilding
        TStaticFIFOQueue<IStreamOutput> StreamQueueBackup_;

        EState State_ = EState::Open;

        TCoroutine ConnTask_;
    };
}
