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

import UIKit
import CocoaLumberjack

class AdjustAudioVolumesViewController: UIViewController {
	// MARK: Public Properties
	var soundbarPlayerID: String?
    var soundbarDeviceID: String?
	var bailOutViewController: UIViewController?
	
	// MARK: Private Properties/Types
	private struct Segues {
		static let showSuccess = "showSuccess"
		static let showInstructions = "showInstructions"
	}

	private var previousSoundbarInputSelector: IoTInputOutputSelector?
    private var previousSoundbarPlayerGroup: IoTPlayerGroup?
    private var playingTest = false
    private var observers: [ObserverToken] = []
    private var playerGroupObserver: ObserverToken?
    private let possibleAllChannels: [IoTHomeTheaterChannel] = [.none,
                                                                .subwoofer,
                                                                .leftSurround,
                                                                .rightSurround,
                                                                .leftRearSurround,
                                                                .rightRearSurround,
                                                                .leftUpfiringSurround,
                                                                .rightUpfiringSurround,
                                                                //                                                   .leftRearUpfiringSurround,
        //                                                   .rightRearUpfiringSurround
    ]
    typealias ChannelInfo = (channel: IoTHomeTheaterChannel, deviceInfo: IoTMultichannelChannelDevice?)
    private var addedChannels = [ChannelInfo] ()
	
	
	// MARK: UIOutlets
	@IBOutlet private weak var instructionsLabel: UILabel!
	@IBOutlet private weak var playTestAudioSwitch: UISwitch!
    @IBOutlet private weak var tableView: UITableView!
}

// MARK: - Overridden Methods
extension AdjustAudioVolumesViewController {
	override func viewDidLoad() {
		super.viewDidLoad()

        prepareModalNavigationBar()
		
		playTestAudioSwitch.setOn(false, animated: false)
		playTestAudioSwitch.themeForJukebox()

		setUpInstructions()
	}
	
	override func viewWillAppear(_ animated: Bool) {
		super.viewWillAppear(animated)
		
		// No title for back button to centre next title
		let bbi = UIBarButtonItem(title: " ", style: .plain, target: nil, action: nil)
		navigationItem.backBarButtonItem = bbi
		navigationItem.setHidesBackButton(true, animated: false)
		setUpObservers()
        setUpChannels()
	}

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        updateSliders()
    }
	
	override func viewWillDisappear(_ animated: Bool) {
		super.viewWillDisappear(animated)
		
		stopPlayingPinkNoise()
		observers.forEach { NotificationCenter.default.removeObserver($0) }
		observers = []
		if playerGroupObserver != nil {
			NotificationCenter.default.removeObserver(playerGroupObserver!)
			playerGroupObserver = nil
		}
	}
	
	override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
		if let successVC = segue.destination as? AdjustAudioSuccessViewController {
			successVC.bailOutViewController = bailOutViewController
		}
	}
}

// MARK: - UI Actions
extension AdjustAudioVolumesViewController {
	@IBAction private func nextTapped(_ sender: Any?) {
		performSegue(withIdentifier: Segues.showSuccess, sender: self)
	}

	@IBAction private func moreInfoTapped(_ sender: Any?) {
		performSegue(withIdentifier: Segues.showInstructions, sender: self)
	}
	
	@IBAction func didToggleSwitch (_ sender: Any?) {
		playTestAudioSwitch.updateStylingForStateForJukebox()
		playTestAudioStage1()
	}
}

// MARK: - Private Methods
extension AdjustAudioVolumesViewController {
	private func setUpInstructions() {
		let font = UIFont.systemFont(ofSize: 14)
		let titleAttr = [NSAttributedString.Key.foregroundColor: UIColor(white: 0.58, alpha: 1.0),
						 NSAttributedString.Key.font: font as Any]

		let mutAttrStr = NSMutableAttributedString(string: NSLocalizedString("Adjust the volume of each speaker until they sound about equal. When your surround system sounds good, tap NEXT.", comment: "Instructions title"),
												   attributes: titleAttr)

		let blue = UIColor(red: 19/255.0, green: 154/255.0, blue: 188/255.0, alpha: 1.0)
		let titleAttr2 = [NSAttributedString.Key.foregroundColor: blue,
						  NSAttributedString.Key.font: font as Any]
		mutAttrStr.append(NSAttributedString(string: " ", attributes: titleAttr2))
		mutAttrStr.append(NSAttributedString(string: NSLocalizedString("More info...", comment: "Instructions title"),
											 attributes: titleAttr2))
		instructionsLabel.attributedText = mutAttrStr
	}
	
