using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Plugin.BLE;
using Plugin.BLE.Abstractions.Contracts;


namespace InABox.Mobile
{
    public class Bluetooth
    {
        public delegate void BluetoothEvent(Bluetooth sender);
        public event BluetoothEvent OnScanFinished;

        public TimeSpan ScanDelay { get; set; }
        public String[] Devices { get; private set; }
        public int[] BatteryLevels { get; private set; }
        public DateTime TimeStamp { get; private set; }
        public string[] KnownBlueToothMACAddresses { get; set; }
        public List<string> DetectedBlueToothMACAddresses { get; set; }
        public List<string> SavedBlueToothMACAddresses { get; set; }

        private Dictionary<String, IDevice> _devicemap = new Dictionary<string, IDevice>();

        IBluetoothLE bluetooth = null;
        private bool bScanning = false;

        private Dictionary<String, int> DiscoveredDevices = new Dictionary<string, int>();

        private Dictionary<String, String> _keys = null;

        private bool _disabled = false;

        public bool Disabled
        {
            get { return _disabled; }
            set
            {
                if (IsScanning)
                    StopScanning();
                _disabled = true;
            }
        }

        ////List<IDevice> devices = new List<IDevice>();
        //List<GPSTrackerLocation> bluetoothdevices = new List<GPSTrackerLocation>();

        public Bluetooth() : base()
        {
            TimeStamp = DateTime.MinValue;
            ScanDelay = new TimeSpan(0, 6, 0);
            //ScanDelay = new TimeSpan(0, 0, 0);
            bluetooth = CrossBluetoothLE.Current;
            bluetooth.Adapter.DeviceDiscovered += Adapter_DeviceDiscovered;
            bluetooth.Adapter.ScanMode = Plugin.BLE.Abstractions.Contracts.ScanMode.LowLatency;
            bluetooth.Adapter.ScanTimeout = 5000;
            bluetooth.Adapter.ScanTimeoutElapsed += ScanFinished;
            Devices = new String[] { };
            BatteryLevels = new int[] { };
            DetectedBlueToothMACAddresses = new List<string>();
            KnownBlueToothMACAddresses = new String[] { };
            SavedBlueToothMACAddresses = new List<string>();
        }

        public async Task UnlockDigitalKey(String macaddress, Guid serviceid, Guid characteristicid, String key)
        {

            
            
            //if (!_disabled)
            //    throw new Exception("BT Scanning must be disabled!");

            _devicemap.TryGetValue(macaddress, out IDevice device);
            if (device == null)
                throw new Exception("Device not Found!");

            try
            {
                await bluetooth.Adapter.ConnectToDeviceAsync(device);
                if (!bluetooth.Adapter.ConnectedDevices.Contains(device))
                    throw new Exception("Cannot Connect to Device!");

                var service = await device.GetServiceAsync(serviceid);
                if (service == null)
                    throw new Exception("Service Not Found!");

                var characteristic = await service.GetCharacteristicAsync(characteristicid);
                if (characteristic == null)
                    throw new Exception("Characteristic Not Found!");

                var write = await characteristic.WriteAsync(Encoding.ASCII.GetBytes(key.ToUpper()));

                await bluetooth.Adapter.DisconnectDeviceAsync(device);

            }
            catch
            {
                if (bluetooth.Adapter.ConnectedDevices.Contains(device))
                    await bluetooth.Adapter.DisconnectDeviceAsync(device);
                throw;
            }
        }

        public bool IsScanning { get; private set; }

        private async Task<bool> UnlockDevice(IDevice idevice, String key)
        {
            bool result = false;
            //Console.WriteLine(String.Format("** Found Device: {0} {1}", idevice.Name, idevice.Rssi));
            if (idevice.Rssi < -65)
            {
                //Console.WriteLine(String.Format("** Device RSSI is too low: {0}", idevice.Rssi));
                return false;
            }
            try
            {
                await bluetooth.Adapter.ConnectToDeviceAsync(idevice);

                IService service = await idevice.GetServiceAsync(Guid.Parse("3317F54C-1C7E-EBE7-026E-3C819FD35476"));
                if (service != null)
                {
                    //Console.WriteLine("** Found Service 3317F54C-1C7E-EBE7-026E-3C819FD35476");
                    ICharacteristic chr = await service.GetCharacteristicAsync(Guid.Parse("20852B39-4455-EE15-089B-D1629FE76927"));
                    if (chr != null)
                    {
                        //Console.WriteLine("** Found Characteristic 20852B39-4455-EE15-089B-D1629FE76927");
                        //var data = await chr.ReadAsync();
                        //Console.WriteLine("- Found Data: " + String.Join("-", data.Select(x => String.Format("{0:X2}", x))));
                        var newdata = Encoding.ASCII.GetBytes(key);
                        bool bWrite = await chr.WriteAsync(newdata) == 0;
                        if (bWrite)
                        {
                            //Console.WriteLine("** Wrote Data " + String.Join("-", newdata.Select(x => String.Format("{0:X2}", x))));
                            //var confdata = await chr.ReadAsync();
                            //Console.WriteLine("- Read Data: " + String.Join("-", confdata.Select(x => String.Format("{0:X2}", x))));
                            result = true;
                        }
                    }
                }
                await bluetooth.Adapter.DisconnectDeviceAsync(idevice);
            }
            catch (Exception err)
            {
                // ... could not connect to device
            }
            return result;
        }

