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

import UIKit
import CocoaLumberjack

class SpeakerDetailViewProvider: NSObject, DeviceDetailViewControllerProvider {
    // Private
    private enum RowType {
        case volume
        case audioSources
        case outputSinks
        case surroundSystem
        case grouping
    }

    private var rows = [RowType] ()

    private let playerController: PlayerControllerProtocol
    private weak var parentVC: UIViewController?
    private weak var tableView: UITableView?
    private let isGrouped: Bool
    private var observers = [ObserverToken] ()

    private lazy var player: IoTPlayer? = {
        return playerController.player(id: playerID)
    } ()

    private lazy var device: IoTDevice? = { [weak self] in
        if let p = player ,
            let d = playerController.device(player: p) {
            return d
        }
        return nil
    } ()

    // Public
    let playerID: String

	// DeviceDetailViewControllerProvider
    var representativeImage: UIImage? {
        if let p = player,
            p.isSoundBar {
            return UIImage(named: "device-soundbar")
        } else {
			return UIImage(named: "device-speaker")
        }
    }

    var deviceModel: String? {
		return device?.modelNumber
    }

    var deviceManufacturer: String? {
        return device?.manufacturer
    }

    var connectivity: DeviceConnectivityInfo? {
        if let uDevice = device {
            var bluetoothState = DeviceConnectivityState.missing
            if uDevice.bluetoothSupported {
                bluetoothState = uDevice.bluetoothEnabled == .enabled ?
                    DeviceConnectivityState.enabled :
                    DeviceConnectivityState.present
            }

            var avsVoiceUI = DeviceConnectivityState.missing
            var gvaVoiceUI = DeviceConnectivityState.missing
            var cortanaVoiceUI = DeviceConnectivityState.missing
            var modularVoiceUI = DeviceConnectivityState.missing
            if uDevice.voiceUISupported {
                let clients = uDevice.voiceUIClients
                if let avsClient = clients.first(where: { return $0.type == .AVS } ) {
                    if avsClient.onboarded {
                        avsVoiceUI = .enabled
                    }
                }
                if let _ = clients.first(where: { return $0.type == .GVA } ) {
					gvaVoiceUI = .enabled
                }
                if let _ = clients.first(where: { return $0.type == .cortana } ) {
                    cortanaVoiceUI = .enabled
                }
                if let _ = clients.first(where: { return $0.type == .modular } ) {
                    modularVoiceUI = .enabled
                }
            }

            var wifiState = DeviceConnectivityState.missing
            if uDevice.networkSupported {
                switch uDevice.networkInterface {
                case .ethernet:
                    wifiState = .present
                case .wiFi:
                    wifiState = .enabled
                default:
                    break
                }
            }

            var zigbeeState = DeviceConnectivityState.missing
            if uDevice.zigbeeSupported {
                zigbeeState = uDevice.zigbeeEnabled == .enabled ?
                    DeviceConnectivityState.enabled :
                    DeviceConnectivityState.present
            }
            
            return DeviceConnectivityInfo(bluetooth: bluetoothState,
                                          zigbee: zigbeeState,
                                          wifi: wifiState,
                                          avsVoiceUI: avsVoiceUI,
                                          gvaVoiceUI: gvaVoiceUI,
                                          cortanaVoiceUI: cortanaVoiceUI,
                                          modularVoiceUI: modularVoiceUI)
        } else {
            return DeviceConnectivityInfo.allMissing()
        }
    }

    init(tableView: UITableView?,
         parentVC: UIViewController,
         playerID: String,
         isGrouped: Bool,
         playerController: PlayerControllerProtocol) {
        self.tableView = tableView
        self.parentVC = parentVC
        self.playerID = playerID
        self.isGrouped = isGrouped
        self.playerController = playerController

		super.init()
        setUpObservers()
        setUpRows()
    }

    deinit {
        removeObservers()
    }
}

extension SpeakerDetailViewProvider {
    private func setUpObservers() {
        let playerHandler: (PlayerNotification) -> () = { [weak self] notification in
            guard
                let self = self,
            	notification.player.playerID == self.playerID else {
                    return
            }

            // Check if volume slider is tracking
            if notification.reason == .volumeStateChanged {
                if let cell = self.tableView?.cellForRow(at: IndexPath(row: 0, section: 0)) as? SpeakerSliderTableViewCell {
                    if (cell.isTracking) {
                        DDLogVerbose("Not updating volume as tracking");
                        return
                    }
                }
            }

			self.setUpRows()
            self.tableView?.reloadData()
        }

        let playerObserver = NotificationCenter.default.addObserver(forType: PlayerNotification.self,
                                                                    object: playerController,
                                                                    queue: OperationQueue.main,
                                                                    using: playerHandler)

        observers = [playerObserver]
    }

    private func removeObservers() {
        observers.forEach { NotificationCenter.default.removeObserver($0) }
        observers = []
    }

    private func setUpRows() {
        guard let p = player else {
            rows = []
            return
        }

        rows = [.volume]
        if !isGrouped {
			rows.append(contentsOf: [.audioSources, .outputSinks])
        }
        if p.isSoundBar {
            rows.append(.surroundSystem)
        }
        if !isGrouped && p.groupingSupported {
            rows.append(.grouping)
        }
    }

