// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
#if NETSTANDARD2_1_OR_GREATER
using System.Runtime.InteropServices;
#else
using CommunityToolkit.HighPerformance.Helpers;
#endif

namespace CommunityToolkit.HighPerformance;

/// <summary>
/// A <see langword="struct"/> that can store a reference to a value of a specified type.
/// </summary>
/// <typeparam name="T">The type of value to reference.</typeparam>
[RequiresPreviewFeatures(
    "The Ref<T> type has no compiler support to ensure the lifetime of referenced values is respected, and as such using it incorrectly may lead to GC holes.",
    Url = "https://github.com/dotnet/runtime/issues/46104")]
public readonly ref struct Ref<T>
{
#if NETSTANDARD2_1_OR_GREATER
    /// <summary>
    /// The 1-length <see cref="Span{T}"/> instance used to track the target <typeparamref name="T"/> value.
    /// </summary>
    internal readonly Span<T> Span;

    /// <summary>
    /// Initializes a new instance of the <see cref="Ref{T}"/> struct.
    /// </summary>
    /// <param name="value">The reference to the target <typeparamref name="T"/> value.</param>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public Ref(ref T value)
    {
        Span = MemoryMarshal.CreateSpan(ref value, 1);
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="Ref{T}"/> struct.
    /// </summary>
    /// <param name="pointer">The pointer to the target value.</param>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public unsafe Ref(void* pointer)
        : this(ref Unsafe.AsRef<T>(pointer))
    {
    }

    /// <summary>
    /// Gets the <typeparamref name="T"/> reference represented by the current <see cref="Ref{T}"/> instance.
    /// </summary>
    public ref T Value
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        get => ref MemoryMarshal.GetReference(Span);
    }
#else
    /// <summary>
    /// The owner <see cref="object"/> the current instance belongs to
    /// </summary>
    internal readonly object Owner;

    /// <summary>
    /// The target offset within <see cref="Owner"/> the current instance is pointing to
    /// </summary>
    /// <remarks>
    /// Using an <see cref="IntPtr"/> instead of <see cref="int"/> to avoid the int to
    /// native int conversion in the generated asm (an extra movsxd on x64).
    /// </remarks>
    internal readonly IntPtr Offset;

    /// <summary>
    /// Initializes a new instance of the <see cref="Ref{T}"/> struct.
    /// </summary>
    /// <param name="owner">The owner <see cref="object"/> to create a portable reference for.</param>
    /// <param name="value">The target reference to point to (it must be within <paramref name="owner"/>).</param>
    /// <remarks>The <paramref name="value"/> parameter is not validated, and it's responsibility of the caller to ensure it's valid.</remarks>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public Ref(object owner, ref T value)
    {
        Owner = owner;
        Offset = ObjectMarshal.DangerousGetObjectDataByteOffset(owner, ref value);
    }

    /// <summary>
    /// Gets the <typeparamref name="T"/> reference represented by the current <see cref="Ref{T}"/> instance.
    /// </summary>
    public ref T Value
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        get => ref ObjectMarshal.DangerousGetObjectDataReferenceAt<T>(Owner, Offset);
    }
#endif

    /// <summary>
    /// Implicitly gets the <typeparamref name="T"/> value from a given <see cref="Ref{T}"/> instance.
    /// </summary>
    /// <param name="reference">The input <see cref="Ref{T}"/> instance.</param>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static implicit operator T(Ref<T> reference)
    {
        return reference.Value;
    }
}