//
//  BleModule.swift
//
//  Created by Konrad Rodzik on 7/4/16.
//

import Foundation
import CoreBluetooth

@objc
public protocol BleClientManagerDelegate {
    func dispatchEvent(_ name: String, value: Any)
}

@objc
public class BleClientManager : NSObject {

    // Delegate is used to send events to
    @objc
    public var delegate: BleClientManagerDelegate?

    // RxBlutoothKit's manager
    private let manager : BluetoothManager

    // Dispatch queue used for BLE
    private let queue : DispatchQueue

    // MARK: Caches ----------------------------------------------------------------------------------------------------

    // Map of discovered services in any of connected devices.
    private var discoveredServices = [Double: Service]()

    // Map of discovered characteristics in any of connected devices.
    private var discoveredCharacteristics = [Double: Characteristic]()

    // Map of discovered descriptors in any of connected devices.
    private var discoveredDescriptors = [Double: Descriptor]()

    // Map of currently connected peripherals.
    private var connectedPeripherals = Dictionary<UUID, Peripheral>()

    // Map of monitored characteristics observables. For monitoring sharing.
    private var monitoredCharacteristics = Dictionary<Double, Observable<Characteristic>>()

    // MARK: Disposables -----------------------------------------------------------------------------------------------

    // Disposable for detecting state changes of BleManager
    private var stateDisposable = Disposables.create()

    // Disposable for detecing state restoration of BleManager.
    private var restorationDisposable = Disposables.create()

    // Scan disposable which is removed when new scan is created.
    private let scanDisposable = SerialDisposable()

    // Disposable map for connecting peripherals.
    private let connectingPeripherals = DisposableMap<UUID>()

    // Disposable map for every transaction.
    private let transactions = DisposableMap<String>()

    // Map of pending read operations.
    private var pendingReads = Dictionary<Double, Int>()

    // Constants
    static let cccdUUID = CBUUID(string: "2902")
    
    // MARK: Lifecycle -------------------------------------------------------------------------------------------------

    @objc
    required public init(queue: DispatchQueue, restoreIdentifierKey: String?) {
        self.queue = queue

        if let key = restoreIdentifierKey {
            manager = BluetoothManager(queue: queue,
                                       options: [CBCentralManagerOptionRestoreIdentifierKey: key as AnyObject])
        } else {
            manager = BluetoothManager(queue: queue)
        }

        super.init()
        stateDisposable = manager.rx_state.subscribe(onNext: { [weak self] newState in
            self?.onStateChange(newState)
        })

        if restoreIdentifierKey != nil {
            restorationDisposable = Observable<RestoredState?>.amb([
                    manager.rx_state.skip(1).map { _ in nil },
                    manager.listenOnRestoredState().map { $0 as RestoredState? }
                ])
                .take(1)
                .subscribe(onNext: {[weak self] newRestoredState in
                    self?.onRestoreState(newRestoredState)
                })
        }
    }

    @objc
    public func invalidate() {
        // Disposables
        stateDisposable.dispose()
        restorationDisposable.dispose()
        scanDisposable.disposable = Disposables.create()
        transactions.dispose()
        connectingPeripherals.dispose()

        // Caches
        discoveredServices.removeAll()
        discoveredCharacteristics.removeAll()
        discoveredDescriptors.removeAll()
        monitoredCharacteristics.removeAll()
        connectedPeripherals.forEach { (_, device) in
            _ = device.cancelConnection().subscribe()
        }
        connectedPeripherals.removeAll()
        pendingReads.removeAll()
    }

    deinit {
        // We don't use deinit to deinitialize BleClientManager. User
        // should call invalidate() before destruction of this object.
        // In such case observables can call [weak self] closures properly.
    }
    
    private var updata_over_time: Int = 0
    private var updata_state: Int = 0
    private var tx_data_add: Int = 0
    private var my_command: UInt8 = UInt8(25)
    private var fis: FileHandle?
    private var file_length: Int = 0
    private var frame_number: UInt8 = 0
    private var ota_ing = false
    private var fame_finish = false
    private var fame_tx_sp = 0
    
    private var fame_miss = false
    private var fame_length = 43
    private var fame_data: Data = Data(repeating: 0, count: 45);

    
    private var otaResolve:Resolve?
    private var timer: Timer?
    
    private var xor_tmp:UInt8  = 0
    private var i:Int = 0
    
    private var ota_device_id: String = ""
    
    // Mark: custom ----------------------------------------------------------------------------------------------------
    @objc
    public func ota(_ deviceIdentifier: String, downloadUrl: String, command: Int, resolve: @escaping Resolve, reject: @escaping Reject) {
        print("-- custom  调用了ota方法")
        guard let url = URL(string: downloadUrl) else {
            print("-- custom  下载链接打不开")
            BleError(errorCode: .OTAFirmwareErrorDownloadFailed, internalMessage: "下载链接打不开").callReject(reject)
            return
        }
        ota_device_id = deviceIdentifier
        let seesion = URLSession.shared
        let request = URLRequest(url: url)
        let downloadTask = seesion.downloadTask(with: request) {[weak self] location, res, error in
            guard let self = self else { return }
            if let location = location, let response = res {
                let fileName = response.suggestedFilename
                let PathLibrary = "\(NSHomeDirectory())/Library"
                let PathCaches = "\(PathLibrary)/Caches"
                let path = "\(PathCaches)/\(fileName!)"
                print("-- custom  下载地址 \(location.absoluteString)")
                do {
                    try? FileManager.default.removeItem(atPath: path)
                    try FileManager.default.moveItem(at: location, to: URL(fileURLWithPath: path))
                    print("-- custom  移动目标位置 \(path)")
                    self.fis = FileHandle(forReadingAtPath: path)
                    let fileAttributes = try FileManager.default.attributesOfItem(atPath: path)
                    self.file_length = fileAttributes[.size] as? Int ?? 0
                    if self.file_length == 0 {
                        BleError(errorCode: .OTAFirmwareErrorFileOpenFailed).callReject(reject)
                        return
                    }
                    
                    print("-- custom 准备开始升级")
                    self.updata_state = 1
                    self.tx_data_add = 0
                    self.my_command = UInt8(command)
                    self.otaResolve = resolve
                    self.timer?.invalidate()
                    self.ota_ing = true
                    DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)){[self] in
                        self.timer = Timer.scheduledTimer(timeInterval: 2, target: self, selector: #selector(self.timerFunc), userInfo: nil, repeats: true)
                        self.dispatchEvent(BleEvent.otaEvent, value: [NSNull(), 0, NSNull()])
                    }

                } catch {
                    print("-- custom 固件下载失败 \((error as NSError).description)")
                    BleError(errorCode: .OTAFirmwareErrorDownloadFailed).callReject(reject)
                }
  
            } else {
                print("-- custom 固件下载失败")
                BleError(errorCode: .OTAFirmwareErrorDownloadFailed, internalMessage: error?.localizedDescription).callReject(reject)
            }
            
        }
        
