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

using System.Diagnostics;
using System.Net;

namespace System.Net.Sockets
{
    internal static partial class IPEndPointExtensions
    {
        public static IPAddress GetIPAddress(ReadOnlySpan<byte> socketAddressBuffer)
        {
            AddressFamily  family = SocketAddressPal.GetAddressFamily(socketAddressBuffer);

            if (family == AddressFamily.InterNetworkV6)
            {
                Span<byte> address = stackalloc byte[IPAddressParserStatics.IPv6AddressBytes];
                uint scope;
                SocketAddressPal.GetIPv6Address(socketAddressBuffer, address, out scope);

                // Clear scope if set for anything but Link Local address (always starts with fe80 first 10bits).
                return new IPAddress(address, (address[0] == 0xFE && (address[1] & 0xC0) == 0x80) ? (long)scope : 0);
            }
            else if (family == AddressFamily.InterNetwork)
            {
                return new IPAddress((long)SocketAddressPal.GetIPv4Address(socketAddressBuffer) & 0x0FFFFFFFF);
            }

            throw new SocketException((int)SocketError.AddressFamilyNotSupported);
        }

        public static void SetIPAddress(Span<byte> socketAddressBuffer, IPAddress address)
        {
            SocketAddressPal.SetAddressFamily(socketAddressBuffer, address.AddressFamily);
            SocketAddressPal.SetPort(socketAddressBuffer, 0);
            if (address.AddressFamily == AddressFamily.InterNetwork)
            {
#pragma warning disable CS0618
                SocketAddressPal.SetIPv4Address(socketAddressBuffer, (uint)address.Address);
#pragma warning restore CS0618
            }
            else
            {
                Span<byte> addressBuffer = stackalloc byte[IPAddressParserStatics.IPv6AddressBytes];
                address.TryWriteBytes(addressBuffer, out int written);
                Debug.Assert(written == IPAddressParserStatics.IPv6AddressBytes);
                SocketAddressPal.SetIPv6Address(socketAddressBuffer, addressBuffer, (uint)address.ScopeId);
            }
        }

        public static IPEndPoint CreateIPEndPoint(ReadOnlySpan<byte> socketAddressBuffer)
        {
           return new IPEndPoint(GetIPAddress(socketAddressBuffer), SocketAddressPal.GetPort(socketAddressBuffer));
        }

        // suggestion from https://github.com/dotnet/runtime/issues/78993
        public static void Serialize(this IPEndPoint endPoint, Span<byte> destination)
        {
            SocketAddressPal.SetAddressFamily(destination, endPoint.AddressFamily);
            SetIPAddress(destination, endPoint.Address);
            SocketAddressPal.SetPort(destination, (ushort)endPoint.Port);
        }

        public static bool Equals(this IPEndPoint endPoint, ReadOnlySpan<byte> socketAddressBuffer)
        {
            if (socketAddressBuffer.Length >= SocketAddress.GetMaximumAddressSize(endPoint.AddressFamily) &&
                endPoint.AddressFamily == SocketAddressPal.GetAddressFamily(socketAddressBuffer) &&
                endPoint.Port == (int)SocketAddressPal.GetPort(socketAddressBuffer))
            {
                if (endPoint.AddressFamily == AddressFamily.InterNetwork)
                {
#pragma warning disable CS0618
                    return endPoint.Address.Address == (long)SocketAddressPal.GetIPv4Address(socketAddressBuffer);
#pragma warning restore CS0618
                }
                else
                {
                    Span<byte> addressBuffer1 = stackalloc byte[IPAddressParserStatics.IPv6AddressBytes];
                    Span<byte> addressBuffer2 = stackalloc byte[IPAddressParserStatics.IPv6AddressBytes];
                    SocketAddressPal.GetIPv6Address(socketAddressBuffer, addressBuffer1, out uint scopeid);
                    if (endPoint.Address.ScopeId != (long)scopeid)
                    {
                        return false;
                    }
                    endPoint.Address.TryWriteBytes(addressBuffer2, out _);
                    return addressBuffer1.SequenceEqual(addressBuffer2);
                }
            }

            return false;
        }
    }
}
