#pragma once

#include "chunk.h"

#include <util/generic/buffer.h>
#include <util/generic/intrlist.h>
#include <util/generic/strbuf.h>

#include <library/cpp/digest/murmur/murmur.h>

#include <numeric>

namespace NSrvKernel {
    class TChunkList : private TIntrusiveListWithAutoDelete<TChunk, TDelete> {
        using TBase = TIntrusiveListWithAutoDelete<TChunk, TDelete>;

    public:
        using TIntrusiveListWithAutoDelete::ForEach;
        TChunkList() noexcept = default;

        // TODO(tender-bum): get rid of this
        template <class T, class = std::enable_if_t<!std::is_same_v<std::decay_t<T>, TChunkList>>>
        explicit TChunkList(T&& t) noexcept {
            Push(std::forward<T>(t));
        }

        TChunkList(TChunkList&& lst) noexcept = default;
        TChunkList& operator=(TChunkList&& rhs) noexcept = default;

        TChunkList(const TChunkList& lst): TChunkList(lst.Copy()) {};
        TChunkList& operator=(const TChunkList& lst) {
            return (*this) = TChunkList{lst};
        }

        TChunkList Copy() const noexcept {
            TChunkList ret;
            for (auto it = ChunksBegin(); it != ChunksEnd(); ++it) {
                ret.PushBack(it->Copy());
            }
            return ret;
        }

        void MakeOwned() noexcept {
            TChunkList owned;

            while (!Empty()) {
                auto chunk = PopFront();
                if (!chunk->OwnsData()) {
                    chunk = NewChunkForceCopy(chunk->AsStringBuf());
                }
                owned.PushBack(std::move(chunk));
            }

            owned.Swap(*this);
        }

        void Swap(TChunkList& rhs) noexcept {
            TBase::Swap(rhs);
        }

        // -------------------- Iterators --------------------
    public:
        class iterator {
        public:
            using iterator_category = std::bidirectional_iterator_tag;
            using difference_type = std::ptrdiff_t;
            using value_type = char;
            // We forbid changing underlaying data in TChunkList through iterators.
            using reference = const char&;
            using pointer = const char*;

            iterator() noexcept = default;
            iterator(const iterator&) noexcept = default;

        private:
            friend class TChunkList;
            explicit iterator(const TChunk* chunk, size_t index = 0)
                : Chunk_(chunk)
                , Index_(index)
            {}

        public:
            iterator& operator=(const iterator&) noexcept = default;

            reference operator*() const noexcept {
                return *(Chunk_->Data() + Index_);
            }

            iterator& operator++() noexcept {
                Y_ASSERT(Chunk_ != nullptr);
                if (++Index_ == Chunk_->Length()) {
                    Chunk_ = Chunk_->Next()->Node();
                    Index_ = 0;
                }
                return *this;
            }

            iterator operator++(int) noexcept {
                auto ret = *this;
                ++*this;
                return ret;
            }

            iterator& operator--() noexcept {
                Y_ASSERT(Chunk_ != nullptr);
                if (--Index_ == std::numeric_limits<size_t>::max()) {
                    Chunk_ = Chunk_->Prev()->Node();
                    Index_ = Chunk_->Length() - 1;
                }
                return *this;
            }

            iterator operator--(int) noexcept {
                auto ret = *this;
                --*this;
                return ret;
            }

            friend bool operator==(const iterator& lhs, const iterator& rhs) noexcept {
                return lhs.Chunk_ == rhs.Chunk_ && lhs.Index_ == rhs.Index_;
            }
            friend bool operator!=(const iterator& lhs, const iterator& rhs) noexcept {
                return !(lhs == rhs);
            }

        private:
            const TChunk* Chunk_ = nullptr;
            size_t Index_ = 0;
        };
        using const_iterator = iterator;
        using reverse_iterator = std::reverse_iterator<iterator>;
        using const_reverse_iterator = std::reverse_iterator<const_iterator>;

