using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
namespace FastGithub.Scanner
{
    /// 
    /// RawSocket的ping功能
    /// 
    static class RawSocketPing
    {
        private static readonly byte[] echoRequestPacket = Convert.FromHexString("0800F6FF0100000000000000");
        private static readonly byte[] icmpReceiveBuffer = new byte[72];
        /// 
        /// 获取是否支持
        /// 
        public static bool IsSupported { get; private set; }
        /// 
        /// RawSocket的ping功能
        /// 
        static RawSocketPing()
        {
            try
            {
                new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp).Dispose();
                IsSupported = true;
            }
            catch (Exception)
            {
                IsSupported = false;
            }
        }
        /// 
        /// ping目标ip
        /// 
        /// 
        /// 等待时间
        /// 取消令牌
        /// ping通的ip
        public static async Task> PingAsync(IEnumerable destAddresses, TimeSpan timeWait, CancellationToken cancellationToken = default)
        {
            using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp)
            {
                Ttl = 128,
                DontFragment = false
            };
            socket.Bind(new IPEndPoint(IPAddress.Any, 0));
            using var cancellationTokenSource = new CancellationTokenSource();
            using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, cancellationTokenSource.Token);
            var receiveTask = ReceiveAsync(socket, linkedTokenSource.Token);
            var distinctDestAddresses = destAddresses.Distinct();
            foreach (var address in distinctDestAddresses)
            {
                var remoteEndPoint = new IPEndPoint(address, 0);
                await socket.SendToAsync(echoRequestPacket, SocketFlags.None, remoteEndPoint);
            }
            await Task.Delay(timeWait, cancellationToken);
            cancellationTokenSource.Cancel();
            socket.Close();
            var hashSet = await receiveTask;
            hashSet.IntersectWith(distinctDestAddresses);
            return hashSet;
        }
        /// 
        /// 循环接收任务
        /// 
        /// 
        /// 
        /// 
        private static async Task> ReceiveAsync(Socket socket, CancellationToken cancellationToken)
        {
            await Task.Yield();
            var hashSet = new HashSet();
            var remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
            while (cancellationToken.IsCancellationRequested == false)
            {
                try
                {
                    var result = await socket.ReceiveFromAsync(icmpReceiveBuffer, SocketFlags.None, remoteEndPoint);
                    if (result.RemoteEndPoint is IPEndPoint ipEndPoint)
                    {
                        hashSet.Add(ipEndPoint.Address);
                    }
                }
                catch (SocketException ex) when (ex.SocketErrorCode == SocketError.OperationAborted)
                {
                    break;
                }
                catch (Exception)
                {
                }
            }
            return hashSet;
        }
    }
}