	private func setUpObservers() {
		let deviceHandler: (DeviceNotification) -> () = { [weak self] notification in
			guard let self = self else {
				return
			}
			
			if notification.reason == .removed &&
				notification.device.deviceID == self.soundbarDeviceID {
				// Our soundbar has gone away...
			} else if notification.reason == .added {
				self.updateUI()
			}
		}
		
		let deviceObserver = NotificationCenter.default.addObserver(forType: DeviceNotification.self,
																	object: playerController,
																	queue: OperationQueue.main,
																	using: deviceHandler)

		// *******************
		
		let playerHandler: (PlayerNotification) -> () = { [weak self] notification in
			guard
                let self = self,
                notification.player.playerID == self.soundbarPlayerID else {
                    return
            }

            if notification.reason == .volumeStateChanged {
                self.updateUI()
            }
        }

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

        // *******************

        let playerInfoHandler: (HomeTheaterNotification) -> () = { [weak self] notification in
            guard
                let self = self,
                notification.player.playerID == self.soundbarPlayerID else {
                    return
            }

            if notification.reason == .availabilityChanged ||
                notification.reason == .volumeChanged {
                self.updateUI()
            }
        }

        let playerInfoObserver = NotificationCenter.default.addObserver(forType: HomeTheaterNotification.self,
                                                                        object: playerController,
                                                                        queue: OperationQueue.main,
                                                                        using: playerInfoHandler)
        observers = [deviceObserver, playerObserver, playerInfoObserver]
    }

    private func setUpChannels() {
        if let soundbar = playerController.player(id: soundbarPlayerID!) {
            addedChannels = possibleAllChannels.map({ channel in
                if channel == .none {
                    return [ChannelInfo(.none, nil)]
                }
                let deviceInfo = soundbar.homeTheaterChannelDevices(channel)
                return deviceInfo.map({ device in
                    return ChannelInfo(channel,device)
                })
            }).reduce([],+)
        } else {
            addedChannels = [ChannelInfo] ()
        }
    }

    private func updateUI() {
		setUpChannels()
        tableView.reloadData()
        updateSliders()
    }

    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 soundbar = playerController.player(id: soundbarPlayerID!),
            let cellIndex = tableView.indexPath(for: cell)else {
                return
        }

        let volume = Double(cell.volumePercent) / 100
        let channelInfo = addedChannels[cellIndex.row]

