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

import UIKit
import CocoaLumberjack

class BluetoothOverviewSettingsViewController: UITableViewController {
    var deviceID: String?

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

    private enum RowType {
        case enabled
        case discoverable
        case pairedDevice
        case connectedDevice
        case startPairing
    }

    private struct Row {
        let type: RowType
        let title: String
        let subtitle: String?
    }

    private struct Section {
        var title: String?
        var rows: [Row]
    }

    // MARK: Private Properties
    private var observers = [ObserverToken] ()
    private let tableViewHeaderHeight: CGFloat = 38.0
    private var sections = [Section] ()

    private var btPairedDevices = [IoTBluetoothDevice]()
    private var btConnectedDevice: IoTBluetoothDevice?

    private weak var timeoutTimer: Timer?
}

extension BluetoothOverviewSettingsViewController {
    override func viewDidLoad() {
        title = NSLocalizedString("BLUETOOTH DEVICES", comment: "Bluetooth Title")

        super.viewDidLoad()

        let nib1 = UINib(nibName: SettingsOneRowTableViewCell.reuseIdentifier, bundle: nil)
        tableView?.register(nib1, forCellReuseIdentifier: SettingsOneRowTableViewCell.reuseIdentifier)

        let nib2 = UINib(nibName: SettingsTwoRowTableViewCell.reuseIdentifier, bundle: nil)
        tableView?.register(nib2, forCellReuseIdentifier: SettingsTwoRowTableViewCell.reuseIdentifier)

        let nib = UINib(nibName: SettingsTableViewHeaderView.reuseIdentifier, bundle: nil)
        tableView?.register(nib, forHeaderFooterViewReuseIdentifier: SettingsTableViewHeaderView.reuseIdentifier)

        // Accessibility
        tableView.makeAccessible(Accessibility.BluetoothSettingsScreen.listTitle)
    }

    override func viewWillAppear(_ animated: Bool) {
        DDLogVerbose("BluetoothOverviewSettingsViewController-viewWillAppear")
        super.viewWillAppear(animated)
        setUpObservers()
        reloadState()
    }

    override func viewWillDisappear(_ animated: Bool) {
        DDLogVerbose("BluetoothOverviewSettingsViewController-viewWillDisappear")
        super.viewWillDisappear(animated)
        removeObservers()
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let navVC = segue.destination as? UINavigationController {
            if let top = navVC.topViewController as? BluetoothOnboardingSearchingViewController {
                top.injectDependencies(preferences: preferences, playerController: playerController)
                top.deviceID = deviceID
            }
        }
    }
}

extension BluetoothOverviewSettingsViewController {
    private func cancelTimeoutTimer() {
        if let t = timeoutTimer {
            t.invalidate()
            timeoutTimer = nil
        }
    }

    private func startTimeoutTimer() {
		cancelTimeoutTimer()
        let t = Timer(timeInterval: 10,
                      target: self,
                      selector: #selector(timeoutTimerFired(_:)),
                      userInfo: nil,
                      repeats: false)
        RunLoop.current.add(t, forMode: .common)
        timeoutTimer = t
    }

    @objc private func timeoutTimerFired(_: Any?) {
        timeoutTimer = nil
        reloadState()
    }

    private func reloadState() {
        setUpRows()
        tableView.reloadData()
    }

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

