#pragma once

#include <balancer/kernel/http2/server/common/http2_common.h>

#include <util/system/defaults.h>

namespace NSrvKernel::NHTTP2 {
    class TSettings;

    // TODO (velavokr): unit tests, fuzzing

    enum class EFrameType : ui32 {
        DATA = 0x0,
        HEADERS = 0x1,
        PRIORITY = 0x2,
        RST_STREAM = 0x3,
        SETTINGS = 0x4,
        PUSH_PROMISE = 0x5,
        PING = 0x6,
        GOAWAY = 0x7,
        WINDOW_UPDATE = 0x8,
        CONTINUATION = 0x9,
        ALTSVC = 0xA, // RFC 7838
        Unassigned_0 = 0xB,
        ORIGIN = 0xC, // RFC 8336
        INVALID = (ui32)-1
    };

    const ui8 FLAG_END_STREAM = 0x1;
    const ui8 FLAG_END_HEADERS = 0x4;
    const ui8 FLAG_PADDED = 0x8;
    const ui8 FLAG_PRIORITY = 0x20;
    const ui8 FLAG_ACK = 0x1;


    // TFrameHeading ===================================================================================================
    // RFC 7540:
    //     +-----------------------------------------------+
    //     |                 Length (24)                   |
    //     +---------------+---------------+---------------+
    //     |   Type (8)    |   Flags (8)   |
    //     +-+-------------+---------------+-------------------------------+
    //     |R|                 Stream Identifier (31)                      |
    //     +=+=============================================================+

    class TFrameHeading {
        ui32 RawType = (ui32)EFrameType::INVALID;

    public:
        ui32 Length = 0;
        ui32 StreamId = 0;
        EFrameType Type = EFrameType::INVALID;
        ui8 Flags = 0;

    public:
        // TODO(velavokr): make this inlinable
        TFrameHeading() noexcept = default;

        explicit TFrameHeading(EFrameType type) noexcept;

        TFrameHeading& SetStreamId(ui32 streamId) noexcept;

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

        TFrameHeading& SetFlagPadded(bool padded) noexcept;

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

        TFrameHeading& SetFlagPriority(bool priority) noexcept;

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

        TFrameHeading& SetFlagAck(bool ack) noexcept;

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

        TFrameHeading& SetFlagEndHeaders(bool endHeaders) noexcept;

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

        TFrameHeading& SetFlagEndStream(bool endStream) noexcept;

    public:
        [[nodiscard]] bool IsData() const noexcept;

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

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

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

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

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

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

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

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

    public:
        [[nodiscard]] static TFrameHeading NewData(ui32 streamId) noexcept;

        [[nodiscard]] static TFrameHeading NewDataEndStream(ui32 streamId) noexcept;

        [[nodiscard]] static TFrameHeading NewHeaders(ui32 streamId) noexcept;

        [[nodiscard]] static TFrameHeading NewRstStream(ui32 streamId) noexcept;

        [[nodiscard]] static TFrameHeading NewSettings() noexcept;

        [[nodiscard]] static TFrameHeading NewSettingsAck() noexcept;

        [[nodiscard]] static TFrameHeading NewPing() noexcept;

        [[nodiscard]] static TFrameHeading NewPingAck() noexcept;

        [[nodiscard]] static TFrameHeading NewGoAway() noexcept;

        [[nodiscard]] static TFrameHeading NewConnectionWindowUpdate() noexcept;

        [[nodiscard]] static TFrameHeading NewStreamWindowUpdate(ui32 streamId) noexcept;

        [[nodiscard]] static TFrameHeading NewContinuation(ui32 streamId) noexcept;

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

        [[nodiscard]] static TFrameHeading Parse(TStringBuf data) noexcept;

        void PrintTo(IOutputStream&) const;

    private:
        [[nodiscard]] bool DoGetFlag(std::initializer_list<EFrameType> allowed, ui8 flag) const noexcept;

        TFrameHeading& DoSetFlag(std::initializer_list<EFrameType> allowed, ui8 flag, bool value) noexcept;
    };


    struct TFrame {
        TFrameHeading Heading;
        TChunkPtr Payload;
    };


    // DATA ============================================================================================================
    // RFC 7540:
    //     +---------------+
    //     |Pad Length? (8)|
    //     +---------------+-----------------------------------------------+
    //     |                            Data (*)                         ...
    //     +---------------------------------------------------------------+
    //     |                           Padding (*)                       ...
    //     +---------------------------------------------------------------+
    struct TData {
    public:
        void PrintTo(IOutputStream&) const;

    public:
        TChunkList Data;
        bool EndOfStream = false;
    };

    TError ValidateDataHeading(
        const TFrameHeading& heading, const TSettings& serverSettings) noexcept;

    TError StripPadding(TFrame& frame) noexcept;


