using System;
using System.Threading.Tasks;
using WinRT.BxlComponent.Plugin.BxlMpXamarinSDK.Abstractions;

using Windows.Networking.Sockets;
using Windows.Networking;
using Windows.Storage.Streams;
using Windows.Networking.Connectivity;
using System.Linq;
using System.Threading;
using System.Collections.ObjectModel;
using Windows.UI.Core;

namespace WinRT.BxlComponent.Plugin.BxlMpXamarinSDK
{
    internal class MPosEthernetInfo
    {
        public byte[] MacAddr { get; set; }
        public byte[] Ip { get; set; }
        public byte[] Subnet { get; set; }
        public byte[] Gateway { get; set; }
        public byte[] Port { get; set; }
        public byte Baudrate { get; set; }
        public byte[] FwVer { get; set; }
        public byte Dhcp { get; set; }
        public byte[] I_activeTime { get; set; }
        public byte Http { get; set; }

        public MPosEthernetInfo(byte[] data)
        {
            if (data != null && data.Length >= 30)
            {
                MacAddr = new byte[6];
                Ip = new byte[4];
                Subnet = new byte[4];
                Gateway = new byte[4];
                Port = new byte[2];
                FwVer = new byte[2];
                I_activeTime = new byte[2];

                int index = 4;
                System.Buffer.BlockCopy(data, index, MacAddr, 0, 6); index = index + 6;
                System.Buffer.BlockCopy(data, index, Ip, 0, 4); index = index + 4;
                System.Buffer.BlockCopy(data, index, Subnet, 0, 4); index = index + 4;
                System.Buffer.BlockCopy(data, index, Gateway, 0, 4); index = index + 4;
                System.Buffer.BlockCopy(data, index, Port, 0, 2); index = index + 2;
                Baudrate = data[index]; index = index + 1;
                System.Buffer.BlockCopy(data, index, FwVer, 0, 2); index = index + 2;
                Dhcp = data[index]; index = index + 1;
                System.Buffer.BlockCopy(data, index, I_activeTime, 0, 2); index = index + 2;

                if (data.Length > 30)
                {
                    // 30 bytes, 31 bytes or more depending on the printers.
                    Http = data[index];
                }
            }
        }
    }

    internal class MPosWifiInfo
    {
        public byte[] MacAddr { get; set; }
        public byte[] Ip { get; set; }
        public byte[] Subnet { get; set; }
        public byte[] Gateway { get; set; }
        public byte[] Port { get; set; }
        public byte Baudrate { get; set; }
        public byte[] FwVer { get; set; }
        public byte Dhcp { get; set; }
        public byte[] I_activeTime { get; set; }
        public byte Http { get; set; }
        public byte[] Mask { get; set; }
        public byte[] SystemName { get; set; }

        public MPosWifiInfo(byte[] data)
        {
            if (data != null && data.Length >= 47)
            {
                MacAddr = new byte[6];
                Ip = new byte[4];
                Subnet = new byte[4];
                Gateway = new byte[4];
                Port = new byte[2];
                FwVer = new byte[2];
                I_activeTime = new byte[2];
                Mask = new byte[2];
                SystemName = new byte[64];

                int index = 18;
                System.Buffer.BlockCopy(data, index, MacAddr, 0, 6); index = index + 6;
                System.Buffer.BlockCopy(data, index, Ip, 0, 4); index = index + 4;
                System.Buffer.BlockCopy(data, index, Subnet, 0, 4); index = index + 4;
                System.Buffer.BlockCopy(data, index, Gateway, 0, 4); index = index + 4;
                System.Buffer.BlockCopy(data, index, Port, 0, 2); index = index + 2;
                Baudrate = data[index]; index = index + 1;
                System.Buffer.BlockCopy(data, index, FwVer, 0, 2); index = index + 2;
                Dhcp = data[index]; index = index + 1;
                System.Buffer.BlockCopy(data, index, I_activeTime, 0, 2); index = index + 2;
                Http = data[index]; index = index + 1;
                System.Buffer.BlockCopy(data, index, Mask, 0, 2); index = index + 2;

                if ((Mask[0] & 0x80) != 0 && data.Length >= 111)
                    System.Buffer.BlockCopy(data, index, SystemName, 0, 64);
                else
                    SystemName = null;
            }
        }
    }

    internal class MPosNetworkDeviceDiscovery
    {
        public int SearchTime { get; set; }
        public MPosInterfaceType InterfaceType { get; set; }
        public static ObservableCollection<MposConnectionInformation> networkDeviceCollection = new ObservableCollection<MposConnectionInformation>();
        private static SemaphoreSlim _SocketSemaphore = new SemaphoreSlim(1, 1);
        public MPosNetworkDeviceDiscovery(MPosInterfaceType type, int time = 2 /*second*/)
        {
            InterfaceType = type;
            SearchTime = time;
        }

