//
//  NSBluetoothManager.swift
//  nRF Toolbox
//
//  Created by Mostafa Berg on 06/05/16.
//  Copyright © 2016 Nordic Semiconductor. All rights reserved.
//

import UIKit
import CoreBluetooth
fileprivate func < <T : Comparable>(lhs: T?, rhs: T?) -> Bool {
    switch (lhs, rhs) {
    case let (l?, r?):
        return l < r
    case (nil, _?):
        return true
    default:
        return false
    }
}

fileprivate func > <T : Comparable>(lhs: T?, rhs: T?) -> Bool {
    switch (lhs, rhs) {
    case let (l?, r?):
        return l > r
    default:
        return rhs < lhs
    }
}


protocol NSBluetoothManagerDelegate {
    func didConnectPeripheral(deviceName aName : String)
    func didDisconnectPeripheral()
    func receivedDoubleData(allData:[Double], sender:AppDelegate.MainViews)
    func receivedFloatData(allData:[Float], sender:AppDelegate.MainViews)
    func reportError(error:UInt8, sender:AppDelegate.MainViews)
}

class NSBluetoothManager: NSObject, CBPeripheralDelegate, CBCentralManagerDelegate {
    
    //MARK: - Delegate Properties
    var delegate : NSBluetoothManagerDelegate?
    
    //MARK: - Class Properties
    var UARTServiceUUID             : CBUUID?
    var UARTRXCharacteristicUUID    : CBUUID?
    var UARTTXCharacteristicUUID    : CBUUID?
    
    var centralManager              : CBCentralManager?
    var bluetoothPeripheral         : CBPeripheral?
    var uartRXCharacteristic        : CBCharacteristic?
    var uartTXCharacteristic        : CBCharacteristic?
    
    var sender: AppDelegate.MainViews = AppDelegate.MainViews.HOME
    var expected_double = false
    var alldata: Data = Data();
    var count = 0
    var maxCount = 0
    var dataLength = 0
    var error_first = true
    var current_command = Constants.BATTERY_STATUS_COMMAND
    
    
    //MARK: - Implementation
    required init(withManager aManager : CBCentralManager) {
        super.init()
        // ret: instancetype
        UARTServiceUUID          = CBUUID(string: NORServiceIdentifiers.uartServiceUUIDString)
        UARTTXCharacteristicUUID = CBUUID(string: NORServiceIdentifiers.uartTXCharacteristicUUIDString)
        UARTRXCharacteristicUUID = CBUUID(string: NORServiceIdentifiers.uartRXCharacteristicUUIDString)
        centralManager = aManager
        centralManager?.delegate = self
    }
    
    //MARK: - BluetoothManager API
    func connectPeripheral(peripheral aPeripheral : CBPeripheral) {
        centralManager?.connect(aPeripheral, options: nil)
    }
    
    func cancelPeriphralConnection() {
        guard bluetoothPeripheral != nil else {
            return
        }
        centralManager?.cancelPeripheralConnection(bluetoothPeripheral!)
    }
    
    func isConnected() -> Bool {
        return bluetoothPeripheral != nil
    }
    
