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

import UIKit
import CocoaLumberjack

class MasterViewController: UIViewController {
	// MARK: Private Types
	private struct Segues {
		static let showDetail = "showDetail"
        static let showGrouping = "showGrouping"
		static let showOptionsMenu = "showOptionsMenu"
		static let showAbout = "showAbout"
		static let showSpeakerSettings = "showSpeakerSettings"
		static let showBluetoothAdd = "showBluetoothAdd"
        static let showBluetoothManage = "showBluetoothManage"
        static let showZigbeeAdd = "showZigbeeAdd"
        static let showZigbeeManage = "showZigbeeManage"
        static let showIoTDevices = "showIoTDevices"
	}

    // MARK: Private Types
    private struct Options {
		static let title = "title"
        static let editGroup = "editGroup"
        static let createGroup = "createGroup"
        static let addBT = "addBT"
        static let manageBT = "manageBT"
        static let addZB = "addZB"
        static let manageZB = "manageZB"
        static let zbBulbSettings = "zbBulbSettings"
        static let zbThermoSettings = "zbThermoSettings"
        static let createZBGroup = "createZBGroup"
        static let speakerSettings = "speakerSettings"
        static let groupSettings = "groupSettings"

        static let viewByAlpha = "viewByAlpha"
        static let viewByType = "viewByType"
        static let viewByNetwork = "viewByNetwork"

        static let viewAbout = "viewAbout"
    }

    private enum OptionActionType {
		case speakerOrGroup
        case bluetooth
        case zigbee
    }

	// MARK: Private Properties

    private var observers = [ObserverToken] ()
    private var detailPlayerGroup: IoTPlayerGroup? = nil
    private var actionPlayerGroup: IoTPlayerGroup? = nil
    private let tableViewHeaderHeight: CGFloat = 46.0

    private var dataSource: MasterViewControllerDataSource?
    private var viewMode: MasterViewControllerMode = .alphabetical
    private var items = [MasterViewControllerDisplayable] ()
    private var selectedItemForOptions: MasterViewControllerDisplayable? = nil
	
	// MARK: UI Outlets
	@IBOutlet private weak var tableView: UITableView?
	@IBOutlet private weak var noSpeakersView: UIView?
	@IBOutlet private weak var noSpeakersViewSpinner: UIImageView?

    private let refreshControl = UIRefreshControl()
    private enum RefreshState {
        case normal
        case awaitingZero
        case awaitingData
    }
    private var refreshState = RefreshState.normal
    private weak var refreshTimeoutTimer: Timer?
}

