#pragma once

#include "new.h"
#include "range.h"
#include "shared_range.h"

#include <type_traits>

namespace NYT {

////////////////////////////////////////////////////////////////////////////////

// Forward declaration.
class TBlob;

//! A non-owning reference to a range of memory.
class TRef
    : public TRange<char>
{
public:
    //! Creates a null TRef.
    TRef() = default;

    //! Creates a TRef for a given block of memory.
    TRef(const void* data, size_t size);

    //! Creates a TRef for a given range of memory.
    TRef(const void* begin, const void* end);

    //! Creates an empty TRef.
    static TRef MakeEmpty();

    //! Creates a non-owning TRef for a given blob.
    static TRef FromBlob(const TBlob& blob);

    //! Creates a non-owning TRef for a given string.
    static TRef FromString(const TString& str);

    //! Creates a non-owning TRef for a given stringbuf.
    static TRef FromStringBuf(TStringBuf strBuf);

    //! Creates a non-owning TRef for a given pod structure.
    template <class T>
    static TRef FromPod(const T& data);

    //! Converts to TStringBuf.
    TStringBuf ToStringBuf() const;

    //! Creates a TRef for a part of existing range.
    TRef Slice(size_t startOffset, size_t endOffset) const;

    //! Compares the content for bitwise equality.
    static bool AreBitwiseEqual(TRef lhs, TRef rhs);
};

////////////////////////////////////////////////////////////////////////////////

//! A non-owning reference to a mutable range of memory.
//! Use with caution :)
class TMutableRef
    : public TMutableRange<char>
{
public:
    //! Creates a null TMutableRef.
    //! Note empty TMutableRef is not the same as null TMutableRef.
    //! `operator bool` can be used to check if ref is nonnull.
    TMutableRef() = default;

    //! Creates a TMutableRef for a given block of memory.
    TMutableRef(void* data, size_t size);

    //! Creates a TMutableRef for a given range of memory.
    TMutableRef(void* begin, void* end);

    //! Creates an empty TMutableRef.
    static TMutableRef MakeEmpty();

    //! Converts a TMutableRef to TRef.
    operator TRef() const;

    //! Creates a non-owning TMutableRef for a given blob.
    static TMutableRef FromBlob(TBlob& blob);

    //! Creates a non-owning TMutableRef for a given pod structure.
    template <class T>
    static TMutableRef FromPod(T& data);

    //! Creates a non-owning TMutableRef for a given string.
    //! Ensures that the string is not shared.
    static TMutableRef FromString(TString& str);

    //! Creates a TMutableRef for a part of existing range.
    TMutableRef Slice(size_t startOffset, size_t endOffset) const;
};

////////////////////////////////////////////////////////////////////////////////

//! Default tag type for memory blocks allocated via TSharedRef.
/*!
 *  Each newly allocated TSharedRef blob is associated with a tag type
 *  that appears in ref-counted statistics.
 */
struct TDefaultSharedBlobTag { };

//! A reference to a range of memory with shared ownership.
class TSharedRef
    : public TSharedRange<char>
{
public:
    //! Creates a null TSharedRef.
    TSharedRef() = default;

    //! Creates a TSharedRef with a given holder.
    TSharedRef(TRef ref, TSharedRangeHolderPtr holder);

    //! Creates a TSharedRef from a pointer and length.
    TSharedRef(const void* data, size_t length, TSharedRangeHolderPtr holder);

    //! Creates a TSharedRef from a range.
    TSharedRef(const void* begin, const void* end, TSharedRangeHolderPtr holder);

    //! Creates an empty TSharedRef.
    static TSharedRef MakeEmpty();

    //! Converts a TSharedRef to TRef.
    operator TRef() const;


    //! Creates a TSharedRef from a string.
    //! Since strings are ref-counted, no data is copied.
    //! The memory is marked with a given tag.
    template <class TTag>
    static TSharedRef FromString(TString str);

    //! Creates a TSharedRef from a string.
    //! Since strings are ref-counted, no data is copied.
    //! The memory is marked with TDefaultSharedBlobTag.
    static TSharedRef FromString(TString str);

    //! Creates a TSharedRef reference from a string.
    //! Since strings are ref-counted, no data is copied.
    //! The memory is marked with a given tag.
    static TSharedRef FromString(TString str, TRefCountedTypeCookie tagCookie);

    //! Creates a TSharedRef for a given blob taking ownership of its content.
    static TSharedRef FromBlob(TBlob&& blob);

    //! Converts to TStringBuf.
    TStringBuf ToStringBuf() const;

    //! Creates a copy of a given TRef.
    //! The memory is marked with a given tag.
    static TSharedRef MakeCopy(TRef ref, TRefCountedTypeCookie tagCookie);

    //! Creates a copy of a given TRef.
    //! The memory is marked with a given tag.
    template <class TTag>
    static TSharedRef MakeCopy(TRef ref);

    //! Creates a TSharedRef for a part of existing range.
    TSharedRef Slice(size_t startOffset, size_t endOffset) const;

    //! Creates a TSharedRef for a part of existing range.
    TSharedRef Slice(const void* begin, const void* end) const;

    //! Creates a vector of slices with specified size.
    std::vector<TSharedRef> Split(size_t partSize) const;

private:
    friend class TSharedRefArrayImpl;
};

////////////////////////////////////////////////////////////////////////////////

//! Various options for allocating TSharedMutableRef.
struct TSharedMutableRefAllocateOptions
{
    bool InitializeStorage = true;
    bool ExtendToUsableSize = false;
};

//! A reference to a mutable range of memory with shared ownership.
//! Use with caution :)
class TSharedMutableRef
    : public TSharedMutableRange<char>
{
public:
    //! Creates a null TSharedMutableRef.
    TSharedMutableRef() = default;

    //! Creates a TSharedMutableRef with a given holder.
    TSharedMutableRef(const TMutableRef& ref, TSharedRangeHolderPtr holder);

    //! Creates a TSharedMutableRef from a pointer and length.
    TSharedMutableRef(void* data, size_t length, TSharedRangeHolderPtr holder);

    //! Creates a TSharedMutableRef from a range.
    TSharedMutableRef(void* begin, void* end, TSharedRangeHolderPtr holder);

    //! Creates an empty TSharedMutableRef.
    static TSharedMutableRef MakeEmpty();

    //! Converts a TSharedMutableRef to TMutableRef.
    operator TMutableRef() const;

    //! Converts a TSharedMutableRef to TSharedRef.
    operator TSharedRef() const;

    //! Converts a TSharedMutableRef to TRef.
    operator TRef() const;


    //! Allocates a new shared block of memory.
    //! The memory is marked with a given tag.
    template <class TTag>
    static TSharedMutableRef Allocate(size_t size, TSharedMutableRefAllocateOptions options = {});

    //! Allocates a new shared block of memory.
    //! The memory is marked with TDefaultSharedBlobTag.
    static TSharedMutableRef Allocate(size_t size, TSharedMutableRefAllocateOptions options = {});

    //! Allocates a new shared block of memory.
    //! The memory is marked with a given tag.
    static TSharedMutableRef Allocate(size_t size, TSharedMutableRefAllocateOptions options, TRefCountedTypeCookie tagCookie);

    //! Allocates a new page aligned shared block of memory.
    //! #size must be divisible by page size.
    //! The memory is marked with a given tag.
    template <class TTag>
    static TSharedMutableRef AllocatePageAligned(size_t size, TSharedMutableRefAllocateOptions options = {});

    //! Allocates a new page aligned shared block of memory.
    //! #size must be divisible by page size.
    //! The memory is marked with TDefaultSharedBlobTag.
    static TSharedMutableRef AllocatePageAligned(size_t size, TSharedMutableRefAllocateOptions options = {});

    //! Allocates a new page aligned shared block of memory.
    //! #size must be divisible by page size.
    //! The memory is marked with a given tag.
    static TSharedMutableRef AllocatePageAligned(size_t size, TSharedMutableRefAllocateOptions options, TRefCountedTypeCookie tagCookie);

    //! Creates a TSharedMutableRef for the whole blob taking ownership of its content.
    static TSharedMutableRef FromBlob(TBlob&& blob);

    //! Creates a copy of a given TRef.
    //! The memory is marked with a given tag.
    static TSharedMutableRef MakeCopy(TRef ref, TRefCountedTypeCookie tagCookie);

    //! Creates a copy of a given TRef.
    //! The memory is marked with a given tag.
    template <class TTag>
    static TSharedMutableRef MakeCopy(TRef ref);

    //! Creates a reference for a part of existing range.
    TSharedMutableRef Slice(size_t startOffset, size_t endOffset) const;

    //! Creates a reference for a part of existing range.
    TSharedMutableRef Slice(void* begin, void* end) const;
};

////////////////////////////////////////////////////////////////////////////////

DECLARE_REFCOUNTED_CLASS(TSharedRefArrayImpl)

//! A smart-pointer to a ref-counted immutable sequence of TSharedRef-s.
class TSharedRefArray
{
public:
    TSharedRefArray() = default;
    TSharedRefArray(const TSharedRefArray& other);
    TSharedRefArray(TSharedRefArray&& other) noexcept;

