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

import UIKit
import CocoaLumberjack

class SpeakerGroupDetailViewProvider: NSObject, DeviceDetailViewControllerProvider {
    private enum RowType {
        case groupVolume
        case playerVolume
        case audioSources
        case outputSinks
        case surroundSystem
        case grouping
    }

    private typealias Rows = [RowType]
    private var sections = [Rows] ()
    private var volumesExpanded = false
    private var availablePlayers = [IoTPlayer] ()

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

    var playerGroup: IoTPlayerGroup?

    var representativeImage: UIImage? {
        return UIImage(named: "device-group")
    }

    var deviceModel: String? {
        return nil
    }

    var deviceManufacturer: String? {
        return nil
    }

    var connectivity: DeviceConnectivityInfo? {
        return nil
    }

    init(tableView: UITableView?,
         parentVC: UIViewController,
         playerGroup: IoTPlayerGroup,
         playerController: PlayerControllerProtocol) {
        self.tableView = tableView
        self.parentVC = parentVC
        self.playerGroup = playerGroup
        self.playerController = playerController
        super.init()
        setUpObservers()
        setUpSections()
    }

    deinit {
        removeObservers()
    }
}

extension SpeakerGroupDetailViewProvider {
    private func setUpObservers() {
        let playerHandler: (PlayerNotification) -> () = { [weak self] notification in
            guard
                let self = self,
                self.volumesExpanded == true,
            	let _ = self.playerGroup,
                let indexOfPlayer = self.availablePlayers.firstIndex(where: { (availablePlayer) -> Bool in
                    return availablePlayer.playerID == notification.player.playerID
                })
			else {
                    return
            }

            if notification.reason == .volumeStateChanged {
                if let cell = self.tableView?.cellForRow(at: IndexPath(row: indexOfPlayer + 1, section: 0)) as? SpeakerSliderTableViewCell {
                    if (cell.isTracking) {
                        DDLogVerbose("Not updating player volume as tracking");
                        return
                    }
					self.tableView?.reloadRows(at: [IndexPath(row: indexOfPlayer + 1, section: 0)], with: .none)
                }
            } else if notification.reason == .displayNameChanged{
                self.tableView?.reloadRows(at: [IndexPath(row: indexOfPlayer + 1, section: 0)], with: .none)
            }
        }

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

        let playerGroupHandler: (PlayerGroupNotification) -> () = { [weak self] notification in
            guard
                let self = self,
                let playerGroup = self.playerGroup,
            	playerGroup == notification.playerGroup else {
                    return
            }

            let relevant = notification.reason == .playerListChanged ||
                notification.reason == .volumeStateChanged ||
                notification.reason == .nameChanged

            if !relevant {
                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 group volume as tracking");
                        return
                    }
                    self.tableView?.reloadRows(at: [IndexPath(row: 0, section: 0)], with: .none)
                }
            } else if notification.reason == .playerListChanged {
                // We ignore if user is changing volume if player list changed.
                self.setUpSections()
                self.tableView?.reloadSections(IndexSet(integer: 0), with: .none)
            } else {
                self.parentVC?.title = playerGroup.displayName
            }
        }

        let playerGroupObserver = NotificationCenter.default.addObserver(forType: PlayerGroupNotification.self,
                                                                         object: playerController,
                                                                         queue: OperationQueue.main,
                                                                         using: playerGroupHandler)

        observers = [playerObserver, playerGroupObserver]
    }

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

    private func setUpSections() {
        sections = []
        guard let gp = playerGroup else {
            return
        }

        var volumesSection: Rows = [.groupVolume]

        availablePlayers = gp.members.compactMap({ member in
            let id = member.playerID
            let player = playerController.player(id: id)
            if player != nil && volumesExpanded {
                volumesSection.append(.playerVolume)
            }
            return player
        })

        let otherSection: Rows = [.audioSources, .outputSinks, .grouping]
        sections = [volumesSection, otherSection]
    }

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

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

    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, index: Int) {
        guard
            let pg = playerGroup else {
                return
        }


        if index < 0 {
            let maxVolume = pg.maxVolume
            let requested = Double(cell.volumePercent)/100 * maxVolume
            pg.setVolume(requested,
                         completion: nil,
                         completionQueue:nil)
        } else {
            let player = availablePlayers[index]
            let maxVolume = player.maxVolume
            let requested = Double(cell.volumePercent)/100 * maxVolume
            player.setVolume(requested,
                             completion: nil,
                             completionQueue: nil)
        }
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let pg = playerGroup else {
            return UITableViewCell()
        }

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

        switch rowType {
        case .groupVolume:
            var cell = tableView.dequeueReusableCell(withIdentifier: "SpeakerSliderTableViewCell", for: indexPath) as? SpeakerSliderTableViewCell
            if cell == nil {
                cell = SpeakerSliderTableViewCell()
            }
            cell?.expanded = volumesExpanded ? .expanded : .collapsed
            cell?.makeAccessible(volumesExpanded ? Accessibility.DeviceDetailScreen.collapse : Accessibility.DeviceDetailScreen.expand)
            cell?.accessibilityName = Accessibility.DeviceDetailScreen.group
            cell?.speakerName = NSLocalizedString("Group Volume", comment: "Group Volume Cell")
            cell?.showPercent = false
            cell?.volumePercent = Int(volumePercent(value: pg.volume, maxVolume: pg.maxVolume))
            cell?.sliderDidChange = { [weak self] cell in
                guard let self = self else {
                    return
                }

                self.didChangeVolume(cell: cell, index: -1)
            }
            return cell!
        case .playerVolume:
            var cell = tableView.dequeueReusableCell(withIdentifier: "SpeakerSliderTableViewCell", for: indexPath) as? SpeakerSliderTableViewCell
            if cell == nil {
                cell = SpeakerSliderTableViewCell()
            }

            let player = availablePlayers[indexPath.row - 1]
            cell?.expanded = .hidden
            cell?.accessibilityName = player.displayName
            cell?.speakerName = player.displayName
            cell?.showPercent = false
            cell?.volumePercent = Int(volumePercent(value: player.volume, maxVolume: player.maxVolume))
            cell?.sliderDidChange = { [weak self] cell in
                guard let self = self else {
                    return
                }

                self.didChangeVolume(cell: cell, index: indexPath.row - 1)
            }
            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("Devices in Group", comment: "Devices in Group")
            cell?.makeAccessible(Accessibility.DeviceDetailScreen.editGroup)
            pg.members.forEach {
                let label = UILabel(frame: CGRect.zero)
                let availabilityText = $0.available ? "" : " " + NSLocalizedString("(Unavailable)", comment: "(Unavailable)")
                label.text = $0.displayName + availabilityText
                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?.titleLabel?.textColor = UIColor.white
            cell?.makeAccessible(Accessibility.DeviceDetailScreen.surroundSystem)
            cell?.accessoryType = .disclosureIndicator
            return cell!
        }
    }

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

        switch rowType {
        case .groupVolume:
            volumesExpanded = !volumesExpanded
            setUpSections()
            tableView.reloadSections(IndexSet(integer: 0), with: .fade)
        case .playerVolume:
            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...
    }
}
