// 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.Buffers;
using System.IO;
using System.Runtime.InteropServices;

namespace CommunityToolkit.HighPerformance.Streams;

/// <summary>
/// A factory class to produce <see cref="MemoryStream{TSource}"/> instances.
/// </summary>
internal static partial class MemoryStream
{
    /// <summary>
    /// Creates a new <see cref="Stream"/> from the input <see cref="ReadOnlyMemory{T}"/> of <see cref="byte"/> instance.
    /// </summary>
    /// <param name="memory">The input <see cref="ReadOnlyMemory{T}"/> instance.</param>
    /// <param name="isReadOnly">Indicates whether or not <paramref name="memory"/> can be written to.</param>
    /// <returns>A <see cref="Stream"/> wrapping the underlying data for <paramref name="memory"/>.</returns>
    /// <exception cref="ArgumentException">Thrown when <paramref name="memory"/> has an invalid data store.</exception>
    public static Stream Create(ReadOnlyMemory<byte> memory, bool isReadOnly)
    {
        if (memory.IsEmpty)
        {
            // Return an empty stream if the memory was empty
            return new MemoryStream<ArrayOwner>(ArrayOwner.Empty, isReadOnly);
        }

        if (MemoryMarshal.TryGetArray(memory, out ArraySegment<byte> segment))
        {
            ArrayOwner arraySpanSource = new(segment.Array!, segment.Offset, segment.Count);

            return new MemoryStream<ArrayOwner>(arraySpanSource, isReadOnly);
        }

        if (MemoryMarshal.TryGetMemoryManager(memory, out MemoryManager<byte>? memoryManager, out int start, out int length))
        {
            MemoryManagerOwner memoryManagerSpanSource = new(memoryManager!, start, length);

            return new MemoryStream<MemoryManagerOwner>(memoryManagerSpanSource, isReadOnly);
        }

        return ThrowNotSupportedExceptionForInvalidMemory();
    }

    /// <summary>
    /// Creates a new <see cref="Stream"/> from the input <see cref="IMemoryOwner{T}"/> of <see cref="byte"/> instance.
    /// </summary>
    /// <param name="memoryOwner">The input <see cref="IMemoryOwner{T}"/> instance.</param>
    /// <returns>A <see cref="Stream"/> wrapping the underlying data for <paramref name="memoryOwner"/>.</returns>
    /// <exception cref="ArgumentException">Thrown when <paramref name="memoryOwner"/> has an invalid data store.</exception>
    public static Stream Create(IMemoryOwner<byte> memoryOwner)
    {
        Memory<byte> memory = memoryOwner.Memory;

        if (memory.IsEmpty)
        {
            // Return an empty stream if the memory was empty
            return new IMemoryOwnerStream<ArrayOwner>(ArrayOwner.Empty, memoryOwner);
        }

        if (MemoryMarshal.TryGetArray(memory, out ArraySegment<byte> segment))
        {
            ArrayOwner arraySpanSource = new(segment.Array!, segment.Offset, segment.Count);

            return new IMemoryOwnerStream<ArrayOwner>(arraySpanSource, memoryOwner);
        }

        if (MemoryMarshal.TryGetMemoryManager<byte, MemoryManager<byte>>(memory, out MemoryManager<byte>? memoryManager, out int start, out int length))
        {
            MemoryManagerOwner memoryManagerSpanSource = new(memoryManager, start, length);

            return new IMemoryOwnerStream<MemoryManagerOwner>(memoryManagerSpanSource, memoryOwner);
        }

        return ThrowNotSupportedExceptionForInvalidMemory();
    }

    /// <summary>
    /// Throws a <see cref="ArgumentException"/> when a given <see cref="Memory{T}"/>
    /// or <see cref="IMemoryOwner{T}"/> instance has an unsupported backing store.
    /// </summary>
    /// <returns>Nothing, this method always throws.</returns>
    private static Stream ThrowNotSupportedExceptionForInvalidMemory()
    {
        throw new ArgumentException("The input instance doesn't have a valid underlying data store.");
    }
}