<commit_before>//
//  Bluejay.swift
//  Bluejay
//
//  Created by <NAME> on 2017-01-03.
//  Copyright © 2017 Steamclock Software. All rights reserved.
//

import Foundation
import CoreBluetooth

/**
 Bluejay is a simple wrapper around CoreBluetooth that focuses on making a common usage case as straight forward as possible: a single connected peripheral that the user is interacting with regularly (think most personal electronics devices that have an associated iOS app: fitness trackers, guitar amps, etc).
 
 It also supports a few other niceties for simplifying usage, including automatic discovery of services and characteristics as they are used, as well as supporting a background task mode where the interaction with the device can be written as synchronous calls running on a background thread to avoid callback pyramids of death, or heavily chained promises.
 */
public class Bluejay: NSObject {
    
    // MARK: - Private Properties
    
    /// Internal reference to CoreBluetooth's CBCentralManager.
    fileprivate var cbCentralManager: CBCentralManager!
    
    /// The value for CBCentralManagerOptionRestoreIdentifierKey.
    fileprivate var restoreIdentifier: String?
    
    /// List of weak references to objects interested in receiving notifications on Bluetooth connection events and state changes.
    fileprivate var observers = [WeakConnectionObserver]()
    
    /// Reference to a peripheral that is still connecting. If this is nil, then the peripheral should either be disconnected or connected. This is used to help determine the state of the peripheral's connection.
    fileprivate var connectingPeripheral: Peripheral?
    
    /// Reference to a peripheral that is connected. If this is nil, then the peripheral should either be disconnected or still connecting. This is used to help determine the state of the peripheral's connection.
    fileprivate var connectedPeripheral: Peripheral?
    
    /// Allowing or disallowing reconnection attempts upon a disconnection. It should always be set to true, unless there is a manual and explicit disconnection request that is not caused by an error or an unexpected and programmatic disconnection.
    fileprivate var shouldAutoReconnect = true
    
    /// Reference to the background task used for supporting state restoration.
    fileprivate var startupBackgroundTask: UIBackgroundTaskIdentifier = UIBackgroundTaskInvalid
    
    /// Reference to the peripheral identifier used for supporting state restoration.
    fileprivate var peripheralIdentifierToRestore: PeripheralIdentifier?
    
    /// Reference to the object capable of restoring listens during state restoration.
    fileprivate var listenRestorer: WeakListenRestorer?
    
    /// Determines whether state restoration is allowed.
    fileprivate var shouldRestoreState = false
    
    // MARK: - Internal Properties
    
    /// Contains the operations to execute in FIFO order.
    var queue: Queue!
    
    // MARK: - Public Properties
    
    /// Helps distinguish one Bluejay instance from another.
    public var uuid = UUID()
    
    /// Allows checking whether Bluetooth is powered on.
    public var isBluetoothAvailable: Bool {
        return cbCentralManager.state == .poweredOn
    }
    
    /// Allows checking whether Bluejay is currently connecting to a peripheral.
    public var isConnecting: Bool {
        return connectingPeripheral != nil
    }
    
    /// Allows checking whether Bluejay is currently connected to a peripheral.
    public var isConnected: Bool {
        return connectedPeripheral != nil
    }
    
    /// Allows checking whether Bluejay is currently disconnecting from a peripheral.
    public var isDisconnecting: Bool = false
    
    /// Allows checking whether Bluejay is currently scanning.
    public var isScanning: Bool {
        // Cannot rely on the manager's state for isScanning as it is not usually updated immediately, and while that delay might be a more accurate representation of the current state, it is almost always more useful to evaluate whether Bluejay is running a scan request at the top of its queue.
        return queue.isScanning()
    }
    
    // MARK: - Initialization
    