    // HEADERS =========================================================================================================
    // RFC 7540:
    //     +---------------+
    //     |Pad Length? (8)|
    //     +-+-------------+-----------------------------------------------+
    //     |E|                 Stream Dependency? (31)                     |
    //     +-+-------------+-----------------------------------------------+
    //     |  Weight? (8)  |
    //     +-+-------------+-----------------------------------------------+
    //     |                   Header Block Fragment (*)                 ...
    //     +---------------------------------------------------------------+
    //     |                           Padding (*)                       ...
    //     +---------------------------------------------------------------+

    TError ValidateHeadersHeading(
        const TFrameHeading& heading, const TSettings& serverSettings) noexcept;


    // PRIORITY ========================================================================================================
    // RFC 7540:
    //     +-+-------------------------------------------------------------+
    //     |E|                  Stream Dependency (31)                     |
    //     +-+-------------+-----------------------------------------------+
    //     |   Weight (8)  |
    //     +-+-------------+
    //     All streams are initially assigned a non-exclusive dependency on
    //     stream 0x0.  Pushed streams (Section 8.2) initially depend on their
    //     associated stream.  In both cases, streams are assigned a default
    //     weight of 16.
    class TPriority {
    public:
        enum class EOrigin : ui32 {
            None,
            PriorityFrame,
            HeadersFrame,
            EdgePrioFix
        };

    public:
        EOrigin Origin = EOrigin::None;

        ui32 StreamDependency = 0;
        ui8 RawWeight = RFC_PRIO_RAW_WEIGHT_DEFAULT;
        bool Exclusive = false;

    public:
        TPriority() = default;

        void PrintTo(IOutputStream&) const;

    public:
        [[nodiscard]] static TPriority Parse(TStringBuf data) noexcept;

        static TError ValidateHeading(const TFrameHeading& heading) noexcept;

        static TErrorOr<TMaybe<TPriority>> Strip(TFrame& frame) noexcept;
    };


    // RST_STREAM ======================================================================================================
    // RFC 7540:
    //     +---------------------------------------------------------------+
    //     |                        Error Code (32)                        |
    //     +---------------------------------------------------------------+

    [[nodiscard]] TChunkPtr WriteRstStream(EErrorCode errorCode) noexcept;

    [[nodiscard]] EErrorCode ParseRstStream(TStringBuf data) noexcept;

    TError ValidateRstStreamHeading(const TFrameHeading& heading) noexcept;


    // PING ============================================================================================================

    // RFC 7540:
    //     +---------------------------------------------------------------+
    //     |                      Opaque Data (64)                         |
    //     +---------------------------------------------------------------+

    enum class EPingType : ui8 {
        Invalid = 0,
        PongRTT = 1,
        FlushRTT = 2,
        GuardGoAwayNoId = 3,
    };

    class TPing {
    public:
        TInstant Send;
        TInstant Recv;
        EPingType PingType = EPingType::Invalid;

    public:
        TPing() = default;

        explicit TPing(EPingType pingType, TInstant send) noexcept;

        [[nodiscard]] TDuration GetRTT(TDuration maxRTT = TDuration::Max()) const noexcept;

        void PrintTo(IOutputStream&) const;

        [[nodiscard]] TChunkPtr Write() const noexcept;

        [[nodiscard]] static TPing Parse(TStringBuf data) noexcept;

        static TError Validate(const TFrameHeading& heading) noexcept;
    };


    // GOAWAY ==========================================================================================================

    // RFC 7540:
    //     +-+-------------------------------------------------------------+
    //     |R|                  Last-Stream-ID (31)                        |
    //     +-+-------------------------------------------------------------+
    //     |                      Error Code (32)                          |
    //     +---------------------------------------------------------------+
    //     |                  Additional Debug Data (*)                    |
    //     +---------------------------------------------------------------+

    class TGoAway {
    public:
        TChunkList DebugData;
        ui32 LastStreamId = 0;
        EErrorCode ErrorCode = EErrorCode::NO_ERROR;

    public:
        TGoAway() noexcept = default;

        explicit TGoAway(ui32 lastStreamId, TGoAway other) noexcept;

        explicit TGoAway(ui32 lastStreamId, EErrorCode errorCode, TStringBuf debugData = "") noexcept;

        void PrintTo(IOutputStream&) const;

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

        [[nodiscard]] static TGoAway Parse(TStringBuf data) noexcept;

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


    // WINDOW_UPDATE ===================================================================================================
    // RFC 7540:
    //     +-+-------------------------------------------------------------+
    //     |R|              Window Size Increment (31)                     |
    //     +-+-------------------------------------------------------------+

    [[nodiscard]] TChunkPtr WriteWindowUpdate(ui32 windowSizeIncrement) noexcept;

    [[nodiscard]] ui32 ParseWindowUpdate(TStringBuf data) noexcept;

    TError ValidateWindowUpdateHeading(const TFrameHeading& heading) noexcept;


    // CONTINUATION ====================================================================================================
    // RFC 7540:
    // +---------------------------------------------------------------+
    // |                   Header Block Fragment (*)                 ...
    // +---------------------------------------------------------------+

    TError ValidateContinuationHeading(
        const TFrameHeading& heading, const TSettings& serverSettings) noexcept;
}
