using System;
using System.Buffers.Binary;
using System.IO;
using System.Runtime.InteropServices;

namespace Ryujinx.Common
{
    public static class StreamExtensions
    {
        /// <summary>
        /// Writes a <cref="ReadOnlySpan<int>" /> to this stream.
        ///
        /// This default implementation converts each buffer value to a stack-allocated
        /// byte array, then writes it to the Stream using <cref="System.Stream.Write(byte[])" />.
        /// </summary>
        /// <param name="stream">The stream to be written to</param>
        /// <param name="buffer">The buffer of values to be written</param>
        public static void Write(this Stream stream, ReadOnlySpan<int> buffer)
        {
            if (buffer.Length == 0)
            {
                return;
            }

            if (BitConverter.IsLittleEndian)
            {
                ReadOnlySpan<byte> byteBuffer = MemoryMarshal.Cast<int, byte>(buffer);
                stream.Write(byteBuffer);
            }
            else
            {
                Span<byte> byteBuffer = stackalloc byte[sizeof(int)];

                foreach (int value in buffer)
                {
                    BinaryPrimitives.WriteInt32LittleEndian(byteBuffer, value);
                    stream.Write(byteBuffer);
                }
            }
        }

        /// <summary>
        /// Writes a four-byte signed integer to this stream. The current position
        /// of the stream is advanced by four.
        /// </summary>
        /// <param name="stream">The stream to be written to</param>
        /// <param name="value">The value to be written</param>
        public static void Write(this Stream stream, int value)
        {
            Span<byte> buffer = stackalloc byte[sizeof(int)];
            BinaryPrimitives.WriteInt32LittleEndian(buffer, value);
            stream.Write(buffer);
        }

        /// <summary>
        /// Writes an eight-byte signed integer to this stream. The current position
        /// of the stream is advanced by eight.
        /// </summary>
        /// <param name="stream">The stream to be written to</param>
        /// <param name="value">The value to be written</param>
        public static void Write(this Stream stream, long value)
        {
            Span<byte> buffer = stackalloc byte[sizeof(long)];
            BinaryPrimitives.WriteInt64LittleEndian(buffer, value);
            stream.Write(buffer);
        }

        /// <summary>
        // Writes a four-byte unsigned integer to this stream. The current position
        // of the stream is advanced by four.
        /// </summary>
        /// <param name="stream">The stream to be written to</param>
        /// <param name="value">The value to be written</param>
        public static void Write(this Stream stream, uint value)
        {
            Span<byte> buffer = stackalloc byte[sizeof(uint)];
            BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
            stream.Write(buffer);
        }

        /// <summary>
        /// Writes an eight-byte unsigned integer to this stream. The current
        /// position of the stream is advanced by eight.
        /// </summary>
        /// <param name="stream">The stream to be written to</param>
        /// <param name="value">The value to be written</param>
        public static void Write(this Stream stream, ulong value)
        {
            Span<byte> buffer = stackalloc byte[sizeof(ulong)];
            BinaryPrimitives.WriteUInt64LittleEndian(buffer, value);
            stream.Write(buffer);
        }

        /// <summary>
        /// Writes the contents of source to stream by calling source.CopyTo(stream).
        /// Provides consistency with other Stream.Write methods.
        /// </summary>
        /// <param name="stream">The stream to be written to</param>
        /// <param name="source">The stream to be read from</param>
        public static void Write(this Stream stream, Stream source)
        {
            source.CopyTo(stream);
        }

        /// <summary>
        /// Writes a sequence of bytes to the Stream.
        /// </summary>
        /// <param name="stream">The stream to be written to.</param>
        /// <param name="value">The byte to be written</param>
        /// <param name="count">The number of times the value should be written</param>
        public static void WriteByte(this Stream stream, byte value, int count)
        {
            if (count <= 0)
            {
                return;
            }

            const int BlockSize = 16;

            int blockCount = count / BlockSize;
            if (blockCount > 0)
            {
                Span<byte> span = stackalloc byte[BlockSize];
                span.Fill(value);
                for (int x = 0; x < blockCount; x++)
                {
                    stream.Write(span);
                }
            }

            int nonBlockBytes = count % BlockSize;
            for (int x = 0; x < nonBlockBytes; x++)
            {
                stream.WriteByte(value);
            }
        }
    }
}
