//
//  GMFGFotaHandler.swift
//  MVM5G
//
//  Created by Jason Beck on 4/20/20.
//  Copyright © 2020 Kyle. All rights reserved.
//
import CoreBluetooth

private enum GMFotaKey: String {
    case status = "FOTAStatus"
    case error = "ErrorMessage"
    case version = "FirmwareVersion"
    case check = "Check"
    case fotaOp = "FOTAOperation"
}

public enum GMFotaStatus: String {
    case notReady = "NotReady"
    case latest = "Latest"
    case available = "NewAvailable"
    case success = "SuccessfulUpgrade"
    case fail = "FailedUpgrade"
    case failDownload = "FailedDownload"
    case progress = "DownloadProgress"
    case upgrading = "Upgrading"
}

protocol GMFGFotaDelegate: AnyObject {
    func didStartUpdate()
    func fotaStatus(statusResponse: GMFotaResponse)
    func unknownResponse(rawResponse: [String: Any]?)
}

extension GMFGFotaDelegate {
    func didStartUpdate() {}
}

final class GMFotaResponse: CustomStringConvertible {
    var status: GMFotaStatus
    var errorMsg: String?
    private var _version = ""
    var version: String {
        set {
            if (newValue.isEmpty) || (newValue == "XXX.YYY.ZZZZ") || (newValue == "Unknown") {
                _version = "0.0.0.0"
            } else {
                _version = newValue
            }
        }
        get {
            return _version
        }
    }
    var rawResponse: [String: Any]
    
    init(status: GMFotaStatus, version: String, rawResponse: [String: Any]) {
        self.status = status
        self.rawResponse = rawResponse
        self.version = version
    }
    
    var description: String {
        return  """
                Status: \(self.status.rawValue)
                Error Message: \(self.errorMsg ?? GMFGConstant.empty)
                Version: \(self.version)
                Raw Response:\n\(self.rawResponse)
                """
    }
}

final class GMFGFotaHandler: GMFGBLEHandlerProtocol {
    
    private let multicast = MulticastDelegate<GMFGFotaDelegate>()

    public func setDelegate(delegates: [GMFGFotaDelegate?]) {
        self.multicast.add(delegates: delegates)
    }
    
    public func reset() {
        self.multicast.removeAll()
    }
    
    //MARK:- Helper
    private func rawKey(_ type: GMFotaKey) -> String { return type.rawValue }
    private func rawStatus(_ type: GMFotaStatus) -> String { return type.rawValue }
    
    //MARK:- Fota Functions
    public func requestVersion(_ peripheral: CBPeripheral, _ characteristic: CBCharacteristic) {
        peripheral.readValue(for: characteristic)
    }
    
    public func startUpdate(_ peripheral: CBPeripheral, _ characteristic: CBCharacteristic) {
        let requestValue = [rawKey(.fotaOp): rawKey(.check)]
        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.didStartUpdate() }
    }

    public func response(response: [String : Any]) {
        let status: GMFotaStatus!
        var version = "0.0.0.0"
        
        guard let statusString = response[rawKey(.status)] as? String else {
            multicast.invokeForEachDelegate{ $0.unknownResponse(rawResponse: response) }
            return
        }
        
        if statusString.contains("DownloadProgress") {
            status = .progress
        } else if let _status = GMFotaStatus(rawValue: statusString),
            let _version = response[rawKey(.version)] as? String {
            status = _status
            version = _version
        } else {
            multicast.invokeForEachDelegate{ $0.unknownResponse(rawResponse: response) }
            return
        }
    
        let responseObj = GMFotaResponse(status: status, version: version, rawResponse: response)
        responseObj.errorMsg = response[rawKey(.error)] as? String
        multicast.invokeForEachDelegate{ $0.fotaStatus(statusResponse: responseObj) }
    }

    public func deviceNotReady() {
        let responseObj = GMFotaResponse(status: .notReady, version: "Unknown", rawResponse: [:])
        multicast.invokeForEachDelegate{ $0.fotaStatus(statusResponse: responseObj) }
    }
}