    /**
     Initializing a Bluejay instance will not yet initialize the CoreBluetooth stack. An explicit call to start running a Bluejay instance after it is intialized is required because in cases where a state resotration is trying to restore a listen on a characteristic, a listen restorer must be available before the CoreBluetooth stack is re-initialized. This two-step startup allows you to insert and gaurantee the setup of your listen restorer in between the initialization of Bluejay and the initialization of the CoreBluetooth stack triggered via this call.
     */
    public override init() {
        super.init()
        
        shouldRestoreState = UIApplication.shared.applicationState == .background
        
        if shouldRestoreState {
            debugPrint("Begin startup background task for restoring CoreBluetooth.")
            startupBackgroundTask = UIApplication.shared.beginBackgroundTask(expirationHandler: nil)
        }
        
        log("Bluejay initialized with UUID: \(uuid.uuidString).")
        
        queue = Queue(bluejay: self)
    }
    
    deinit {
        cancelEverything()
        
        log("Deinit Bluejay with UUID: \(uuid.uuidString).")
    }
    
    /**
     Starting Bluejay will initialize the CoreBluetooth stack. Initializing a Bluejay instance will not yet initialize the CoreBluetooth stack. An explicit call to start running a Bluejay instance after it is intialized is required because in cases where a state resotration is trying to restore a listen on a characteristic, a listen restorer must be available before the CoreBluetooth stack is re-initialized. This two-step startup allows you to insert and gaurantee the setup of your listen restorer in between the initialization of Bluejay and the initialization of the CoreBluetooth stack triggered via this call.
     
     - Parameters:
        - observer: An object interested in observing Bluetooth connection events and state changes. You can register more observers using the `register` function.
        - restoreMode: Determines whether Bluejay will opt-in to state restoration, and if so, can optionally provide a listen restorer as well for restoring listens.
    */
    public func start(
        connectionObserver observer: ConnectionObserver? = nil,
        backgroundRestore restoreMode: BackgroundRestoreMode = .disable
        )
    {
        register(observer: queue)
        
        if let observer = observer {
            register(observer: observer)
        }<commit_msg>Protect against calling start multiple times<commit_after>//
//  Bluejay.swift
//  Bluejay
//
//  Created by <NAME> on 2017-01-03.
//  Copyright © 2017 Steamclock Software. All rights reserved.
//

import Foundation
import CoreBluetooth

/**
 Bluejay is a simple wrapper around CoreBluetooth that focuses on making a common usage case as straight forward as possible: a single connected peripheral that the user is interacting with regularly (think most personal electronics devices that have an associated iOS app: fitness trackers, guitar amps, etc).
 
 It also supports a few other niceties for simplifying usage, including automatic discovery of services and characteristics as they are used, as well as supporting a background task mode where the interaction with the device can be written as synchronous calls running on a background thread to avoid callback pyramids of death, or heavily chained promises.
 */
public class Bluejay: NSObject {
    
    // MARK: - Private Properties
    
    /// Internal reference to CoreBluetooth's CBCentralManager.
    fileprivate var cbCentralManager: CBCentralManager!
    
    /// The value for CBCentralManagerOptionRestoreIdentifierKey.
    fileprivate var restoreIdentifier: String?
    
    /// List of weak references to objects interested in receiving notifications on Bluetooth connection events and state changes.
    fileprivate var observers = [WeakConnectionObserver]()
    
    /// Reference to a peripheral that is still connecting. If this is nil, then the peripheral should either be disconnected or connected. This is used to help determine the state of the peripheral's connection.
    fileprivate var connectingPeripheral: Peripheral?
    
    /// Reference to a peripheral that is connected. If this is nil, then the peripheral should either be disconnected or still connecting. This is used to help determine the state of the peripheral's connection.
    fileprivate var connectedPeripheral: Peripheral?
    
    /// Allowing or disallowing reconnection attempts upon a disconnection. It should always be set to true, unless there is a manual and explicit disconnection request that is not caused by an error or an unexpected and programmatic disconnection.
    fileprivate var shouldAutoReconnect = true
    
    /// Reference to the background task used for supporting state restoration.
    fileprivate var startupBackgroundTask: UIBackgroundTaskIdentifier = UIBackgroundTaskInvalid
    
    /// Reference to the peripheral identifier used for supporting state restoration.
    fileprivate var peripheralIdentifierToRestore: PeripheralIdentifier?
    
    /// Reference to the object capable of restoring listens during state restoration.
    fileprivate var listenRestorer: WeakListenRestorer?
    
    /// Determines whether state restoration is allowed.
    fileprivate var shouldRestoreState = false
    
    // MARK: - Internal Properties
    
    /// Contains the operations to execute in FIFO order.
    var queue: Queue!
    
    // MARK: - Public Properties
    
    /// Helps distinguish one Bluejay instance from another.
    public var uuid = UUID()
    
    /// Allows checking whether Bluetooth is powered on.
    public var isBluetoothAvailable: Bool {
        return cbCentralManager.state == .poweredOn
    }
    
    /// Allows checking whether Bluejay is currently connecting to a peripheral.
    public var isConnecting: Bool {
        return connectingPeripheral != nil
    }
    
    /// Allows checking whether Bluejay is currently connected to a peripheral.
    public var isConnected: Bool {
        return connectedPeripheral != nil
    }
    
    /// Allows checking whether Bluejay is currently disconnecting from a peripheral.
    public var isDisconnecting: Bool = false
    
    /// Allows checking whether Bluejay is currently scanning.
    public var isScanning: Bool {
        // Cannot rely on the manager's state for isScanning as it is not usually updated immediately, and while that delay might be a more accurate representation of the current state, it is almost always more useful to evaluate whether Bluejay is running a scan request at the top of its queue.
        return queue.isScanning()
    }
    
    // MARK: - Initialization
    
    /**
     Initializing a Bluejay instance will not yet initialize the CoreBluetooth stack. An explicit call to start running a Bluejay instance after it is intialized is required because in cases where a state resotration is trying to restore a listen on a characteristic, a listen restorer must be available before the CoreBluetooth stack is re-initialized. This two-step startup allows you to insert and gaurantee the setup of your listen restorer in between the initialization of Bluejay and the initialization of the CoreBluetooth stack triggered via this call.
     */
    public override init() {
        super.init()
        
        shouldRestoreState = UIApplication.shared.applicationState == .background
        
        if shouldRestoreState {
            debugPrint("Begin startup background task for restoring CoreBluetooth.")
            startupBackgroundTask = UIApplication.shared.beginBackgroundTask(expirationHandler: nil)
        }
        
        log("Bluejay initialized with UUID: \(uuid.uuidString).")
        
        queue = Queue(bluejay: self)
    }
    
    deinit {
        cancelEverything()
        
        log("Deinit Bluejay with UUID: \(uuid.uuidString).")
    }
    
    /**
     Starting Bluejay will initialize the CoreBluetooth stack. Initializing a Bluejay instance will not yet initialize the CoreBluetooth stack. An explicit call to start running a Bluejay instance after it is intialized is required because in cases where a state resotration is trying to restore a listen on a characteristic, a listen restorer must be available before the CoreBluetooth stack is re-initialized. This two-step startup allows you to insert and gaurantee the setup of your listen restorer in between the initialization of Bluejay and the initialization of the CoreBluetooth stack triggered via this call.
     
     - Parameters:
        - observer: An object interested in observing Bluetooth connection events and state changes. You can register more observers using the `register` function.
        - restoreMode: Determines whether Bluejay will opt-in to state restoration, and if so, can optionally provide a listen restorer as well for restoring listens.
    */
    public func start(
        connectionObserver observer: ConnectionObserver? = nil,
        backgroundRestore restoreMode: BackgroundRestoreMode = .disable
        )
    {
        if cbCentralManager != nil {
            log("Error: The Bluejay instance with UUID: \(uuid.uuidString) has already started.")
            return
        }
        
        register(observer: queue)
        
        if let observer = observer {
            register(observer: observer)
        }
{'fraction_non_alphanumeric': 0.0459475304118423, 'fraction_numerical': 0.0017587571449509014, 'mean_word_length': 3.129198184568835, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1979398', 'n_tokens_mistral': 3204, 'n_tokens_neox': 3012, 'n_words': 1561}