        private async void Adapter_DeviceDiscovered(object sender, Plugin.BLE.Abstractions.EventArgs.DeviceEventArgs e)
        {
            try
            {
                await Task.Run(() =>
                {
                    if (string.IsNullOrWhiteSpace(e.Device.NativeDevice.ToString()))
                        return;

                    if (_devicemap.ContainsKey(e.Device.NativeDevice.ToString()))
                        _devicemap[e.Device.NativeDevice.ToString()] = e.Device;
                    else
                        return;

                    if (KnownBlueToothMACAddresses?.Any() == false)
                        return;

                    string deviceID = e.Device.NativeDevice.ToString();
                    if (KnownBlueToothMACAddresses.Contains(deviceID))
                    {
                        if (!DetectedBlueToothMACAddresses.Contains(deviceID))
                        {
                            DetectedBlueToothMACAddresses.Add(deviceID);
                        }
                        if (!SavedBlueToothMACAddresses.Contains(deviceID))
                        {
                            SavedBlueToothMACAddresses.Add(deviceID);
                        }
                    }
                });
            }
            catch
            { }


            #region OLD
            //if (_keys != null)
            //{
            //    String address = e.Device.NativeDevice?.ToString();
            //    if (_keys.ContainsKey(address))
            //    {
            //        //bool result = await UnlockDevice(e.Device, _keys[address]);
            //        //if (result)
            //        //{ 
            //        //    await bluetooth.Adapter.StopScanningForDevicesAsync();
            //        //    IsScanning = false;
            //        //}
            //    }
            //}

            //var record = e.Device.AdvertisementRecords.FirstOrDefault(X => X.Type == Plugin.BLE.Abstractions.AdvertisementRecordType.ServiceData);
            //if (record != null)
            //{

            //    byte[] id = record.Data.TakeLast(record.Data.Length == 14 ? 6 : 4).ToArray();
            //    String sid = "";
            //    foreach (byte bs in id)
            //        sid = sid + Convert.ToChar(bs);

            //    DiscoveredDevices[sid] = record.Data.Length >= 7 ? (int)record.Data[6] : -1;
            //}
            #endregion
        }

        public bool RecentlyScanned
        {
            get
            {
                return (DateTime.Now.Subtract(TimeStamp).Ticks < ScanDelay.Ticks);
            }
        }

        public void ScanForDevices(Dictionary<String, String> keys = null)
        {
            if (_disabled)
                return;

            try
            {
                if (bluetooth.Adapter.IsScanning)
                {
                    if (keys != null)
                    {
                        StopScanning();
                    }
                    else
                        return;
                }
                
                DetectedBlueToothMACAddresses.Clear();
                SavedBlueToothMACAddresses.Clear();

                if (!bluetooth.IsAvailable)
                {
                    ScanFinished(this, new EventArgs());
                    IsScanning = false;
                }
                else
                {
                    IsScanning = true;
                    bluetooth.Adapter.StartScanningForDevicesAsync().ConfigureAwait(true);
                }
            }
            catch (Exception e)
            { }
        }

        private void StopScanning()
        {
            bluetooth.Adapter.StopScanningForDevicesAsync().ConfigureAwait(true);
            IsScanning = false;
        }

        private void ScanFinished(object sender, EventArgs e)
        {
            try
            {
                _keys = null;

                TimeStamp = DateTime.Now;

                //List<String> devices = new List<String>();
                //List<int> levels = new List<int>();

                //foreach (var key in DiscoveredDevices.Keys)
                //{
                //    devices.Add(key);
                //    levels.Add(DiscoveredDevices[key]);
                //}

                Devices = SavedBlueToothMACAddresses.ToArray();
                //BatteryLevels = levels.ToArray();

                OnScanFinished?.Invoke(this);
                IsScanning = false;
            }
            catch
            { }

        }


    }
}
