//
//  GMFGSpeedTestHandler.swift
//  MVM5G
//
//  Created by Jason Beck on 5/26/20.
//  Copyright © 2020 Kyle. All rights reserved.
//
import CoreBluetooth

protocol GMFGSpeedTestDelegate: AnyObject {
    func didStartSpeedTest()
    func didStopSpeedTest()
    func speedTestStatus(status: GMFGSpeedTestUpdate)
}

extension GMFGSpeedTestDelegate {
    func didStartSpeedTest(){}
    func didStopSpeedTest(){}
}

private enum ResultKeys: String {
    case result            = "Result"
    case speedTestServer   = "SIP"
    case speedTestServerID = "SID"
    case routerIP          = "RIP"
    case downlink          = "DL"
    case uplink            = "UL"
    case latency           = "Lat"
    case jitter            = "Jitt"
    case progress          = "Prog"
    case errorMessage      = "Error"
}

enum Status: String {
    case ready    = "Ready"
    case running  = "Running"
    case failed   = "Failed"
    case done     = "Done"
    case notReady = "NotReady"
}

struct GMFGSpeedTestUpdate {
    var result: Status
    var speedTestServerIP: String?
    var speedTestServerID: Int?
    var routerIP: String?
    var downlink: String?
    var uplink: String?
    var latency: String?
    var jitter: Int?
    var progress: Double?
    var errorMessage: String?
    var rawResults: [String: Any]?
    
    func getDictFromSelf() -> [String: Any]? {
        let dataDict: [String: Any] = [
            ResultKeys.result.rawValue           : result.rawValue,
            ResultKeys.speedTestServer.rawValue  : speedTestServerIP ?? GMFGConstant.empty,
            ResultKeys.speedTestServerID.rawValue: speedTestServerID ?? -1,
            ResultKeys.routerIP.rawValue         : routerIP ?? GMFGConstant.empty,
            ResultKeys.downlink.rawValue         : downlink ?? GMFGConstant.empty,
            ResultKeys.uplink.rawValue           : uplink ?? GMFGConstant.empty,
            ResultKeys.latency.rawValue          : latency ?? GMFGConstant.empty,
            ResultKeys.jitter.rawValue           : jitter ?? -1,
            ResultKeys.progress.rawValue         : progress ?? 0.0,
            ResultKeys.errorMessage.rawValue     : errorMessage ?? GMFGConstant.empty
        ]
        return dataDict
    }
}

class GMFGSpeedTestHandler: GMFGBLEHandlerProtocol {
    
    private let multicast = MulticastDelegate<GMFGSpeedTestDelegate>()
    public func setDelegate(delegates: [GMFGSpeedTestDelegate?]) {
        self.multicast.add(delegates: delegates)
    }
    
    public func reset() {
        multicast.removeAll()
    }
    
    //MARK:- Helper
    private func rawKey(_ type: ResultKeys) -> String { return type.rawValue }
    private func rawStatus(_ type: Status) -> String { return type.rawValue }
    
    public func start(_ peripheral: CBPeripheral, _ characteristic: CBCharacteristic) {
        let requestValue = ["SpeedTest": "Start"]
        guard let data = convertToData(requestValue) else { return }
        let writeType: CBCharacteristicWriteType = characteristic.properties.contains(.writeWithoutResponse) ? .withoutResponse : .withResponse
        peripheral.writeValue(data, for: characteristic, type: writeType)
        multicast.invokeForEachDelegate{ $0.didStartSpeedTest() }
        
        //Notify process started having status = running
        let responseObj = GMFGSpeedTestUpdate(result: .running)
        multicast.invokeForEachDelegate{ $0.speedTestStatus(status: responseObj) }
    }
    
    public func stop(_ peripheral: CBPeripheral, _ characteristic: CBCharacteristic) {
        let requestValue = ["SpeedTest": "Stop"]
        guard let data = convertToData(requestValue) else { return }
        let writeType: CBCharacteristicWriteType = characteristic.properties.contains(.writeWithoutResponse) ? .withoutResponse : .withResponse
        peripheral.writeValue(data, for: characteristic, type: writeType)
        multicast.invokeForEachDelegate{ $0.didStopSpeedTest() }
    }
    
    public func update(_ peripheral: CBPeripheral, _ characteristic: CBCharacteristic) {
        peripheral.readValue(for: characteristic)
    }
    
    //It is possible to try and call the device before it's done loading
    public func deviceNotReady() {
        let responseObj = GMFGSpeedTestUpdate(result: .notReady)
        multicast.invokeForEachDelegate{ $0.speedTestStatus(status: responseObj) }
    }
    
    public func response(response: [String : Any]) {
       guard let statusString = response[rawKey(.result)] as? String,
           let status = Status(rawValue: statusString) else {
               return
        }
        
        let responseObj = responseFormat(status: status, response: response)
        if responseObj.result == .done || responseObj.result == .failed {
            multicast.invokeForEachDelegate{ $0.didStopSpeedTest() }
        }
        multicast.invokeForEachDelegate{ $0.speedTestStatus(status: responseObj) }
    }
    
    func responseFormat(status: Status, response: [String: Any]) -> GMFGSpeedTestUpdate {
        return GMFGSpeedTestUpdate(result: status, speedTestServerIP: response[rawKey(.speedTestServer)] as? String,
                                   speedTestServerID: response[rawKey(.speedTestServerID)] as? Int,
                                   routerIP:          response[rawKey(.routerIP)] as? String,
                                   downlink:          response[rawKey(.downlink)] as? String,
                                   uplink:            response[rawKey(.uplink)] as? String,
                                   latency:           response[rawKey(.latency)] as? String,
                                   jitter:            response[rawKey(.jitter)] as? Int,
                                   progress:          (response[rawKey(.progress)] as? NSString)?.doubleValue,
                                   errorMessage:      response[rawKey(.errorMessage)] as? String, rawResults: response)
    }
}
