using System;
using System.Text;

public static class SpanByteExtensions
{
    public static ushort ReadUInt16LittleEndian(this ReadOnlySpan<byte> span, int offset)
    {
        if ((uint)offset > (uint)(span.Length - 2))
            throw new ArgumentOutOfRangeException(nameof(offset));

        return (ushort)(
            span[offset] |
            (span[offset + 1] << 8));
    }

    public static ushort ReadUInt16BigEndian(this ReadOnlySpan<byte> span, int offset)
    {
        if ((uint)offset > (uint)(span.Length - 2))
            throw new ArgumentOutOfRangeException(nameof(offset));

        return (ushort)(
            (span[offset] << 8) |
            span[offset + 1]);
    }

    public static uint ReadUInt32LittleEndian(this ReadOnlySpan<byte> span, int offset)
    {
        if ((uint)offset > (uint)(span.Length - 4))
            throw new ArgumentOutOfRangeException(nameof(offset));

        return (uint)(
            span[offset] |
            (span[offset + 1] << 8) |
            (span[offset + 2] << 16) |
            (span[offset + 3] << 24));
    }

    public static uint ReadUInt32BigEndian(this ReadOnlySpan<byte> span, int offset)
    {
        if ((uint)offset > (uint)(span.Length - 4))
            throw new ArgumentOutOfRangeException(nameof(offset));

        return (uint)(
            (span[offset] << 24) |
            (span[offset + 1] << 16) |
            (span[offset + 2] << 8) |
            span[offset + 3]);
    }

    public static ushort ReadUInt16LittleEndian(this Span<byte> span, int offset)
        => ReadUInt16LittleEndian((ReadOnlySpan<byte>)span, offset);

    public static ushort ReadUInt16BigEndian(this Span<byte> span, int offset)
        => ReadUInt16BigEndian((ReadOnlySpan<byte>)span, offset);

    public static uint ReadUInt32LittleEndian(this Span<byte> span, int offset)
        => ReadUInt32LittleEndian((ReadOnlySpan<byte>)span, offset);

    public static uint ReadUInt32BigEndian(this Span<byte> span, int offset)
        => ReadUInt32BigEndian((ReadOnlySpan<byte>)span, offset);
    
    public static string ReadNullTerminatedUtf8String(this ReadOnlySpan<byte> span, int offset)
    {
        if ((uint)offset >= (uint)span.Length)
            throw new ArgumentOutOfRangeException(nameof(offset));

        int nullIndex = span.Slice(offset).IndexOf((byte)0);
        if (nullIndex < 0)
            throw new InvalidOperationException("No null terminator found in span.");

        return Encoding.UTF8.GetString(span.Slice(offset, nullIndex));
    }

    public static string ReadNullTerminatedUtf8String(this ReadOnlySpan<byte> span, int offset, out int nextOffset)
    {
        if ((uint)offset >= (uint)span.Length)
            throw new ArgumentOutOfRangeException(nameof(offset));

        int nullIndex = span.Slice(offset).IndexOf((byte)0);
        if (nullIndex < 0)
            throw new InvalidOperationException("No null terminator found in span.");

        nextOffset = offset + nullIndex + 1;
        return Encoding.UTF8.GetString(span.Slice(offset, nullIndex));
    }

    public static string ReadNullTerminatedUtf8String(this Span<byte> span, int offset)
        => ReadNullTerminatedUtf8String((ReadOnlySpan<byte>)span, offset);

    public static string ReadNullTerminatedUtf8String(this Span<byte> span, int offset, out int nextOffset)
        => ReadNullTerminatedUtf8String((ReadOnlySpan<byte>)span, offset, out nextOffset);

    public static bool IsBlank(this Span<byte> span)
    {
        if (span.Length == 0)
            return true;
        else
        {
            for (int i = 0; i < span.Length; i++)
            {
                if (span[i] != 0)
                    return false;
            }
            return true;
        }
    }
}