    explicit TSharedRefArray(const TSharedRef& part);
    explicit TSharedRefArray(TSharedRef&& part);

    struct TCopyParts
    { };
    struct TMoveParts
    { };

    template <class TParts>
    TSharedRefArray(const TParts& parts, TCopyParts);
    template <class TParts>
    TSharedRefArray(TParts&& parts, TMoveParts);

    TSharedRefArray& operator = (const TSharedRefArray& other);
    TSharedRefArray& operator = (TSharedRefArray&& other);

    explicit operator bool() const;

    void Reset();

    size_t Size() const;
    size_t size() const;
    i64 ByteSize() const;
    bool Empty() const;
    const TSharedRef& operator [] (size_t index) const;

    const TSharedRef* Begin() const;
    const TSharedRef* End() const;

    std::vector<TSharedRef> ToVector() const;
    TString ToString() const;

private:
    friend class TSharedRefArrayBuilder;

    TSharedRefArrayImplPtr Impl_;

    explicit TSharedRefArray(TSharedRefArrayImplPtr impl);

    template <class... As>
    static TSharedRefArrayImplPtr NewImpl(
        size_t size,
        size_t poolCapacity,
        TRefCountedTypeCookie cookie,
        As&&... args);
};

// STL interop.
const TSharedRef* begin(const TSharedRefArray& array);
const TSharedRef* end(const TSharedRefArray& array);

////////////////////////////////////////////////////////////////////////////////

struct TDefaultSharedRefArrayBuilderTag { };

//! A helper for creating TSharedRefArray.
class TSharedRefArrayBuilder
{
public:
    //! Creates a builder instance.
    /*
     *  The user must provide the total (resulting) part count in #size.
     *
     *  Additionally, the user may request a certain memory pool of size #poolCapacity
     *  to be created. Parts occupiying space in the above pool are created with #AllocateAndAdd
     *  calls.
     *
     *  The pool (if any) and the array are created within a single memory allocation tagged with
     *  #tagCookie.
     *
     *  If less than #size parts are added, the trailing ones are null.
     */
    explicit TSharedRefArrayBuilder(
        size_t size,
        size_t poolCapacity = 0,
        TRefCountedTypeCookie tagCookie = GetRefCountedTypeCookie<TDefaultSharedRefArrayBuilderTag>());