// MARK: - Overridden Methods
extension MasterViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        noSpeakersViewSpinner?.loadAnimationImages(nameStem: "ind-progress-small", count: 20)
        noSpeakersView?.isHidden = false

        // Accessibility
        tableView?.makeAccessible(Accessibility.HomeScreen.itemsList)

        refreshControl.addTarget(self,
                                 action: #selector(forceRefresh(_:)),
                                 for: .valueChanged)
        tableView?.refreshControl = refreshControl

        prepareNavigationBar()
    }

    override func viewDidAppear(_ animated: Bool) {        
        super.viewDidAppear(animated)

        dataSource = MasterViewControllerDataSource(playerController: playerController)
        reloadState()
        
        let playerGroupHandler: (PlayerGroupNotification) -> () = { [weak self] notification in
            guard let self = self else { return }
            self.reloadState()
        }
        
        let playerGroupObserver = NotificationCenter.default.addObserver(forType: PlayerGroupNotification.self,
                                                                         object: playerController,
                                                                         queue: OperationQueue.main,
                                                                         using: playerGroupHandler)

		// -----------

        let btHandler: (BluetoothNotification) -> () = { [weak self] notification in
            guard let self = self else { return }
            if notification.reason == .adapterStateChanged ||
                notification.reason == .pairedDevicesChanged {
                self.reloadState()
            }
        }

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

        // -----------

        let zbHandler: (ZigbeeNotification) -> () = { [weak self] notification in
            guard let self = self else { return }
            if notification.reason == .enabledChanged ||
                notification.reason == .joinedDevicesChanged {
                self.reloadState()
            }
        }

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

        // -----------

        let wiFiHandler: (Notification) -> () = { [weak self] notification in
            guard let self = self else { return }
            self.handleWiFiStateChange()
        }
        
        let wiFiToken = NotificationCenter.default.addObserver(forName: .wiFiStateDidChange,
                                                               object: WiFiManager.sharedInstance,
                                                               queue: OperationQueue.main,
                                                               using: wiFiHandler)

        let wiFiObserver = ObserverToken(notificationCenter: NotificationCenter.default, token: wiFiToken)
        observers = [playerGroupObserver, btObserver, zbObserver, wiFiObserver]
    }
    
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)

        observers.forEach { (observer) in
            NotificationCenter.default.removeObserver(observer)
        }
        observers = []
    }
	
	override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
		segue.destination.injectDependencies(preferences: preferences, playerController: playerController)
		if let id = segue.identifier {
			switch id {
            case Segues.showDetail:
                prepareShowDetailSegue(segue, sender: sender)
            case Segues.showGrouping:
				prepareShowGroupingSegue(segue, sender: sender)
            case Segues.showSpeakerSettings:
				prepareShowSpeakerSettingsSegue(segue, sender: sender)
			case Segues.showOptionsMenu:
				prepareShowOptionsMenuSegue(segue, sender: sender)
            case Segues.showBluetoothAdd:
                prepareShowBluetoothAdd(segue, sender: sender)
            case Segues.showBluetoothManage:
                prepareShowBluetoothManage(segue, sender: sender)
            case Segues.showZigbeeAdd:
                prepareShowZigbeeAdd(segue, sender: sender)
            case Segues.showZigbeeManage:
                prepareShowZigbeeManage(segue, sender: sender)
            default:
                break
            }
		}
	}

    private func prepareShowDetailSegue(_ segue: UIStoryboardSegue, sender: Any?) {
        if let currentDetail = splitViewController?.viewControllers.last as? DetailNavigationController {
            currentDetail.prepareForReplacement()
        }

        if let controller = segue.destination as? DeviceDetailNavigationController {
            let deviceDetailVC = DeviceDetailViewController.instantiateFromStoryboard()
            controller.viewControllers = [deviceDetailVC]
            controller.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem
            controller.navigationItem.leftItemsSupplementBackButton = true

            if let item = sender as? MasterViewControllerDisplayable,
                let ancestry = dataSource?.ancestry(item: item),
                let playerGroupItem = ancestry.last as? PlayerGroupDisplayableWrapper,
                let playerGroup = playerGroupItem.playerGroup as? IoTPlayerGroup {
                controller.playerGroup = playerGroup
                let last = ancestry.last!
                // Check we still have the player group in the list.
                if items.contains(where: { return $0 === last }) {
                    deviceDetailVC.item = item
                }
            }
        }
    }

    private func prepareShowGroupingSegue(_ segue: UIStoryboardSegue, sender: Any?) {
        if let currentDetail = splitViewController?.viewControllers.last as? DetailNavigationController {
            currentDetail.prepareForReplacement()
        }

        if let controller = segue.destination as? DeviceDetailNavigationController {
            let groupingVC = GroupingViewController.instantiateFromStoryboard(name: "Grouping")
            controller.viewControllers = [groupingVC]
            controller.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem
            controller.navigationItem.leftItemsSupplementBackButton = true

            if let item = sender as? MasterViewControllerDisplayable,
                let ancestry = dataSource?.ancestry(item: item),
                let playerGroupItem = ancestry.last as? PlayerGroupDisplayableWrapper,
                let playerGroup = playerGroupItem.playerGroup as? IoTPlayerGroup {
                controller.playerGroup = playerGroup
            }
        }
    }

    private func soundbarOrFirstPresent(group: IoTPlayerGroup) -> IoTPlayer? {
        var player: IoTPlayer? = nil
        let soundbarPlayer = group.members.compactMap({ member -> (IoTPlayer?) in
            if let p = playerController.player(id: member.playerID),
                p.isSoundBar {
                return p
            }
            return nil
        }).first
        if let p = soundbarPlayer {
            player = p
        } else {
            player = group.members.compactMap({
                if let p = playerController.player(id: $0.playerID) {
                    return p
                }
                return nil
            }).first
        }
        return player
    }

    private func deviceIDForWrapper(_ wrapper: MasterViewControllerDisplayable, action: OptionActionType) -> String? {
        var player: IoTPlayer? = nil
        if let item = wrapper as? PlayerGroupDisplayableWrapper,
            let group = item.playerGroup as? IoTPlayerGroup {
            if item.encapsulatedType == .speakerConvenienceGroup {
                if let leadID = group.members.first?.playerID {
                	player = playerController.player(id: leadID)
                }
            } else {
                // Find most sensible device for a proper group
                switch action {
                case .speakerOrGroup,
                     .bluetooth:
                    player = soundbarOrFirstPresent(group: group)
                case .zigbee:
                    if let device = playerController.zigbeeCoordinator(playerGroupID: group.groupID),
                        let p = playerController.player(device: device) {
                        player = p
                    } else {
                        // No coordinator
                        player = soundbarOrFirstPresent(group: group)
                    }
                }
            }
        } else if let item = wrapper as? PlayerDisplayableWrapper {
            player = playerController.player(id: item.playerID)
        } else if let item = wrapper as? BluetoothDisplayableWrapper {
            player = playerController.player(id: item.playerID)
        } else if let item = wrapper as? ZigbeeDisplayableWrapper {
            player = playerController.player(id: item.playerID)
        }

        if let p = player,
            let device = playerController.device(player: p) {
            return device.deviceID
        } else {
            return nil
        }
    }

    private func prepareShowSpeakerSettingsSegue(_ segue: UIStoryboardSegue, sender: Any?) {
        // We need to tell any existing view it's going to be replaced
        if let currentDetail = splitViewController?.viewControllers.last as? DetailNavigationController {
            currentDetail.prepareForReplacement()
        }

        if let navController = segue.destination as? SettingsNavigationController,
            let wrapper = sender as? MasterViewControllerDisplayable {
            navController.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem
            navController.navigationItem.leftItemsSupplementBackButton = true

            let settingsVC = SpeakerSettingsViewController.instantiateFromStoryboard(name: "Settings")
            settingsVC.injectDependencies(preferences: preferences, playerController: playerController)
            navController.viewControllers = [settingsVC]

            let deviceID = deviceIDForWrapper(wrapper, action: .speakerOrGroup)
            settingsVC.deviceID = deviceID
        	navController.deviceID = deviceID
        }
    }

    private func prepareShowBluetoothAdd(_ segue: UIStoryboardSegue, sender: Any?) {
        // Modal View in Nav Controller
        if let navVC = segue.destination as? UINavigationController {
            if let top = navVC.topViewController as? BluetoothOnboardingSearchingViewController,
            let wrapper = sender as? MasterViewControllerDisplayable {
                top.injectDependencies(preferences: preferences, playerController: playerController)
                let deviceID = deviceIDForWrapper(wrapper, action: .bluetooth)
                top.deviceID = deviceID
            }
        }
    }

    private func prepareShowBluetoothManage(_ segue: UIStoryboardSegue, sender: Any?) {
        if let currentDetail = splitViewController?.viewControllers.last as? DetailNavigationController {
            currentDetail.prepareForReplacement()
        }
        
        if let navController = segue.destination as? SettingsNavigationController,
            let wrapper = sender as? MasterViewControllerDisplayable {
            navController.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem
            navController.navigationItem.leftItemsSupplementBackButton = true

            let btVC = BluetoothOverviewSettingsViewController.instantiateFromStoryboard(name: "Settings")
            btVC.injectDependencies(preferences: preferences, playerController: playerController)
            navController.viewControllers = [btVC]

            let deviceID = deviceIDForWrapper(wrapper, action: .bluetooth)
            btVC.deviceID = deviceID
            navController.deviceID = deviceID
        }
    }

    private func prepareShowZigbeeAdd(_ segue: UIStoryboardSegue, sender: Any?) {
        // Modal View in Nav Controller
        if let navVC = segue.destination as? UINavigationController {
            if let top = navVC.topViewController as? ZigbeeOnboardingViewController,
                let wrapper = sender as? MasterViewControllerDisplayable {
                top.injectDependencies(preferences: preferences, playerController: playerController)
                let deviceID = deviceIDForWrapper(wrapper, action: .zigbee)
                top.deviceID = deviceID
            }
        }
    }

    private func prepareShowZigbeeManage(_ segue: UIStoryboardSegue, sender: Any?) {
        if let currentDetail = splitViewController?.viewControllers.last as? DetailNavigationController {
            currentDetail.prepareForReplacement()
        }

        if let navController = segue.destination as? SettingsNavigationController,
            let wrapper = sender as? MasterViewControllerDisplayable {
            navController.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem
            navController.navigationItem.leftItemsSupplementBackButton = true

            let zbVC = ZigbeeOverviewSettingsViewController.instantiateFromStoryboard(name: "Settings")
            zbVC.injectDependencies(preferences: preferences, playerController: playerController)
            navController.viewControllers = [zbVC]

            let deviceID = deviceIDForWrapper(wrapper,action: .zigbee)
            zbVC.deviceID = deviceID
            navController.deviceID = deviceID
        }
    }

    private func prepareShowOptionsMenuSegue(_ segue: UIStoryboardSegue, sender: Any?) {
        guard let menu = segue.destination as? BottomSheetViewController else {
            print ("Not menu but: \(segue.destination)")
            return
        }

        if let item = sender as? MasterViewControllerDisplayable {
            // Options for an item
            switch item.encapsulatedType {

            case .unknown:
                menu.titleItem = BottomSheetViewControllerItem(identifier: Options.title, image: nil, title: "UNKNOWN", accessibilityTitle: nil, checked: false)
            case .speakerGroup,
                 .speakerConvenienceGroup:
                let image = item.encapsulatedType == .speakerGroup ? UIImage(named: "ic-group") : UIImage(named: "ic-speaker")
                menu.titleItem = BottomSheetViewControllerItem(identifier: Options.title,
                                                               image: image,
                                                               title: item.name.uppercased(),
                                                               accessibilityTitle: nil,
                                                               checked: false)

                var options = [BottomSheetViewControllerItem] ()
                let groupWrapper = item as? PlayerGroupDisplayableWrapper
                let group = groupWrapper?.playerGroup as? IoTPlayerGroup
                if let groupID = group?.groupID {
                    if item.encapsulatedType == .speakerGroup {
                        options.append(BottomSheetViewControllerItem(identifier: Options.editGroup,
                                                                     image: UIImage(named: "ic-group"),
                                                                     title: NSLocalizedString("Edit the group", comment: "Edit the group"),
                                                                     accessibilityTitle: Accessibility.HomeScreen.SlideUpPanel.editGroupButton,
                                                                     checked: false))
                    } else {
                        if let player = playerController.player(id: groupID),
                            player.groupingSupported {
                            options.append(BottomSheetViewControllerItem(identifier: Options.createGroup,
                                                                         image: UIImage(named: "ic-group"),
                                                                         title: NSLocalizedString("Create a group with other speakers", comment: "Create a group with other speakers"),
                                                                         accessibilityTitle: Accessibility.HomeScreen.SlideUpPanel.createGroupButton,
                                                                         checked: false))
                        }
                    }

                    if playerController.hasBTDevices(playerGroupID: groupID) {
                        options.append(BottomSheetViewControllerItem(identifier: Options.manageBT,
                                                                     image: UIImage(named: "ic-bt-headphones-list"),
                                                                     title: NSLocalizedString("Manage Bluetooth devices", comment: "Manage Bluetooth devices"),
                                                                     accessibilityTitle: Accessibility.HomeScreen.SlideUpPanel.manageBluetoothDevicesButton,
                                                                     checked: false))
                    } else if playerController.supportsBluetooth(playerGroupID: groupID) {
                        options.append(BottomSheetViewControllerItem(identifier: Options.addBT,
                                                                     image: UIImage(named: "ic-bt-headphones-list"),
                                                                     title: NSLocalizedString("Pair Bluetooth device", comment: "Pair Bluetooth device"),
                                                                     accessibilityTitle: Accessibility.HomeScreen.SlideUpPanel.pairBluetoothDeviceButton,
                                                                     checked: false))
                    }

                    if playerController.hasZigbeeDevices(playerGroupID: groupID) {
                        options.append(BottomSheetViewControllerItem(identifier: Options.manageZB,
                                                                     image: UIImage(named: "ic-zigbee-static"),
                                                                     title: NSLocalizedString("Manage Zigbee devices", comment: "Add a Zigbee device"),
                                                                     accessibilityTitle: Accessibility.HomeScreen.SlideUpPanel.manageZigbeeDevicesButton,
                                                                     checked: false))
                    } else if playerController.supportsZigbee(playerGroupID: groupID) {
                        let    networkHasCoordinator = playerController.zigbeeCoordinatorExists()
                        let groupHasCoordinator = playerController.zigbeeCoordinator(playerGroupID: groupID) != nil
                        if groupHasCoordinator || !networkHasCoordinator {
                            options.append(BottomSheetViewControllerItem(identifier: Options.addZB,
                                                                         image: UIImage(named: "ic-zigbee-static"),
                                                                         title: NSLocalizedString("Add Zigbee devices", comment: "Add a Zigbee device"),
                                                                         accessibilityTitle: Accessibility.HomeScreen.SlideUpPanel.addZigbeeDeviceButton,
                                                                         checked: false))
                        }
                    }

                    options.append(BottomSheetViewControllerItem(identifier: item.encapsulatedType == .speakerGroup ?
                        Options.groupSettings :
                        Options.speakerSettings,
                                                                 image: UIImage(named: "ic-settings"),
                                                                 title: NSLocalizedString("Settings", comment: "Settings"),
                                                                 accessibilityTitle: Accessibility.HomeScreen.SlideUpPanel.showSettingsButton,
                                                                 checked: false))
                    menu.optionItems = options
                }
            case .speaker:
                menu.titleItem = BottomSheetViewControllerItem(identifier: Options.title,
                                                               image: UIImage(named: "ic-speaker"),
                                                               title: item.name.uppercased(),
                                                               accessibilityTitle: nil,
                                                               checked: false)
                var options = [BottomSheetViewControllerItem] ()

                if let playerWrapper = item as? PlayerDisplayableWrapper,
                    let player = playerController.player(id: playerWrapper.playerID),
                    let device = playerController.device(player: player) {

                    if device.bluetoothSupported {
                        if device.pairedBluetoothDevices.count > 0 {
                            options.append(BottomSheetViewControllerItem(identifier: Options.manageBT,
                                                                         image: UIImage(named: "ic-bt-headphones-list"),
                                                                         title: NSLocalizedString("Manage Bluetooth device", comment: "Manage Bluetooth device"),
                                                                         accessibilityTitle: Accessibility.HomeScreen.SlideUpPanel.manageBluetoothDevicesButton,
                                                                         checked: false))
                        } else {
                            options.append(BottomSheetViewControllerItem(identifier: Options.addBT,
                                                                         image: UIImage(named: "ic-bt-headphones-list"),
                                                                         title: NSLocalizedString("Pair Bluetooth device", comment: "Pair Bluetooth device"),
                                                                         accessibilityTitle: Accessibility.HomeScreen.SlideUpPanel.pairBluetoothDeviceButton,
                                                                         checked: false))
                        }
                    }

                    if device.zigbeeSupported {
                        if device.zigbeeDevices.count > 0 {
                            options.append(BottomSheetViewControllerItem(identifier: Options.manageZB,
                                                                         image: UIImage(named: "ic-zigbee-static"),
                                                                         title: NSLocalizedString("Manage Zigbee devices", comment: "Add a Zigbee device"),
                                                                         accessibilityTitle: Accessibility.HomeScreen.SlideUpPanel.manageZigbeeDevicesButton,
                                                                         checked: false))
                        } else {
                            let networkHasCoordinator = playerController.zigbeeCoordinatorExists()
                            if device.zigbeeCoordinatorState == .nominated ||
                                !networkHasCoordinator {
                                options.append(BottomSheetViewControllerItem(identifier: Options.addZB,
                                                                             image: UIImage(named: "ic-zigbee-static"),
                                                                             title: NSLocalizedString("Add Zigbee devices", comment: "Add a Zigbee device"),
                                                                             accessibilityTitle: Accessibility.HomeScreen.SlideUpPanel.addZigbeeDeviceButton,
                                                                             checked: false))
                            }
                        }
                    }
                }

                options.append(BottomSheetViewControllerItem(identifier: Options.speakerSettings,
                                                             image: UIImage(named: "ic-settings"),
                                                             title: NSLocalizedString("Settings", comment: "Settings"),
                                                             accessibilityTitle: Accessibility.HomeScreen.SlideUpPanel.showSettingsButton,
                                                             checked: false))
                menu.optionItems = options
            case .speakerChannel:
                break
            case .bluetoothHeadphone:
                menu.titleItem = BottomSheetViewControllerItem(identifier: Options.title, image: nil, title: item.name.uppercased(), accessibilityTitle: nil, checked: false)
                menu.optionItems = [BottomSheetViewControllerItem(identifier: Options.manageBT,
                                                                  image: nil,
                                                                  title: NSLocalizedString("Manage Bluetooth devices", comment: "Manage Bluetooth devices"),
                                                                  accessibilityTitle: Accessibility.HomeScreen.SlideUpPanel.manageBluetoothDevicesButton,
                                                                  checked: false)]
            case .zigbeeLight:
                menu.titleItem = BottomSheetViewControllerItem(identifier: Options.title, image: nil, title: item.name.uppercased(), accessibilityTitle: nil, checked: false)
                menu.optionItems = [BottomSheetViewControllerItem(identifier: Options.createZBGroup,
                                                                  image: nil,
                                                                  title: NSLocalizedString("Create a Zigbee group", comment: "Create a Zigbee group"),
                                                                  accessibilityTitle: Accessibility.HomeScreen.SlideUpPanel.createZigbeeGroupButton,
                                                                  checked: false),
                                    BottomSheetViewControllerItem(identifier: Options.manageZB,
                                                                  image: UIImage(named: "ic-zigbee-static"),
                                                                  title: NSLocalizedString("Manage Zigbee devices", comment: "Manage Zigbee devices"),
                                                                  accessibilityTitle: Accessibility.HomeScreen.SlideUpPanel.manageZigbeeDevicesButton,
                                                                  checked: false),
                                    BottomSheetViewControllerItem(identifier: Options.zbBulbSettings,
                                                                  image: UIImage(named: "ic-settings"),
                                                                  title: NSLocalizedString("Settings", comment: "Settings"),
                                                                  accessibilityTitle: Accessibility.HomeScreen.SlideUpPanel.showSettingsButton,
                                                                  checked: false)]
            case .zigbeeThermostat:
                menu.titleItem = BottomSheetViewControllerItem(identifier: Options.title, image: nil, title: item.name.uppercased(), accessibilityTitle: nil, checked: false)
                menu.optionItems = [BottomSheetViewControllerItem(identifier: Options.createZBGroup,
                                                                  image: UIImage(named: "ic-group"),
                                                                  title: NSLocalizedString("Create a Zigbee group", comment: "Create a Zigbee group"),
                                                                  accessibilityTitle: Accessibility.HomeScreen.SlideUpPanel.createZigbeeGroupButton,
                                                                  checked: false),
                                    BottomSheetViewControllerItem(identifier: Options.manageZB,
                                                                  image: UIImage(named: "ic-zigbee-static"),
                                                                  title: NSLocalizedString("Manage Zigbee devices", comment: "Manage Zigbee devices"),
                                                                  accessibilityTitle: Accessibility.HomeScreen.SlideUpPanel.manageZigbeeDevicesButton,
                                                                  checked: false),
                                    BottomSheetViewControllerItem(identifier: Options.zbThermoSettings,
                                                                  image: UIImage(named: "ic-settings"),
                                                                  title: NSLocalizedString("Settings", comment: "Settings"),
                                                                  accessibilityTitle: Accessibility.HomeScreen.SlideUpPanel.showSettingsButton,
                                                                  checked: false)]
            case .zigbeeOther:
                menu.titleItem = BottomSheetViewControllerItem(identifier: Options.title, image: nil, title: item.name.uppercased(), accessibilityTitle: nil, checked: false)
                menu.optionItems = [BottomSheetViewControllerItem(identifier: Options.createZBGroup,
                                                                  image: UIImage(named: "ic-group"),
                                                                  title: NSLocalizedString("Create a Zigbee group", comment: "Create a Zigbee group"),
                                                                  accessibilityTitle: Accessibility.HomeScreen.SlideUpPanel.createZigbeeGroupButton,
                                                                  checked: false),
                                    BottomSheetViewControllerItem(identifier: Options.manageZB,
                                                                  image: UIImage(named: "ic-zigbee-static"),
                                                                  title: NSLocalizedString("Manage Zigbee devices", comment: "Manage Zigbee devices"),
                                                                  accessibilityTitle: Accessibility.HomeScreen.SlideUpPanel.manageZigbeeDevicesButton,
                                                                  checked: false)]
            }
        } else {
            // Options for nav bar button
            menu.titleItem = BottomSheetViewControllerItem(identifier: Options.title,
                                                           image: UIImage(named: "ic-home"),
                                                           title: NSLocalizedString("HOME", comment: ""),
                                                           accessibilityTitle: nil,
                                                           checked: false)
            let viewAlphaItem = BottomSheetViewControllerItem(identifier: Options.viewByAlpha,
                                                              image: UIImage(named: "ic-alphabetically"),
                                                              title: NSLocalizedString("View alphabetically", comment: ""),
                                                              accessibilityTitle: Accessibility.HomeScreen.SlideUpPanel.viewAlphabeticallyButton,
                                                              checked: viewMode == .alphabetical)
            let viewKindItem = BottomSheetViewControllerItem(identifier: Options.viewByType,
                                                             image: UIImage(named: "ic-bytype"),
                                                             title: NSLocalizedString("View by type", comment: ""),
                                                             accessibilityTitle: Accessibility.HomeScreen.SlideUpPanel.viewByTypeButton,
                                                             checked: viewMode == .kind)
            let viewNetworkItem = BottomSheetViewControllerItem(identifier: Options.viewByNetwork,
                                                                image: UIImage(named: "ic-networkmap"),
                                                                title: NSLocalizedString("View network map", comment: ""),
                                                                accessibilityTitle: Accessibility.HomeScreen.SlideUpPanel.viewNetworkMapButton,
                                                                checked: viewMode == .network)
            let aboutItem = BottomSheetViewControllerItem(identifier: Options.viewAbout,
                                                          image: UIImage(named: "ic-about"),
                                                          title: NSLocalizedString("About", comment: "About"),
                                                          accessibilityTitle: Accessibility.HomeScreen.SlideUpPanel.showAboutButton,
                                                          checked: false)
            menu.optionItems = [viewAlphaItem, viewKindItem, viewNetworkItem, aboutItem]
        }
        menu.delegate = self
    }
}

