#pragma once

#include "public.h"
#include "intrusive_ptr.h"
#include "range.h"
#include "ref_counted.h"

#include <library/cpp/yt/assert/assert.h>

#include <util/ysaveload.h>

#include <optional>

namespace NYT {

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

template <class T, size_t N>
class TCompactVector;

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

struct TSharedRangeHolderCloneOptions
{
    bool KeepMemoryReferenceTracking = true;
};

struct TSharedRangeHolder
    : public TRefCounted
{
    //! Clones the holder possibly adjusting its flavor based on #options.
    /*!
     *  The default implementation just returns this.
     */
    virtual TSharedRangeHolderPtr Clone(const TSharedRangeHolderCloneOptions& options);

    //! Returns the (estimated) total number of bytes being held or |null| if unable to estimate.
    /*!
     *  The returned value is static and never changes.
     *  The default implementation returns |null|.
     */
    virtual std::optional<size_t> GetTotalByteSize() const;
};

DEFINE_REFCOUNTED_TYPE(TSharedRangeHolder)

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

//! TRange with ownership semantics.
template <class T>
class TSharedRange
    : public TRange<T>
{
public:
    //! Constructs a null TSharedRange.
    TSharedRange()
    { }

    //! Constructs an empty TSharedRange from a nullptr expression.
    TSharedRange(std::nullptr_t)
        : TRange<T>(nullptr, 0UL)
        , Holder_(nullptr)
    { }

    //! Constructs a TSharedRange from TRange.
    TSharedRange(TRange<T> range, TSharedRangeHolderPtr holder)
        : TRange<T>(range)
        , Holder_(std::move(holder))
    { }

    //! Constructs a TSharedRange from a pointer and length.
    TSharedRange(const T* data, size_t length, TSharedRangeHolderPtr holder)
        : TRange<T>(data, length)
        , Holder_(std::move(holder))
    { }

    //! Constructs a TSharedRange from a range.
    TSharedRange(const T* begin, const T* end, TSharedRangeHolderPtr holder)
        : TRange<T>(begin, end)
        , Holder_(std::move(holder))
    { }

    //! Constructs a TSharedRange from a TCompactVector.
    template <size_t N>
    TSharedRange(const TCompactVector<T, N>& elements, TSharedRangeHolderPtr holder)
        : TRange<T>(elements)
        , Holder_(std::move(holder))
    { }

    //! Constructs a TSharedRange from an std::vector.
    TSharedRange(const std::vector<T>& elements, TSharedRangeHolderPtr holder)
        : TRange<T>(elements)
        , Holder_(std::move(holder))
    { }

    //! Constructs a TSharedRange from a C array.
    template <size_t N>
    TSharedRange(const T (& elements)[N], TSharedRangeHolderPtr holder)
        : TRange<T>(elements)
        , Holder_(std::move(holder))
    { }

    TSharedRange(const TSharedRange& other) = default;

    TSharedRange(TSharedRange&& other) noexcept
        : TSharedRange()
    {
        other.Swap(*this);
    }

    TSharedRange& operator=(TSharedRange other) noexcept
    {
        other.Swap(*this);
        return *this;
    }

    void Swap(TSharedRange& other) noexcept
    {
        DoSwap(TRange<T>::Data_, other.Data_);
        DoSwap(TRange<T>::Length_, other.Length_);
        Holder_.Swap(other.Holder_);
    }

    void Reset()
    {
        TRange<T>::Data_ = nullptr;
        TRange<T>::Length_ = 0;
        Holder_.Reset();
    }

    TSharedRange<T> Slice(size_t startOffset, size_t endOffset) const
    {
        YT_ASSERT(startOffset <= this->Size());
        YT_ASSERT(endOffset >= startOffset && endOffset <= this->Size());
        return TSharedRange<T>(this->Begin() + startOffset, endOffset - startOffset, Holder_);
    }

    TSharedRange<T> Slice(const T* begin, const T* end) const
    {
        YT_ASSERT(begin >= this->Begin());
        YT_ASSERT(end <= this->End());
        return TSharedRange<T>(begin, end, Holder_);
    }

    const TSharedRangeHolderPtr& GetHolder() const
    {
        return Holder_;
    }

    TSharedRangeHolderPtr&& ReleaseHolder()
    {
        return std::move(Holder_);
    }

protected:
    TSharedRangeHolderPtr Holder_;

};

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

//! Constructs a combined holder from a vector of typed holders.
TSharedRangeHolderPtr MakeCompositeSharedRangeHolder(std::vector<TSharedRangeHolderPtr> holders);

//! Constructs a combined holder instance by taking ownership of a given list of holders.
template <class... THolders>
TSharedRangeHolderPtr MakeSharedRangeHolder(THolders&&... holders)
{
    struct THolder
        : public TSharedRangeHolder
    {
        std::tuple<typename std::decay<THolders>::type...> Holders;
    };

    auto holder = New<THolder>();
    holder->Holders = std::tuple<THolders...>(std::forward<THolders>(holders)...);
    return holder;
}

template <class T, class TContainer, class... THolders>
TSharedRange<T> DoMakeSharedRange(TContainer&& elements, THolders&&... holders)
{
    struct THolder
        : public TSharedRangeHolder
    {
        typename std::decay<TContainer>::type Elements;
        std::tuple<typename std::decay<THolders>::type...> Holders;
    };

    auto holder = New<THolder>();
    holder->Holders = std::tuple<THolders...>(std::forward<THolders>(holders)...);
    holder->Elements = std::forward<TContainer>(elements);

    auto range = TRange<T>(holder->Elements);

    return TSharedRange<T>(range, std::move(holder));
}

//! Constructs a TSharedRange by taking ownership of an std::vector.
template <class T, class... THolders>
TSharedRange<T> MakeSharedRange(std::vector<T>&& elements, THolders&&... holders)
{
    return DoMakeSharedRange<T>(std::move(elements), std::forward<THolders>(holders)...);
}

//! Constructs a TSharedRange by taking ownership of an TCompactVector.
template <class T, size_t N, class... THolders>
TSharedRange<T> MakeSharedRange(TCompactVector<T, N>&& elements, THolders&&... holders)
{
    return DoMakeSharedRange<T>(std::move(elements), std::forward<THolders>(holders)...);
}

//! Constructs a TSharedRange by copying an std::vector.
template <class T, class... THolders>
TSharedRange<T> MakeSharedRange(const std::vector<T>& elements, THolders&&... holders)
{
    return DoMakeSharedRange<T>(elements, std::forward<THolders>(holders)...);
}

template <class T, class... THolders>
TSharedRange<T> MakeSharedRange(TRange<T> range, THolders&&... holders)
{
    return TSharedRange<T>(range, MakeSharedRangeHolder(std::forward<THolders>(holders)...));
}

template <class T, class THolder>
TSharedRange<T> MakeSharedRange(TRange<T> range, TSharedRangeHolderPtr holder)
{
    return TSharedRange<T>(range, std::move(holder));
}

template <class U, class T>
TSharedRange<U> ReinterpretCastRange(const TSharedRange<T>& range)
{
    static_assert(sizeof(T) == sizeof(U), "T and U must have equal sizes.");
    return TSharedRange<U>(reinterpret_cast<const U*>(range.Begin()), range.Size(), range.GetHolder());
}

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

//! TMutableRange with ownership semantics.
//! Use with caution :)
template <class T>
class TSharedMutableRange
    : public TMutableRange<T>
{
public:
    //! Constructs a null TSharedMutableRange.
    TSharedMutableRange()
    { }

    //! Constructs a TSharedMutableRange from TMutableRange.
    TSharedMutableRange(TMutableRange<T> range, TSharedRangeHolderPtr holder)
        : TMutableRange<T>(range)
        , Holder_(std::move(holder))
    { }

    //! Constructs a TSharedMutableRange from a pointer and length.
    TSharedMutableRange(T* data, size_t length, TSharedRangeHolderPtr holder)
        : TMutableRange<T>(data, length)
        , Holder_(std::move(holder))
    { }

    //! Constructs a TSharedMutableRange from a range.
    TSharedMutableRange(T* begin, T* end, TSharedRangeHolderPtr holder)
        : TMutableRange<T>(begin, end)
        , Holder_(std::move(holder))
    { }

    //! Constructs a TSharedMutableRange from a TCompactVector.
    template <size_t N>
    TSharedMutableRange(TCompactVector<T, N>& elements, TSharedRangeHolderPtr holder)
        : TMutableRange<T>(elements)
        , Holder_(std::move(holder))
    { }

    //! Constructs a TSharedMutableRange from an std::vector.
    TSharedMutableRange(std::vector<T>& elements, TSharedRangeHolderPtr holder)
        : TMutableRange<T>(elements)
        , Holder_(std::move(holder))
    { }

    //! Constructs a TSharedMutableRange from a C array.
    template <size_t N>
    TSharedMutableRange(T (& elements)[N], TSharedRangeHolderPtr holder)
        : TMutableRange<T>(elements)
        , Holder_(std::move(holder))
    { }

    TSharedMutableRange(const TSharedMutableRange& other) = default;

    TSharedMutableRange(TSharedMutableRange&& other) noexcept
        : TSharedMutableRange()
    {
        other.Swap(*this);
    }

    TSharedMutableRange& operator=(TSharedMutableRange other) noexcept
    {
        other.Swap(*this);
        return *this;
    }

    void Swap(TSharedMutableRange& other) noexcept
    {
        DoSwap(TRange<T>::Data_, other.Data_);
        DoSwap(TRange<T>::Length_, other.Length_);
        Holder_.Swap(other.Holder_);
    }

    void Reset()
    {
        TRange<T>::Data_ = nullptr;
        TRange<T>::Length_ = 0;
        Holder_.Reset();
    }

    TSharedMutableRange<T> Slice(size_t startOffset, size_t endOffset) const
    {
        YT_ASSERT(startOffset <= this->Size());
        YT_ASSERT(endOffset >= startOffset && endOffset <= this->Size());
        return TSharedMutableRange<T>(this->Begin() + startOffset, endOffset - startOffset, Holder_);
    }

    TSharedMutableRange<T> Slice(T* begin, T* end) const
    {
        YT_ASSERT(begin >= this->Begin());
        YT_ASSERT(end <= this->End());
        return TSharedMutableRange<T>(begin, end, Holder_);
    }

    TSharedRangeHolderPtr GetHolder() const
    {
        return Holder_;
    }

    TSharedRangeHolderPtr&& ReleaseHolder()
    {
        return std::move(Holder_);
    }

protected:
    TSharedRangeHolderPtr Holder_;
};

template <class T, class TContainer, class... THolders>
TSharedMutableRange<T> DoMakeSharedMutableRange(TContainer&& elements, THolders&&... holders)
{
    struct THolder
        : public TSharedRangeHolder
    {
        typename std::decay<TContainer>::type Elements;
        std::tuple<typename std::decay<THolders>::type...> Holders;
    };

    auto holder = New<THolder>();
    holder->Holders = std::tuple<THolders...>(std::forward<THolders>(holders)...);
    holder->Elements = std::forward<TContainer>(elements);

    auto range = TMutableRange<T>(holder->Elements);

    return TSharedMutableRange<T>(range, holder);
}

//! Constructs a TSharedMutableRange by taking ownership of an std::vector.
template <class T, class... THolders>
TSharedMutableRange<T> MakeSharedMutableRange(std::vector<T>&& elements, THolders&&... holders)
{
    return DoMakeSharedMutableRange<T>(std::move(elements), std::forward<THolders>(holders)...);
}

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

} // namespace NYT