using System;
using System.Threading.Tasks;
using WinRT.BxlComponent.Plugin.BxlMpXamarinSDK.Abstractions;
using Windows.Devices.Enumeration;
using System.Collections.ObjectModel;
using Windows.Foundation;
using Windows.Devices.Bluetooth;
using System.Linq;
using Windows.UI.Core;
using System.IO;

namespace WinRT.BxlComponent.Plugin.BxlMpXamarinSDK
{
    internal class MposRfcommDeviceWatcher
    {
        private static DeviceWatcher _deviceWatcher = null;
        private static ObservableCollection<MPosRfcommDevice> _resultCollection = new ObservableCollection<MPosRfcommDevice>();

        public static async Task<ObservableCollection<MPosRfcommDevice>> getRfcommDeviceCollection()
        {
            startDeviceWatcher();
            await Task.Delay(100);
            stopDeviceWatcher();
            //await Task.Delay(100);
            return _resultCollection;
        }
        public static int getCurrentDeviceCount()
        {
            if (_resultCollection == null)
                return 0;
            MPosDebug.WriteLine("getCurrentDeviceCount : Count = " + _resultCollection.Count.ToString());
            return _resultCollection.Count;
        }
        private static void stopDeviceWatcher()
        {
            MPosDebug.WriteLine("stopDeviceWatcher : START");
            if (_deviceWatcher != null)
            {
                if ((DeviceWatcherStatus.Started == _deviceWatcher.Status ||
                     DeviceWatcherStatus.EnumerationCompleted == _deviceWatcher.Status))
                {
                    _deviceWatcher.Stop();
                    //_deviceWatcher = null;
                }
            }
            MPosDebug.WriteLine("stopDeviceWatcher : END");
        }

        private static void startDeviceWatcher()
        {
            MPosDebug.WriteLine("startDeviceWatcher : START");
            //MPosDebug.WriteLine("startDeviceWatcher : paired = " + BluetoothDevice.GetDeviceSelectorFromPairingState(true));
            //MPosDebug.WriteLine("startDeviceWatcher : unpaired = " + BluetoothDevice.GetDeviceSelectorFromPairingState(false));

            stopDeviceWatcher();

            if (_resultCollection != null){
                _resultCollection.Clear();
            }

            // Request additional properties
            // https://docs.microsoft.com/ko-kr/windows/uwp/devices-sensors/device-information-properties
            string[] requestedProperties = new string[] { "System.Devices.Aep.DeviceAddress", "System.Devices.Aep.IsConnected" };
            //string[] requestedProperties = new string[] { "System.Devices.Aep.DeviceAddress", "System.Devices.Aep.IsConnected", "System.Devices.Aep.IsPaired"};

            if (_deviceWatcher == null){
                // Reference : https://docs.microsoft.com/en-us/windows/uwp/devices-sensors/aep-service-class-ids
                // Bluetooth protocol ID: {e0cbf06c-cd8b-4647-bb8a263b43f0f974}
                // Bluetooth LE protocol ID: {bb7bb05e-5972-42b5-94fc76eaa7084d49}
                _deviceWatcher = DeviceInformation.CreateWatcher(//"(System.Devices.Aep.ProtocolId:=\"{e0cbf06c-cd8b-4647-bb8a-263b43f0f974}\")",    // for paired and unpaired devices
                                                            BluetoothDevice.GetDeviceSelectorFromPairingState(true),                         // for only paired devices
                                                            requestedProperties,
                                                            DeviceInformationKind.AssociationEndpoint);
            }

            if (_deviceWatcher == null){
                MPosDebug.WriteLine("startDeviceWatcher : deviceWatcher is null");
                MPosDebug.WriteLine("startDeviceWatcher : END");
                return;
            }

            // Hook up handlers for the watcher events before starting the watcher
            _deviceWatcher.Added += new TypedEventHandler<DeviceWatcher, DeviceInformation>(async (watcher, deviceInfo) =>
            {
                await Task.Factory.StartNew(() =>
                {
                    // Make sure device name isn't blank
                    if (deviceInfo.Name != "")
                    {
                        MPosDebug.WriteLine("startDeviceWatcher : Added");
                        _resultCollection.Add(new MPosRfcommDevice(deviceInfo));
                        MPosDebug.WriteLine("startDeviceWatcher : Added, id = " + deviceInfo.Id.ToString());
                    }
                });
            });

            /*
            _deviceWatcher.Updated += new TypedEventHandler<DeviceWatcher, DeviceInformationUpdate>(async (watcher, deviceInfoUpdate) =>
            {
                await Task.Factory.StartNew(() =>
                {
                    foreach (MPosRfcommDevice rfcommInfoDisp in _resultCollection)
                    {
                        MPosDebug.WriteLine("MposLookupImplementation startPairedDeviceWatcher : deviceWatcher.Updated, rfcommInfoDisp.Id = " + rfcommInfoDisp.Id.ToString());
                        if (rfcommInfoDisp.Id == deviceInfoUpdate.Id)
                        {
                            MPosDebug.WriteLine("MposLookupImplementation startPairedDeviceWatcher : deviceWatcher.Updated, deviceInfoUpdate.Id = " + deviceInfoUpdate.Id.ToString());
                            rfcommInfoDisp.Update(deviceInfoUpdate);
                            break;
                        }
                    }
                });
            });
            */

            _deviceWatcher.EnumerationCompleted += new TypedEventHandler<DeviceWatcher, Object>(async (watcher, obj) =>
            {
                await Task.Factory.StartNew(() =>
                {
                    MPosDebug.WriteLine("startDeviceWatcher : Enumeration completed. ");
                });
            });

            _deviceWatcher.Removed += new TypedEventHandler<DeviceWatcher, DeviceInformationUpdate>(async (watcher, deviceInfoUpdate) =>
            {
                await Task.Factory.StartNew(() =>
                {
                    // Find the corresponding DeviceInformation in the collection and remove it
                    foreach (MPosRfcommDevice rfcommInfoDisp in _resultCollection)
                    {
                        MPosDebug.WriteLine("startDeviceWatcher : deviceWatcher.Removed, rfcommInfoDisp.Id = " + rfcommInfoDisp.Id.ToString());
                        if (rfcommInfoDisp.Id == deviceInfoUpdate.Id)
                        {
                            MPosDebug.WriteLine("startDeviceWatcher : deviceWatcher.Removed, deviceInfoUpdate.Id = " + deviceInfoUpdate.Id.ToString());
                            _resultCollection.Remove(rfcommInfoDisp);
                            break;
                        }
                    }
                    //rootPage.NotifyUser(String.Format("{0} devices found.", ResultCollection.Count), NotifyType.StatusMessage);
                });
            });

            _deviceWatcher.Stopped += new TypedEventHandler<DeviceWatcher, Object>(async (watcher, obj) =>
            {
                await Task.Factory.StartNew(() =>
                {
                    MPosDebug.WriteLine("startDeviceWatcher : deviceWatcher.Stopped");
                });
            });

            _resultCollection.Clear();
            _deviceWatcher.Start();

            MPosDebug.WriteLine("startDeviceWatcher : END");
        }
    }
    internal sealed class MposLookupImplementation : MposLookup
    {
        private static object syncRoot = new Object();
        private static MposLookupImplementation instance;
        private ObservableCollection<MposConnectionInformation> _pairedDeviceCollection = new ObservableCollection<MposConnectionInformation>();
        private ObservableCollection<MposConnectionInformation> _networkDeviceCollection = new ObservableCollection<MposConnectionInformation>();