        public async Task<bool> startDiscover()
        {
            networkDeviceCollection.Clear();

            // Wait for all tasks to complete.
            //var sendTasks = new System.Collections.Generic.List<Task>();
            foreach (HostName localHostInfo in NetworkInformation.GetHostNames())
            {
                try
                {
                    if (localHostInfo.IPInformation == null)
                        continue;

                    MPosDebug.WriteLine("StartDiscover : DisplayName = " + localHostInfo.DisplayName);
                    MPosDebug.WriteLine("StartDiscover : IanaInterfaceType = " + localHostInfo.IPInformation.NetworkAdapter.IanaInterfaceType);

                    switch (localHostInfo.IPInformation.NetworkAdapter.IanaInterfaceType)
                    {
                        case 6:     // Ethernet Network Interface     
                        case 71:    // IEEE 802.11 Wireless Network Interface    
                            if (InterfaceType == MPosInterfaceType.MPOS_INTERFACE_ETHERNET)
                                await sendMessage("FIND", 48780, 48781, localHostInfo.IPInformation.NetworkAdapter);
                            else if (InterfaceType == MPosInterfaceType.MPOS_INTERFACE_WIFI)
                                await sendMessage("__[I_F]__[PRT_REG]", 9000, 3337, localHostInfo.IPInformation.NetworkAdapter);
                            break;
                        default:
                            break;
                    }
                }
                catch (Exception ex)
                {
                    MPosDebug.WriteLine("StartDiscover : ex.Message = " + ex.Message);
                }
            }

            // Wait for all the tasks to finish.
            //MPosDebug.WriteLine("StartDiscover : sendTasks.Count = " + sendTasks.Count);
            //Task.WaitAll(sendTasks.ToArray());
            return true;
        }
        private async Task<uint> sendMessage(string message, int localPort, int remotePort, NetworkAdapter adapter = null)
        {
            MPosDebug.WriteLine("sendMessage : START");

            _SocketSemaphore.Wait();
            var listenerSocket = new DatagramSocket();
            listenerSocket.MessageReceived += socketOnMessageReceived;

            uint writtenBytes = 0;

            try
            {
                if (adapter == null)
                    await listenerSocket.BindServiceNameAsync(localPort.ToString());
                else
                {
                    await listenerSocket.BindServiceNameAsync(localPort.ToString(), adapter);
                    var icp = await adapter.GetConnectedProfileAsync();
                    if (icp != null)
                    {
                        var networkConnectivityLevel = icp.GetNetworkConnectivityLevel();
                        MPosDebug.WriteLine("sendMessage : NetworkConnectivityLevel = " + networkConnectivityLevel);
                        if (networkConnectivityLevel == NetworkConnectivityLevel.None)
                        {
                            _SocketSemaphore.Release();
                            listenerSocket.Dispose();
                            return 0;
                        }
                    }
                }

                using (var outputStream = await listenerSocket.GetOutputStreamAsync(new HostName("255.255.255.255"), remotePort.ToString()))
                {
                    using (var writer = new DataWriter(outputStream))
                    {
                        writer.WriteBytes(System.Text.Encoding.UTF8.GetBytes(message));
                        writtenBytes = await writer.StoreAsync();
                        MPosDebug.WriteLine("SendMessage : writtenBytes = " + writtenBytes);
                        await writer.FlushAsync();
                        writer.DetachStream();
                    }
                }

                if (writtenBytes > 0)
                {
                    System.Diagnostics.Stopwatch stopWatch = new System.Diagnostics.Stopwatch();
                    stopWatch.Start();
                    while (true)
                    {
                        int itemCount = networkDeviceCollection.Count;
                        TimeSpan ts = stopWatch.Elapsed;
                        if (ts.Seconds >= SearchTime)
                            break;
                        await Task.Delay(100);
                        ts = stopWatch.Elapsed;
                        if (itemCount > 0 && itemCount == networkDeviceCollection.Count && ts.Milliseconds >= 500)
                            break;
                    }
                    MPosDebug.WriteLine("sendMessage : end");
                    //await Task.Delay(SearchTime * 1000);
                }
            }
            catch (Exception ex)
            {
                MPosDebug.WriteLine("sendMessage : ex.Message = " + ex.Message);
            }
            finally
            {
                MPosDebug.WriteLine("sendMessage : _SocketSemaphore.Release, listenerSocket.Dispose");
                _SocketSemaphore.Release();
                listenerSocket.Dispose();
            }

            MPosDebug.WriteLine("sendMessage : END");
            return writtenBytes;
        }

