using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using System.Runtime.InteropServices;

namespace SmartBowSDK
{
    /// <summary>
    /// Windows连接BluetoothLE
    /// 我的扫描逻辑默认了读写特征都在同一服务下
    /// </summary>
    public class BleWinHelper : MonoBehaviour
    {
        public string LogTag = "BleWinHelper-Log: ";
        private  void Log(string text)
        {
            Debug.Log(LogTag + text);
        }
        private void Warn(string text)
        {
            Debug.LogWarning(LogTag + text);
        }
        private  void Error(string text)
        {
            Debug.Log(LogTag + text);
        }

        private string targetDeviceName = "Bbow_20210501 | ARTEMIS Pro | HOUYI Pro | Pistol | Pistol M9 | BGBox_202012";
        private string targetDeviceNameHOUYIPro = "HOUYI Pro";
        private string targetDeviceNameGun = "Pistol | Pistol M9 | Bbow_20210501";
        private string targetService = "{0000fff0-0000-1000-8000-00805f9b34fb}";
        private string targetCharacteristicsNotify = "{0000fff1-0000-1000-8000-00805f9b34fb}";
        private string targetCharacteristicsWrite = "{0000fff2-0000-1000-8000-00805f9b34fb}";

        private bool isConnectLocking = false;
        private bool isScanningDevices = false;
        private bool isScanningServices = false;
        private bool isScanningCharacteristics = false;
        private bool isSubscribed = false;
        private bool isSubscribing = false;

        private string lastError = null;
        [SerializeField]
        private string selectedDeviceId = null;

        private Dictionary<string, Dictionary<string, string>> deviceList = new Dictionary<string, Dictionary<string, string>>();
        private List<string> serviceList = new List<string>();
        private List<string> characteristicsList = new List<string>();

        private float _connectedTime = 0;
        private float _receiveDataTime = 0;
        private float _heartBeatInterval = 0;


        private Action OnConnected;
        /// <summary>
        /// 主动调用Disconnect()不会触发该委托
        /// </summary>
        private Action OnConnectionFailed;
        private static Action<string, byte[]> OnCharacteristicChanged;

        /// <summary>
        /// 注册window对象
        /// </summary>
        /// <param name="o">挂载对象</param>
        /// <param name="bluetoothWindows">关联的BluetoothWindows</param>
        /// <param name="logTip">提示的log标签</param>
        /// <returns></returns>
        public static BleWinHelper RegisterTo(GameObject o,BluetoothWindows bluetoothWindows,string logTip = "first")
        {
            //if (_Instance)
            //{
            //    Error("Register fail, because only one can be registered.");
            //    return null;
            //}
       
            GameObject obj = new GameObject("BleWinHelper"+ logTip);
            obj.transform.SetParent(o.transform);
         
            BleWinHelper bleWinHelper = obj.AddComponent<BleWinHelper>();
            //日志名字
            bleWinHelper.LogTag = "BleWinHelper-" + logTip + ": ";
            bluetoothWindows.Connect = bleWinHelper.Connect;
            bluetoothWindows.Disconnect = bleWinHelper.Disconnect;
            bluetoothWindows.Write = bleWinHelper.Write;
            bluetoothWindows.WriteByte = bleWinHelper.WriteByte;
            bleWinHelper.OnConnected = () => bluetoothWindows.OnConnected?.Invoke();
            bleWinHelper.OnConnectionFailed = () => bluetoothWindows.OnConnectionFailed?.Invoke();
            //多个定义共用一个OnCharacteristicChanged
            OnCharacteristicChanged += (deviceID,bytes) => {
                if (deviceID == bleWinHelper.selectedDeviceId) { 
                
                    bluetoothWindows.OnCharacteristicChanged?.Invoke(deviceID, bytes);
                }
            };

            return bleWinHelper;
        }

        /// <summary>
        /// 设置心跳检测
        /// 1.每次收到的蓝牙数据都视为心跳
        /// 2.帮助触发蓝牙断开监听
        /// </summary>
        /// <param name="interval">心跳检测间隔</param>
        public void SetHeartBeat(float interval)
        {
            _heartBeatInterval = interval;
        }

        //private static BleWinHelper _Instance;

        void Awake()
        {
           // _Instance = this;
        }

        void OnDestroy()
        {
          //  if (_Instance == this) _Instance = null;
            BleApi.Quit();
        }