// MARK: - UI Actions
extension MasterViewController {
    @objc private func forceRefresh(_ sender: Any) {
        if refreshState == .normal {
            refreshState = .awaitingZero
            if items.count == 0 {
                // We won't get a notification that items have changed if already zero
                reloadState()
            } else {
        		playerController.restart()
            }
        }
    }

	@IBAction func didTapNavBarAddButton(_: UIButton) {
	}
	
	@IBAction func didTapNavBarOptionsButton(_: UIButton) {
        selectedItemForOptions = nil
		self.performSegue(withIdentifier: Segues.showOptionsMenu, sender: self)
	}
}

// MARK: - SpeakerTableViewCellDelegate Conformance
extension MasterViewController: MasterViewControllerTableViewCellDelegate {
    func cell(_ cell: MasterViewControllerTableViewCell, didTapActionForItem item: MasterViewControllerDisplayable) {
        if let item = cell.item as? PlayerGroupDisplayableWrapper,
            let group = item.playerGroup as? IoTPlayerGroup {
        	if cell.actionType == .play {
                group.play(completion: nil, completionQueue: nil)
            } else {
                group.pause(completion: nil, completionQueue: nil)
            }
        }
    }

    func cell(_ cell: MasterViewControllerTableViewCell, didTapOptionsButtonForItem item: MasterViewControllerDisplayable) {
        selectedItemForOptions = item
        self.performSegue(withIdentifier: Segues.showOptionsMenu, sender: item)
    }
}