        private void socketOnMessageReceived(DatagramSocket socket, DatagramSocketMessageReceivedEventArgs eventArguments)
        {
            MPosDebug.WriteLine("SocketOnMessageReceived : START");
            try
            {
                // Interpret the incoming datagram's entire contents as a string.
                uint bufLength = eventArguments.GetDataReader().UnconsumedBufferLength;
                if (bufLength > 0)
                {
                    MPosDebug.WriteLine("SocketOnMessageReceived : bufLength = " + bufLength.ToString());
                    var data = new byte[bufLength];
                    eventArguments.GetDataReader().ReadBytes(data); ;
                    networkInfoReceived(data);
                }
            }
            catch (Exception exception)
            {
                MPosDebug.WriteLine("SocketOnMessageReceived : ex = " + exception.Message);
            }
            MPosDebug.WriteLine("SocketOnMessageReceived : END");
        }
        private async void networkInfoReceived(byte[] byData)
        {
            MPosDebug.WriteLine("networkInfoReceived : START");

            if (byData == null)
                return;

            if (byData.Length >= 30 &&
                byData[0] == 'I' &&
                byData[1] == 'M' &&
                byData[2] == 'I' &&
                byData[3] == 'N')
            {
                var ethernetInfo = new MPosEthernetInfo(byData);
                var connectionInfo = new MposConnectionInformation();

                connectionInfo.IntefaceType = MPosInterfaceType.MPOS_INTERFACE_ETHERNET;

                for (int i = 0; i < 4; i++)
                    connectionInfo.IpAddress += ethernetInfo.Ip[i].ToString() + ".";
                for (int i = 0; i < 6; i++)
                    connectionInfo.MacAddress += ethernetInfo.MacAddr[i].ToString("X2") + ":";

                MPosDebug.WriteLine("networkInfoReceived : IpAddress = " + connectionInfo.IpAddress + ", MacAddress = " + connectionInfo.MacAddress);
                connectionInfo.IpAddress = connectionInfo.IpAddress.Remove(connectionInfo.IpAddress.Length - 1, 1);
                connectionInfo.MacAddress = connectionInfo.MacAddress.Remove(connectionInfo.MacAddress.Length - 1, 1);
                connectionInfo.PortNumber = ((ethernetInfo.Port[0] * 256) + (ethernetInfo.Port[1])).ToString();
                //connectionInfo.Name = "Ethernet Printer (" + connectionInfo.MacAddress + ")";
                connectionInfo.Name = "Ethernet (" + connectionInfo.MacAddress + ")";
                if (networkDeviceCollection.Any(x => x.MacAddress == connectionInfo.MacAddress) == false)
                {
                    await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => {
                        MPosDebug.WriteLine("networkInfoReceived : Ethernet Name = " + connectionInfo.Name + ", Address = " + connectionInfo.Address);
                        networkDeviceCollection.Add(connectionInfo);
                    });
                }
            }
            else if (byData.Length >= 47 &&
                byData[0] == '_' &&
                byData[1] == '_' &&
                byData[2] == '[' &&
                byData[3] == 'I' &&
                byData[4] == '_' &&
                byData[5] == 'F' &&
                byData[6] == ']' &&
                byData[7] == '_' &&
                byData[8] == '_' &&
                byData[9] == '[' &&
                byData[10] == 'R' &&
                byData[11] == 'E' &&
                byData[12] == 'G' &&
                byData[13] == '_' &&
                byData[14] == 'R' &&
                byData[15] == 'S' &&
                byData[16] == 'P' &&
                byData[17] == ']')
            {
                var wifiInfo = new MPosWifiInfo(byData);
                var connectionInfo = new MposConnectionInformation();

                connectionInfo.IntefaceType = MPosInterfaceType.MPOS_INTERFACE_WIFI;

                for (int i = 0; i < 4; i++)
                    connectionInfo.IpAddress += wifiInfo.Ip[i].ToString() + ".";
                for (int i = 0; i < 6; i++)
                    connectionInfo.MacAddress += wifiInfo.MacAddr[i].ToString("X2") + ":";

                connectionInfo.IpAddress = connectionInfo.IpAddress.Remove(connectionInfo.IpAddress.Length - 1, 1);
                connectionInfo.MacAddress = connectionInfo.MacAddress.Remove(connectionInfo.MacAddress.Length - 1, 1);
                connectionInfo.PortNumber = ((wifiInfo.Port[0] * 256) + (wifiInfo.Port[1])).ToString();
                //connectionInfo.Name         = "WiFi Printer (" + connectionInfo.MacAddress + ")";
                connectionInfo.Name = "WiFi (" + connectionInfo.MacAddress + ")";
                if (wifiInfo.SystemName != null){
                    connectionInfo.Name = "";
                    int endOfChar = 0;
                    foreach (byte ch in wifiInfo.SystemName){
                        if (ch == 0x00)
                            break;
                        endOfChar++;
                    }
                    byte[] data = new byte[endOfChar];
                    MPosDebug.WriteLine("networkInfoReceived : endOfChar = " + endOfChar);
                    Array.Copy(wifiInfo.SystemName, data, data.Length);
                    connectionInfo.Name = System.Text.Encoding.ASCII.GetString(data);
                    connectionInfo.Name = connectionInfo.Name.Trim();
                    connectionInfo.Name += "(" + connectionInfo.MacAddress + ")";
                }

                if (networkDeviceCollection.Any(x => x.MacAddress == connectionInfo.MacAddress) == false)
                {
                    MPosDebug.WriteLine("networkInfoReceived : Wi-Fi Name = " + connectionInfo.Name + ", Address = " + connectionInfo.Address);
                    await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => {
                        networkDeviceCollection.Add(connectionInfo);
                    });
                }
            }

            MPosDebug.WriteLine("networkInfoReceived : END");
        }
    }
}