        downloadTask.resume()
        
    }
    
    
    @objc
    func timerFunc() {
        var buff = Data(repeating: 0, count: 43)
        if (ota_ing){
            switch updata_state {
            case 0:
                buff[0] = 0xaa
                buff[1] = 0x55
                buff[2] = frame_number;
                buff[3] = 0x0d;
                buff[4] = 0x0d;
                
                for i in 5..<18 {
                    buff[i] = 0
                }
                
                for i in 0..<18 {
                    xor_tmp ^= buff[i]
                }
                
                buff[18] = xor_tmp
                buff[19] = 0x0d
                
                // 发送
                innerWriteCharacteristicForDevice(ota_device_id, buff: buff)
                
                frame_number += 1
            case 1:
                buff[0] = 0xaa
                buff[1] = 0x55
                buff[2] = frame_number
                buff[3] = 0x0d
                buff[4] = my_command
                buff[5] = 0xa0
                buff[6] = 0x0a

                for i in 7..<18 {
                    buff[i] = 0
                }
                for i in 0..<18 {
                    xor_tmp ^= buff[i]
                }
                buff[18] = xor_tmp
                buff[19] = 0x0d;
                
                // 发送
                innerWriteCharacteristicForDevice(ota_device_id, buff: buff)
                
                frame_number += 1
            case 2:
                let s_buff = "Linking"
                var byteArray = [UInt8](repeating: 0, count: s_buff.utf8.count)
                for (i, char) in s_buff.utf8.enumerated() {
                    byteArray[i] = UInt8(char)
                }
                print("-- custom  发送 Linking")
                innerWriteCharacteristicForDevice(ota_device_id, buff: Data(byteArray))
            case 3:
                let s_buff = "UPDATA"
                var byteArray = [UInt8](repeating: 0, count: s_buff.utf8.count)
                for (i, char) in s_buff.utf8.enumerated() {
                    byteArray[i] = UInt8(char)
                }
                print("-- custom  发送 UPDATA")
                innerWriteCharacteristicForDevice(ota_device_id, buff: Data(byteArray))
                
                // 开始发送文件数据了
                updata_state = 4
                fame_finish = false
                timer?.invalidate()
                DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1000)){[self] in
                    self.timer = Timer.scheduledTimer(timeInterval: 0.025, target: self, selector: #selector(self.timerFunc), userInfo: nil, repeats: true)
                    self.dispatchEvent(BleEvent.otaEvent, value: [NSNull(), 3, NSNull()])
                }
            case 4:// 发送 ROM 数据
                if (!fame_finish) {
                    print("-- custom  准备发送文件长度: \(file_length), 当前已发送文件长度: \(tx_data_add)")
                    if (file_length > tx_data_add) {
                        if (fame_tx_sp == 0 && !fame_miss) {
                            var i = 0
                            while i < fame_length {
                                fame_data[i] = fis?.readData(ofLength: 1).first ?? 0
                                tx_data_add += 1
                                
                                if (fame_data[0] != 58) || (fame_data[1] > 49) {
                                    // 处理读取文件错误的情况
                                    updata_state = 0
                                    timer?.invalidate()
                                    ota_ing = false
                                    self.dispatchEvent(BleEvent.otaEvent, value: [NSNull(), 11, NSNull()])
                                    return
                                } else {
                                    if i == 2 {
                                        if (fame_data[2] > 47 && fame_data[2] < 58) {
                                            fame_length = (Int(fame_data[1]) - 48) * 32 + (Int(fame_data[2]) - 48) * 2 + 13
                                        } else if (fame_data[2] > 64 && fame_data[2] < 71) {
                                            fame_length = (Int(fame_data[1]) - 48) * 32 + (Int(fame_data[2]) - 55) * 2 + 13
                                        }
                                    }
                                }
                                
                                i += 1
                            }
                            // ios for 循环机制 不会重新读循环条件
//                            for i in 0..<fame_length {
//                                
//                                if let byte = fis?.readData(ofLength: 1).first {
//                                    fame_data[i] = byte
//                                    tx_data_add += 1
//                                    
//                                    if i == 2 {
//                                        if (fame_data[2] > 47 && fame_data[2] < 58) {
//                                            fame_length = (Int(fame_data[1]) - 48) * 32 + (Int(fame_data[2]) - 48) * 2 + 13
//                                        } else if (fame_data[2] > 64 && fame_data[2] < 71) {
//                                            fame_length = (Int(fame_data[1]) - 48) * 32 + (Int(fame_data[2]) - 55) * 2 + 13
//                                        }
//                                    }
//                                    
//                                } else {
//                                    // 处理读取文件错误的情况
//                                    updata_state = 0
//                                    timer?.invalidate()
//                                    ota_ing = false
//                                    self.dispatchEvent(BleEvent.otaEvent, value: [NSNull(), 11, NSNull()])
//                                    return
//                                }
//                                
//                            }
                            
                        }
                        
                        if fame_miss {
                            print("-- custom  文件发送miss，重新发上一帧")
                        }
                        
                        fame_miss = false
                        fame_finish = true
                        print("-- custom  准备发送数据:\(fame_data.map({ $0 }))")
                        innerWriteCharacteristicForDevice(ota_device_id, buff: fame_data)
                        
                    }

                }
            default:
                break
            }
            
        }
        
        if updata_state == 4 {
            if updata_over_time > 80 {
                updata_state = 0
                timer?.invalidate()
                ota_ing = false
                // 超时了
                self.dispatchEvent(BleEvent.otaEvent, value: [NSNull(), 10, NSNull()])
            } else if updata_over_time < 300 {
                updata_over_time += 1
            }
        }
        
    }
    
    
    private func displayData(data: Data) {
        var data_tmp = 0
        var anumber = 0
        var number: CGFloat = 0
        var idata = data_charge(data: data)
        var xor_tmp: UInt8 = 0
        var RX_DATA = String(data: data, encoding: .utf8)
        
        updata_over_time = 0

        print("-- custom  RX_DATA:\(RX_DATA ?? ""), \(updata_state)")
        
        switch updata_state {
        case 0:
            if (data[0] == 0x55) && (data[1] == 0xaa) && (data[3] == 0x0d) {
                for i in 0..<18 {
                    xor_tmp ^= data[i]
                }
                if xor_tmp == data[18] {
                    if data[4] == 0x0D {
                        data_tmp = idata[6]
                        number = CGFloat(data_tmp) / 10.0
                        print("-- custom  HardWareVersion: \(number)")

                        data_tmp = idata[5]
                        number = CGFloat(data_tmp)
                        
                        print("-- custom  SoftWareVersion: \(number)")

                    }
                }
                
            } else {
                anumber = 0
                for i in 0..<data.count {
                    switch anumber {
                    case 0:
                        if data[i] == UInt8(ascii: "R") {
                            anumber += 1
                        }
                    case 1:
                        if data[i] == UInt8(ascii: "e") {
                            anumber += 1
                        } else {
                            anumber = 0
                        }
                    case 2:
                        if data[i] == UInt8(ascii: "a") {
                            anumber += 1
                        } else {
                            anumber = 0
                        }
                    case 3:
                        if data[i] == UInt8(ascii: "d") {
                            anumber += 1
                        } else {
                            anumber = 0
                        }
                    case 4:
                        if data[i] == UInt8(ascii: "y") {
                            anumber += 1
                            print("-- custom  Ready!")
                        } else {
                            anumber = 0
                        }
                    default:
                        return
                    }
                }
                
            }
        case 1:
            anumber = 0
            for i in 0..<data.count {
                switch anumber {
                case 0:
                    if data[i] == UInt8(ascii: "R") {
                        anumber += 1
                    }
                case 1:
                    if data[i] == UInt8(ascii: "e") {
                        anumber += 1
                    } else {
                        anumber = 0
                    }
                case 2:
                    if data[i] == UInt8(ascii: "a") {
                        anumber += 1
                    } else {
                        anumber = 0
                    }
                case 3:
                    if data[i] == UInt8(ascii: "d") {
                        anumber += 1
                    } else {
                        anumber = 0
                    }
                case 4:
                    if data[i] == UInt8(ascii: "y") {
                        anumber += 1
                        updata_state = 2
                        print("-- custom  Ready!")
                        self.dispatchEvent(BleEvent.otaEvent, value: [NSNull(), 1, NSNull()])
                        my_command = 0
                    } else {
                        anumber = 0
                    }
                default:
                    return
                }
            }
        case 2:
            anumber = 0
            for i in 0..<data.count {
                switch anumber {
                case 0:
                    if data[i] == UInt8(ascii: "L") {
                        anumber += 1
                    }
                case 1:
                    if data[i] == UInt8(ascii: "i") {
                        anumber += 1
                    } else {
                        anumber = 0
                    }
                case 2:
                    if data[i] == UInt8(ascii: "n") {
                        anumber += 1
                    } else {
                        anumber = 0
                    }
                case 3:
                    if data[i] == UInt8(ascii: "k") {
                        anumber += 1
                    } else {
                        anumber = 0
                    }
                case 4:
                    if data[i] == UInt8(ascii: "e") {
                        anumber += 1
                    } else {
                        anumber = 0
                    }
                case 5:
                    if data[i] == UInt8(ascii: "d") {
                        anumber += 1
                        updata_state = 3
                        print("-- custom  Linked!")
                        self.dispatchEvent(BleEvent.otaEvent, value: [NSNull(), 2, NSNull()])
                    } else {
                        anumber = 0
                    }
                default:
                    return
                }
            }
        case 4:
            anumber = 1
            fame_finish = false
            if data[0] == UInt8(ascii: "T") {
                for i in 1..<data.count {
                    switch anumber {
                    case 1:
                        if data[i] == UInt8(ascii: "a") {
                            anumber += 1
                        } else {
                            anumber = 0
                        }
                    case 2:
                        if data[i] == UInt8(ascii: "k") {
                            anumber += 1
                        } else {
                            anumber = 0
                        }
                    case 3:
                        if data[i] == UInt8(ascii: "e") {
                            anumber += 1
                            updata_state = 4
                            print("-- custom  Take!")
                            self.dispatchEvent(BleEvent.otaEvent, value: [NSNull(), 5, NSNull()])
                        } else {
                            anumber = 0
                        }
                    default:
                        return
                    }
                }
            } else if data[0] == UInt8(ascii: "D") {
                for i in 1..<data.count {
                    switch anumber {
                    case 1:
                        if data[i] == UInt8(ascii: "o") {
                            anumber += 1
                        } else {
                            anumber = 0
                        }
                    case 2:
                        if data[i] == UInt8(ascii: "n") {
                            anumber += 1
                        } else {
                            anumber = 0
                        }
                    case 3:
                        if data[i] == UInt8(ascii: "e") {
                            anumber += 1
                            updata_state = 4
                            print("-- custom  Done!")
                            self.dispatchEvent(BleEvent.otaEvent, value: [NSNull(), 4, "\(tx_data_add)-\(file_length)"])
                        } else {
                            anumber = 0
                        }
                    default:
                        return
                    }
                }
            } else if data[0] == UInt8(ascii: "M") {
                for i in 1..<data.count {
                    switch anumber {
                    case 1:
                        if data[i] == UInt8(ascii: "i") {
                            anumber += 1
                        } else {
                            anumber = 0
                        }
                    case 2:
                        if data[i] == UInt8(ascii: "s") {
                            anumber += 1
                        } else {
                            anumber = 0
                        }
                    case 3:
                        if data[i] == UInt8(ascii: "s") {
                            anumber += 1
                            updata_state = 4
                            fame_miss = true
                            print("-- custom  Miss!")
//                            self.dispatchEvent(BleEvent.otaEvent, value: [NSNull(), 6, NSNull()])
                        } else {
                            anumber = 0
                        }
                    default:
                        return
                    }
                }
            } else if data[0] == UInt8(ascii: "E") {
                for i in 1..<data.count {
                    switch anumber {
                    case 1:
                        if data[i] == UInt8(ascii: "R") {
                            anumber += 1
                        } else {
                            anumber = 0
                        }
                    case 2:
                        if data[i] == UInt8(ascii: "R") {
                            anumber += 1
                        } else {
                            anumber = 0
                        }
                    case 3:
                        if data[i] == UInt8(ascii: ":") {
                            anumber += 1
                        } else {
                            anumber = 0
                        }
                    case 4:
                        if data[i] == UInt8(ascii: "0") {
                            anumber += 1
                        } else {
                            anumber = 0
                        }
                    case 5:
                        if data[i] == UInt8(ascii: "1") {
                            print("-- custom  传输数据错误!")
                            self.dispatchEvent(BleEvent.otaEvent, value: [NSNull(), 12, NSNull()])
                        } else {
                            print("-- custom  驱动版本错误!")
                            self.dispatchEvent(BleEvent.otaEvent, value: [NSNull(), 13, NSNull()])
                        }
                        updata_state = 0
                        anumber += 1
                        timer?.invalidate()
                        tx_data_add = 0
                        ota_ing = false
                        fis?.closeFile()
                    default:
                        return
                    }
                }
            } else if data[0] == UInt8(ascii: "F") {
                for i in 1..<data.count {
                    switch anumber {
                    case 1:
                        if data[i] == UInt8(ascii: "i") {
                            anumber += 1
                        } else {
                            anumber = 0
                        }
                    case 2:
                        if data[i] == UInt8(ascii: "n") {
                            anumber += 1
                        } else {
                            anumber = 0
                        }
                    case 3:
                        if data[i] == UInt8(ascii: "i") {
                            anumber += 1
                        } else {
                            anumber = 0
                        }
                    case 4:
                        if data[i] == UInt8(ascii: "s") {
                            anumber += 1
                        } else {
                            anumber = 0
                        }
                    case 5:
                        if data[i] == UInt8(ascii: "h") {
                            print("-- custom  Finish!")
                            print("-- custom  驱动更新成功!")
                            updata_state = 0
                            anumber += 1
                            timer?.invalidate()
                            ota_ing = false
                            self.dispatchEvent(BleEvent.otaEvent, value: [NSNull(), 200, NSNull()])
                            fis?.closeFile()
                        }
                    default:
                        return
                    }
                }
            }
            
        default:
            break
        }
        
    }
    
    private func data_charge(data: Data) -> [Int] {
        var idata = [Int](repeating: 0, count: 32)
        for i in 0..<data.count {
            if data[i] < 0 {
                idata[i] = Int(data[i]) + 256
            } else {
                idata[i] = Int(data[i])
            }
        }
        return idata
    }


    // Mark: Common ----------------------------------------------------------------------------------------------------

    // User is able to cancel any "atomic" operation which is contained in transactions map.
    @objc
    public func cancelTransaction(_ transactionId: String) {
        transactions.removeDisposable(transactionId)
    }

    // User is able to enable logging of RxBluetoothKit to show how real device responds.
    @objc
    public func setLogLevel(_ logLevel: String) {
        RxBluetoothKitLog.setLogLevel(RxBluetoothKitLog.LogLevel(jsObject: logLevel))
    }

    // User can retrieve current log level.
    @objc
    public func logLevel(_ resolve: Resolve, reject: Reject) {
        resolve(RxBluetoothKitLog.getLogLevel().asJSObject)
    }

    // Mark: Monitoring state ------------------------------------------------------------------------------------------

    @objc
    public func enable(_ transactionId: String, resolve: Resolve, reject: Reject) {
        BleError(errorCode: .BluetoothStateChangeFailed).callReject(reject)
    }

    @objc
    public func disable(_ transactionId: String, resolve: Resolve, reject: Reject) {
        BleError(errorCode: .BluetoothStateChangeFailed).callReject(reject)
    }

    // Retrieve current BleManager's state.
    @objc
    public func state(_ resolve: Resolve, reject: Reject) {
        resolve(manager.state.asJSObject)
    }

    // Dispatch events when state changes.
    private func onStateChange(_ state: BluetoothState) {
        dispatchEvent(BleEvent.stateChangeEvent, value: state.asJSObject)
    }

    // Restore internal manager state.
    private func onRestoreState(_ restoredState: RestoredState?) {

        // When restored state is null then application is run for the first time.
        guard let restoredState = restoredState else {
            dispatchEvent(BleEvent.restoreStateEvent, value: NSNull())
            return
        }

        // When state is to be restored update all caches.
        restoredState.peripherals.forEach { peripheral in
            connectedPeripherals[peripheral.identifier] = peripheral

            _ = manager.rx_state.skip(1).take(1).flatMap { [weak self] state -> Observable<Peripheral> in
                    if let self = self {
                        return self.manager.monitorDisconnection(for: peripheral)
                    } else {
                        return Observable.error(BleError.init(errorCode: BleErrorCode.BluetoothManagerDestroyed))
                    }
                }
                .take(1)
                .subscribe(
                    onNext: { [weak self] peripheral in
                        self?.onPeripheralDisconnected(peripheral)
                    },
                    onError: { [weak self] error in
                        self?.onPeripheralDisconnected(peripheral)
                    })

            peripheral.services?.forEach { service in
                discoveredServices[service.jsIdentifier] = service
                service.characteristics?.forEach { characteristic in
                    discoveredCharacteristics[characteristic.jsIdentifier] = characteristic
                }
            }
        }

        dispatchEvent(BleEvent.restoreStateEvent, value: restoredState.asJSObject)
    }

    // Mark: Scanning --------------------------------------------------------------------------------------------------

    // Start BLE scanning.
    @objc
    public func startDeviceScan(_ filteredUUIDs: [String]?, options:[String:AnyObject]?) {

        // iOS handles allowDuplicates option to receive more scan records.
        var rxOptions = [String:Any]()
        if let options = options {
            if ((options["allowDuplicates"]?.isEqual(to: NSNumber(value: true as Bool))) ?? false) {
                rxOptions[CBCentralManagerScanOptionAllowDuplicatesKey] = true
            }
        }

        // If passed iOS will show only devices with specified service UUIDs.
        var uuids: [CBUUID]? = nil
        if let filteredUUIDs = filteredUUIDs {
            guard let cbuuids = filteredUUIDs.toCBUUIDS() else {
                dispatchEvent(BleEvent.scanEvent, value: BleError.invalidIdentifiers(filteredUUIDs).toJSResult)
                return
            }
            uuids = cbuuids
        }

        // Scanning will emit Scan peripherals as events.
        scanDisposable.disposable = manager.scanForPeripherals(withServices: uuids, options: rxOptions)
            .subscribe(onNext: { [weak self] scannedPeripheral in
                self?.dispatchEvent(BleEvent.scanEvent, value: [NSNull(), scannedPeripheral.asJSObject])
            }, onError: { [weak self] errorType in
                self?.dispatchEvent(BleEvent.scanEvent, value: errorType.bleError.toJSResult)
            })
    }

    // Stop BLE scanning.
    @objc
    public func stopDeviceScan() {
        scanDisposable.disposable = Disposables.create()
    }

    // Read peripheral's RSSI.
    @objc
    public func readRSSIForDevice(_ deviceIdentifier: String,
                                       transactionId: String,
                                             resolve: @escaping Resolve,
                                              reject: @escaping Reject) {
        guard let deviceId = UUID(uuidString: deviceIdentifier) else {
            BleError.invalidIdentifiers(deviceIdentifier).callReject(reject)
            return
        }

        guard let peripheral = connectedPeripherals[deviceId] else {
            BleError.peripheralNotConnected(deviceIdentifier).callReject(reject)
            return
        }

        let safePromise = SafePromise(resolve: resolve, reject: reject)
        let disposable = peripheral.readRSSI()
            .subscribe(
                onNext: { (peripheral, rssi) in
                    safePromise.resolve(peripheral.asJSObject(withRssi: rssi))
                },
                onError: {error in
                    error.bleError.callReject(safePromise)
                },
                onCompleted: nil,
                onDisposed: { [weak self] in
                    self?.transactions.removeDisposable(transactionId)
                    BleError.cancelled().callReject(safePromise)
                })

        transactions.replaceDisposable(transactionId, disposable: disposable)
    }

    @objc
    public func requestMTUForDevice(_ deviceIdentifier: String,
                                                   mtu: Int,
                                         transactionId: String,
                                               resolve: @escaping Resolve,
                                                reject: @escaping Reject) {

        guard let deviceId = UUID(uuidString: deviceIdentifier) else {
            BleError.invalidIdentifiers(deviceIdentifier).callReject(reject)
            return
        }

        guard let peripheral = connectedPeripherals[deviceId] else {
            BleError.peripheralNotConnected(deviceIdentifier).callReject(reject)
            return
        }

        resolve(peripheral.asJSObject())
    }

    @objc
    public func requestConnectionPriorityForDevice(_ deviceIdentifier: String,
                                                   connectionPriority: Int,
                                                        transactionId: String,
                                                              resolve: @escaping Resolve,
                                                               reject: @escaping Reject) {

        guard let deviceId = UUID(uuidString: deviceIdentifier) else {
            BleError.invalidIdentifiers(deviceIdentifier).callReject(reject)
            return
        }

        guard let peripheral = connectedPeripherals[deviceId] else {
            BleError.peripheralNotConnected(deviceIdentifier).callReject(reject)
            return
        }

        resolve(peripheral.asJSObject())
    }

    // Mark: Device management -----------------------------------------------------------------------------------------

    @objc
    public func devices(_ deviceIdentifiers: [String],
                                    resolve: @escaping Resolve,
                                     reject: @escaping Reject) {
        let uuids = deviceIdentifiers.compactMap { UUID(uuidString: $0) }
        if (uuids.count != deviceIdentifiers.count) {
            BleError.invalidIdentifiers(deviceIdentifiers).callReject(reject)
            return
        }

        _  = manager.retrievePeripherals(withIdentifiers: uuids)
            .subscribe(
                onNext: { peripherals in
                    resolve(peripherals.map { $0.asJSObject() })
                },
                onError: { error in
                    error.bleError.callReject(reject)
                }
        );
    }

    @objc
    public func connectedDevices(_ serviceUUIDs: [String],
                                        resolve: @escaping Resolve,
                                         reject: @escaping Reject) {
        let uuids = serviceUUIDs.compactMap { $0.toCBUUID() }
        if (uuids.count != serviceUUIDs.count) {
            BleError.invalidIdentifiers(serviceUUIDs).callReject(reject)
            return
        }

        _  = manager.retrieveConnectedPeripherals(withServices: uuids)
            .subscribe(
                onNext: { peripherals in
                    resolve(peripherals.map { $0.asJSObject() })
            },
                onError: { error in
                    error.bleError.callReject(reject)
            }
        );
    }

    // Mark: Connection management -------------------------------------------------------------------------------------

    // Connect to specified device.
    @objc
    public func connectToDevice(_ deviceIdentifier: String,
                                         options:[String: AnyObject]?,
                                         resolve: @escaping Resolve,
                                          reject: @escaping Reject) {
        guard let deviceId = UUID(uuidString: deviceIdentifier) else {
            BleError.invalidIdentifiers(deviceIdentifier).callReject(reject)
            return
        }

        var timeout: Int? = nil

        if let options = options {
            timeout = options["timeout"] as? Int
        }

        safeConnectToDevice(deviceId, timeout: timeout, promise: SafePromise(resolve: resolve, reject: reject))
    }

    private func safeConnectToDevice(_ deviceId: UUID,
                                        timeout: Int?,
                                        promise: SafePromise) {

        var peripheral: Peripheral? = nil
        var connectionObservable = manager.retrievePeripherals(withIdentifiers: [deviceId])
            .flatMap { devices -> Observable<Peripheral> in
                guard let device = devices.first else {
                    return Observable.error(BleError.peripheralNotFound(deviceId.uuidString))
                }
                peripheral = device
                return Observable.just(device)
            }
            .flatMap { $0.connect() }

        if let timeout = timeout {
            let timeoutInterval = RxTimeInterval.milliseconds(Int(timeout))
            connectionObservable = connectionObservable.timeout(timeoutInterval, scheduler: ConcurrentDispatchQueueScheduler(queue: queue))
        }

        let connectionDisposable = connectionObservable
            .do(onSubscribe: { [weak self] in
                self?.dispatchEvent(BleEvent.connectingEvent, value: deviceId.uuidString)
            })
            .subscribe(
                onNext: { [weak self] peripheral in
                    // When device is connected we save it in dictionary and clear all old cached values.
                    self?.connectedPeripherals[deviceId] = peripheral
                    self?.clearCacheForPeripheral(peripheral: peripheral)
                    self?.dispatchEvent(BleEvent.connectedEvent, value: deviceId.uuidString)
                },
                onError: {  [weak self] error in
                    if let peripheral = peripheral {
                        self?.onPeripheralDisconnected(peripheral)
                    } else {
                        self?.dispatchEvent(BleEvent.disconnectionEvent, value: [NSNull(), ["id": deviceId.uuidString]])
                    }
                    error.bleError.callReject(promise)
                },
                onCompleted: { [weak self] in
                    if let device = self?.connectedPeripherals[deviceId] {
                        _ = self?.manager.monitorDisconnection(for: device)
                            .take(1)
                            .subscribe(onNext: { peripheral in
                                // We are monitoring peripheral disconnections to clean up state.
                                self?.onPeripheralDisconnected(peripheral)
                            }, onError: { error in
                                self?.onPeripheralDisconnected(device)
                            })
                        promise.resolve(device.asJSObject())
                    } else {
                        BleError.peripheralNotFound(deviceId.uuidString).callReject(promise)
                    }
                },
                onDisposed: { [weak self] in
                    self?.connectingPeripherals.removeDisposable(deviceId)
                    BleError.cancelled().callReject(promise)
                }
        );

        connectingPeripherals.replaceDisposable(deviceId, disposable: connectionDisposable)
    }

    private func onPeripheralDisconnected(_ peripheral: Peripheral) {
        self.connectedPeripherals[peripheral.identifier] = nil
        clearCacheForPeripheral(peripheral: peripheral)
        dispatchEvent(BleEvent.disconnectionEvent, value: [NSNull(), peripheral.asJSObject()])
    }

    // User is able to cancel device connection.
    @objc
    public func cancelDeviceConnection(_ deviceIdentifier: String,
                                                  resolve: @escaping Resolve,
                                                   reject: @escaping Reject) {
        guard let deviceId = UUID(uuidString: deviceIdentifier) else {
            BleError.invalidIdentifiers(deviceIdentifier).callReject(reject)
            return
        }

        if let peripheral = connectedPeripherals[deviceId] {
            _ = peripheral
                .cancelConnection()
                .subscribe(
                    onNext: nil,
                    onError: { error in
                        error.bleError.callReject(reject)
                    },
                    onCompleted: { [weak self] in
                        self?.clearCacheForPeripheral(peripheral: peripheral)
                        self?.connectedPeripherals[deviceId] = nil
                        resolve(peripheral.asJSObject())
                    }
            );
        } else {
            connectingPeripherals.removeDisposable(deviceId)
            BleError.cancelled().callReject(reject)
        }
    }

    // Retrieve if device is connected.
    @objc
    public func isDeviceConnected(_ deviceIdentifier: String, resolve: Resolve, reject: Reject) {
        guard let deviceId = UUID(uuidString: deviceIdentifier) else {
            BleError.invalidIdentifiers(deviceIdentifier).callReject(reject)
            return
        }

        if let device = connectedPeripherals[deviceId] {
            resolve(device.isConnected)
        } else {
            resolve(false)
        }
    }

    // Mark: Discovery -------------------------------------------------------------------------------------------------

    // After connection for peripheral to be usable,
    // user should discover all services and characteristics for peripheral.
    @objc
    public func discoverAllServicesAndCharacteristicsForDevice(_ deviceIdentifier: String,
                                                                    transactionId: String,
                                                                          resolve: @escaping Resolve,
                                                                           reject: @escaping Reject) {

        guard let deviceId = UUID(uuidString: deviceIdentifier) else {
            BleError.invalidIdentifiers(deviceIdentifier).callReject(reject)
            return
        }

        guard let peripheral = connectedPeripherals[deviceId] else {
            BleError.peripheralNotConnected(deviceIdentifier).callReject(reject)
            return
        }

        safeDiscoverAllServicesAndCharacteristicsForDevice(peripheral, transactionId: transactionId, promise: SafePromise(resolve: resolve, reject: reject))
    }

    func safeDiscoverAllServicesAndCharacteristicsForDevice(_ peripheral: Peripheral,
                                                           transactionId: String,
                                                                 promise: SafePromise) {
        let disposable = peripheral
            .discoverServices(nil)
            .flatMap { [weak self] services -> Observable<Service> in
                for service in services {
                    self?.discoveredServices[service.jsIdentifier] = service
                }
                return Observable.from(services)
            }
            .flatMap { $0.discoverCharacteristics(nil) }
            .flatMap { [weak self] characteristics -> Observable<Characteristic> in
                for characteristic in characteristics {
                    self?.discoveredCharacteristics[characteristic.jsIdentifier] = characteristic
                }
                return Observable.from(characteristics)
            }
            .flatMap { $0.discoverDescriptors() }
            .subscribe(
                onNext: { [weak self] descriptors in
                    for descriptor in descriptors {
                        self?.discoveredDescriptors[descriptor.jsIdentifier] = descriptor
                    }
                },
                onError: { error in error.bleError.callReject(promise) },
                onCompleted: { promise.resolve(peripheral.asJSObject()) },
                onDisposed: { [weak self] in
                    self?.transactions.removeDisposable(transactionId)
                    BleError.cancelled().callReject(promise)
                }
        )

        transactions.replaceDisposable(transactionId, disposable: disposable)
    }

    // Mark: Service and characteristic getters ------------------------------------------------------------------------

    // When fetching services for peripherals we update our cache.
    @objc
    public func servicesForDevice(_ deviceIdentifier: String, resolve: Resolve, reject: Reject) {

        guard let deviceId = UUID(uuidString: deviceIdentifier) else {
            BleError.invalidIdentifiers(deviceIdentifier).callReject(reject)
            return
        }

        guard let peripheral = connectedPeripherals[deviceId] else {
            BleError.peripheralNotConnected(deviceIdentifier).callReject(reject)
            return
        }

        let services = peripheral
            .services?.map { [weak self] service in
                self?.discoveredServices[service.jsIdentifier] = service
                return service.asJSObject
            } ?? []

        resolve(services)
    }

    // When fetching characteristics for peripherals we update our cache.
    @objc
    public func characteristicsForDevice(_ deviceIdentifier: String,
                                                serviceUUID: String,
                                                    resolve: Resolve,
                                                     reject: Reject) {

        guard let deviceId = UUID(uuidString: deviceIdentifier),
                  let serviceId = serviceUUID.toCBUUID() else {
            BleError.invalidIdentifiers([deviceIdentifier, serviceUUID]).callReject(reject)
            return
        }

        guard let peripheral = connectedPeripherals[deviceId] else {
            BleError.peripheralNotConnected(deviceIdentifier).callReject(reject)
            return
        }

        guard let service = (peripheral.services?.first { serviceId == $0.uuid }) else {
            BleError.serviceNotFound(serviceUUID).callReject(reject)
            return
        }

        characteristicForService(service,
                                 resolve: resolve,
                                 reject: reject)
    }

    @objc
    public func characteristicsForService(_ serviceIdentifier: Double,
                                                      resolve: Resolve,
                                                       reject: Reject) {
        guard let service = discoveredServices[serviceIdentifier]  else {
            BleError.serviceNotFound(serviceIdentifier.description).callReject(reject)
            return
        }

        characteristicForService(service, resolve: resolve, reject: reject)
    }

    private func characteristicForService(_ service: Service, resolve: Resolve, reject: Reject) {
        let characteristics = service.characteristics?
            .map { [weak self] characteristic in
                self?.discoveredCharacteristics[characteristic.jsIdentifier] = characteristic
                return characteristic.asJSObject
            } ?? []

        resolve(characteristics)
    }

    @objc
    public func descriptorsForDevice(_ deviceIdentifier: String,
                                            serviceUUID: String,
                                     characteristicUUID: String,
                                                resolve: Resolve,
                                                 reject: Reject) {
        guard let deviceId = UUID(uuidString: deviceIdentifier),
              let serviceId = serviceUUID.toCBUUID(),
              let characteristicId = characteristicUUID.toCBUUID() else {
                BleError.invalidIdentifiers([deviceIdentifier, serviceUUID, characteristicUUID]).callReject(reject)
                return
        }

        guard let peripheral = connectedPeripherals[deviceId] else {
            BleError.peripheralNotConnected(deviceIdentifier).callReject(reject)
            return
        }

        guard let service = (peripheral.services?.first { serviceId == $0.uuid }) else {
            BleError.serviceNotFound(serviceUUID).callReject(reject)
            return
        }

        guard let characteristic = (service.characteristics?.first { characteristicId == $0.uuid }) else {
            BleError.characteristicNotFound(characteristicUUID).callReject(reject)
            return
        }

        descriptorsForCharacteristic(characteristic, resolve: resolve, reject: reject)
    }

    @objc
    public func descriptorsForService(_ serviceIdentifier: Double,
                                      characteristicUUID: String,
                                      resolve: Resolve,
                                      reject: Reject) {
        guard let characteristicId = characteristicUUID.toCBUUID() else {
            BleError.invalidIdentifiers(characteristicUUID).callReject(reject)
            return
        }

        guard let service = discoveredServices[serviceIdentifier] else {
            BleError.serviceNotFound(serviceIdentifier.description).callReject(reject)
            return
        }

        guard let characteristic = (service.characteristics?.first { characteristicId == $0.uuid }) else {
            BleError.characteristicNotFound(characteristicUUID).callReject(reject)
            return
        }

        descriptorsForCharacteristic(characteristic, resolve: resolve, reject: reject)
    }

    @objc
    public func descriptorsForCharacteristic(_ characteristicIdentifier: Double,
                                             resolve: Resolve,
                                             reject: Reject) {
        guard let characteristic = discoveredCharacteristics[characteristicIdentifier] else {
            BleError.characteristicNotFound(characteristicIdentifier.description).callReject(reject)
            return
        }

        descriptorsForCharacteristic(characteristic, resolve: resolve, reject: reject)
    }

    private func descriptorsForCharacteristic(_ characteristic: Characteristic, resolve: Resolve, reject: Reject) {
        let descriptors = characteristic.descriptors?
            .map { [weak self] descriptor in
                self?.discoveredDescriptors[descriptor.jsIdentifier] = descriptor
                return descriptor.asJSObject
            } ?? []

        resolve(descriptors)
    }

    // Mark: Reading ---------------------------------------------------------------------------------------------------

    @objc
    public func readCharacteristicForDevice(_ deviceIdentifier: String,
                                                   serviceUUID: String,
                                            characteristicUUID: String,
                                                 transactionId: String,
                                                       resolve: @escaping Resolve,
                                                        reject: @escaping Reject) {
        let observable = getCharacteristicForDevice(deviceIdentifier,
                                                    serviceUUID: serviceUUID,
                                                    characteristicUUID: characteristicUUID)
        safeReadCharacteristicForDevice(observable,
                                        transactionId: transactionId,
                                        promise: SafePromise(resolve: resolve, reject: reject))
    }

    @objc
    public func readCharacteristicForService(_ serviceIdentifier: Double,
                                              characteristicUUID: String,
                                                   transactionId: String,
                                                         resolve: @escaping Resolve,
                                                          reject: @escaping Reject) {
        let observable = getCharacteristicForService(serviceIdentifier,
                                                     characteristicUUID: characteristicUUID)
        safeReadCharacteristicForDevice(observable,
                                        transactionId: transactionId,
                                        promise: SafePromise(resolve: resolve, reject: reject))
    }

    @objc
    public func readCharacteristic(_ characteristicIdentifier: Double,
                                                transactionId: String,
                                                      resolve: @escaping Resolve,
                                                       reject: @escaping Reject) {
        safeReadCharacteristicForDevice(getCharacteristic(characteristicIdentifier),
                                        transactionId: transactionId,
                                        promise: SafePromise(resolve: resolve, reject: reject))
    }

    private func safeReadCharacteristicForDevice(_ characteristicObservable: Observable<Characteristic>,
                                                              transactionId: String,
                                                                    promise: SafePromise) {
        var characteristicIdentifier: Double? = nil
        let disposable = characteristicObservable
            .flatMap { [weak self] characteristic -> Observable<Characteristic> in
                characteristicIdentifier = characteristic.jsIdentifier
                let reads = self?.pendingReads[characteristic.jsIdentifier] ?? 0
                self?.pendingReads[characteristic.jsIdentifier] = reads + 1
                return characteristic.readValue()
            }
            .subscribe(
                onNext: { characteristic in
                    promise.resolve(characteristic.asJSObject)
                },
                onError: { error in
                    error.bleError.callReject(promise)
                },
                onCompleted: nil,
                onDisposed: { [weak self] in
                    self?.transactions.removeDisposable(transactionId)
                    BleError.cancelled().callReject(promise)
                    if let id = characteristicIdentifier {
                        let reads = self?.pendingReads[id] ?? 0
                        if reads > 0 {
                            self?.pendingReads[id] = reads - 1
                        }
                    }
                }
            )

        transactions.replaceDisposable(transactionId, disposable: disposable)
    }

    // MARK: Writing ---------------------------------------------------------------------------------------------------

    @objc
    public func writeCharacteristicForDevice(  _ deviceIdentifier: String,
                                                      serviceUUID: String,
                                               characteristicUUID: String,
                                                      valueBase64: String,
                                                         response: Bool,
                                                    transactionId: String,
                                                          resolve: @escaping Resolve,
                                                           reject: @escaping Reject) {
        guard let value = valueBase64.fromBase64 else {
            return BleError.invalidWriteDataForCharacteristic(characteristicUUID, data: valueBase64).callReject(reject)
        }

        let observable = getCharacteristicForDevice(deviceIdentifier,
                                                    serviceUUID: serviceUUID,
                                                    characteristicUUID: characteristicUUID)
        safeWriteCharacteristicForDevice(observable,
                                         value: value,
                                         response: response,
                                         transactionId: transactionId,
                                         promise: SafePromise(resolve: resolve, reject: reject))
    }
    
    private func innerWriteCharacteristicForDevice(_ deviceIdentifier: String, buff:Data) {
        let observable = getCharacteristicForDevice(deviceIdentifier,
                                                    serviceUUID: "ff00",
                                                    characteristicUUID: "ff02")
        let uuid = UUID().uuidString
        safeWriteCharacteristicForDevice(observable, value: buff, response: false, transactionId: uuid, promise: SafePromise(resolve: { _ in
            
        }, reject: { _, _, _ in
            
        }))
    }

    @objc
    public func writeCharacteristicForService(  _ serviceIdentifier: Double,
                                                 characteristicUUID: String,
                                                        valueBase64: String,
                                                           response: Bool,
                                                      transactionId: String,
                                                            resolve: @escaping Resolve,
                                                             reject: @escaping Reject) {
        guard let value = valueBase64.fromBase64 else {
            return BleError.invalidWriteDataForCharacteristic(characteristicUUID, data: valueBase64).callReject(reject)
        }

        let observable = getCharacteristicForService(serviceIdentifier,
                                                     characteristicUUID: characteristicUUID)

        safeWriteCharacteristicForDevice(observable,
                                         value: value,
                                         response: response,
                                         transactionId: transactionId,
                                         promise: SafePromise(resolve: resolve, reject: reject))
    }

    @objc
    public func writeCharacteristic(  _ characteristicIdentifier: Double,
                                                     valueBase64: String,
                                                        response: Bool,
                                                   transactionId: String,
                                                         resolve: @escaping Resolve,
                                                          reject: @escaping Reject) {
        guard let value = valueBase64.fromBase64 else {
            return BleError.invalidWriteDataForCharacteristic(characteristicIdentifier.description, data: valueBase64)
                .callReject(reject)
        }

        safeWriteCharacteristicForDevice(getCharacteristic(characteristicIdentifier),
                                         value: value,
                                         response: response,
                                         transactionId: transactionId,
                                         promise: SafePromise(resolve: resolve, reject: reject))
    }

    private func safeWriteCharacteristicForDevice(_ characteristicObservable: Observable<Characteristic>,
                                                                       value: Data,
                                                                    response: Bool,
                                                               transactionId: String,
                                                                     promise: SafePromise) {
        let disposable = characteristicObservable
            .flatMap {
                $0.writeValue(value, type: response ? .withResponse : .withoutResponse)
            }
            .subscribe(
                onNext: { characteristic in
                    promise.resolve(characteristic.asJSObject)
                },
                onError: { error in
                    error.bleError.callReject(promise)
                },
                onCompleted: nil,
                onDisposed: { [weak self] in
                    self?.transactions.removeDisposable(transactionId)
                    BleError.cancelled().callReject(promise)
                }
            )

        transactions.replaceDisposable(transactionId, disposable: disposable)
    }

    // MARK: Monitoring ------------------------------------------------------------------------------------------------

    @objc
    public func monitorCharacteristicForDevice(  _ deviceIdentifier: String,
                                                        serviceUUID: String,
                                                 characteristicUUID: String,
                                                      transactionId: String,
                                                            resolve: @escaping Resolve,
                                                             reject: @escaping Reject) {
        let observable = getCharacteristicForDevice(deviceIdentifier,
                                                    serviceUUID: serviceUUID,
                                                    characteristicUUID: characteristicUUID)

        safeMonitorCharacteristicForDevice(observable,
                                           transactionId: transactionId,
                                           promise: SafePromise(resolve: resolve, reject: reject))
    }

    @objc
    public func monitorCharacteristicForService(  _ serviceIdentifier: Double,
                                                   characteristicUUID: String,
                                                        transactionId: String,
                                                              resolve: @escaping Resolve,
                                                               reject: @escaping Reject) {
        let observable = getCharacteristicForService(serviceIdentifier, characteristicUUID: characteristicUUID)

        safeMonitorCharacteristicForDevice(observable,
                                           transactionId: transactionId,
                                           promise: SafePromise(resolve: resolve, reject: reject))
    }

    @objc
    public func monitorCharacteristic(  _ characteristicIdentifier: Double,
                                                     transactionId: String,
                                                           resolve: @escaping Resolve,
                                                            reject: @escaping Reject) {
        safeMonitorCharacteristicForDevice(getCharacteristic(characteristicIdentifier),
                                           transactionId: transactionId,
                                           promise: SafePromise(resolve: resolve, reject: reject))
    }

    private func safeMonitorCharacteristicForDevice(_ characteristicObservable: Observable<Characteristic>,
                                                                 transactionId: String,
                                                                       promise: SafePromise) {

        let observable: Observable<Characteristic> = characteristicObservable
            .flatMap { [weak self] (characteristic: Characteristic) -> Observable<Characteristic> in
                let characteristicIdentifier = characteristic.jsIdentifier
                if let monitoringObservable = self?.monitoredCharacteristics[characteristicIdentifier] {
                    return monitoringObservable
                } else {
                    let newObservable: Observable<Characteristic> = characteristic
                        .setNotificationAndMonitorUpdates()
                        .do(onNext: nil, onError: nil, onCompleted: nil, onSubscribe: nil, onDispose: {
                            _ = characteristic.setNotifyValue(false).subscribe()
                            self?.monitoredCharacteristics[characteristicIdentifier] = nil
                        })
                        .share()
                    self?.monitoredCharacteristics[characteristicIdentifier] = newObservable
                    return newObservable
                }
            }

        let disposable = observable.subscribe(
            onNext: { [weak self] characteristic in
                if self?.pendingReads[characteristic.jsIdentifier] ?? 0 == 0 {
                    // -- custom 处理OTA逻辑
                    if self?.ota_ing == true, let value = characteristic.value {
                        self?.displayData(data: value)
                    } else {
                        self?.dispatchEvent(BleEvent.readEvent, value: [NSNull(), characteristic.asJSObject, transactionId])
                    }
                }
            }, onError: { [weak self] error in
                self?.dispatchEvent(BleEvent.readEvent, value: [error.bleError.toJS, NSNull(), transactionId])
            }, onCompleted: {

            }, onDisposed: { [weak self] in
                self?.transactions.removeDisposable(transactionId)
                BleError.cancelled().callReject(promise)
            })

        transactions.replaceDisposable(transactionId, disposable: disposable)
    }

    // MARK: Getting characteristics -----------------------------------------------------------------------------------

    private func getCharacteristicForDevice(_ deviceId: String,
                                           serviceUUID: String,
                                    characteristicUUID: String) -> Observable<Characteristic> {
        return Observable.create { [weak self] observer in
            guard let peripheralId = UUID(uuidString: deviceId) else {
                observer.onError(BleError.peripheralNotFound(deviceId))
                return Disposables.create()
            }

            guard let serviceCBUUID = serviceUUID.toCBUUID() else {
                observer.onError(BleError.invalidIdentifiers(serviceUUID))
                return Disposables.create()
            }

            guard let characteristicCBUUID = characteristicUUID.toCBUUID() else {
                observer.onError(BleError.invalidIdentifiers(characteristicUUID))
                return Disposables.create()
            }

            guard let peripheral = self?.connectedPeripherals[peripheralId] else {
                observer.onError(BleError.peripheralNotConnected(deviceId))
                return Disposables.create()
            }

            guard let characteristic = (peripheral.services?
                .first { $0.uuid == serviceCBUUID }?
                .characteristics?
                .first { $0.uuid == characteristicCBUUID }) else {
                observer.onError(BleError.characteristicNotFound(characteristicUUID))
                return Disposables.create()
            }

            observer.onNext(characteristic)
            observer.onCompleted()
            return Disposables.create()
        }
    }

    private func getCharacteristicForService(_ serviceId: Double,
                                      characteristicUUID: String) -> Observable<Characteristic> {
        return Observable.create { [weak self] observer in
            guard let characteristicCBUUID = characteristicUUID.toCBUUID() else {
                observer.onError(BleError.invalidIdentifiers(characteristicUUID))
                return Disposables.create()
            }

            guard let service = self?.discoveredServices[serviceId] else {
                observer.onError(BleError.serviceNotFound(serviceId.description))
                return Disposables.create()
            }

            guard let characteristic = (service
                .characteristics?
                .first { $0.uuid == characteristicCBUUID }) else {
                    observer.onError(BleError.characteristicNotFound(characteristicUUID))
                    return Disposables.create()
            }

            observer.onNext(characteristic)
            observer.onCompleted()
            return Disposables.create()
        }
    }

    private func getCharacteristic(_ characteristicId: Double) -> Observable<Characteristic> {
        return Observable.create { [weak self] observer in
            guard let characteristic = self?.discoveredCharacteristics[characteristicId] else {
                observer.onError(BleError.characteristicNotFound(characteristicId.description))
                return Disposables.create()
            }

            observer.onNext(characteristic)
            observer.onCompleted()
            return Disposables.create()
        }
    }

    // MARK: Descriptors -----------------------------------------------------------------------------------------------

    private func getDescriptorForDevice(_ deviceId: String,
                                          serviceUUID: String,
                                          characteristicUUID: String,
                                          descriptorUUID: String) -> Observable<Descriptor> {
        return getDescriptorByUUID(descriptorUUID, characteristicObservable: getCharacteristicForDevice(deviceId, serviceUUID: serviceUUID, characteristicUUID: characteristicUUID))
    }

    private func getDescriptorForService(_ serviceId: Double,
                                         characteristicUUID: String,
                                         descriptorUUID: String) -> Observable<Descriptor> {
        return getDescriptorByUUID(descriptorUUID, characteristicObservable: getCharacteristicForService(serviceId, characteristicUUID: characteristicUUID))
    }

    private func getDescriptorForCharacteristic(_ characteristicId: Double,
                                                descriptorUUID: String) -> Observable<Descriptor> {
        return getDescriptorByUUID(descriptorUUID, characteristicObservable: getCharacteristic(characteristicId))
    }

    private func getDescriptorByUUID(_ descriptorUUID: String, characteristicObservable: Observable<Characteristic>) -> Observable<Descriptor> {
        guard let descriptorCBUUID = descriptorUUID.toCBUUID() else {
            return Observable.error(BleError.invalidIdentifiers(descriptorUUID))
        }

        return characteristicObservable.flatMap { characteristic -> Observable<Descriptor> in
            guard let descriptor = (characteristic.descriptors?.first { $0.uuid == descriptorCBUUID }) else {
                return Observable.error(BleError.descriptorNotFound(descriptorUUID))
            }
            return Observable.just(descriptor)
        }
    }

    private func getDescriptorByID(_ descriptorId: Double) -> Observable<Descriptor> {
        return Observable.create { [weak self] observer in
            guard let descriptor = self?.discoveredDescriptors[descriptorId] else {
                observer.onError(BleError.descriptorNotFound(descriptorId.description))
                return Disposables.create()
            }

            observer.onNext(descriptor)
            observer.onCompleted()
            return Disposables.create()
        }
    }

    private func safeReadDescriptor(_ descriptorObservable: Observable<Descriptor>,
                                             transactionId: String,
                                                   promise: SafePromise) {
        let disposable = descriptorObservable
            .flatMap { descriptor -> Observable<Descriptor> in
                return descriptor.readValue()
            }
            .subscribe(
                onNext: { descriptor in
                    promise.resolve(descriptor.asJSObject)
            },
                onError: { error in
                    error.bleError.callReject(promise)
            },
                onCompleted: nil,
                onDisposed: { [weak self] in
                    self?.transactions.removeDisposable(transactionId)
                    BleError.cancelled().callReject(promise)
                }
        )

        transactions.replaceDisposable(transactionId, disposable: disposable)
    }

    @objc
    public func readDescriptorForDevice(_ deviceIdentifier: String,
                                               serviceUUID: String,
                                        characteristicUUID: String,
                                            descriptorUUID: String,
                                             transactionId: String,
                                                   resolve: @escaping Resolve,
                                                    reject: @escaping Reject) {
        let descriptor = getDescriptorForDevice(deviceIdentifier,
                               serviceUUID: serviceUUID,
                               characteristicUUID: characteristicUUID,
                               descriptorUUID: descriptorUUID)
        safeReadDescriptor(descriptor,
                           transactionId: transactionId,
                           promise: SafePromise.init(resolve: resolve, reject: reject))
    }

    @objc
    public func readDescriptorForService(_ serviceId: Double,
                                  characteristicUUID: String,
                                      descriptorUUID: String,
                                       transactionId: String,
                                             resolve: @escaping Resolve,
                                              reject: @escaping Reject) {
        let descriptor = getDescriptorForService(serviceId,
                                                characteristicUUID: characteristicUUID,
                                                descriptorUUID: descriptorUUID)
        safeReadDescriptor(descriptor,
                           transactionId: transactionId,
                           promise: SafePromise.init(resolve: resolve, reject: reject))
    }

    @objc
    public func readDescriptorForCharacteristic(_ characteristicID: Double,
                                                    descriptorUUID: String,
                                                     transactionId: String,
                                                           resolve: @escaping Resolve,
                                                            reject: @escaping Reject) {
        let descriptor = getDescriptorForCharacteristic(characteristicID, descriptorUUID: descriptorUUID)
        safeReadDescriptor(descriptor,
                           transactionId: transactionId,
                           promise: SafePromise.init(resolve: resolve, reject: reject))
    }

    @objc
    public func readDescriptor(_ descriptorID: Double,
                                transactionId: String,
                                      resolve: @escaping Resolve,
                                       reject: @escaping Reject) {
        safeReadDescriptor(getDescriptorByID(descriptorID),
                           transactionId: transactionId,
                           promise: SafePromise.init(resolve: resolve, reject: reject))
    }

    private func safeWriteDescriptor(_ descriptorObservable: Observable<Descriptor>,
                                     transactionId: String,
                                     value: Data,
                                     promise: SafePromise) {
        let disposable = descriptorObservable
            .flatMap { descriptor -> Observable<Descriptor> in
                if descriptor.uuid.isEqual(BleClientManager.cccdUUID) {
                    return Observable.error(BleError.descriptorWriteNotAllowed(descriptor.uuid.fullUUIDString))
                }
                return descriptor.writeValue(value)
            }
            .subscribe(
                onNext: { descriptor in
                    promise.resolve(descriptor.asJSObject)
            },
                onError: { error in
                    error.bleError.callReject(promise)
            },
                onCompleted: nil,
                onDisposed: { [weak self] in
                    self?.transactions.removeDisposable(transactionId)
                    BleError.cancelled().callReject(promise)
                }
        )

        transactions.replaceDisposable(transactionId, disposable: disposable)
    }

    @objc
    public func writeDescriptorForDevice(_ deviceIdentifier: String,
                                         serviceUUID: String,
                                         characteristicUUID: String,
                                         descriptorUUID: String,
                                         valueBase64: String,
                                         transactionId: String,
                                         resolve: @escaping Resolve,
                                         reject: @escaping Reject) {
        guard let value = valueBase64.fromBase64 else {
            return BleError.invalidWriteDataForDescriptor(descriptorUUID, data: valueBase64).callReject(reject)
        }

        let descriptor = getDescriptorForDevice(deviceIdentifier,
                                                serviceUUID: serviceUUID,
                                                characteristicUUID: characteristicUUID,
                                                descriptorUUID: descriptorUUID)
        safeWriteDescriptor(descriptor,
                            transactionId: transactionId,
                            value: value,
                            promise: SafePromise(resolve: resolve, reject: reject))
    }

    @objc
    public func writeDescriptorForService(_ serviceID: Double,
                                          characteristicUUID: String,
                                          descriptorUUID: String,
                                          valueBase64: String,
                                          transactionId: String,
                                          resolve: @escaping Resolve,
                                          reject: @escaping Reject) {
        guard let value = valueBase64.fromBase64 else {
            return BleError.invalidWriteDataForDescriptor(descriptorUUID, data: valueBase64).callReject(reject)
        }

        let descriptor = getDescriptorForService(serviceID,
                                                 characteristicUUID: characteristicUUID,
                                                 descriptorUUID: descriptorUUID)
        safeWriteDescriptor(descriptor,
                            transactionId: transactionId,
                            value: value,
                            promise: SafePromise(resolve: resolve, reject: reject))
    }

    @objc
    public func writeDescriptorForCharacteristic(_ characteristicID: Double,
                                                 descriptorUUID: String,
                                                 valueBase64: String,
                                                 transactionId: String,
                                                 resolve: @escaping Resolve,
                                                 reject: @escaping Reject) {
        guard let value = valueBase64.fromBase64 else {
            return BleError.invalidWriteDataForDescriptor(descriptorUUID, data: valueBase64).callReject(reject)
        }

        let descriptor = getDescriptorForCharacteristic(characteristicID, descriptorUUID: descriptorUUID)

        safeWriteDescriptor(descriptor,
                            transactionId: transactionId,
                            value: value,
                            promise: SafePromise(resolve: resolve, reject: reject))
    }

    @objc
    public func writeDescriptor(_ descriptorID: Double,
                                valueBase64: String,
                                transactionId: String,
                                resolve: @escaping Resolve,
                                reject: @escaping Reject) {
        guard let value = valueBase64.fromBase64 else {
            return BleError.invalidWriteDataForDescriptor(descriptorID.description, data: valueBase64).callReject(reject)
        }

        safeWriteDescriptor(getDescriptorByID(descriptorID),
                            transactionId: transactionId,
                            value: value,
                            promise: SafePromise(resolve: resolve, reject: reject))
    }

    // MARK: Private interface -----------------------------------------------------------------------------------------

    private func clearCacheForPeripheral(peripheral: Peripheral) {
        for (key, value) in discoveredDescriptors {
            if value.characteristic.service.peripheral == peripheral {
                discoveredDescriptors[key] = nil
            }
        }
        for (key, value) in discoveredCharacteristics {
            if value.service.peripheral == peripheral {
                discoveredCharacteristics[key] = nil
            }
        }
        for (key, value) in discoveredServices {
            if value.peripheral == peripheral {
                discoveredServices[key] = nil
            }
        }
    }

    private func dispatchEvent(_ event: String, value: Any) {
        delegate?.dispatchEvent(event, value: value)
    }
}