    //! Adds an existing TSharedRef part to the constructed array.
    void Add(TSharedRef part);

    //! Allocates #size memory from the pool and adds a part to the constuctured array.
    /*!
     *  The resulting TMutableRef enables the user to fill the just-created part appropriately.
     *  The total sum of #size during all #AllocateAndAll calls must now exceed #allocationCapacity
     *  passed to the ctor.
     *
     *  The memory is being claimed from the pool contiguously; the user must
     *  take care of the alignment issues on its own.
     */
    TMutableRef AllocateAndAdd(size_t size);

    //! Finalizes the construction; returns the constructed TSharedRefArray.
    TSharedRefArray Finish();

private:
    const size_t AllocationCapacity_;
    TSharedRefArrayImplPtr Impl_;
    char* CurrentAllocationPtr_;
    size_t CurrentPartIndex_ = 0;
};


////////////////////////////////////////////////////////////////////////////////

TString ToString(TRef ref);
TString ToString(const TMutableRef& ref);
TString ToString(const TSharedRef& ref);
TString ToString(const TSharedMutableRef& ref);

size_t GetPageSize();
size_t RoundUpToPage(size_t bytes);

size_t GetByteSize(TRef ref);
size_t GetByteSize(const TSharedRefArray& array);
template <class T>
size_t GetByteSize(TRange<T> parts);
template <class T>
size_t GetByteSize(const std::vector<T>& parts);

////////////////////////////////////////////////////////////////////////////////

} // namespace NYT

#define REF_INL_H_
#include "ref-inl.h"
#undef REF_INL_H_