        private MposLookupImplementation() { }
        public static MposLookupImplementation Instance
        {
            get
            {
                lock (syncRoot)
                {
                    if (instance == null)
                        instance = new MposLookupImplementation();
                    return instance;
                }
            }
        }
        private async Task<int> refreshBluetoothDevicesList(int timeout = 3 /*second*/)
        {
            MPosDebug.WriteLine("MposLookupImplementation refreshBluetoothDevicesList : START");

            ObservableCollection<MPosRfcommDevice> resultCollection = null;

            //if (MposRfcommDeviceWatcher.getCurrentDeviceCount() == 0)
            resultCollection = await MposRfcommDeviceWatcher.getRfcommDeviceCollection();

            if (resultCollection == null)
            {
                MPosDebug.WriteLine("MposLookupImplementation refreshBluetoothDevicesList : resultCollection is null");
                MPosDebug.WriteLine("MposLookupImplementation refreshBluetoothDevicesList : END");
                
                return 0;
            }

            _pairedDeviceCollection.Clear();
            int a = 0;
            foreach (MPosRfcommDevice rfcommInfoDisp in resultCollection)
            {
                try
                {
                    if (rfcommInfoDisp == null)
                        continue;
                    if (rfcommInfoDisp.Id.Length == 0)
                        continue;
                    
                    MPosDebug.WriteLine("MposLookupImplementation refreshBluetoothDevicesList: rfcommInfoDisp.Id = " + rfcommInfoDisp.Id);
                    BluetoothDevice bluetoothDevice = await BluetoothDevice.FromIdAsync(rfcommInfoDisp.Id);

                    if (bluetoothDevice == null)
                        continue;
                    if (bluetoothDevice.HostName == null)
                        continue;
                    
                    MPosDebug.WriteLine("MposLookupImplementation refreshBluetoothDevicesList: HostName = " + bluetoothDevice.HostName);

                    string macAddress = bluetoothDevice.HostName.ToString();
                    if (macAddress.Contains("(") && macAddress.Contains(")"))
                        macAddress = macAddress.Substring(1, macAddress.Length - 2);
                    
                    await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
                    {
                        if (!_pairedDeviceCollection.Any(o => o.MacAddress.Equals(macAddress))) {
                            _pairedDeviceCollection.Add(new MposConnectionInformation()
                            {
                                IntefaceType = MPosInterfaceType.MPOS_INTERFACE_BLUETOOTH,
                                Name = bluetoothDevice.Name,
                                BluetoohDeviceId = bluetoothDevice.DeviceId,
                                MacAddress = macAddress
                            });
                        }
                    });
                    
                    MPosDebug.WriteLine("MposLookupImplementation refreshBluetoothDevicesList: HostName = " + bluetoothDevice.HostName + ", Name = " + bluetoothDevice.Name);
                    MPosDebug.WriteLine("MposLookupImplementation refreshBluetoothDevicesList: BluetoothAddress = " + bluetoothDevice.BluetoothAddress + ", ClassOfDevice = " + bluetoothDevice.ClassOfDevice);
                    MPosDebug.WriteLine("MposLookupImplementation refreshBluetoothDevicesList: ConnectionStatus = " + bluetoothDevice.ConnectionStatus + ", DeviceId = " + bluetoothDevice.DeviceId);
                    //MPosDebug.WriteLine("MposLookupImplementation refreshBluetoothDevicesList: DeviceInformation = " + bluetoothDevice.DeviceInformation);
                    //MPosDebug.WriteLine("MposLookupImplementation refreshBluetoothDevicesList: SdpRecords = " + bluetoothDevice.SdpRecords);
                    //if (bluetoothDevice.RfcommServices != null)
                    //MPosDebug.WriteLine("MposLookupImplementation refreshBluetoothDevicesList: rfcommServices.Count = " + bluetoothDevice.RfcommServices.Count);
                }catch (Exception ex){
                    MPosDebug.WriteLine("MposLookupImplementation refreshBluetoothDevicesList : ex = " + ex.Message);
                }
            }

