#include "http2_streams_manager.h"

#include <util/generic/strbuf.h>

#include <utility>

namespace NSrvKernel::NHTTP2 {

    TStreamsManager::TStreamsManager(IConnection& conn, const TAuxServerSettings& settings) noexcept
        : Conn_(conn)
        , Logger_(conn.GetLogger())
        , AuxServerSettings_(settings)
    {}

    TStreamsManager::~TStreamsManager() {
        Y_VERIFY(Streams_.size() == (Idle_.Size() + Closed_.Size()));
    }

    void TStreamsManager::PrintTo(IOutputStream& out) const {
        Y_HTTP2_PRINT_OBJ(out, Streams_.size(), Idle_.Size(), Closed_.Size());
    }

    size_t TStreamsManager::GetOpenCount() const noexcept {
        return Streams_.size() - (Closed_.Size() + Idle_.Size());
    }

    TStream::TPtr TStreamsManager::Find(ui32 streamId) noexcept {
        Y_HTTP2_METH(Logger_, streamId);

        if (!streamId) {
            return nullptr;
        }

        if (TStream::TPtr* streamPtrPtr = Streams_.FindPtr(streamId)) {
            TouchStream(*streamPtrPtr);
            return *streamPtrPtr;
        } else {
            return nullptr;
        }
    }

    TStream::TPtr TStreamsManager::FindOrCreateIdle(ui32 streamId) noexcept {
        Y_HTTP2_METH(Logger_, streamId);
        return DoFindOrCreate(streamId, Conn_.GetMaxClientStreamId());
    }

    TStream::TPtr TStreamsManager::Open(ui32 streamId) noexcept {
        Y_HTTP2_METH(Logger_, streamId);
        auto streamPtr = DoFindOrCreate(streamId, 0);
        Y_VERIFY(streamPtr);
        // This unlinks the stream from the Idle list so it will not be garbage-collected
        streamPtr->Unlink();
        return streamPtr;
    }

    void TStreamsManager::TouchStream(TStream::TPtr streamPtr) noexcept {
        Y_HTTP2_METH_E(Logger_);
        if (streamPtr) {
            // RFC 7540: Implementations
            //           SHOULD also attempt to retain state for streams that are in active
            //           use in the priority tree.
            Y_HTTP2_BLOCK(Logger_, "Touching stream", *streamPtr);
            if (streamPtr->IsIdle()) {
                Idle_.PushBack(streamPtr.Get());
            } else if (streamPtr->IsClosed()) {
                Closed_.PushBack(streamPtr.Get());
            }
        }
    }

    void TStreamsManager::Close(TStream& s) noexcept {
        Y_HTTP2_METH(Logger_, s.GetStreamId());
        ShrinkAndPush(Closed_, AuxServerSettings_.StreamsClosedMax, &s);
    }

    TStreamsManager::TSnapshot TStreamsManager::GetSnapshotOfRunning() noexcept {
        Y_HTTP2_METH_E(Logger_);
        TSnapshot snap;
        snap.reserve(Streams_.size());
        for (auto& stream : Streams_) {
            Y_VERIFY(stream.second);

            if (stream.second->Running()) {
                snap.emplace_back(stream.second);
            }
        }
        return snap;
    }

    void TStreamsManager::ShrinkAndPush(TIntrusiveList<TStream>& lru, const ui32 maxSize, TStream* newItem) noexcept {
        Y_HTTP2_METH(Logger_, lru.Size(), maxSize);
        for (auto sz = lru.Size(); sz > maxSize && lru; --sz) {
            TStream::TPtr streamPtr = lru.PopFront();
            Y_VERIFY(!streamPtr->Running());
            Y_VERIFY(Streams_.erase(streamPtr->GetStreamId()) == 1u);
        }

        lru.PushBack(newItem);
    }

    TStream::TPtr TStreamsManager::DoFindOrCreate(const ui32 streamId, const ui32 maxStreamId) noexcept {
        Y_HTTP2_METH(Logger_, streamId, maxStreamId);
        TStreamsMap::insert_ctx ctx;
        auto it = Streams_.find(streamId, ctx);
        if (it == Streams_.end()) {
            if (streamId <= maxStreamId) {
                return nullptr;
            }
            Y_HTTP2_BLOCK_E(Logger_, "Creating idle stream");
            it = Streams_.emplace_direct(ctx, streamId, MakeIntrusive<TStream>(Conn_, streamId));
            ShrinkAndPush(Idle_, AuxServerSettings_.StreamsIdleMax, it->second.Get());
        } else {
            TouchStream(it->second);
        }
        return it->second;
    }
}

Y_HTTP2_GEN_PRINT(TStreamsManager);