        void Update()
        {
            BleApi.ScanStatus status;
            if (isScanningDevices)
            {
                BleApi.DeviceUpdate res = new BleApi.DeviceUpdate();
                do
                {
                    status = BleApi.PollDevice(ref res, false);
                    if (status == BleApi.ScanStatus.AVAILABLE)
                    {
                        if (!deviceList.ContainsKey(res.id))
                            deviceList[res.id] = new Dictionary<string, string>() {
                            { "name", "" },
                            { "isConnectable", "False" }
                        };
                        if (res.nameUpdated)
                            deviceList[res.id]["name"] = res.name;
                        if (res.isConnectableUpdated)
                            deviceList[res.id]["isConnectable"] = res.isConnectable.ToString();

                        //deviceList[res.id]["name"] == targetDeviceName
                        if (targetDeviceName.Contains(deviceList[res.id]["name"]) && deviceList[res.id]["isConnectable"] == "True")
                        {
                            selectedDeviceId = res.id;
                            StopDeviceScan();
                        }

                    }
                    else if (status == BleApi.ScanStatus.FINISHED)
                    {
                        StartCoroutine(ScanServiceAndCharacteristicsToSubscribe());
                    }
                } while (status == BleApi.ScanStatus.AVAILABLE);
            }
            if (isScanningServices)
            {
                BleApi.Service res;
                do
                {
                    status = BleApi.PollService(out res, false);
                    if (status == BleApi.ScanStatus.AVAILABLE)
                    {
                        serviceList.Add(res.uuid);
                    }
                    else if (status == BleApi.ScanStatus.FINISHED)
                    {
                        isScanningServices = false;
                    }
                } while (status == BleApi.ScanStatus.AVAILABLE);
            }
            if (isScanningCharacteristics)
            {
                BleApi.Characteristic res;
                do
                {
                    status = BleApi.PollCharacteristic(out res, false);
                    if (status == BleApi.ScanStatus.AVAILABLE)
                    {
                        Log("res.userDescription:"+ res.userDescription+ ",res.uuid:" + res.uuid);
                        if (res.userDescription == "no description available" ||  //旧设备
                            res.userDescription == "SPP Write Channel" ||   //新设备
                            res.userDescription == "SPP Read Channel")
                        {
                           
                            characteristicsList.Add(res.uuid);
                        }
                        else {
                            //跟着之前代码
                            characteristicsList.Add(res.userDescription);
                        }
                        //string name = res.userDescription != "no description available" ? res.userDescription : res.uuid;
                        //characteristicsList.Add(name);
                    }
                    else if (status == BleApi.ScanStatus.FINISHED)
                    {
                        isScanningCharacteristics = false;
                    }
                } while (status == BleApi.ScanStatus.AVAILABLE);
            }
            if (isSubscribed)
            {
                BleApi.BLEData res;
                while (BleApi.PollData(out res, false))
                {
                    //string text = BitConverter.ToString(res.buf, 0, res.size);
                    // string text = Encoding.ASCII.GetString(res.buf, 0, res.size);
                    byte[] bytes = new byte[res.size];
                    Array.Copy(res.buf, bytes, res.size);
                    _receiveDataTime = Time.realtimeSinceStartup;
                    OnCharacteristicChanged?.Invoke(res.deviceId, bytes);

                }
            }
            {
                BleApi.ErrorMessage res;
                BleApi.GetError(out res);
                if (lastError != res.msg)
                {
                    Error(res.msg);
                    lastError = res.msg;
                    //对应设备才断开
                    if (lastError.Contains("SendDataAsync") && lastError.Contains(selectedDeviceId) && isSubscribed)
                    {
                        HandleConnectFail();
                    }
                }
            }
        }

        void LateUpdate()
        {
            if (
                _heartBeatInterval > 0 &&
                isSubscribed &&
                Time.realtimeSinceStartup - _connectedTime >= _heartBeatInterval &&
                Time.realtimeSinceStartup - _receiveDataTime >= _heartBeatInterval
            )
            {
                HandleConnectFail();
            }
              
        }

        private bool Connect()
        {
            if (isConnectLocking || isScanningDevices)
            {
                Warn("Connect Invalid, because is in connect.");
                return false;
            }
            BleApi.StartDeviceScan();
            isConnectLocking = true;
            isScanningDevices = true;
            Log("Start Connect");
            return true;
        }