            MPosDebug.WriteLine("MposLookupImplementation refreshBluetoothDevicesList : Count = " + _pairedDeviceCollection.Count);
            MPosDebug.WriteLine("MposLookupImplementation refreshBluetoothDevicesList : END");
            return _pairedDeviceCollection.Count;
        }
        private async Task<int> refreshWifiDevicesList(int timeout = 2 /*second*/)
        {
            MPosDebug.WriteLine("MposLookupImplementation refreshWifiDevicesList : START");

            _networkDeviceCollection.Clear();
            var discovery = new MPosNetworkDeviceDiscovery(MPosInterfaceType.MPOS_INTERFACE_WIFI, timeout /*search time*/);
            await discovery.startDiscover();
            //await Task.Delay(timeout * 1000);
            MPosDebug.WriteLine("MposLookupImplementation refreshWifiDevicesList : Count = " + MPosNetworkDeviceDiscovery.networkDeviceCollection.Count);
            MPosDebug.WriteLine("MposLookupImplementation refreshWifiDevicesList : END");
            return MPosNetworkDeviceDiscovery.networkDeviceCollection.Count;
        }

        private async Task<int> refreshEthernetDevicesList(int timeout = 2 /*second*/)
        {
            MPosDebug.WriteLine("MposLookupImplementation refreshEthernetDevicesList : START");

            _networkDeviceCollection.Clear();
            var discovery = new MPosNetworkDeviceDiscovery(MPosInterfaceType.MPOS_INTERFACE_ETHERNET, timeout /*search time*/);
            await discovery.startDiscover();
            //await Task.Delay(timeout * 1000);
            MPosDebug.WriteLine("MposLookupImplementation refreshEthernetDevicesList : Count = " + MPosNetworkDeviceDiscovery.networkDeviceCollection.Count);
            MPosDebug.WriteLine("MposLookupImplementation refreshEthernetDevicesList : END");
            return MPosNetworkDeviceDiscovery.networkDeviceCollection.Count;
        }