    public:
        iterator begin() const noexcept {
            return iterator{Front()};
        }
        iterator cbegin() const noexcept {
            return begin();
        }
        iterator end() const noexcept {
            return iterator{ChunksEnd()->Node()};
        }
        iterator cend() const noexcept {
            return end();
        }
        reverse_iterator rbegin() const noexcept {
            return reverse_iterator{end()};
        }
        reverse_iterator crbegin() const noexcept {
            return rbegin();
        }
        reverse_iterator rend() const noexcept {
            return reverse_iterator{begin()};
        }
        reverse_iterator crend() const noexcept {
            return rend();
        }

        TBase::const_iterator ChunksBegin() const noexcept {
            return TBase::cbegin();
        }

        TBase::const_iterator ChunksEnd() const noexcept {
            return TBase::cend();
        }

        TBase::const_reverse_iterator ChunksRBegin() const noexcept {
            return TBase::crbegin();
        }

        TBase::const_reverse_iterator ChunksREnd() const noexcept {
            return TBase::crend();
        }

        // -------------------- Observers --------------------
        using TBase::Empty;

        size_t size() const noexcept {
            return std::accumulate(ChunksBegin(), ChunksEnd(), (size_t)0,
                                   [](auto result, const auto& chunk) {
                                       return result + chunk.Length();
                                   });
        }

        size_t ChunksCount() const noexcept {
            return TBase::Size();
        }

        const TChunk* Front() const noexcept {
            return TBase::Front();
        }

        const TChunk* Back() const noexcept {
            return TBase::Back();
        }

        // -------------------- Modifiers --------------------
        using TBase::Clear;

        void PushFront(TChunkPtr chunk) {
            Y_ASSERT(chunk);
            if (chunk->Length() > 0) {
                TBase::PushFront(chunk.Release());
            }
        }

        void PushBack(TChunkPtr chunk) {
            Y_ASSERT(chunk);
            if (chunk->Length() > 0) {
                TBase::PushBack(chunk.Release());
            }
        }

        // We need to delete this overloads since THolder is broken.
        void PushFront(TChunk*) = delete;
        void PushBack(TChunk*) = delete;

        TChunkPtr PopFront() noexcept {
            return TChunkPtr{TBase::PopFront()};
        }

        TChunkPtr PopBack() noexcept {
            return TChunkPtr{TBase::PopBack()};
        }

        TChunkList& PushNonOwning(TStringBuf str) & noexcept {
            Push(NewChunkNonOwning(str));
            return *this;
        }

        TChunkList PushNonOwning(TStringBuf str) && noexcept {
            Push(NewChunkNonOwning(str));
            return std::move(*this);
        }

        template <class T>
        TChunkList& Push(T&& t) & noexcept {
            Push(NewChunk(std::forward<T>(t)));
            return *this;
        }

        template <class T>
        TChunkList Push(T&& t) && noexcept {
            Push(NewChunk(std::forward<T>(t)));
            return std::move(*this);
        }

        TChunkList& Push(TChunkPtr chunk) & noexcept {
            PushBack(std::move(chunk));
            return *this;
        }

        TChunkList Push(TChunkPtr chunk) && noexcept {
            PushBack(std::move(chunk));
            return std::move(*this);
        }

        void Append(TChunkList lst) noexcept {
            // Will check for lst emptiness
            TBase::Append(lst);
        }

        template <class T>
        void Prepend(const T& t) noexcept {
            Prepend(NewChunk(t));
        }

        void Prepend(TChunkPtr chunk) noexcept {
            PushFront(std::move(chunk));
        }

        void Prepend(TChunkList lst) noexcept {
            lst.Append(std::move(*this));
            Append(std::move(lst));
        }

        size_t CopyDataTo(void* buf, size_t len) const noexcept {
            char* cur = reinterpret_cast<char*>(buf);

            for (auto it = ChunksBegin(); it != ChunksEnd(); ++it) {
                if (0 == len) {
                    break;
                }

                const size_t read = it->AsStringBuf().copy(cur, len);

                len -= read;
                cur += read;
            }

            return cur - reinterpret_cast<char*>(buf);
        }