// MARK: - NavigationMenuViewControllerDelegate Conformance
extension MasterViewController: BottomSheetViewControllerDelegate {
    func bottomSheetViewController(_ viewController: BottomSheetViewController,
                                   didEndWithItem item: BottomSheetViewControllerItem?) {
        var itemAncestry : [MasterViewControllerDisplayable]? = nil
        if let originalSelectedItem = selectedItemForOptions {
            // The menu was originally show due to button on a TV row being tapped.
            // The original item may have been replaced. Is there one refering to the same group/player
            // still there?
            itemAncestry = dataSource?.ancestry(item: originalSelectedItem)
            let last = itemAncestry?.last!
            // Check we still have the player group in the list. ids are based on the wrapped object.
            if !items.contains(where: { return $0.id == last?.id }) {
                return
            }
        }

        if let unwrappedItem = item {
            DDLogDebug("Ended with \(unwrappedItem.identifier)")
            switch unwrappedItem.identifier {
            case Options.editGroup:
                performSegue(withIdentifier: Segues.showGrouping, sender: selectedItemForOptions)
            case Options.createGroup:
                performSegue(withIdentifier: Segues.showGrouping, sender: selectedItemForOptions)
            case Options.addBT:
                if let deviceID = deviceIDForWrapper(selectedItemForOptions!, action: .bluetooth),
                    let device = playerController.device(id: deviceID) {
                    if device.bluetoothEnabled == .enabled {
						performSegue(withIdentifier: Segues.showBluetoothAdd, sender: selectedItemForOptions)
                    } else {
						performSegue(withIdentifier: Segues.showBluetoothManage, sender: selectedItemForOptions)
                    }
                }
            case Options.manageBT:
                performSegue(withIdentifier: Segues.showBluetoothManage, sender: selectedItemForOptions)
            case Options.addZB:
                if let deviceID = deviceIDForWrapper(selectedItemForOptions!, action: .zigbee),
                    let device = playerController.device(id: deviceID) {
                    if device.zigbeeEnabled == .enabled &&
                        device.zigbeeCoordinatorState == .nominated {
                        performSegue(withIdentifier: Segues.showZigbeeAdd, sender: selectedItemForOptions)
                    } else {
                        performSegue(withIdentifier: Segues.showZigbeeManage, sender: selectedItemForOptions)
                    }
                }
            case Options.manageZB:
                performSegue(withIdentifier: Segues.showZigbeeManage, sender: selectedItemForOptions)
            case Options.zbBulbSettings:
                showUnsupported()
            case Options.zbThermoSettings:
                showUnsupported()
            case Options.createZBGroup:
                showUnsupported()
            case Options.speakerSettings:
                performSegue(withIdentifier: Segues.showSpeakerSettings, sender: selectedItemForOptions)
            case Options.groupSettings:
                performSegue(withIdentifier: Segues.showDetail, sender: selectedItemForOptions)
            case Options.viewByAlpha:
                viewMode = .alphabetical
                reloadState()
            case Options.viewByType:
                viewMode = .kind
                reloadState()
            case Options.viewByNetwork:
                viewMode = .network
                reloadState()
            case Options.viewAbout:
                performSegue(withIdentifier: Segues.showAbout, sender: self)
            default:
                break;
            }
        }
    }
}

