// 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 CommunityToolkit.HighPerformance.Enumerables;
using CommunityToolkit.HighPerformance.Memory.Internals;
#if NETSTANDARD2_1_OR_GREATER
using System.Runtime.InteropServices;
#else
using RuntimeHelpers = CommunityToolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
#endif

namespace CommunityToolkit.HighPerformance;

/// <inheritdoc/>
partial struct ReadOnlySpan2D<T>
{
    /// <summary>
    /// Gets an enumerable that traverses items in a specified row.
    /// </summary>
    /// <param name="row">The target row to enumerate within the current <see cref="ReadOnlySpan2D{T}"/> instance.</param>
    /// <returns>A <see cref="ReadOnlyRefEnumerable{T}"/> with target items to enumerate.</returns>
    /// <remarks>The returned <see cref="ReadOnlyRefEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public ReadOnlyRefEnumerable<T> GetRow(int row)
    {
        if ((uint)row >= Height)
        {
            ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow();
        }

        nint startIndex = (nint)(uint)this.stride * (nint)(uint)row;
        ref T r0 = ref DangerousGetReference();
        ref T r1 = ref Unsafe.Add(ref r0, startIndex);

#if NETSTANDARD2_1_OR_GREATER
        return new(in r1, Width, 1);
#else
        IntPtr offset = RuntimeHelpers.GetObjectDataOrReferenceByteOffset(this.instance, ref r1);

        return new(this.instance!, offset, this.width, 1);
#endif
    }

    /// <summary>
    /// Gets an enumerable that traverses items in a specified column.
    /// </summary>
    /// <param name="column">The target column to enumerate within the current <see cref="ReadOnlySpan2D{T}"/> instance.</param>
    /// <returns>A <see cref="ReadOnlyRefEnumerable{T}"/> with target items to enumerate.</returns>
    /// <remarks>The returned <see cref="ReadOnlyRefEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public ReadOnlyRefEnumerable<T> GetColumn(int column)
    {
        if ((uint)column >= Width)
        {
            ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn();
        }

        ref T r0 = ref DangerousGetReference();
        ref T r1 = ref Unsafe.Add(ref r0, (nint)(uint)column);

#if NETSTANDARD2_1_OR_GREATER
        return new(in r1, Height, this.stride);
#else
        IntPtr offset = RuntimeHelpers.GetObjectDataOrReferenceByteOffset(this.instance, ref r1);

        return new(this.instance!, offset, Height, this.stride);
#endif
    }

    /// <summary>
    /// Returns an enumerator for the current <see cref="ReadOnlySpan2D{T}"/> instance.
    /// </summary>
    /// <returns>
    /// An enumerator that can be used to traverse the items in the current <see cref="ReadOnlySpan2D{T}"/> instance
    /// </returns>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public Enumerator GetEnumerator() => new(this);

    /// <summary>
    /// Provides an enumerator for the elements of a <see cref="ReadOnlySpan2D{T}"/> instance.
    /// </summary>
    public ref struct Enumerator
    {
#if NETSTANDARD2_1_OR_GREATER
        /// <summary>
        /// The <see cref="ReadOnlySpan{T}"/> instance pointing to the first item in the target memory area.
        /// </summary>
        /// <remarks>Just like in <see cref="ReadOnlySpan2D{T}"/>, the length is the height of the 2D region.</remarks>
        private readonly ReadOnlySpan<T> span;
#else
        /// <summary>
        /// The target <see cref="object"/> instance, if present.
        /// </summary>
        private readonly object? instance;

        /// <summary>
        /// The initial offset within <see cref="instance"/>.
        /// </summary>
        private readonly IntPtr offset;

        /// <summary>
        /// The height of the specified 2D region.
        /// </summary>
        private readonly int height;
#endif

        /// <summary>
        /// The width of the specified 2D region.
        /// </summary>
        private readonly int width;

        /// <summary>
        /// The stride of the specified 2D region.
        /// </summary>
        private readonly int stride;

        /// <summary>
        /// The current horizontal offset.
        /// </summary>
        private int x;

        /// <summary>
        /// The current vertical offset.
        /// </summary>
        private int y;

        /// <summary>
        /// Initializes a new instance of the <see cref="Enumerator"/> struct.
        /// </summary>
        /// <param name="span">The target <see cref="ReadOnlySpan2D{T}"/> instance to enumerate.</param>
        internal Enumerator(ReadOnlySpan2D<T> span)
        {
#if NETSTANDARD2_1_OR_GREATER
            this.span = span.span;
#else
            this.instance = span.instance;
            this.offset = span.offset;
            this.height = span.height;
#endif
            this.width = span.width;
            this.stride = span.stride;
            this.x = -1;
            this.y = 0;
        }

        /// <summary>
        /// Implements the duck-typed <see cref="System.Collections.IEnumerator.MoveNext"/> method.
        /// </summary>
        /// <returns><see langword="true"/> whether a new element is available, <see langword="false"/> otherwise</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool MoveNext()
        {
            int x = this.x + 1;

            // Horizontal move, within range
            if (x < this.width)
            {
                this.x = x;

                return true;
            }

            // We reached the end of a row and there is at least
            // another row available: wrap to a new line and continue.
            this.x = 0;

#if NETSTANDARD2_1_OR_GREATER
            return ++this.y < this.span.Length;
#else
            return ++this.y < this.height;
#endif
        }

        /// <summary>
        /// Gets the duck-typed <see cref="System.Collections.Generic.IEnumerator{T}.Current"/> property.
        /// </summary>
        public readonly ref readonly T Current
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            get
            {
#if NETSTANDARD2_1_OR_GREATER
                ref T r0 = ref MemoryMarshal.GetReference(this.span);
#else
                ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.instance, this.offset);
#endif
                nint index = ((nint)(uint)this.y * (nint)(uint)this.stride) + (nint)(uint)this.x;

                return ref Unsafe.Add(ref r0, index);
            }
        }
    }
}