//
//  PeripheralManager.swift
//  BluetoothPeripheral
//
//  Created by Christopher Gilardi on 7/29/20.
//  Copyright © 2020 Facebook. All rights reserved.
//

import Foundation
import CoreBluetooth

@available(iOS 10.0, *)
class PeripheralManager: NSObject, CBPeripheralManagerDelegate {
    
    // MARK: Public Properties
    
    public var isAdvertising: Bool = false;
    
    // MARK: Properties
    private var peripheralManager: CBPeripheralManager?;
    private var stateChangeListeners: [(CBManagerState) -> Void] = []
    private var advertisingData: [String: Any]?
    
    
    private var deviceName: String = "Unknown Device";
    private var serviceUUID: CBUUID?
    private var characteristics: [CBCharacteristic] = []
    
    private override init() { }
    
    private func setup() {
        self.peripheralManager = CBPeripheralManager(delegate: self, queue: nil);
    }
    
    func setServiceUUID(_ serviceUUID: String) {
        self.serviceUUID = CBUUID(string: serviceUUID)
    }
    
    func setDeviceName(_ name: String) {
        self.deviceName = name;
    }
    
    func addCharacteristic(_ characteristic: CBMutableCharacteristic) {
        characteristics.append(characteristic)
        log("Added characteristic " + characteristic.uuid.uuidString)
    }
    
    func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) {
        for request in requests {
            if let stringData = request.value {
                log(String(data: stringData, encoding: .utf8) ?? "Received data but couldn't decode it")
            }
        }
        
        self.peripheralManager?.respond(to: requests[0], withResult: .success)
    }
    
    func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
        
        switch peripheral.state {
        case .poweredOn:
            self.configPeripheral()
            break;
        default:
            log("Peripheral is not in state .poweredOn");
        }
        
        for listener in self.stateChangeListeners {
            listener(peripheral.state)
        }
    }
    
    func subscribeToStateChanges(_ callback: @escaping(_ state: CBManagerState) -> Void) {
        stateChangeListeners.append(callback)
    }
    
    private func configPeripheral() {
        let serviceToAdd = CBMutableService(type: self.serviceUUID!, primary: true)
        serviceToAdd.characteristics = self.characteristics

        self.peripheralManager?.add(serviceToAdd)
    }
    
    func startAdvertising() {
        let advertisingData: [String: Any] = [
            CBAdvertisementDataServiceUUIDsKey: [self.serviceUUID],
            CBAdvertisementDataLocalNameKey: deviceName
        ]

        self.peripheralManager?.startAdvertising(advertisingData)
        
        log("Started Advertising Service " + (self.serviceUUID?.uuidString ?? "NO_UUID") + " with device name " + self.deviceName)
    }
    
    func stopAdvertising() {
        if self.peripheralManager?.isAdvertising ?? false {
            self.peripheralManager?.stopAdvertising()
            log("Stopped Advertising.")
        } else {
            log("CBPeripheralManager was not alreaady advertising. Not stopping.")
        }
    }
    
    private func log(_ message: String) {
        print("BluetoothPeripheral: " + message)
    }
    
    // MARK: Singleton Setup
    
    public static var instance: PeripheralManager {
        get {
            if PeripheralManager._instance == nil {
                PeripheralManager._instance = PeripheralManager()
                PeripheralManager._instance?.setup()
            }
            
            return PeripheralManager._instance!
        }
    }
    
    private static var _instance: PeripheralManager?
}