        private void StopDeviceScan()
        {
            if (!isScanningDevices) return;
            BleApi.StopDeviceScan();
            isScanningDevices = false;
        }

        private IEnumerator ScanServiceAndCharacteristicsToSubscribe()
        {
            isSubscribing = true;

            if (selectedDeviceId == null)
            {
                HandleConnectFail();
                yield break;
            }
            Log("SelectedDeviceId OK");

            BleApi.ScanServices(selectedDeviceId);
            isScanningServices = true;
            serviceList.Clear();

            while (isScanningServices) yield return null;

            bool findTargetService = false;

            foreach (string service in serviceList)
            {
                if (service == targetService)
                {
                    findTargetService = true;
                    Log("FindTargetService OK");
                    break;
                }
            }

            if (!findTargetService)
            {
                HandleConnectFail();
                yield break;
            }

            BleApi.ScanCharacteristics(selectedDeviceId, targetService);
            isScanningCharacteristics = true;
            characteristicsList.Clear();

            while (isScanningCharacteristics) yield return null;

            bool findTargetCharacteristicsNotify = false;
            bool findTargetCharacteristicsWrite = false;

            foreach (string characteristics in characteristicsList)
            {
                if (characteristics == targetCharacteristicsNotify)
                {
                    findTargetCharacteristicsNotify = true;
                    Log("FindTargetCharacteristicsNotify OK");
                }
                else if (characteristics == targetCharacteristicsWrite)
                {
                    findTargetCharacteristicsWrite = true;
                    Log("FindTargetCharacteristicsWrite OK");
                }
            }

            if (!findTargetCharacteristicsNotify || !findTargetCharacteristicsWrite)
            {
                HandleConnectFail();
                yield break;
            }

            BleApi.SubscribeCharacteristic(selectedDeviceId, targetService, targetCharacteristicsNotify, false);
            isSubscribed = true;
            isSubscribing = false;
            Log("SubscribeCharacteristicNotify OK");

            _connectedTime = Time.realtimeSinceStartup;
            OnConnected?.Invoke();
        }

        private void HandleConnectFail()
        {
            if (selectedDeviceId != null) BleApi.Disconnect(selectedDeviceId);
            bool isLockBefore = isConnectLocking;
            ReinitAfterConnectFail();
            if (isLockBefore) OnConnectionFailed?.Invoke();
        }

        private void ReinitAfterConnectFail()
        {
            isConnectLocking = false;
            isScanningDevices = false;
            isScanningServices = false;
            isScanningCharacteristics = false;
            isSubscribed = false;
            isSubscribing = false;
            selectedDeviceId = null;
            deviceList.Clear();
            serviceList.Clear();
            characteristicsList.Clear();
        }

        private bool Write(string text)
        {
            if (!isSubscribed) return false;
            byte[] payload = Encoding.ASCII.GetBytes(text);
            BleApi.BLEData data = new BleApi.BLEData();
            data.buf = new byte[512];
            data.size = (short)payload.Length;
            data.deviceId = selectedDeviceId;
            data.serviceUuid = targetService;
            data.characteristicUuid = targetCharacteristicsWrite;
            for (int i = 0; i < payload.Length; i++)
                data.buf[i] = payload[i];
            // no error code available in non-blocking mode
            BleApi.SendData(in data, false);
            Log("Write(" + text + ")");
            return true;
        }


        private bool WriteByte(byte[] payload)
        {
            if (!isSubscribed) return false;

            BleApi.BLEData data = new BleApi.BLEData();
            data.buf = new byte[512];
            data.size = (short)payload.Length;
            data.deviceId = selectedDeviceId;
            data.serviceUuid = targetService;
            data.characteristicUuid = targetCharacteristicsWrite;

            for (int i = 0; i < payload.Length; i++)
                data.buf[i] = payload[i];

            // no error code available in non-blocking mode
            BleApi.SendData(in data, false);

            Log("Write(byte[])");
            return true;
        }

        private bool Disconnect()
        {
            if (!isConnectLocking)
            {
                Warn("Disconnect Invalid, because not in connect.");
                return false;
            }
            if (isSubscribing)
            {
                Warn("Disconnect Invalid, because is subscribing.");
                return false;
            }
            if (isScanningDevices) StopDeviceScan();
            if (selectedDeviceId != null) BleApi.Disconnect(selectedDeviceId);
            ReinitAfterConnectFail();
            Log("Disconnect OK");
            return true;
        }
    }
   
}
