//
//  ABCCardTerminalMonitor.swift
//  ABCBluetoothDemo
//
//  Created by AB Circle on 22/4/2021.
//  Copyright © 2020-2024 AB Circle. All rights reserved.
//

import Foundation
import ABCSmartCardIO


/// Delegate protocol for delegate classes to implement
protocol ABCCardTerminalMonitorProtocol {
    /// Call back function definition to implement by the delegate.
    /// Called once when a terminal has hit the battery threshold
    ///
    /// - Parameters:
    ///   - terminalName: Name of card terminal hitting the threshold
    ///   - batteryThreshold: Battery threshold reached
    func onBatteryThresholdReached(terminalName: String, batteryThreshold: UInt8)
    
    /// Call back function definition to implement by the delegate.
    /// Called once when a terminal has disconnected
    ///
    /// - Parameter terminalName: Name of card terminal that has disconnected
    func onDisconnected(terminalName: String)
}

/// CardTerminal helper class for monitoring detected CardTerminals.
///
/// Monitors for disconnection and battery level. If a delegate has been set will
/// call callback functions once a CardTerminal disconnect or when
/// a CardTerminal hits a pre-set battery threshold level.
class ABCCardTerminalMonitor {
    
    /// Delegate, set this to the class acting as delegate for this class using the ABCCardTerminalMonitorProtocol
    var delegate: ABCCardTerminalMonitorProtocol?
    
    /// Main instance of the AB Circle Terminal Factory.
    private let abcTerminalFactory = ABCircleTerminalFactory.getDefault()
    /// CardTerminals to monitor
    private var terminalsToMonitor = [String: CardTerminalToMonitor]()
    /// Timer (used for automatically updating the Card Terminal list and information)
    private var timer: DispatchSourceTimer? = nil
    /// Timer refresh rate in seconds
    private var timerRefreshRateInSeconds: UInt16 = 0
    /// Battery threshold to monitor
    private var batteryThreshold: UInt8 = 0x00
    /// Queue for timer
    private let timerQueue = DispatchQueue(label: "abc.monitortimer.queue")
    /// Flag indicating if monitor is running
    private var monitorRunning: Bool = false
    
    /// Initialiser.
    init() {
    }
    
    /// De-initialiser
    deinit {
        stopMonitor()
    }
    
    /// Start monitoring CardTerminals using the entered values for refresh rate and battery threshold.
    ///
    /// - Parameters:
    ///   - refreshRateInSeconds: Monitor refresh rate in seconds
    ///   - batteryThreshold: Battery threshold to monitor for
    func startMonitoring(refreshRateInSeconds: UInt16 = 1, batteryThreshold: UInt8 = 20)
    {
        if timer == nil {
            print("[ABCCardTerminalMonitor] Start monitoring")
            self.timerRefreshRateInSeconds  = refreshRateInSeconds
            self.batteryThreshold           = batteryThreshold
            startMonitor()
        }
    }
    
    /// Stop monitoring CardTerminals
    func stopMonitoring()
    {
        print("[ABCCardTerminalMonitor] Stop monitoring")
        stopMonitor()
        timerRefreshRateInSeconds = 0
        batteryThreshold          = 0x00
    }
    
    /// Flag indicating if the monitor is currently running or not
    var isMonitorRunning: Bool {
        return monitorRunning
    }
    
    /// Set the battery threshold to monitor for all connected readers.
    /// Notes:
    /// - Setting this to 0 will disable monitoring the battery level
    /// - Setting a different value from the previous value will reset the notice
    ///
    /// - Parameter batteryThreshold: Threshold value from 0 to 100%
    func setBatteryThreshold(batteryThreshold: UInt8)
    {
        if batteryThreshold <= 100 {
            if self.batteryThreshold != batteryThreshold {
                print("[ABCCardTerminalMonitor] Battery treshold: old = %u, new = %u", self.batteryThreshold, batteryThreshold)
                self.batteryThreshold = batteryThreshold
                for terminal in terminalsToMonitor.values {
                    terminal.batteryThreshold   = batteryThreshold
                    terminal.batteryNoticeGiven = false
                }
            }
        }
    }
    
    /// Set the monitor refresh rate in seconds. This is the frequency where the monitors updates
    /// the information of the monitored CardTerminals.
    /// Note: setting this to 0 will disable monitoring completely.
    ///
    /// - Parameter refreshRateInSeconds: refresh rate in seconds
    func setMonitorRefreshRate(refreshRateInSeconds: UInt16) {
        if timerRefreshRateInSeconds != refreshRateInSeconds {
            print("[ABCCardTerminalMonitor] Refresh rate (seconds): old = %u, new = %u", timerRefreshRateInSeconds, refreshRateInSeconds)
            timerRefreshRateInSeconds = refreshRateInSeconds
            if monitorRunning {
                stopMonitor()
                startMonitor()
            }
        }
    }
    