            if notification.reason == .removed {
                self.reloadState()
            }
        }

        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.reason == .adapterStateChanged ||
                notification.reason == .discoverabilityChanged {
                self.cancelTimeoutTimer()
                self.reloadState()
            }

            if notification.reason == .pairedDevicesChanged {
                self.reloadState()
            }

            if notification.reason == .deviceConnected ||
                notification.reason == .deviceDisconnected {
                self.reloadState()
            }
        }

        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 setUpRows() {
        sections = [Section] ()

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

        var generalRows = [Row] ()
        let enabledRow = Row(type: .enabled,
                             title: NSLocalizedString("Bluetooth", comment: "Bluetooth enabled cell"),
                             subtitle: nil)
        generalRows.append(enabledRow)

        if device.bluetoothEnabled == .enabled {

            let discoverableRow = Row(type: .discoverable,
                                      title: NSLocalizedString("Discoverable", comment: "Bluetooth discoverable cell"),
                                      subtitle: NSLocalizedString("Make device visible for upto 60 seconds.", comment: "Bluetooth discoverable cell subtitle"))
            generalRows.append(discoverableRow)

        }
        let generalSection = Section(title: nil,
                                     rows: generalRows)

        sections.append(generalSection)
        if device.bluetoothEnabled == .enabled {
            btPairedDevices = device.pairedBluetoothDevices
            btConnectedDevice = device.connectedBluetoothDevice
            let deviceRows = btPairedDevices.map { (btDevice) -> Row in
                let isConnected = btConnectedDevice == btDevice
                var name = btDevice.name.count > 0 ? btDevice.name : btDevice.address
                if isConnected {
                    name = name + NSLocalizedString(" (Connected)", comment: "Connected name addition")
                }
                return Row(type: isConnected ? .connectedDevice : .pairedDevice,
                           title: name,
                           subtitle: nil)
            }

            if deviceRows.count > 0 {
                let deviceSection = Section(title: NSLocalizedString("PAIRED DEVICES", comment: "Section header"),
                                            rows: deviceRows)
                sections.append(deviceSection)
            }

            let startPairingRow = Row(type: .startPairing,
                                      title: NSLocalizedString("PAIR BLUETOOTH DEVICES", comment: "Pair BT cell"),
                                      subtitle: nil)
            let startPairingSection = Section(title: NSLocalizedString("", comment: ""),
                                              rows: [startPairingRow])
            sections.append(startPairingSection)
        }
    }
}

extension BluetoothOverviewSettingsViewController {
    // MARK: - Table view data source

    override func numberOfSections(in tableView: UITableView) -> Int {
        return sections.count
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return sections[section].rows.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let section = sections[indexPath.section]
        let row = section.rows[indexPath.row]

        switch row.type {
        case .enabled:
            var cell = tableView.dequeueReusableCell(withIdentifier: SettingsOneRowTableViewCell.reuseIdentifier) as? SettingsOneRowTableViewCell
            if cell == nil {
                cell = SettingsOneRowTableViewCell()
            }
            cell?.style = .toggle
            if let deviceID = deviceID,
                let device = playerController.device(id: deviceID) {
                cell?.setToggle(isOn: device.bluetoothEnabled == .enabled)
                cell?.makeAccessible(device.bluetoothEnabled == .enabled ?
                    Accessibility.BluetoothSettingsScreen.bluetoothEnabled :
                    Accessibility.BluetoothSettingsScreen.bluetoothDisabled)
            }
            cell?.onToggle = { [weak self] newState in
                guard
                    let self = self,
                    let deviceID = self.deviceID,
                    let device = self.playerController.device(id: deviceID)else {
                        return
                }

                cell?.style = .waiting
                self.startTimeoutTimer()

                device.setBtAdapterEnabled(newState,
                                           completion: nil,
                                           completionQueue: nil)
            }
            cell?.accessoryType = .none
            cell?.titleLabel?.textColor = ColorCompat.label
            cell?.titleLabel?.text = row.title
            return cell!

        case .discoverable:
            var cell = tableView.dequeueReusableCell(withIdentifier: SettingsTwoRowTableViewCell.reuseIdentifier) as? SettingsTwoRowTableViewCell
            if cell == nil {
                cell = SettingsTwoRowTableViewCell()
            }
            cell?.style = .toggle
            if let deviceID = deviceID,
                let device = playerController.device(id: deviceID) {
                cell?.setToggle(isOn: device.bluetoothDiscoverable == .discoverable)
                cell?.makeAccessible(device.bluetoothDiscoverable == .discoverable ?
                    Accessibility.BluetoothSettingsScreen.discoverableEnabled :
                    Accessibility.BluetoothSettingsScreen.discoverableDisabled)
            }
            cell?.onToggle = { [weak self] newState in
                guard
                    let self = self,
                    let deviceID = self.deviceID,
                    let device = self.playerController.device(id: deviceID)else {
                        return
                }
                cell?.style = .waiting
                self.startTimeoutTimer()

                if newState {
                    device.startBtDiscoveryMode(withTimeout: 60,
                                                completion: nil,
                                                completionQueue: nil)
                } else {
                    device.exitBtDiscoveryMode(completion: nil,
                                               completionQueue: nil)
                }
            }
            cell?.accessoryType = .none
            cell?.titleLabel?.text = row.title
            cell?.subtitleLabel?.text = row.subtitle
            return cell!
        case .pairedDevice,
             .connectedDevice:
            var cell = tableView.dequeueReusableCell(withIdentifier: SettingsOneRowTableViewCell.reuseIdentifier) as? SettingsOneRowTableViewCell
            if cell == nil {
                cell = SettingsOneRowTableViewCell()
            }
            cell?.style = .plain
            cell?.accessoryType = .disclosureIndicator
            cell?.titleLabel?.text = row.title
            cell?.titleLabel?.textColor = ColorCompat.label
            if row.type == .connectedDevice {
                cell?.makeAccessible(row.title, localizableSuffix: Accessibility.BluetoothSettingsScreen.connectedSuffix)
            } else {
				cell?.makeAccessible(row.title, doNotLocalizeForHuman: true)
            }
            return cell!
        case .startPairing:
            var cell = tableView.dequeueReusableCell(withIdentifier: SettingsOneRowTableViewCell.reuseIdentifier) as? SettingsOneRowTableViewCell
            if cell == nil {
                cell = SettingsOneRowTableViewCell()
            }
            cell?.style = .plain
            cell?.accessoryType = .none
            cell?.titleLabel?.text = row.title
            cell?.titleLabel?.textColor = UIColor.blue
            cell?.makeAccessible(Accessibility.BluetoothSettingsScreen.pairDeviceButton)
            return cell!
        }
    }