        void Skip(size_t left) noexcept {
            while (left && !Empty()) {
                auto chunk = TBase::Front();
                if (chunk->Length() <= left) {
                    left -= chunk->Length();
                    delete chunk;
                } else {
                    chunk->Skip(left);
                    return;
                }
            }
        }

        TChunkList SubList(size_t size) {
            TChunkList left;
            for (auto chunk = ChunksBegin(); chunk != ChunksEnd() && size > 0; chunk++) {
                if (chunk->Length() <= size) {
                    size -= chunk->Length();
                    left.PushBack(chunk->Copy());
                } else {
                    auto subchunk = chunk->SubChunk(size);
                    left.PushBack(std::move(subchunk));
                    break;
                }
            }
            return left;
        }

        TChunkList Cut(size_t size) {
            TChunkList left;
            while (!Empty() && size > 0) {
                auto chunk = TBase::Front();
                if (chunk->Length() <= size) {
                    size -= chunk->Length();
                    left.PushBack(PopFront());
                } else {
                    auto subchunk = chunk->SubChunk(size);
                    chunk->Skip(size);
                    left.PushBack(std::move(subchunk));
                    break;
                }
            }
            return left;
        }

        friend bool operator==(const TChunkList& lhs, const TChunkList& rhs) noexcept {
            return std::equal(lhs.cbegin(), lhs.cend(), rhs.cbegin(), rhs.cend());
        }

        friend bool operator!=(const TChunkList& lhs, const TChunkList& rhs) noexcept {
            return !(lhs == rhs);
        }

        friend bool operator==(const TChunkList& lhs, TStringBuf rhs) noexcept {
            return std::equal(lhs.cbegin(), lhs.cend(), rhs.cbegin(), rhs.cend());
        }

        friend bool operator!=(const TChunkList& l, TStringBuf r) noexcept {
            return !(l == r);
        }

        friend bool operator==(TStringBuf r, const TChunkList& l) noexcept {
            return l == r;
        }

        friend bool operator!=(TStringBuf r, const TChunkList& l) noexcept {
            return !(l == r);
        }

        using TBase::Cut;
    };


    // TODO (velavokr): Wasteful. Most of the time what we actually need is a ref to the single chunk in the TChunkList.
    inline TChunkPtr Union(const TChunkList& lst) noexcept {
        if (lst.Empty()) {
            return EmptyChunk();
        } else if (lst.Front() == lst.Back()) {
            return lst.Front()->Copy();
        } else {
            TBuffer buf;
            buf.Resize(lst.size());
            lst.CopyDataTo(buf.Data(), buf.Size());
            return NewChunk(std::move(buf));
        }
    }

    inline const TChunk* UnionInplace(TChunkList& lst) noexcept {
        if (lst.Empty()) {
            return nullptr;
        }
        if (lst.Front() == lst.Back()) {
            return lst.Front();
        }
        auto chunk = Union(lst);
        lst.Clear();
        lst.PushBack(std::move(chunk));
        return lst.Front();
    }

    inline TChunkPtr Union(TChunkList&& lst) noexcept {
        if (!lst.Empty()) {
            UnionInplace(lst);
            return lst.PopFront();
        }
        return EmptyChunk();
    }

    inline TStringBuf GetStringBuf(const TChunk* chunk) noexcept {
        return chunk ? chunk->AsStringBuf() : TStringBuf();
    }

    inline TStringBuf StrInplace(TChunkList& lst) noexcept {
        return GetStringBuf(UnionInplace(lst));
    }

    template <class THashType>
    void UpdateChunkListHash(TMurmurHash2A<THashType>& hash, const TChunkList& lst) {
        for (auto it = lst.ChunksBegin(); it != lst.ChunksEnd(); ++it) {
            hash.Update(it->Data(), it->Length());
        }
    }

    template <class THashType, class... Args>
    THashType CalcChunkListHash(Args&&... args) noexcept {
        TMurmurHash2A<THashType> hash;
        (UpdateChunkListHash(hash, args), ...);
        return hash.Value();
    }
}