// MARK: - UITableViewDelegate, UITableViewDataSource Conformance
extension MasterViewController: UITableViewDelegate, UITableViewDataSource {
    func traverseDescendentsGivingNthItem(item: MasterViewControllerDisplayable, index: inout Int) -> MasterViewControllerDisplayable? {
        if index == 0 {
            return item
        }
        for child in item.children {
			index = index - 1
            if let foundItem = traverseDescendentsGivingNthItem(item: child, index: &index) {
                return foundItem;
            }
        }
        return nil;
    }

    func displayableItem(section: Int, row: Int) -> MasterViewControllerDisplayable {
        let baseItem = items[section]
        var index = row
        if let found = traverseDescendentsGivingNthItem(item: baseItem, index: &index) {
            return found
        } else {
            fatalError()
        }
    }

    func numberOfDescendentsOfItem(item: MasterViewControllerDisplayable) -> Int {
        var count = 1
        for child in item.children {
            count = count + numberOfDescendentsOfItem(item: child)
        }
        return count
    }

	func numberOfSections(in tableView: UITableView) -> Int {
		return items.count
	}
	
	func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        let baseItem = items[section]
        return numberOfDescendentsOfItem(item: baseItem)
	}
	
	func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
		var cell = tableView.dequeueReusableCell(withIdentifier: "MasterViewControllerTableViewCell", for: indexPath) as? MasterViewControllerTableViewCell
		if cell == nil {
			cell = MasterViewControllerTableViewCell()
		}

        cell!.delegate = self
        let item = displayableItem(section: indexPath.section, row: indexPath.row)
        cell!.item = item
        if item.encapsulatedType == .speakerGroup ||
            item.encapsulatedType == .speakerConvenienceGroup {
            if let groupItem = item as? PlayerGroupDisplayableWrapper,
                let currentItem = groupItem.playerGroup.playlist?.currentItem,
                !currentItem.isEmpty,
                let playerGroup = groupItem.playerGroup as? IoTPlayerGroup,
            	playerGroup.isInterruptible {
                if playerGroup.playState == .playing {
                    cell?.actionType = .pause
                } else {
                    cell?.actionType = .play
                }
            } else {
                cell?.actionType = .none
            }
        } else {
            cell?.actionType = .none
        }
        return cell!
    }

	func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
		tableView.deselectRow(at: indexPath, animated: false)
		let item = displayableItem(section: indexPath.section, row: indexPath.row)
        if item.encapsulatedType == .bluetoothHeadphone {
            performSegue(withIdentifier: Segues.showBluetoothManage, sender: item)
        } else if (item.encapsulatedType == .zigbeeLight) ||
            (item.encapsulatedType == .zigbeeThermostat) ||
            (item.encapsulatedType == .zigbeeOther) {
                performSegue(withIdentifier: Segues.showZigbeeManage, sender: item)
        } else {
			performSegue(withIdentifier: Segues.showDetail, sender: item)
        }
	}

    func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
        let item = displayableItem(section: indexPath.section, row: indexPath.row)
        return item.encapsulatedType != .speakerChannel ? indexPath : nil
    }
	
	func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
		return 20 //CGFloat.leastNormalMagnitude
	}
	
	func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
		return CGFloat.leastNormalMagnitude // if we return zero it gets ignored...
	}
	
	func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
		// Return false if you do not want the specified item to be editable.
		return false
	}
}