        if channelInfo.channel != .none  {
            if let deviceInfo = channelInfo.deviceInfo {
                soundbar.setHomeTheaterChannel(channelInfo.channel,
                                               playerID: deviceInfo.playerID,
                                               volume: volume,
                                               completion: { (success, e) in
                                                if !success, let error = e {
                                                    DDLogError("Error thrown setting volume: \(error)")
                                                }
                }, completionQueue: DispatchQueue.main)
            }
        } else {
            let maxVolume = soundbar.maxVolume
            let requested = volume * maxVolume
            soundbar.setVolume(requested,
                               completion: { (success, e) in
                                if !success, let error = e {
                                    DDLogError("Error thrown setting volume: \(error)")
                                }
            }, completionQueue: DispatchQueue.main)
        }
    }

    private func updateSlider(cell: SpeakerSliderTableViewCell, channelInfo: ChannelInfo, soundbar: IoTPlayer) {
        var volume: Double = 0.5
        var maxVolume: Double = 1.0
        var haveChannel = true
        var sliderEnabled = true
        if (channelInfo.channel == .none) {
            volume = soundbar.volume
            maxVolume = soundbar.maxVolume
        } else {
            haveChannel = channelInfo.deviceInfo?.connected ?? false
            sliderEnabled = haveChannel && (channelInfo.deviceInfo?.removable ?? false)
            volume = channelInfo.deviceInfo?.volume ?? 0.0
            maxVolume = 1.0
        }

        let percent = volumePercent(value: volume, maxVolume: maxVolume)

        cell.volumePercent = Int(percent)
        cell.enabled = sliderEnabled
    }

	private func updateSliders() {
		guard let soundbar = playerController.player(id: soundbarPlayerID!) else {
			return
		}

        for cellIndex in 0..<addedChannels.count {
            let channelInfo = addedChannels[cellIndex]
            if let cell = tableView.cellForRow(at: IndexPath(row: cellIndex, section: 0)) as? SpeakerSliderTableViewCell {
                updateSlider(cell: cell, channelInfo: channelInfo, soundbar: soundbar)
            }
        }
	}
	
	//MARK: Play Test Audio
	private func playTestAudioStage1() {
		previousSoundbarInputSelector = nil
		previousSoundbarPlayerGroup = nil
		
		if let soundbar = playerController.player(id: soundbarPlayerID!) {
			if let playerGroup = playerController.playerGroupForPlayer(soundbar) {
				if (playTestAudioSwitch.isOn) {
					if playerGroup.members.count > 1 {
						// In a group
						showRemoveFromGroupDialog()
					} else {
						playTestAudioStage2()
					}
				} else {
					stopPlayingPinkNoise()
				}
			}
		}
	}
	
	private func playTestAudioStage2() {
		if let soundbar = playerController.player(id: soundbarPlayerID!) {
			if !soundbar.isInterruptible {
				showNotInterruptibleDialog()
			} else {
				playTestAudioStage3()
			}
		}
	}
	
	private func playTestAudioStage3() {
		if !playPinkNoise() {
			playTestAudioFailed()
		}
	}
	
	private func showRemoveFromGroupDialog() {
		let grayColor = UIColor(red: 0.48, green: 0.49, blue: 0.52, alpha: 1.0)
		let textAttrs: [NSAttributedString.Key : Any] = [.font : UIFont.systemFont(ofSize: 14, weight: .light) as Any,
														.foregroundColor: grayColor]
		let text = NSAttributedString(string: NSLocalizedString("Your surround system is part of a group. To adjust audio settings while playing the test sound, we must disconnect it from the group.", comment: "Alert"),
									  attributes: textAttrs)
		
		
		let alert = UIAlertController(title: "", message: "", preferredStyle: .alert)
		alert.setValue(text, forKey: "attributedMessage")
		alert.addAction(UIAlertAction(title: NSLocalizedString("CANCEL", comment: "Alert"),
									  style: .cancel,
									  handler: { _ in
										self.playTestAudioFailed()
		}))
		
		alert.addAction(UIAlertAction(title: NSLocalizedString("CONTINUE", comment: "Alert"),
									  style: .default,
									  handler: { _ in
										self.removeSoundbarFromGroup()
		}))
		
		present(alert, animated: true, completion: nil)
	}
	
	private func showNotInterruptibleDialog() {
		let grayColor = UIColor(red: 0.48, green: 0.49, blue: 0.52, alpha: 1.0)
		let textAttrs: [NSAttributedString.Key : Any] = [.font : UIFont.systemFont(ofSize: 14, weight: .light) as Any,
														.foregroundColor: grayColor]
		let text = NSAttributedString(string: NSLocalizedString("Your soundbar does not allow automatic input switching from the current source. To play the test sound, please change the input on your soundbar manually.", comment: "Alert"),
									  attributes: textAttrs)
		
		
		let alert = UIAlertController(title: "", message: "", preferredStyle: .alert)
		alert.setValue(text, forKey: "attributedMessage")
		alert.addAction(UIAlertAction(title: NSLocalizedString("CANCEL", comment: "Alert"),
									  style: .cancel,
									  handler: { _ in
										self.playTestAudioFailed()
		}))
		
		alert.addAction(UIAlertAction(title: NSLocalizedString("CONTINUE", comment: "Alert"),
									  style: .default,
									  handler: { _ in
										self.playTestAudioStage3()
		}))
		
		present(alert, animated: true, completion: nil)
	}
	
	private func removeSoundbarFromGroup() {
		if let soundbar = playerController.player(id: soundbarPlayerID!),
			soundbar.isInterruptible,
			let soundbarPlayerGroup = playerController.playerGroupForPlayer(soundbar) {
			previousSoundbarPlayerGroup = soundbarPlayerGroup
			setUpPlayerGroupObserver()
			
            let playerIDs = soundbarPlayerGroup.members.map { return $0.playerID }
			var playerIDsSet = Set(playerIDs)
			playerIDsSet.subtract([soundbar.playerID])
			let newPlayers = Array(playerIDsSet)
            playerController.editMembers(playerGroupID: soundbarPlayerGroup.groupID,
                                         playerIDs: newPlayers,
                                         completion: nil,
                                         queue: nil);
		}
	}
	
	private func playTestAudioFailed() {
		playTestAudioSwitch.setOn(false, animated: true)
		playTestAudioSwitch.updateStylingForStateForJukebox()
	}

    private func playPinkNoise() -> Bool {
        if
            let soundbar = playerController.player(id: soundbarPlayerID!),
            soundbar.isInterruptible,
            let soundbarPlayerGroup = playerController.playerGroupForPlayer(soundbar),
            let playlist = soundbarPlayerGroup.playlist {

            let mediaItem = IoTMediaItem()

            mediaItem.title = NSLocalizedString("Test Loop", comment: "Test sound loop")
            if soundbar.haveHomeTheaterChannel(.subwoofer) &&
                soundbar.isHomeTheaterChannelAvailable(.subwoofer) {
                mediaItem.thumbnailURL = HTTPServer.sharedInstance.urlForTestArtwork(.pinkNoiseWithSubwoofer) ?? ""
                mediaItem.streamURL = HTTPServer.sharedInstance.urlForTestAudio(.pinkNoiseWithSubwoofer) ?? ""
            } else {
                mediaItem.thumbnailURL = HTTPServer.sharedInstance.urlForTestArtwork(.pinkNoiseWithoutSubwoofer) ?? ""
                mediaItem.streamURL = HTTPServer.sharedInstance.urlForTestAudio(.pinkNoiseWithoutSubwoofer) ?? ""
            }

            QueueOperationsHelper.sharedInstance.add(toPlaylist: playlist,
                                                     item: mediaItem,
                                                     atEnd: true,
                                                     playNow: true,
                                                     completion: { [weak self] success, error in
                                                        guard let self = self else {
                                                            return
                                                        }
                                                        if success {
                                                            playlist.setLoopMode(.one,
                                                                                 completion: nil,
                                                                                 completionQueue: nil)
                                                        } else {
                                                            self.playingTest = false
                                                            self.playTestAudioFailed()
                                                        }
                },
                                                     completionQueue: DispatchQueue.main)

            playingTest = true
            return true
        } else {
            return false
        }
	}
	
    private func stopPlayingPinkNoise() {
        if playingTest,
            let soundbar = playerController.player(id: soundbarPlayerID!),
            let soundbarPlayerGroup = playerController.playerGroupForPlayer(soundbar),
            let playlist = soundbarPlayerGroup.playlist {
            
            playingTest = false
            let previous = previousSoundbarInputSelector
            soundbarPlayerGroup.stop(completion: nil, completionQueue: nil)
            if previous != nil {
                if soundbar.activeInputSelector.identifier != previous?.identifier {
                    soundbar.setInputSelector(previous!,
                                              completion: nil,
                                              completionQueue: nil)
                }
            }
            playlist.setLoopMode(.none,
                                 completion: nil,
                                 completionQueue: nil)
        }
    }
    
	private func setUpPlayerGroupObserver() {
		let playerGroupHandler: (PlayerGroupNotification) -> () = { [weak self] notification in
			guard let self = self,
				let soundbar = self.playerController.player(id: self.soundbarPlayerID!) else {
					return
			}
			
			if notification.reason == .playerListChanged &&
				notification.playerGroup == self.previousSoundbarPlayerGroup {
				
                if !(self.previousSoundbarPlayerGroup?.members.contains(where: { return $0.playerID == soundbar.playerID }) ?? true) {
					// Our soundbar has been removed from its old playerGroup
					NotificationCenter.default.removeObserver(self.playerGroupObserver!)
					self.playerGroupObserver = nil
					self.playTestAudioStage2()
				}
			}
		}
		
		playerGroupObserver = NotificationCenter.default.addObserver(forType: PlayerGroupNotification.self,
															  object: playerController,
															  queue: OperationQueue.main,
															  using: playerGroupHandler)
	}
}

