using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace Icc4cExample
{
    public static class EthernetSearcher
    {
        private const int SourcePort = 30320;
        private const int DestinationPort = 30321;
        private static readonly byte[] SearchMessage = Encoding.ASCII.GetBytes("OptotuneSearch");

        public static List<EthernetDevice> SearchEthernetIcc4cBoards()
        {
            var udpClient = new UdpClient { Client = { ReceiveTimeout = 2000 } };
            try
            {
                udpClient.Client.Bind(new IPEndPoint(IPAddress.Any, SourcePort));
                var from = new IPEndPoint(0, 0);
                var deviceList = new List<EthernetDevice>();
                SendUdpSearchPacket();
                while (true)
                {
                    try
                    {
                        var receiveBuffer = udpClient.Receive(ref from);
                        ReceiveMessage(receiveBuffer, @from, deviceList);
                    }
                    catch (Exception)
                    {
                        udpClient.Dispose();
                        return deviceList;
                    }
                }
            }
            catch (Exception)
            {
                return new List<EthernetDevice>();
            }
        }

        private static void SendUdpSearchPacket()
        {
            foreach (var ipAddress in GetLocalIpAddresses())
            {
                var localEndPoint = new IPEndPoint(IPAddress.Parse(ipAddress), SourcePort);
                var targetEndPoint = new IPEndPoint(IPAddress.Broadcast, DestinationPort);
                var sendUdpClient = new UdpClient(localEndPoint);
                sendUdpClient.Send(SearchMessage, SearchMessage.Length, targetEndPoint);
                sendUdpClient.Dispose();
            }
        }

        private static IEnumerable<string> GetLocalIpAddresses()
        {
            var host = Dns.GetHostEntry(Dns.GetHostName());
            foreach (var ip in host.AddressList)
            {
                if (ip.AddressFamily == AddressFamily.InterNetwork)
                {
                    yield return ip.ToString();
                }
            }
        }

        private static void ReceiveMessage(byte[] receiveBuffer, IPEndPoint @from, List<EthernetDevice> deviceList)
        {
            var receivedString = Encoding.UTF8.GetString(receiveBuffer);
            if (receivedString.StartsWith("ICC4con"))
            {
                var device = ParseDevice(receivedString, @from);
                if (device != null &&
                    deviceList.SingleOrDefault(dev => dev.Ip == device.Ip) == null)
                {
                    deviceList.Add(device);
                }
            }
        }

        private static EthernetDevice ParseDevice(string receivedString, IPEndPoint @from)
        {
            var splitPayload = receivedString.Split('-');
            if (Equals(@from.Address.ToString(), "0.0.0.0") || splitPayload.Length < 2)
            {
                return null;
            }

            var serialNumber = splitPayload[1];
            return new EthernetDevice(@from.Address.ToString(), 5000, serialNumber);
        }
    }

    public class EthernetDevice
    {
        public string Ip { get; set; }
        public int Port { get; set; }
        public string SerialNumber { get; set; }

        public EthernetDevice(string ip, int port, string serialNumber)
        {
            Ip = ip;
            Port = port;
            SerialNumber = serialNumber;
        }
    }
}