    /// Function called when the battery threshold of a CardTerminal has been reached
    /// If a delegate has been set, will call the implemented callback function of the delegate.
    ///
    /// - Parameters:
    ///   - terminalName: Name of the terminal that reached the battery threshold
    ///   - batteryThreshold: Value of the battery threshold
    private func onBatteryThresholdReached(terminalName: String, batteryThreshold: UInt8) {
        delegate?.onBatteryThresholdReached(terminalName: terminalName, batteryThreshold: batteryThreshold)
    }
    
    /// Function called when a CardTerminal has disconnected
    /// If a delegate has been set, will call the implemented callback function of the delegate.
    ///
    /// - Parameter terminalName: Name of the terminal that has disconnected
    private func onDisconnected(terminalName: String) {
        delegate?.onDisconnected(terminalName: terminalName)
    }
    
    /// Starts the CardTerminal monitor
    private func startMonitor() {
        if self.timerRefreshRateInSeconds > 0 {
            print("[ABCCardTerminalMonitor] Start monitor")
            timer = DispatchSource.makeTimerSource(queue: timerQueue)
            if let timer = timer {
                timer.setEventHandler { [weak self] in
                    guard let self = self else {
                        return
                    }
                    self.monitorCardTerminals()
                }
                timer.schedule(deadline: .now(), repeating:  .seconds(Int(timerRefreshRateInSeconds)), leeway: .milliseconds(100))
                timer.resume()
                monitorRunning = true
            }
        }
    }
    
    /// Stops the CardTerminal monitor
    private func stopMonitor() {
        print("[ABCCardTerminalMonitor] Stop monitor")
        timer = nil
        for terminal in terminalsToMonitor.values {
            terminal.cleanup()
        }
        terminalsToMonitor.removeAll()
        monitorRunning = false
    }
    
    /// Callback function called by the timer which does all the monitoring
    ///@objc
    private func monitorCardTerminals() {
        let terminals: [CardTerminal] = self.abcTerminalFactory.terminals().list()
        
        self.checkDisconnectedTerminals(terminals: terminals)
        self.checkBatteryLevel()
    }
    
    /// Checks if a CardTerminal has disconnected
    ///
    /// - Parameter terminals: latest CardTerminal list to check against
    private func checkDisconnectedTerminals(terminals: [CardTerminal]) {
        for monitoredTerminal in terminalsToMonitor.values {
            let name    : String = monitoredTerminal.cardTerminalName
            var found   : Bool   = false
            
            for terminal in terminals {
                if terminal.getName() == name {
                    found = true
                    break
                }
            }
            if !found {
                onDisconnected(terminalName: name)
                removeMonitoredTerminal(terminalName: name)
            }
        }
        refreshMonitoredTerminals(terminals: terminals)
    }

    /// Checks if a CardTerminal has reached the battery threshold
    private func checkBatteryLevel() {
        if batteryThreshold > 0 {
            for terminal in terminalsToMonitor.values {
                if !terminal.batteryNoticeGiven {
                    if terminal.batteryThresholdReached() {
                        onBatteryThresholdReached(terminalName: terminal.cardTerminalName, batteryThreshold: terminal.batteryThreshold)
                        terminal.batteryNoticeGiven = true
                    }
                }
            }
        }
    }
    
    /// Removes a monitored CardTerminal from the internal list
    ///
    /// - Parameter terminalName: Name of monitored CardTerminal to remove.
    private func removeMonitoredTerminal(terminalName: String)
    {
        if let removedTerminal = terminalsToMonitor.removeValue(forKey: terminalName) {
            removedTerminal.cleanup()
            print("[ABCCardTerminalMonitor] Removed ", removedTerminal.cardTerminalName)
        }
    }
    
    /// Refreshes the internal list of monitored CardTerminals
    ///
    /// - Parameter terminals: latest CardTerminal list to check against
    private func refreshMonitoredTerminals(terminals: [CardTerminal]) {
        for terminal in terminals {
            let name: String = terminal.getName()
            if terminalsToMonitor[name] == nil {
                terminalsToMonitor[name] = CardTerminalToMonitor(cardTerminal: terminal, batteryThreshold: batteryThreshold)
                print("[ABCCardTerminalMonitor] Added ", name)
            }
        }
    }
}