// MARK: - Private Methods
extension MasterViewController {
    private func showUnsupported() -> () {
        let alertController = UIAlertController(title: NSLocalizedString("Unsupported Option", comment: "Unsupported option"),
                                                message: NSLocalizedString("That option is not curently implemented", comment: "Unsupported option"),
                                                preferredStyle: .alert)


        alertController.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "OK"),
                                                style: .default,
                                                handler: nil))

        present(alertController, animated: true, completion: nil)
    }

	private func prepareNavigationBar() {
        if let optionsNormal = UIImage(named: "btn-more-options"),
            let optionsPressed = UIImage(named: "btn-more-options-pressed") {
            // We need image buttons that *do not* respect tint color so we use background images
            // They need to be resizable so they can be stretched without distorting the button image.
            let resizableOptions = optionsNormal.resizableImage(withCapInsets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: optionsNormal.size.width-1))
            let resizableOptionsPressed = optionsPressed.resizableImage(withCapInsets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: optionsPressed.size.width-1))
            // A title of " " is required for correct positioning
            let rightBarButton = UIBarButtonItem(title: " ", style: .plain, target: self, action: #selector(didTapNavBarOptionsButton(_:)))
            rightBarButton.setBackgroundImage(resizableOptions, for: .normal, barMetrics: .default)
            rightBarButton.setBackgroundImage(resizableOptionsPressed, for: .highlighted, barMetrics: .default)
            rightBarButton.makeAccessible(Accessibility.HomeScreen.navBarOptionsButton)
            navigationItem.rightBarButtonItem = rightBarButton
        }

        title = NSLocalizedString("HOME", comment: "MainScreenTitle")
	}
    
    private func handleWiFiStateChange() {
        reloadState()
	}


	private func reloadState() {
		items = dataSource?.displayableItems(mode: viewMode) ?? []
        tableView?.reloadData()

        switch refreshState {
        case .normal:
            if items.count == 0 {
                noSpeakersView?.isHidden = false
                noSpeakersViewSpinner?.startAnimating()
            } else {
                if !preferences.hasPrevioulyFoundConfiguredSpeaker() {
                    preferences.foundConfiguredSpeaker()
                }
                noSpeakersView?.isHidden = true
                noSpeakersViewSpinner?.stopAnimating()
            }
        case .awaitingZero:
            if items.count == 0 {
                refreshState = .awaitingData

                // Timeout in case nothing found
                let t = Timer(timeInterval: 5,
                              target: self,
                              selector: #selector(refreshTimeoutTimerFired(_:)),
                              userInfo: nil,
                              repeats: false)
                RunLoop.current.add(t, forMode: RunLoop.Mode.common)
                refreshTimeoutTimer = t
            }
        case .awaitingData:
            if items.count > 0 {
                refreshState = .normal
                refreshControl.endRefreshing()
                refreshTimeoutTimer?.invalidate()
                refreshTimeoutTimer = nil

                if !preferences.hasPrevioulyFoundConfiguredSpeaker() {
                    preferences.foundConfiguredSpeaker()
                }
                noSpeakersView?.isHidden = true
                noSpeakersViewSpinner?.stopAnimating()
            }
        }
	}

    @objc func refreshTimeoutTimerFired(_ sender: Any) {
        refreshTimeoutTimer = nil
        refreshState = .normal
        refreshControl.endRefreshing()
        noSpeakersView?.isHidden = false
        noSpeakersViewSpinner?.startAnimating()
    }
}

