#pragma once

#include "http2_frame.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>

namespace NSrvKernel::NHTTP2 {

    const ui32 RFC_SETTING_SIZE = 6;

    const ui32 RFC_MAX_CONCURRENT_STREAMS_DEFAULT = -1;
    const ui32 IMPL_MAX_CONCURRENT_STREAMS_DEFAULT = 128;

    const ui32 RFC_INITIAL_WINDOW_SIZE_DEFAULT = (1u << 16) - 1;
    const ui32 IMPL_INITIAL_WINDOW_SIZE_DEFAULT = 1u << 18;

    const bool RFC_ENABLE_PUSH_DEFAULT = true;

    enum class ESettingIds : ui32 {
        HEADER_TABLE_SIZE = 0x1,
        ENABLE_PUSH = 0x2,
        MAX_CONCURRENT_STREAMS = 0x3,
        INITIAL_WINDOW_SIZE = 0x4,
        MAX_FRAME_SIZE = 0x5,
        MAX_HEADER_LIST_SIZE = 0x6,
    };

    class TAuxServerSettings;


    // TSettings =======================================================================================================

    class TSettings {
    public:
        void PrintTo(IOutputStream& out) const;

    public:
        ui32 HeaderTableSize = RFC_HEADER_TABLE_SIZE_DEFAULT;
        ui32 MaxConcurrentStreams = RFC_MAX_CONCURRENT_STREAMS_DEFAULT;
        ui32 InitialWindowSize = RFC_INITIAL_WINDOW_SIZE_DEFAULT;
        ui32 MaxFrameSize = RFC_MAX_FRAME_SIZE_DEFAULT;
        ui32 MaxHeaderListSize = RFC_MAX_HEADER_LIST_SIZE_DEFAULT;

    public:
        [[nodiscard]] TChunkPtr Write() const noexcept;
    };


    // TClientSettings =================================================================================================

    class TClientSettings : public TSettings {
    public:
        bool EnablePush = RFC_ENABLE_PUSH_DEFAULT;
        bool ReceivedGoaway = false;

        bool IsPushAllowed() const noexcept {
            return EnablePush && !ReceivedGoaway;
        }

    public:
        static TError Validate(
            const TFrameHeading& heading, const TSettings& serverSettings) noexcept;

        TError Parse(
            TLogger& logger, TStringBuf data,
            THPackEncoder& hpackEncoder, const TAuxServerSettings& auxServerSettings) noexcept;
    };


    // TServerSettings =================================================================================================

    class TServerSettings {
    public:
        explicit TServerSettings(const TSettings& settings) noexcept;

        void PrintTo(IOutputStream& out) const;

        const TSettings& GetCurrent() const noexcept;

        bool HasPending() const noexcept;

        [[nodiscard]] TSettings& ProvidePending() noexcept;

        void AckNextWaiting() noexcept;

        [[nodiscard]] TChunkPtr WritePending() noexcept;

    private:
        TSettings Current_;
        TDeque<TSettings> WaitingAck_;
        TMaybe<TSettings> Pending_;
    };


    // TAuxServerSettings ==============================================================================================

    enum class EStreamsPrioQueueType {
        FIFO,
        RoundRobin /* "RR" */,
        Nginx /* "nginx" */,
        // Possible other implementations:
        // SPDY - calculate effective weights by dependencies, disregard dependencies
        // HTTP2 - full implementation
        // Go as in https://github.com/golang/net/blob/500e7a4f953ddaf55d316b4d3adc516aa0379622/http2/writesched_priority.go#L17
        // YandexV1 - SPDY with special treatment for the top priority resources
    };


    class TAuxServerSettings {
    public:
        void PrintTo(IOutputStream& out) const;

    public:
        TDuration ClientRTTEstimateMax = TDuration::Seconds(60);

        // Do not send frames bigger than this even if client allows that.
        ui32 ServerFrameSizeMax = 2 * IMPL_FRAME_SIZE_MAX_MIN;
        // Do not send DATA frames bigger than this. Smaller frames allow better scheduling.
        ui32 ServerDataFrameSizeMax = 2 * IMPL_DATA_FRAME_SIZE_MAX_MIN;

        ui32 FramesNoYieldMax = 8u;

        ui32 ClientHeadersCountMax = HTTP_MAX_HEADERS_COUNT_DEFAULT;
        ui32 EncoderTableSizeMax = std::max<size_t>(RFC_HEADER_TABLE_SIZE_DEFAULT, HTTP_MAX_HEADERS_SIZE_DEFAULT);

        // BALANCER-1352
        ui32 StreamSendBufferLoMax = ServerDataFrameSizeMax;
        ui32 StreamSendBufferHiMax = 8 * StreamSendBufferLoMax;
        ui32 StreamSendBufferLoadStep = 16;

        // The TClientOutput buffer limit for data and headers.
        ui32 ConnDataSendBufferMax = 1u << 17;
        // The additional space in TClientOutput buffer reserved for control frames.
        ui32 ConnCtrlSendBufferMax = 1u << 16;

        // Reduces the queueing of fast-starting low-priority streams in front of slower high-priority ones
        ui32 SockSendBufferSizeMax = 1u << 17;

        // We need to track idle streams to be able to properly handle firefox-like priorities
        ui32 StreamsIdleMax = 8u;
        // We need to track closed streams to be able to properly handle chromium-like priorities
        ui32 StreamsClosedMax = 16u;

        EStreamsPrioQueueType StreamsPrioQueueType = EStreamsPrioQueueType::Nginx;

        bool RefusedStreamOnErrorEnabled = false;
        bool GoAwayDebugDataEnabled = false;
        bool EdgePrioFixEnabled = true;
        bool ReciprocalPingEnabled = true;
        bool FlushPingEnabled = true;
        bool AllowHttp2WithoutSsl = false;
        bool AllowSendingTrailers = false;
    };


    // THPackSettings ==================================================================================================

    [[nodiscard]] THPackEncoderSettings GetHPackEncoderSettings(
        const TSettings& settings, const TAuxServerSettings& auxSettings) noexcept;

    [[nodiscard]] THPackDecoderSettings GetHPackDecoderSettings(
        const TSettings& settings, const TAuxServerSettings& auxSettings) noexcept;
}
