// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Buffers.Binary;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.CompilerServices;

namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;

// Mimics BinaryPrimitives with oddly sized units
internal static class Bitshifter
{
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static uint ReadUInt24BigEndian(ReadOnlySpan<byte> source)
    {
        return (uint)((source[0] << 16) | (source[1] << 8) | source[2]);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void WriteUInt24BigEndian(Span<byte> destination, uint value)
    {
        Debug.Assert(value <= 0xFF_FF_FF, value.ToString(CultureInfo.InvariantCulture));
        destination[0] = (byte)((value & 0xFF_00_00) >> 16);
        destination[1] = (byte)((value & 0x00_FF_00) >> 8);
        destination[2] = (byte)(value & 0x00_00_FF);
    }

    // Drops the highest order bit
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static uint ReadUInt31BigEndian(ReadOnlySpan<byte> source)
    {
        return BinaryPrimitives.ReadUInt32BigEndian(source) & 0x7F_FF_FF_FF;
    }

    // Does not overwrite the highest order bit
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void WriteUInt31BigEndian(Span<byte> destination, uint value)
        => WriteUInt31BigEndian(destination, value, true);

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void WriteUInt31BigEndian(Span<byte> destination, uint value, bool preserveHighestBit)
    {
        Debug.Assert(value <= 0x7F_FF_FF_FF, value.ToString(CultureInfo.InvariantCulture));

        if (preserveHighestBit)
        {
            // Keep the highest bit
            value |= (destination[0] & 0x80u) << 24;
        }

        BinaryPrimitives.WriteUInt32BigEndian(destination, value);
    }
}