        public async Task<int> refreshDeivcesList(int interfaceType, int timeout = 3 /*second*/)
        {
            if (timeout <= 0)
                timeout = 5;

            int count = 0;
            MPosDebug.WriteLine("MposLookupImplementation refreshDeivcesList : START");
            MPosDebug.WriteLine("MposLookupImplementation refreshDeivcesList : interfaceType = " + interfaceType + ", timeout = " + timeout);

            bool isMember = Enum.GetValues(typeof(MPosInterfaceType)).Cast<int>().Any(x => x == (int)interfaceType);
            if (!isMember){
                MPosDebug.WriteLine("MposLookupImplementation refreshDeivcesList : MPOS_FAIL_INVALID_INTERFACE");
                MPosDebug.WriteLine("MposLookupImplementation refreshDeivcesList : END");
                return (int)MPosResult.MPOS_FAIL_INVALID_INTERFACE;
            }

            if (timeout < 0)
                timeout = 0;
            try
            {
                switch ((MPosInterfaceType)interfaceType)
                {
                    case MPosInterfaceType.MPOS_INTERFACE_BLUETOOTH:
                        count = await refreshBluetoothDevicesList(timeout);
                        break;
                    case MPosInterfaceType.MPOS_INTERFACE_ETHERNET:
                        count = await refreshEthernetDevicesList(timeout);
                        break;
                    case MPosInterfaceType.MPOS_INTERFACE_WIFI:
                        count = await refreshWifiDevicesList(timeout);
                        break;
                }
            }catch (Exception ex){
                MPosDebug.WriteLine("MposLookupImplementation refreshDeivcesList : ex = " + ex.Message);
            }
            
            MPosDebug.WriteLine("MposLookupImplementation refreshDeivcesList : count = " + count);
            MPosDebug.WriteLine("MposLookupImplementation refreshDeivcesList : END");
            if (count > 0)
                return (int)MPosResult.MPOS_SUCCESS;
            return (int)MPosResult.MPOS_FAIL_NO_DEVICE_FOUND;
        }

        public ObservableCollection<MposConnectionInformation> getBluetoothDevicesList()
        {
            MPosDebug.WriteLine("MposLookupImplementation getBluetoothDevicesList : START");
            MPosDebug.WriteLine("MposLookupImplementation getBluetoothDevicesList : Count = " + _pairedDeviceCollection.Count.ToString());
            MPosDebug.WriteLine("MposLookupImplementation getBluetoothDevicesList : END");
            if (_pairedDeviceCollection.Count > 0)
                return _pairedDeviceCollection;
            return null;
        }
        public ObservableCollection<MposConnectionInformation> getEthernetDeviceList()
        {
            MPosDebug.WriteLine("MposLookupImplementation getEthernetDeviceList : START");
            MPosDebug.WriteLine("MposLookupImplementation getEthernetDeviceList : Count = " + MPosNetworkDeviceDiscovery.networkDeviceCollection.Count);
            MPosDebug.WriteLine("MposLookupImplementation getEthernetDeviceList : END");
            if (MPosNetworkDeviceDiscovery.networkDeviceCollection.Count > 0)
            {
                if (MPosNetworkDeviceDiscovery.networkDeviceCollection[0].IntefaceType != MPosInterfaceType.MPOS_INTERFACE_ETHERNET)
                    return null;
                return MPosNetworkDeviceDiscovery.networkDeviceCollection;
            }
            return null;
        }
        public ObservableCollection<MposConnectionInformation> getWifiDevicesList()
        {
            MPosDebug.WriteLine("MposLookupImplementation getWifiDevicesList : START");
            MPosDebug.WriteLine("MposLookupImplementation getWifiDevicesList : Count = " + MPosNetworkDeviceDiscovery.networkDeviceCollection.Count);
            MPosDebug.WriteLine("MposLookupImplementation getWifiDevicesList : END");
            if (MPosNetworkDeviceDiscovery.networkDeviceCollection.Count > 0)
            {
                if (MPosNetworkDeviceDiscovery.networkDeviceCollection[0].IntefaceType != MPosInterfaceType.MPOS_INTERFACE_WIFI)
                    return null;
                return MPosNetworkDeviceDiscovery.networkDeviceCollection;
            }
            return null;
        }
        public ObservableCollection<MposConnectionInformation> getDeviceList(int interfaceType)
        {
            ObservableCollection<MposConnectionInformation> list = null;
            MPosDebug.WriteLine("MposLookupImplementation getDeviceList : START");

            bool isMember = Enum.GetValues(typeof(MPosInterfaceType)).Cast<int>().Any(x => x == (int)interfaceType);

            if (!isMember)
            {
                MPosDebug.WriteLine("MposLookupImplementation getDeviceList : END");
                return null;
            }

            switch ((MPosInterfaceType)interfaceType)
            {
                case MPosInterfaceType.MPOS_INTERFACE_BLUETOOTH:
                    list = getBluetoothDevicesList();
                    break;
                case MPosInterfaceType.MPOS_INTERFACE_ETHERNET:
                    list = getEthernetDeviceList();
                    break;
                case MPosInterfaceType.MPOS_INTERFACE_WIFI:
                    list = getWifiDevicesList();
                    break;
            }

            MPosDebug.WriteLine("MposLookupImplementation getDeviceList : END");
            return list;
        }


    }
}