//
//  main.swift
//  Bluetooth
//
//  Created by Serhiy Mytrovtsiy on 08/06/2021.
//  Using Swift 5.0.
//  Running on macOS 10.15.
//
//  Copyright © 2021 Serhiy Mytrovtsiy. All rights reserved.
//

import Foundation
import Kit
import CoreBluetooth

public struct BLEDevice: Codable {
    let address: String
    var name: String
    var uuid: UUID?
    
    var RSSI: Int? = nil
    var batteryLevel: [KeyValue_t] = []
    
    var isConnected: Bool = false
    var isPaired: Bool = false
    
    var peripheral: CBPeripheral? = nil
    var isPeripheralInitialized: Bool = false
    
    var id: String {
        get { self.uuid?.uuidString ?? self.address }
    }
    
    var state: Bool {
        get { Store.shared.bool(key: "ble_\(self.id)", defaultValue: false) }
    }
    var notificationThreshold: String {
        Store.shared.string(key: "ble_\(self.id)_notification", defaultValue: "")
    }
    
    private enum CodingKeys: String, CodingKey {
        case address, name, uuid, RSSI, batteryLevel, isConnected, isPaired
    }
    
    init(address: String, name: String, uuid: UUID?, RSSI: Int?, batteryLevel: [KeyValue_t], isConnected: Bool, isPaired: Bool) {
        self.address = address
        self.name = name
        self.uuid = uuid
        self.RSSI = RSSI
        self.batteryLevel = batteryLevel
        self.isConnected = isConnected
        self.isPaired = isPaired
    }
    
    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.address = try container.decode(String.self, forKey: .address)
        self.name = try container.decode(String.self, forKey: .name)
        self.uuid = try? container.decode(UUID.self, forKey: .uuid)
        self.RSSI = try? container.decode(Int.self, forKey: .RSSI)
        self.batteryLevel = try container.decode(Array<KeyValue_t>.self, forKey: .batteryLevel)
        self.isConnected = try container.decode(Bool.self, forKey: .isConnected)
        self.isPaired = try container.decode(Bool.self, forKey: .isPaired)
    }
    
    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(address, forKey: .address)
        try container.encode(name, forKey: .name)
        try container.encode(uuid, forKey: .uuid)
        try container.encode(RSSI, forKey: .RSSI)
        try container.encode(batteryLevel, forKey: .batteryLevel)
        try container.encode(isConnected, forKey: .isConnected)
        try container.encode(isPaired, forKey: .isPaired)
    }
}

public class Bluetooth: Module {
    private var devicesReader: DevicesReader?
    private let popupView: Popup = Popup()
    private let settingsView: Settings = Settings()
    private let notificationsView: Notifications
    
    public init() {
        self.notificationsView = Notifications(.bluetooth)
        
        super.init(
            moduleType: .bluetooth,
            popup: self.popupView,
            settings: self.settingsView,
            notifications: self.notificationsView
        )
        guard self.available else { return }
        
        self.devicesReader = DevicesReader { [weak self] value in
            self?.batteryCallback(value)
        }
        
        self.settingsView.callback = { [weak self] in
            self?.devicesReader?.read()
        }
        
        self.setReaders([self.devicesReader])
    }
    
    private func batteryCallback(_ raw: [BLEDevice]?) {
        guard let value = raw, self.enabled else { return }
        
        let active = value.filter{ $0.isPaired || ($0.isConnected && !$0.batteryLevel.isEmpty) }
        DispatchQueue.main.async(execute: {
            self.popupView.batteryCallback(active)
            self.settingsView.setList(active)
            self.notificationsView.callback(active)
        })
        
        var list: [Stack_t] = []
        active.forEach { (d: BLEDevice) in
            if d.state {
                d.batteryLevel.forEach { (p: KeyValue_t) in
                    list.append(Stack_t(key: "\(d.address)-\(p.key)", value: "\(p.value)%"))
                }
            }
        }
        
        self.menuBar.widgets.filter{ $0.isActive }.forEach { (w: SWidget) in
            switch w.item {
            case let widget as StackWidget: widget.setValues(list)
            default: break
            }
        }
    }
}
