#pragma once

#include <balancer/kernel/helpers/errors.h>
#include <util/generic/noncopyable.h>
#include <util/generic/vector.h>
#include <util/generic/yexception.h>

namespace NSrvKernel::NHTTP2 {
    namespace NPrivate {
        struct THeapItemAccessor {
            template <class Item>
            static void SetHeap(Item& item, typename Item::THeap* heap) noexcept {
                return item.SetHeap(heap);
            }

            template <class Item>
            static size_t GetOffset(Item&& item) noexcept {
                return item.GetOffset();
            }

            template <class Item>
            static void SetOffset(Item& item, size_t offset) noexcept {
                return item.SetOffset(offset);
            }
        };
    }

    template <class T>
    class TIntrusiveFlatHeapItem {
    public:
        class THeap {
        public:
            virtual void DoDelete(TIntrusiveFlatHeapItem& item) = 0;
            virtual void DoUpdate(TIntrusiveFlatHeapItem& item) = 0;
        };

    public:
        ~TIntrusiveFlatHeapItem() {
            Unlink();
        }

        void Update() {
            if (Heap_) {
                Heap_->DoUpdate(*this);
            }
        }

        void Unlink() {
            if (Heap_) {
                Heap_->DoDelete(*this);
            }
        }

    public:
        T& Get() noexcept {
            return static_cast<T&>(*this);
        }

        const T& Get() const noexcept {
            return static_cast<const T&>(*this);
        }

    public:
        bool IsLinked() const noexcept {
            return Heap_ != nullptr;
        }

        THeap* GetHeap() noexcept {
            return Heap_;
        }

        const THeap* GetHeap() const noexcept {
            return Heap_;
        }

    private:
        void SetHeap(THeap* heap) noexcept {
            Heap_ = heap;
        }

        size_t GetOffset() const noexcept {
            return Offset_;
        }

        void SetOffset(size_t offset) noexcept {
            Offset_ = offset;
        }
        friend struct NPrivate::THeapItemAccessor;

    private:
        THeap* Heap_ = nullptr;
        size_t Offset_;
    };

    template <class T, class Comp = std::less<T>>
    class TIntrusiveFlatHeap : public TIntrusiveFlatHeapItem<T>::THeap {
    public:
        using TItem = TIntrusiveFlatHeapItem<T>;

    public:
        ~TIntrusiveFlatHeap() {
            for (auto ip : Items_) {
                NPrivate::THeapItemAccessor::SetHeap(*ip, nullptr);
            }
        }

        void Insert(TItem& item) noexcept {
            Y_VERIFY(!item.IsLinked());

            NPrivate::THeapItemAccessor::SetHeap(item, this);
            Items_.push_back(&item);
            SiftUp(Size() - 1);
        }

        void Update(TItem& item) noexcept {
            Y_VERIFY(this == item.GetHeap());

            SiftUp(NPrivate::THeapItemAccessor::GetOffset(item));
            SiftDown(NPrivate::THeapItemAccessor::GetOffset(item));
        }

        void Delete(TItem& item) noexcept {
            Y_VERIFY(this == item.GetHeap());

            NPrivate::THeapItemAccessor::SetHeap(item, nullptr);
            Items_[NPrivate::THeapItemAccessor::GetOffset(item)] = Items_.back();
            Items_.pop_back();
            if (NPrivate::THeapItemAccessor::GetOffset(item) < Size()) {
                SiftUp(NPrivate::THeapItemAccessor::GetOffset(item));
                SiftDown(NPrivate::THeapItemAccessor::GetOffset(item));
            }
        }

        TItem* ExtractMin() noexcept {
            if (Y_LIKELY(!Empty())) {
                NPrivate::THeapItemAccessor::SetHeap(*Items_[0], nullptr);
                auto tmp = Items_[0];
                Items_[0] = Items_.back();
                Items_.pop_back();
                if (!Empty()) {
                    SiftDown(0);
                }
                return tmp;
            }
            return nullptr;
        }

        TItem* GetMin() noexcept {
            return Empty() ? nullptr : Items_[0];
        }

        const TItem* GetMin() const noexcept {
            return Empty() ? nullptr : Items_[0];
        }

        size_t Size() const noexcept {
            return Items_.size();
        }

        bool Empty() const noexcept {
            return Items_.empty();
        }

    private:
        void DoDelete(TItem& item) noexcept override {
            Delete(item);
        }

        void DoUpdate(TItem& item) noexcept override {
            Update(item);
        }

    private:
        static constexpr auto Left(size_t offset) noexcept {
            return (offset << 1) + 1;
        }

        static constexpr auto Right(size_t offset) noexcept {
            return (offset << 1) + 2;
        }

        static constexpr auto Parent(size_t offset) noexcept {
            return (offset >> 1);
        }

    private:
        void SiftUp(size_t offset) noexcept {
            auto* p = Items_[offset];
            for (; offset != 0 && Comp{}(p->Get(), Items_[Parent(offset)]->Get());
                offset = Parent(offset))
            {
                Items_[offset] = Items_[Parent(offset)];
                NPrivate::THeapItemAccessor::SetOffset(*Items_[offset], offset);
            }
            NPrivate::THeapItemAccessor::SetOffset(*(Items_[offset] = p), offset);
        }

        void SiftDown(size_t offset) noexcept {
            auto* p = Items_[offset];
            while (Left(offset) < Size()) {
                auto* minItem = p;
                if (Comp{}(Items_[Left(offset)]->Get(), minItem->Get())) {
                    minItem = Items_[Left(offset)];
                }
                if (Right(offset) < Size() &&
                    Comp{}(Items_[Right(offset)]->Get(), minItem->Get()))
                {
                    minItem = Items_[Right(offset)];
                }
                if (minItem == p) {
                    break;
                }
                auto minOffset = NPrivate::THeapItemAccessor::GetOffset(*minItem);
                NPrivate::THeapItemAccessor::SetOffset(*minItem, offset);
                Items_[offset] = minItem;
                offset = minOffset;
            }
            NPrivate::THeapItemAccessor::SetOffset(*(Items_[offset] = p), offset);
        }

    private:
        TVector<TItem*> Items_;
    };
}