    override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        let section = sections[section]
        return section.title == nil ? CGFloat.leastNormalMagnitude : tableViewHeaderHeight
    }

    override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
        return CGFloat.leastNormalMagnitude
    }

    override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        guard let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: SettingsTableViewHeaderView.reuseIdentifier) as? SettingsTableViewHeaderView else {
            return nil
        }

        header.titleLabel?.text = sections[section].title
        return header
    }

    override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
        return indexPath.section == 0 ? nil : indexPath
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)

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

        let section = sections[indexPath.section]
        let row = section.rows[indexPath.row]

        switch row.type {
        case .pairedDevice,
             .connectedDevice:
            showBtDeviceOptions(device: btPairedDevices[indexPath.row])
        case .startPairing:
            startPairing()
        default:
            break
        }
    }
}

extension BluetoothOverviewSettingsViewController {
    private func showBtDeviceOptions(device btDevice: IoTBluetoothDevice) {
		let alertController = UIAlertController(title: btDevice.name,
                                                message: NSLocalizedString("What would you like to do?", comment: "message for paired device"),
                                                preferredStyle: .actionSheet)

        let unpairAction = UIAlertAction(title: NSLocalizedString("Unpair", comment: "Unpair action"),
                                         style: .destructive) { [weak self] _ in
                                            // Unpair device
                                            guard
                                                let self = self,
                                                let id = self.deviceID,
                                                let device = self.playerController.device(id: id) else {
                                                    return
                                            }

                                            device.startUnpairBtDevice(btDevice,
                                                                       completion: nil,
                                                                       completionQueue: nil)

        }
        alertController.addAction(unpairAction)

        if btConnectedDevice == nil {
            let connectAction = UIAlertAction(title: NSLocalizedString("Connect", comment: "Connect action"),
                                             style: .default) { [weak self] _ in
                                                // Connect device
                                                guard
                                                    let self = self,
                                                    let id = self.deviceID,
                                                    let device = self.playerController.device(id: id) else {
                                                        return
                                                }

                                                device.startConnectBtDevice(btDevice,
                                                                            completion: nil,
                                                                            completionQueue: nil)

            }
            alertController.addAction(connectAction)
        } else {
            if btConnectedDevice == btDevice {
                let disconnectAction = UIAlertAction(title: NSLocalizedString("Disconnect", comment: "Disconnect action"),
                                                     style: .default) { [weak self] _ in
                                                        // Disconnect device
                                                        guard
                                                            let self = self,
                                                            let id = self.deviceID,
                                                            let device = self.playerController.device(id: id) else {
                                                                return
                                                        }

                                                        device.startDisconnectBtDevice(btDevice,
                                                                                       completion: nil,
                                                                                       completionQueue: nil)

                }
                alertController.addAction(disconnectAction)
            }
        }

        let cancelAction = UIAlertAction(title: NSLocalizedString("Cancel", comment: "Cancel action"),
                                         style: .cancel,
                                         handler: nil)
        alertController.addAction(cancelAction)
        present(alertController, animated: true, completion: nil)
    }

    private func startPairing() {
		performSegue(withIdentifier: Segues.showBTPairing, sender: self)
    }
}
