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

import UIKit
import CocoaLumberjack
import NVActivityIndicatorView

class ZigbeeOnboardingViewController: ModalViewController {

    var deviceID: String?
    @IBOutlet weak var spinnerContainer: UIView!
    @IBOutlet weak var topTextLabel: UILabel!
    @IBOutlet weak var middleTextLabel: UILabel!
    @IBOutlet weak var bottomTextLabel: UILabel!
    
    var animation: NVActivityIndicatorView!

    // MARK: Private Types
    private enum OnboardingState {
        case quiescent
        case forming
        case joining
        case finished
        case error
    }

    // MARK: Private Properties
    private var observers = [ObserverToken] ()
    private weak var scanTimer: Timer?

    private var state = OnboardingState.quiescent
}

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

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

        state = .quiescent

    	setUpObservers()

        if device.zigbeeCoordinatorState != .nominated {
			startFormation()
        } else {
            startJoining()
        }
    }

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

        scanTimer?.invalidate()
        scanTimer = nil
    }
}

extension ZigbeeOnboardingViewController {
    private func startFormation() {
        guard
            let deviceID = deviceID,
            let device = playerController.device(id: deviceID) else {
                return
        }

        device .startFormZbNetwork(completion: { [weak self] (success, error) in
            guard let self = self else { return }
            if success {
                self.waitForFormation()
            } else {
                self.showError()
            }
            },
                           completionQueue: DispatchQueue.main)
    }

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

        device .startZbJoining(withTimeout: 30,
                               completion: { [weak self] (success, error) in
            guard let self = self else { return }
            if success {
                self.waitForJoining()
            } else {
                self.showError()
            }
            },
                                   completionQueue: DispatchQueue.main)
    }

    private func waitForFormation() {
        state = .forming
        animation.startAnimating()

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

    private func waitForJoining() {
        state = .joining
        animation.startAnimating()

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

    private func prepareUIForFormation() {
        topTextLabel.text = NSLocalizedString("Setting up your coordinator",
                                              comment: "ZB Formation Top")
        middleTextLabel.text = NSLocalizedString("This may take a few seconds.",
                                                 comment: "ZB Formation Middle")
        bottomTextLabel.text = NSLocalizedString("Please do not unplug your speaker.",
                                                 comment: "ZB Formation Bottom")
    }

    private func prepareUIForJoining() {
        topTextLabel.text = NSLocalizedString("Looking for Zigbee devices",
                                              comment: "ZB Joining Top")
        middleTextLabel.text = NSLocalizedString("This may take up to 30 seconds.",
                                                 comment: "ZB Joining Middle")
        bottomTextLabel.text = NSLocalizedString("Make sure your new devices are powered on and in pairing or discovery mode.",
                                                 comment: "ZB Joining Bottom")
    }

    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.presentingViewController?.dismiss(animated: true, completion: nil)
            }
        }

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


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

            if notification.device.deviceID == self.deviceID! {
                switch notification.reason {
                case .enabledChanged:
                    // Having the adapter disable would be bad.
                    self.removeObservers()
                    self.stopScan()
                    self.presentingViewController?.dismiss(animated: true, completion: nil)
                case .coordinatorStateChanged:
                    if let newState = notification.payload as? IoTCoordinatorState,
                        self.state == .forming {
						self.stopScan()
                        if newState == .nominated {
                        	self.waitForJoining()
                        } else {
                            self.showError()
                        }
                    }
                    break;
                case .joiningStateChanged:
                    if let joiningAllowed = notification.payload as? Bool,
                        self.state == .joining {
                        if joiningAllowed {
                            DDLogVerbose("Joining allowed")
                        } else {
							self.joiningEnded()
                        }
                    }
                default:
                    break
                }
            }
        }

        let zigbeeObserver = NotificationCenter.default.addObserver(forType: ZigbeeNotification.self,
                                                                    object: playerController,
                                                                    queue: OperationQueue.main,
                                                                    using: zigbeeHandler)

        observers = [deviceObserver, zigbeeObserver]
    }

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

    @objc private func scanTimerExpired(_ timer: Timer) {
        scanTimer?.invalidate()
        scanTimer = nil
        showError()
    }

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

    private func showError() {
        state = .error
        removeObservers()
        stopScan()
        if state == .forming {
			showCouldNotSetCoordinatorError()
        } else {
            // Don't treat this as an error yet.
			joiningEnded()
        }
    }

    private func showCouldNotSetCoordinatorError() {
        let alertController = UIAlertController(title: NSLocalizedString("Coordinator Not Set", comment: "Scan error title"),
                                                message: NSLocalizedString("Could not set smart home coordinator.", 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 joiningEnded() {
        state = .finished
        removeObservers()
        stopScan()
        self.presentingViewController?.dismiss(animated: true, completion: nil)
    }
}