    private func volumePercent(value: Double, maxVolume: Double) -> Double {
        var volume: Double = 0
        if (maxVolume > 0) {
            volume = (value / maxVolume) * 100
        }
        return volume
    }

    private func didChangeVolume(cell: SpeakerSliderTableViewCell) {
        guard let p = player else {
            return
        }
        
        let maxVolume = p.maxVolume
        let requested = Double(cell.volumePercent)/100 * maxVolume
        p.setVolume(requested,
                    completion: nil,
                    completionQueue:nil)
    }

}

// UITableViewDataSource / UITableViewDelegate
extension SpeakerDetailViewProvider {
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

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

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let p = player else {
            rows = []
            return UITableViewCell()
        }

        let rowType = rows[indexPath.row]

        switch rowType {
        case .volume:
            var cell = tableView.dequeueReusableCell(withIdentifier: "SpeakerSliderTableViewCell", for: indexPath) as? SpeakerSliderTableViewCell
            if cell == nil {
                cell = SpeakerSliderTableViewCell()
            }
            cell?.speakerName = NSLocalizedString("Volume", comment: "Speaker Volume Cell")
            cell?.accessibilityName = p.displayName
            cell?.showPercent = false
            cell?.volumePercent = Int(volumePercent(value: p.volume, maxVolume: p.maxVolume))
            cell?.sliderDidChange = { [weak self] cell in
                guard let self = self else {
                    return
                }

                self.didChangeVolume(cell: cell)
            }
            return cell!
        case .audioSources:
            var cell = tableView.dequeueReusableCell(withIdentifier: "DeviceDetailTableViewCell", for: indexPath) as? DeviceDetailTableViewCell
            if cell == nil {
                cell = DeviceDetailTableViewCell()
            }
            cell?.titleLabel?.text = NSLocalizedString("Available Audio Sources", comment: "Available Audio Sources")
            cell?.titleLabel?.textColor = ColorCompat.label
            cell?.makeAccessible(Accessibility.DeviceDetailScreen.audioSources)
            cell?.accessoryType = .disclosureIndicator
            return cell!
        case .outputSinks:
            var cell = tableView.dequeueReusableCell(withIdentifier: "DeviceDetailTableViewCell", for: indexPath) as? DeviceDetailTableViewCell
            if cell == nil {
                cell = DeviceDetailTableViewCell()
            }
            cell?.titleLabel?.text = NSLocalizedString("Available Output Destinations", comment: "Available Output Destinations")
            cell?.titleLabel?.textColor = ColorCompat.label
            cell?.makeAccessible(Accessibility.DeviceDetailScreen.outputDestinations)
            cell?.accessoryType = .disclosureIndicator
            return cell!
        case .grouping:
            var cell = tableView.dequeueReusableCell(withIdentifier: "DeviceDetailGroupingTableViewCell", for: indexPath) as? DeviceDetailGroupingTableViewCell
            if cell == nil {
                cell = DeviceDetailGroupingTableViewCell()
            }
            cell?.membersStackView?.arrangedSubviews.forEach {
                $0.removeFromSuperview()
            }
            cell?.titleLabel?.text = NSLocalizedString("Group with Other Speakers", comment: "Group with Other Speakers")
            cell?.makeAccessible(Accessibility.DeviceDetailScreen.createGroup)
            let label = UILabel(frame: CGRect.zero)
            label.text = NSLocalizedString("Ungrouped", comment: "Ungrouped")
            label.font = UIFont.systemFont(ofSize: 14, weight: .semibold)
            label.textColor = ColorCompat.secondaryLabel
            cell?.membersStackView?.addArrangedSubview(label)

            cell?.accessoryType = .disclosureIndicator
            cell?.setNeedsLayout()
            return cell!
        case .surroundSystem:
            var cell = tableView.dequeueReusableCell(withIdentifier: "DeviceDetailTableViewCell", for: indexPath) as? DeviceDetailTableViewCell
            if cell == nil {
                cell = DeviceDetailTableViewCell()
            }
            cell?.titleLabel?.text = NSLocalizedString("Surround System", comment: "Surround System")
            cell?.makeAccessible(Accessibility.DeviceDetailScreen.surroundSystem)
            cell?.titleLabel?.textColor = ColorCompat.label
            cell?.accessoryType = .disclosureIndicator
            return cell!
        }
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        let rowType = rows[indexPath.row]

        switch rowType {
        case .volume:
            break
        case .audioSources:
            parentVC?.performSegue(withIdentifier: DeviceDetailViewController.Segues.showAudioSources, sender: self)
        case .outputSinks:
            parentVC?.performSegue(withIdentifier: DeviceDetailViewController.Segues.showOutputSinks, sender: self)
        case .surroundSystem:
            parentVC?.performSegue(withIdentifier: DeviceDetailViewController.Segues.showSurroundSound, sender: self)
        case .grouping:
            parentVC?.performSegue(withIdentifier: DeviceDetailViewController.Segues.showSpeakerGrouping, sender: self)
        }
    }

    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return CGFloat.leastNormalMagnitude // if we return zero it gets ignored...
    }

    func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
        return CGFloat.leastNormalMagnitude // if we return zero it gets ignored...
    }
}
