//
// © 2018-2020 Qualcomm Technologies, Inc. and/or its subsidiaries. All rights reserved.
//

import UIKit
import NVActivityIndicatorView

class BluetoothOnboardingSearchingViewController: ModalViewController {
    var deviceID: String?

    @IBOutlet weak var spinnerContainer: UIView!
    var animation: NVActivityIndicatorView!

    // MARK: Private Types
    private struct Segues {
        static let showNewDevices = "showNewDevices"
    }

    // MARK: Private Properties
    private var observers = [ObserverToken] ()
	private weak var scanTimer: Timer?
    private var discoveredDevices = Set<IoTBluetoothDevice> ()
    private enum OnboardingState {
        case quiescent
        case waitingForScanStopped
        case scanning
        case done
    }
    private var state = OnboardingState.quiescent
}

extension BluetoothOnboardingSearchingViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        animation = NVActivityIndicatorView(frame: spinnerContainer.bounds,
                                            type: .ballRotateChase,
                                            color: UIColor.darkGray,
                                            padding: 0.0)
        spinnerContainer.addSubview(animation)
        title = NSLocalizedString("ADD BLUETOOTH DEVICE", comment: "Add Bluetooth Device title")
    }

    override func viewWillAppear(_ animated: Bool) {
        state = .quiescent
        super.viewWillAppear(animated)
        guard
            let id = deviceID,
            let device = playerController.device(id: id) else {
                return
        }

        setUpObservers()

        if device.isBluetoothScanning {
            state = .waitingForScanStopped
            device.stopBtScan(completion: { [weak self] (success, error) in
                guard let self = self else { return }
                if success {
                    self.animation.startAnimating()
                }
            }, completionQueue: DispatchQueue.main)
        } else {
            startNewScan()
        }
    }

    func startNewScan() {
        guard
            let id = deviceID,
            let device = playerController.device(id: id) else {
                return
        }

        self.state = .scanning
        device.startBtScan(completion: { [weak self] (success, error) in
            guard let self = self else { return }
            if success {
                self.animation.startAnimating()
                self.waitForScan()
            } else {
                self.showCouldNotStartScanError()
            }
            },
                           completionQueue: DispatchQueue.main)
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        guard
            let id = deviceID,
            let device = playerController.device(id: id) else {
                return
        }
        removeObservers()

        scanTimer?.invalidate()
        scanTimer = nil
        device.stopBtScan(completion: nil,
                          completionQueue: nil)
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let destVC = segue.destination as? BluetoothOnboardingNewDevicesViewController {
            destVC.deviceID = deviceID
            destVC.discoveredDevices = Array(discoveredDevices).sorted(by: { (device1, device2) -> Bool in
                return device1.name.localizedCompare(device2.name) == .orderedAscending
            })
            segue.destination.injectDependencies(preferences: preferences, playerController: playerController)
        }
    }
}

extension BluetoothOnboardingSearchingViewController {
    private func setUpObservers() {
        let deviceHandler: (DeviceNotification) -> () = { [weak self] notification in
            guard let self = self else { return }

            if notification.device.deviceID == self.deviceID! &&
                notification.reason == .removed {
                self.stopScan()
                self.removeObservers()
                self.presentingViewController?.dismiss(animated: true, completion: nil)
            }
        }

        let deviceObserver = NotificationCenter.default.addObserver(forType: DeviceNotification.self,
                                                                    object: playerController,
                                                                    queue: OperationQueue.main,
                                                                    using: deviceHandler)
        // ************


        let bluetoothHandler: (BluetoothNotification) -> () = { [weak self] notification in
            guard let self = self else { return }

            if notification.device.deviceID == self.deviceID! {
                switch notification.reason {
                case .adapterStateChanged:
                    // Having the adapter disable would be bad.
                    self.stopScan()
                    self.removeObservers()
                    self.presentingViewController?.dismiss(animated: true, completion: nil)
                case .deviceDiscovered:
                    if let btDevice = notification.payload as? IoTBluetoothDevice {
                        self.handleDeviceDiscovered(btDevice)
                    }
                    break;
                case .scanStateChanged:
                    if let scanning = notification.payload as? Bool {
                        if self.state == .waitingForScanStopped && !scanning {
                            self.startNewScan()
                        } else if self.state == .scanning && !scanning {
                            self.handleScanStopped()
                        }
                    }
                default:
                    break
                }
            }
        }

        let bluetoothObserver = NotificationCenter.default.addObserver(forType: BluetoothNotification.self,
                                                                       object: playerController,
                                                                       queue: OperationQueue.main,
                                                                       using: bluetoothHandler)

        observers = [deviceObserver, bluetoothObserver]
    }

    private func removeObservers () {
        observers.forEach {
            NotificationCenter.default.removeObserver($0)
        }
        observers.removeAll()
    }

    private func waitForScan() {
        let timer = Timer(timeInterval: 30,
                          target: self,
                          selector: #selector(scanTimerExpired(_:)),
                          userInfo: nil,
                          repeats: false)
        RunLoop.current.add(timer, forMode: RunLoop.Mode.common)
        scanTimer = timer
    }

    @objc private func scanTimerExpired(_ timer: Timer) {
        removeObservers()
        handleScanStopped()
    }

    private func stopScan() {
        state = .done
        scanTimer?.invalidate()
        scanTimer = nil
        animation.stopAnimating()
    }

    private func handleScanStopped() {
		// Filter discovereds in case already paired are present
        stopScan()
        guard
            let id = deviceID,
            let device = playerController.device(id: id) else {
                return
        }
        let pairedDevices = device.pairedBluetoothDevices
        let filteredDevices = discoveredDevices.filter( { return !pairedDevices.contains($0) } )
        if filteredDevices.count == 0 {
            handleNoNewDevices()
        } else {
            performSegue(withIdentifier: Segues.showNewDevices, sender: self)
        }
    }

    private func handleDeviceDiscovered(_ btDevice: IoTBluetoothDevice) {
        if btDevice.name.count > 0 || btDevice.address.count > 0 {
            discoveredDevices.insert(btDevice)
        }
    }

    private func showCouldNotStartScanError() {
        let alertController = UIAlertController(title: NSLocalizedString("Scan Error", comment: "Scan error title"),
                                                message: NSLocalizedString("Could not start scanning for new devices.", comment: "message for scan start failure"),
                                                preferredStyle: .alert)

        let okAction = UIAlertAction(title: NSLocalizedString("OK", comment: "OK action"),
                                     style: .default,
                                     handler: nil)
        alertController.addAction(okAction)
        present(alertController, animated: true, completion: nil)
    }

    private func handleNoNewDevices() {
        let alertController = UIAlertController(title: NSLocalizedString("No Devices Found", comment: "Scan error title"),
                                                message: NSLocalizedString("No new unpaired devices could be detected.", comment: "message for no new devices"),
                                                preferredStyle: .alert)

        let okAction = UIAlertAction(title: NSLocalizedString("OK", comment: "OK action"),
                                     style: .default,
                                     handler: { [weak self] _ in
                                        guard let self = self else { return }
                                        self.presentingViewController?.dismiss(animated: true, completion: nil)
        })
        alertController.addAction(okAction)
        present(alertController,
                animated: true,
                completion: nil)
    }
}