extension AdjustAudioVolumesViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return addedChannels.count
    }

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

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
		let channelInfo = addedChannels[indexPath.row]
        var cell = tableView.dequeueReusableCell(withIdentifier: SpeakerSliderTableViewCell.reuseIdentifier, for: indexPath) as? SpeakerSliderTableViewCell
        if cell == nil {
            cell = SpeakerSliderTableViewCell()
        }

        if let deviceName = channelInfo.deviceInfo?.displayName, deviceName.count > 0 {
            cell?.speakerName = deviceName
            return cell!
        }

        let isBuiltIn = (channelInfo.channel != .none) && (!(channelInfo.deviceInfo?.removable ?? false))

        switch channelInfo.channel {
        case .none:
            cell?.speakerName = NSLocalizedString("System Volume", comment:"System Volume Cell")
        case .subwoofer:
            if isBuiltIn {
				cell?.speakerName = NSLocalizedString("Built-In Subwoofer", comment:"Subwoofer Cell")
            } else {
				cell?.speakerName = NSLocalizedString("Subwoofer", comment:"Subwoofer Cell")
            }
        case .leftSurround:
            if isBuiltIn {
                cell?.speakerName = NSLocalizedString("Built-In Left Surround", comment:"Left Surround Cell")
            } else {
                cell?.speakerName = NSLocalizedString("Left Surround", comment:"Left Surround Cell")
            }
        case .rightSurround:
            if isBuiltIn {
                cell?.speakerName = NSLocalizedString("Built-In Right Surround", comment:"Right Surround Cell")
            } else {
                cell?.speakerName = NSLocalizedString("Right Surround", comment:"Right Surround Cell")
            }
        case .leftRearSurround:
            if isBuiltIn {
                cell?.speakerName = NSLocalizedString("Built-In Left Rear Surround", comment:"Left Rear Surround Cell")
            } else {
                cell?.speakerName = NSLocalizedString("Left Rear Surround", comment:"Left Rear Surround Cell")
            }
        case .rightRearSurround:
            if isBuiltIn {
                cell?.speakerName = NSLocalizedString("Built-In Right Rear Surround", comment:"Right Rear Surround Cell")
            } else {
                cell?.speakerName = NSLocalizedString("Right Rear Surround", comment:"Right Rear Surround Cell")
            }
        case .leftUpfiringSurround:
            if isBuiltIn {
                cell?.speakerName = NSLocalizedString("Built-In Left Upfiring Surround", comment:"Left Upfiring Surround Cell")
            } else {
                cell?.speakerName = NSLocalizedString("Left Upfiring Surround", comment:"Left Upfiring Surround Cell")
            }
        case .rightUpfiringSurround:
            if isBuiltIn {
                cell?.speakerName = NSLocalizedString("Built-In Right Upfiring Surround", comment:"Right Upfiring Surround Cell")
            } else {
                cell?.speakerName = NSLocalizedString("Right Upfiring Surround", comment:"Right Upfiring Surround Cell")
            }
        case .leftRearUpfiringSurround:
            if isBuiltIn {
                cell?.speakerName = NSLocalizedString("Built-In Left Rear Upfiring Surround", comment:"Left Rear Upfiring Surround Cell")
            } else {
                cell?.speakerName = NSLocalizedString("Left Rear Upfiring Surround", comment:"Left Rear Upfiring Surround Cell")
            }
        case .rightRearUpfiringSurround:
            if isBuiltIn {
                cell?.speakerName = NSLocalizedString("Built-In Right Rear Upfiring Surround", comment:"Right Rear Upfiring Surround Cell")
            } else {
                cell?.speakerName = NSLocalizedString("Right Rear Upfiring Surround", comment:"Right Rear Upfiring Surround Cell")
            }
        default:
            break
        }

        if let soundbar = self.playerController.player(id: self.soundbarPlayerID!) {
            updateSlider(cell: cell!, channelInfo: channelInfo, soundbar: soundbar)
        }

        cell!.sliderDidChange = { [weak self] cell in
            guard let self = self else {
                return
            }

            self.didChangeVolume(cell)
        }

        return cell!
    }
}
