#include "http2_prio_tree.h"
#include "http2_log.h"

#include <balancer/kernel/helpers/errors.h>

#include <util/generic/scope.h>

namespace NSrvKernel::NHTTP2 {

    TPrioTreeNode::TPrioTreeNode(TPrioTreeNode& root, ui32 streamId) noexcept
        : Parent_(&root)
        , StreamId_(streamId)
        , Depth_(root.Depth_ + 1)
        , RawWeight_(RFC_PRIO_RAW_WEIGHT_DEFAULT)
    {
        Y_VERIFY(!root.Parent_);

        root.Children_.PushBack(this);
    }

    TPrioTreeNode::~TPrioTreeNode() {
        if (Parent_) {
            Children_.ForEach([this](TPrioTreeNode* child) {
                (void) child->UpdatePrio(*Parent_, child->RawWeight_, false);
            });
        } else {
            // The root should die last
            Y_VERIFY(Children_.Empty());
        }
    }

    void TPrioTreeNode::PrintTo(IOutputStream& out) const {
        auto parentId = (Parent_ ? Parent_->StreamId_ : 0);
        Y_HTTP2_PRINT_OBJ(out, StreamId_, parentId, Children_.Size(), Depth_, RawWeight_, RelWeight_);
    }

    bool TPrioTreeNode::UpdatePrio(TPrioTreeNode& newParent, ui8 newRawWeight, bool exclusive) noexcept {
        if (&newParent == Parent_ && !exclusive && newRawWeight == RawWeight_) {
            return true;
        }

        if (&newParent == this || !Parent_) {
            return false;
        }

        if (CycleFormed(newParent) && !newParent.UpdatePrio(*Parent_, newParent.RawWeight_, false)) {
            return false;
        }

        if (exclusive) {
            newParent.Children_.ForEach([this](TPrioTreeNode* child) {
                child->Parent_ = this;
            });
            Children_.Append(newParent.Children_);
        }

        RawWeight_ = newRawWeight;
        Parent_ = &newParent;
        UpdateAllWeights();

        return true;
    }

    void TPrioTreeNode::UpdateWeight() noexcept {
        if (Parent_) {
            Depth_ = Parent_->Depth_ + 1;
            RelWeight_ = Parent_->RelWeight_ * AsRelWeight(RawWeight_);
        } else {
            Depth_ = 0;
            RelWeight_ = AsRelWeight(RawWeight_);
        }
    }

    void TPrioTreeNode::UpdateAllWeights() noexcept {
        TIntrusiveList<TPrioTreeNode> chQueue;
        chQueue.PushBack(this);

        while (chQueue) {
            TPrioTreeNode* child = chQueue.PopFront();
            chQueue.Append(child->Children_);
            child->UpdateWeight();
            if (child->Parent_) {
                child->Parent_->Children_.PushBack(child);
            }
        }
    }

    bool TPrioTreeNode::CycleFormed(TPrioTreeNode& parent) noexcept {
        using namespace NSrvKernel;
        if (&parent == this) {
            return true;
        }

        if (Children_.Empty() || !parent.Parent_) {
            return false;
        }

        TPrioTreeNode* const oldParent = Parent_;
        Parent_ = &parent;
        Y_SCOPE_EXIT(this, oldParent) {
            Parent_ = oldParent;
        };

        for (const TPrioTreeNode* curParent = Parent_; curParent; curParent = curParent->Parent_) {
            if (this == curParent) {
                return true;
            }
        }

        return false;
    }

    double TPrioTreeNode::AsRelWeight(ui32 rawWeight) noexcept {
        return (rawWeight + 1) / double(RFC_PRIO_WEIGHT_MAX);
    }
}

Y_HTTP2_GEN_PRINT(TPrioTreeNode);