    func send(data aData : Data, sender: AppDelegate.MainViews) {
        self.sender = sender
        self.count = 0;
        self.current_command = aData
        self.expected_double = false
        self.error_first = true
        
        
        
        //if(maxCount == 1  && self.current_command != Constants.OPTICAL_GAIN_COMMAND) {
        self.expected_double = true
        //}
        
        guard self.uartRXCharacteristic != nil else {
            return
        }
        
        var type = CBCharacteristicWriteType.withoutResponse
        if (self.uartRXCharacteristic?.properties.rawValue)! & CBCharacteristicProperties.write.rawValue > 0 {
            type = CBCharacteristicWriteType.withResponse
        }
        
        self.bluetoothPeripheral?.writeValue(aData, for: self.uartRXCharacteristic!, type: type)
    }
    
    
    //MARK: - CBCentralManagerDelegate
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        var state : String
        switch(central.state){
        case .poweredOn:
            state = "Powered ON"
            break
        case .poweredOff:
            state = "Powered OFF"
            break
        case .resetting:
            state = "Resetting"
            break
        case .unauthorized:
            state = "Unautthorized"
            break
        case .unsupported:
            state = "Unsupported"
            break
        case .unknown:
            state = "Unknown"
            break
        }
        
    }
    
    
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        bluetoothPeripheral = peripheral
        bluetoothPeripheral?.delegate = self
        delegate?.didConnectPeripheral(deviceName: peripheral.name!)
        peripheral.discoverServices([UARTServiceUUID!])
    }
    
    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        guard error == nil else {
            delegate?.didDisconnectPeripheral()
            bluetoothPeripheral?.delegate = nil
            bluetoothPeripheral = nil
            return
        }
        
        delegate?.didDisconnectPeripheral()
        bluetoothPeripheral?.delegate = nil
        bluetoothPeripheral = nil
        
    }
    
    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
        guard error == nil else {
            return
        }
        
        delegate?.didDisconnectPeripheral()
        bluetoothPeripheral?.delegate = nil
        bluetoothPeripheral = nil
    }
    
    //MARK: - CBPeripheralDelegate
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        guard error == nil else {
            return
        }
        
        for aService: CBService in peripheral.services! {
            if aService.uuid.isEqual(UARTServiceUUID) {
                bluetoothPeripheral?.discoverCharacteristics(nil, for: aService)
                return
            }
        }
        
        //No UART service discovered
        cancelPeriphralConnection()
    }
    
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        guard error == nil else {
            
            return
        }
        
        if service.uuid.isEqual(UARTServiceUUID) {
            for aCharacteristic : CBCharacteristic in service.characteristics! {
                if aCharacteristic.uuid.isEqual(UARTTXCharacteristicUUID) {
                    uartTXCharacteristic = aCharacteristic
                }else if aCharacteristic.uuid.isEqual(UARTRXCharacteristicUUID) {
                    uartRXCharacteristic = aCharacteristic
                }
            }
            //Enable notifications on TX Characteristic
            if(uartTXCharacteristic != nil && uartRXCharacteristic != nil) {
                bluetoothPeripheral?.setNotifyValue(true, for: uartTXCharacteristic!)
            }else{
                cancelPeriphralConnection()
            }
        }
        
    }
    
    func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
        guard error == nil else {
            return
        }
        
        if characteristic.isNotifying {
            
        }else{
            
        }
        
    }
    
    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
        guard error == nil else {
            return
        }
    }
    
    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor descriptor: CBDescriptor, error: Error?) {
        guard error == nil else {
            
            return
        }
    }
    
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        guard error == nil else {
            
            return
        }
        
        if(self.error_first){
            var error_data = Data()
            error_data.append(characteristic.value!);
            
            var error_code = getErrorNumber(data: error_data)
            
            if(error_code[0] == 0){
                self.error_first = false
                
                self.dataLength = Int(error_code[1]) + (Int(error_code[2]) << 8)
                if(self.dataLength == 1 || self.dataLength == 2){
                    self.maxCount = 1
                }else{
                    var frames = 0.0
                    if(UserDefaults.standard.bool(forKey: "Interpolation_enabled")){
                        self.dataLength = self.dataLength + 2
                        frames = ceil(Double(self.dataLength) * 8.0 / 20.0)
                    }else{
                        frames = ceil(Double(self.dataLength) * 8.0 * 2.0 / 20.0)
                    }
                    self.maxCount = (Int(frames))
                }
            }else{
                self.delegate?.reportError(error: error_code[0], sender:self.sender)
            }
        }else{
            if(count == 0 && maxCount != 0){
                self.alldata = Data();
            }
            
            self.alldata.append(characteristic.value!);
            count = count + 1
            if(count == maxCount){
                if(expected_double){
                    delegate?.receivedDoubleData(allData: self.convertDataToDoubleArray(), sender: self.sender)
                    //Constants.methodFinish = NSDate()
                    //let executionTime = Constants.methodFinish.timeIntervalSince(Constants.methodStart as Date)
                    //print("Execution time: \(executionTime)")
                }else{
                    delegate?.receivedFloatData(allData: self.convertDataToFloatArray(), sender: self.sender)
                }
            }
        }
    }
    
    func getErrorNumber(data:Data) -> [UInt8]
    {
        let bytes_count = data.count / MemoryLayout<UInt8>.size
        var array = [UInt8](repeating: 0, count: bytes_count)
        data.copyBytes(to: &array, count:bytes_count * MemoryLayout<UInt8>.size)
        
        return array
    }
    
    func convertDataToFloatArray() -> [Float]
    {
        var i = 0
        var values: [Float] = [Float]()
        
        while(i < alldata.count - 4){
            let start = i, end = i+4
            let range:Range<Int> = start..<end
            
            let subdata = (self.alldata.subdata(in: range)) as Data
            let value = subdata.withUnsafeBytes { (ptr: UnsafePointer<Float>) -> Float in
                return ptr.pointee
            }
            values.append(value)
            print(value)
            i = i+4
        }
        
        return values
    }
    
    func convertDataToDoubleArray() -> [Double]
    {
        //Constants.methodStart = NSDate()
        var values: [Double] = [Double]()
        
        if(self.dataLength == 1)
        {
            values.append(0.0)
        }else if(self.dataLength == 2){
            let start = 0, end = 2
            let range:Range<Int> = start..<end
            let subdata = (self.alldata.subdata(in: range)) as Data
            let value = subdata.withUnsafeBytes { (ptr: UnsafePointer<UInt16>) -> UInt16 in
                return ptr.pointee
            }
            values.append(Double(value))
        }else{
            var loopCount = 0
            var i = 0
            var flag = false
            var wvn: [Int64] = [Int64]()
            
            if(UserDefaults.standard.bool(forKey: "Interpolation_enabled"))
            {
                loopCount = self.dataLength * 8
                flag = true
            }else{
                loopCount = self.dataLength * 2 * 8
            }
            
            while(i < loopCount){
                let start = i, end = i+8
                let range:Range<Int> = start..<end
                let subdata = (self.alldata.subdata(in: range)) as Data
                
                if((flag == true) && (i >= (loopCount - 16))){
                    let value = subdata.withUnsafeBytes { (ptr: UnsafePointer<Int64>) -> Int64 in
                        return ptr.pointee
                    }
                    wvn.append(value)
                }else{
                    let value = subdata.withUnsafeBytes { (ptr: UnsafePointer<Double>) -> Double in
                        return ptr.pointee
                    }
                    values.append(value)
                }
                i = i+8
            }
            
            if(UserDefaults.standard.bool(forKey: "Interpolation_enabled"))
            {
                let xStep = wvn[wvn.count - 1]
                wvn.remove(at: wvn.count - 1)
                
                for i in 1 ..< values.count{
                    let x = wvn[i - 1] + xStep
                    wvn.append(x)
                }
                
                for i in 0..<wvn.count{
                    wvn[i] = (wvn[i] >> 3) * 10000
                    let x = Double(wvn[i]) / Double(1 << 30)
                    values.append(x)
                }
                
            }
        }
        return values
    }
    
}
