#pragma once

#include "alloc.h"

#include <util/memory/blob.h>
#include <util/system/yassert.h>
#include <util/memory/smallobj.h>
#include <util/generic/ptr.h>
#include <util/generic/strbuf.h>
#include <util/system/info.h>

namespace NSrvKernel {
    class TChunkData: public TBigObj<TChunkData>, public TBlob::TBase, public TAtomicRefCount<TChunkData> {
    public:
        char* Data() const noexcept {
            return reinterpret_cast<char*>(AdditionalData());
        }

        size_t Length() const noexcept {
            return AdditionalLength();
        }

        size_t CopyFrom(TStringBuf str) noexcept {
            const auto sz = std::min(str.size(), Length());
            str.copy(Data(), sz);
            return sz;
        }

    private:
        using TRefBase = TAtomicRefCount<TChunkData>;

        void Ref() noexcept override {
            TRefBase::Ref();
        }

        void UnRef() noexcept override {
            TRefBase::UnRef();
        }
    };
    static_assert(alignof(TChunkData) == alignof(size_t), "TBigObj's template parameter must be size_t-aligned type");

    class TChunk;
    using TChunkPtr = THolder<TChunk>;

    class TChunk : public TIntrusiveListItem<TChunk> {
        static size_t DefaultLength;
    public:
        static size_t GetDefaultLength() {
            return DefaultLength;
        }
        static void SetDefaultLengthBase(size_t base);
        static void ResetDefaultLengthBase();
    public:
        TChunk(const TBlob& blob) noexcept
            : Blob_(blob)
        {}

        // TODO (velavokr): BALANCER-2025 get rid of the TChunk data mutability
        char* Data() noexcept {
            return (char*)Blob_.AsCharPtr();
        }

        const char* Data() const noexcept {
            return Blob_.AsCharPtr();
        }

        size_t Length() const noexcept {
            return Blob_.Length();
        }

        bool OwnsData() const noexcept {
            return Blob_.OwnsData();
        }

        void Assign(const TChunk& right) noexcept {
            Blob_ = right.Blob_;
        }

        void Shrink(size_t len) noexcept {
            Blob_ = Blob_.SubBlob(Min<size_t>(Blob_.Length(), len));
        }

        void Skip(size_t len) noexcept {
            Blob_ = Blob_.SubBlob(Min<size_t>(Blob_.Length(), len), Blob_.Length());
        }

        void Chop(size_t len) noexcept {
            Shrink(Blob_.Length() - std::min(len, Blob_.Length()));
        }

        [[nodiscard]] TChunkPtr SubChunk(size_t b, size_t e) const noexcept {
            Y_ASSERT(b <= e);
            Y_ASSERT(e <= Length());

            // it will throw if one of b or e is out of bound
            return MakeHolder<TChunk>(Blob_.SubBlob(b, e));
        }

        [[nodiscard]] TChunkPtr SubChunk(const char* b, const char* e) const noexcept {
            return SubChunk(Offset(b), Offset(e));
        }

        size_t Offset(const char* p) const noexcept {
            const size_t ret = p - Data();

            Y_ASSERT(ret <= Length());

            return ret;
        }

        [[nodiscard]] TChunkPtr SubChunk(size_t len) const noexcept {
            return SubChunk(0, len);
        }

        [[nodiscard]] TChunkPtr Copy() const noexcept {
            return MakeHolder<TChunk>(Blob_);
        }

        size_t CopyTo(char* data, size_t sz) const noexcept {
            const size_t toCopy = std::min(Length(), sz);
            AsStringBuf().copy(data, toCopy);
            return toCopy;
        }

        TStringBuf AsStringBuf() const noexcept {
            return {Data(), Length()};
        }

    private:
        TBlob Blob_;
    };


    inline THolder<TChunkData> NewChunkData(size_t sz) noexcept {
        return THolder<TChunkData>(new (sz) TChunkData);
    }

    inline THolder<TChunkData> NewChunkData(TStringBuf str) noexcept {
        THolder<TChunkData> data = NewChunkData(str.size());
        data->CopyFrom(str);
        return data;
    }

    inline THolder<TChunkData> NewChunkData(const TChunk& chunk) noexcept {
        return NewChunkData(chunk.AsStringBuf());
    }


    inline TChunkPtr NewChunk(const TBlob& blob) noexcept {
        return MakeHolder<TChunk>(blob);
    }

    inline TChunkPtr NewChunk(size_t len, TChunkData* data) noexcept {
        return NewChunk(TBlob{data->Data(), std::min(len, data->Length()), data});
    }

    inline TChunkPtr NewChunk(size_t len, THolder<TChunkData> data) noexcept {
        return NewChunk(len, data.Release());
    }

    // TODO (velavokr): this function relies on the TChunk data mutability. Should be removed.
    inline TChunkPtr NewChunk(TChunkData* data) noexcept {
        return NewChunk(TBlob{data->Data(), data->Length(), data});
    }

    // TODO (velavokr): this function relies on the TChunk data mutability. Should be removed.
    inline TChunkPtr NewChunkReserve(size_t len = TChunk::GetDefaultLength()) noexcept {
        return NewChunk(new (len) TChunkData());
    }

    inline TChunkPtr NewChunkNonOwning(TStringBuf s) noexcept {
        return NewChunk(TBlob::NoCopy(s.data(), s.size()));
    }

    inline TChunkPtr NewChunkForceCopy(TStringBuf s) noexcept {
        return NewChunk(s.size(), NewChunkData(s));
    }

    inline TChunkPtr NewChunk(const TString& s) noexcept {
        return NewChunk(TBlob::FromString(s));
    }

    inline TChunkPtr NewChunk(TString&& s) noexcept {
        return NewChunk(TBlob::FromString(std::move(s)));
    }

    TChunkPtr NewChunk(size_t s) noexcept = delete;

    inline TChunkPtr NewChunk(TBuffer&& s) noexcept {
        return NewChunk(TBlob::FromBuffer(s));
    }

    inline TChunkPtr EmptyChunk() noexcept {
        return NewChunk(TBlob());
    }
}
