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

import UIKit
import CocoaLumberjack

class PlayerCheckBoxTableViewCell: UITableViewCell {
    @IBOutlet weak var checkbox: UIImageView?
    @IBOutlet weak var speakerLabel: UILabel?
    @IBOutlet weak var playingIndicatorImageView: UIImageView?
}

class GroupingViewControllerNameTableViewCell: UITableViewCell {
    @IBOutlet weak var groupNameLabel: UILabel?
}

class GroupingViewController: MiniPlayerEmbeddingViewController, GroupingNameViewControllerDelegate {
    private struct Segues {
        static let showChooseName = "showChooseName"
    }

    struct PlayerChoiceItem: Hashable {
        let name: String
        let id: String
    }

	// MARK: Private Properties
    private var playerChoices = [PlayerChoiceItem] ()
	private var playersToRemoveFromPlayerGroup = Set<PlayerChoiceItem> ()
	private var playersToAddToPlayerGroup = Set<PlayerChoiceItem> ()

    private var defaultName: String?
    private var chosenName: String?
	
	// MARK: UI Outlets
    private var saveButton: UIBarButtonItem?
    @IBOutlet private weak var tableView: UITableView?

    override func handlePlayerGroupNotification(_ notification: PlayerGroupNotification) {
        super.handlePlayerGroupNotification(notification)
        if notification.reason == .playerListChanged {
            update()
        }
    }

    func nameController(_ controller: GroupingNameViewController, didSelectName newName: String) {
        chosenName = newName
        tableView?.reloadData()
        updateSaveButton()
    }
}

// MARK: - Overridden Methods
extension GroupingViewController {
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

    }

    override func viewDidLoad() {
        hasSearchButton = false

        super.viewDidLoad()
        // Accessibility
        tableView?.makeAccessible(Accessibility.GroupingScreen.speakersList)

        // Set up Save Button
        saveButton = UIBarButtonItem(title: NSLocalizedString("SAVE", comment: "Save button"),
                                     style: .done,
                                     target: self,
                                     action: #selector(didTapSave(_ :)))
        saveButton?.makeAccessible(Accessibility.GroupingScreen.saveButton)

        if let pg = playerGroup {
            if pg.isConvenienceGroup {
                title = NSLocalizedString("CREATE GROUP", comment: "CREATE GROUP")
                defaultName = pg.displayName
                chosenName = defaultName
            } else {
                title = NSLocalizedString("EDIT GROUP", comment: "EDIT GROUP")
                defaultName = nil
                chosenName = pg.displayName
            }
        } else {
            title = ""
            defaultName = ""
            chosenName = ""
        }
        update()
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        segue.destination.injectDependencies(preferences: preferences, playerController: playerController)
        if let dest = segue.destination as? GroupingNameViewController {
            dest.playerGroup = playerGroup
            dest.currentNameChoice = chosenName
            dest.nameDelegate = self
        }
    }
}

// MARK: - UI Actions
extension GroupingViewController {
	@IBAction func didTapSave(_ sender: UIButton) {
		let conflicting = conflictingPlayers()
		if conflicting.count > 0 {
			showConflictingAndApprove(conflicting)
		} else {
			modify()
		}
	}
}

// MARK: - Private Methods
extension GroupingViewController {
    private func update() {
        let onlinePlayers = playerController.players.map { return PlayerChoiceItem(name: $0.displayName, id: $0.playerID) }
        let onlinePlayersSet = Set(onlinePlayers)
        var playersInPlayerGroup = [PlayerChoiceItem] ()
        if let pg = playerGroup {
            playersInPlayerGroup = pg.members.map { (member) -> PlayerChoiceItem in
                return PlayerChoiceItem(name: member.displayName, id: member.playerID)
            }
        }

        let playersInPlayerGroupSet = Set(playersInPlayerGroup)
        let allPlayersSet = playersInPlayerGroupSet.union(onlinePlayersSet)

        playersToRemoveFromPlayerGroup = playersToRemoveFromPlayerGroup.intersection(playersInPlayerGroupSet)
        playersToAddToPlayerGroup = playersToAddToPlayerGroup.intersection(allPlayersSet)
        playersToAddToPlayerGroup = playersToAddToPlayerGroup.subtracting(playersInPlayerGroupSet)

        playerChoices = prepareChoices()
        tableView?.reloadData()
        updateSaveButton()
    }
    
    private func prepareChoices() -> [PlayerChoiceItem] {
		var memberIDs = Set<String> ()
        var choices = [PlayerChoiceItem] ()

        playerGroup?.members.forEach {
            choices.append(PlayerChoiceItem(name: $0.displayName, id: $0.playerID))
            memberIDs.insert($0.playerID)
        }

        let allOnlinePlayers = playerController.players
        allOnlinePlayers.forEach {
            if $0.groupingSupported && !memberIDs.contains($0.playerID) {
                choices.append(PlayerChoiceItem(name: $0.displayName, id: $0.playerID))
            }
        }
        return choices
    }

    private func showConflictingAndApprove(_ conflicting: Set<PlayerChoiceItem>) {
        var message = NSLocalizedString("These speakers are in another group: ", comment: "Speaker Conflict Message")
        let commaPlayers = conflicting.map({$0.name}).joined(separator: ", ")
        message.append(commaPlayers)
        message = message + NSLocalizedString("\n\nShould they join this group instead?", comment: "Speaker Conflict Message")
        let alert = UIAlertController(title: NSLocalizedString("Speakers Already Grouped", comment: "Speaker Conflict Alert"),
                                      message: message,
                                      preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: NSLocalizedString("No", comment: "Speaker Conflict Option"),
                                      style: .default,
                                      handler: { [weak self] (action) in
                                        guard let self = self else { return }
                                        self.playersToAddToPlayerGroup.subtract(conflicting)
                                        self.modify()
        }))
        
        alert.addAction(UIAlertAction(title: NSLocalizedString("Yes, Join this group", comment: "Speaker Conflict Option"),
                                      style: .default,
                                      handler: { [weak self] (action) in
                                        guard let self = self else { return }
                                        self.modify()
        }))
        
        present(alert, animated: true, completion: nil)
    }
    
    private func modify() {
        if let pg = playerGroup {
            if !pg.isConvenienceGroup {
                // We are editing not creating.
                if pg.displayName != chosenName {
                    playerController.rename(playerGroupID: pg.groupID,
                                            newName: chosenName!,
                                            completion: { (_, _) in
                                                self.update()
                    }, queue: DispatchQueue.main)
                }
                let playersInGroup = pg.members.map { return PlayerChoiceItem(name: $0.displayName, id: $0.playerID) }
                var playerSet = Set(playersInGroup)
                playerSet.formUnion(playersToAddToPlayerGroup)
                playerSet.subtract(playersToRemoveFromPlayerGroup)
                let newPlayerIDs = playerSet.map { return $0.id }
                playerController.editMembers(playerGroupID: pg.groupID,
                                             playerIDs: newPlayerIDs,
                                             completion: { (_, _) in
                                                self.update()
                }, queue: DispatchQueue.main)
            } else {
                // Creating Group
                let newPlayerIDs = playersToAddToPlayerGroup.map { return $0.id }
                playerController.create(playerGroupName: chosenName!,
                                        completion: { [weak self] (success, groupId, error) in
                                            guard
                                                let self = self else { return }
                                            if let groupId = groupId, success {
                                                DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1000),
                                                                              execute: {
                                                                                self.playerController.editMembers(playerGroupID: groupId,
                                                                                                                  playerIDs: newPlayerIDs,
                                                                                                                  completion: { (_, _) in
                                                                                                                    self.update()
                                                                                }, queue: DispatchQueue.main)
                                                })

                                            } else {
                                                let alert = UIAlertController(title: NSLocalizedString("Cannot Create Group", comment: "Cannot Create Group"),
                                                                              message: NSLocalizedString("An error occurred while creating the new group.", comment: "Cannot Create Group"), preferredStyle: .alert)
                                                alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "Cannot Create Group"),
                                                                              style: .default,
                                                                              handler: nil))
                                                self.present(alert, animated: true, completion: nil)
                                            }

                    } , queue: DispatchQueue.main)
            }
        }
    }

    private func updateSaveButton () {
        if let pg = playerGroup {
            var editsMade = (playersToRemoveFromPlayerGroup.count + playersToAddToPlayerGroup.count) > 0
            if !pg.isConvenienceGroup {
                editsMade = editsMade || chosenName != pg.displayName
            }
            if editsMade {
                navigationItem.rightBarButtonItem = saveButton
            } else {
                navigationItem.rightBarButtonItem = nil
            }
        } else {
			navigationItem.rightBarButtonItem = nil
        }
    }
    
    private func conflictingPlayers() -> Set<PlayerChoiceItem> {
        var conflicting = Set<PlayerChoiceItem> ()
        for item in playersToAddToPlayerGroup {
            if let player = playerController.player(id: item.id),
            	let playerGroup = playerController.playerGroupForPlayer(player),
                !playerGroup.isConvenienceGroup {
                conflicting.insert(item)
            }
        }
        return conflicting
    }
}

// MARK: - UITableViewDelegate, UITableViewDataSource Conformance
extension GroupingViewController: UITableViewDelegate, UITableViewDataSource {
    func numberOfSections(in tableView: UITableView) -> Int {
        if let pg = playerGroup {
            return pg.isConvenienceGroup ? 2 : 3
        } else {
            return 0
        }
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        guard let _ = playerGroup else {
            return 0
        }

        switch section {
        case 0:
            return 1
        case 1:
            return playerChoices.count
        case 2:
            return 1
        default:
            return 0
        }
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        if let pg = playerGroup {
            if indexPath.section == 0 {
                let cell = tableView.dequeueReusableCell(withIdentifier: "GroupingViewControllerNameTableViewCell", for: indexPath) as! GroupingViewControllerNameTableViewCell
                if pg.isConvenienceGroup {
                    if defaultName == chosenName {
                        cell.groupNameLabel?.text = NSLocalizedString("Use default name", comment: "Use default name")
                        cell.groupNameLabel?.textColor = ColorCompat.secondaryLabel
                    } else {
                        cell.groupNameLabel?.text = chosenName
                        cell.groupNameLabel?.textColor = ColorCompat.label
                    }
                } else {
                    cell.groupNameLabel?.text = chosenName ?? pg.displayName
                    cell.groupNameLabel?.textColor = ColorCompat.label
                }
                cell.makeAccessible(Accessibility.GroupingScreen.changeName)
                cell.accessoryType = .disclosureIndicator
                return cell
            } else if indexPath.section == 1 {
                let cell = tableView.dequeueReusableCell(withIdentifier: "PlayerCheckBoxTableViewCell", for: indexPath) as! PlayerCheckBoxTableViewCell
                let playerChoice = playerChoices[indexPath.row]
                cell.speakerLabel?.text = playerChoice.name
                cell.makeAccessible(playerChoice.name, doNotLocalizeForHuman: true)
                cell.checkbox?.isHighlighted = false

                if playersToAddToPlayerGroup.contains(playerChoice) {
                    cell.checkbox?.isHighlighted = true
                }
                if (!pg.isConvenienceGroup &&
                    pg.members.contains(where: { $0.playerID == playerChoice.id  })) &&
                    !playersToRemoveFromPlayerGroup.contains(playerChoice) {
                    cell.checkbox?.isHighlighted = true
                }
                cell.playingIndicatorImageView?.isHidden = true

                return cell
            } else {
                var cell = tableView.dequeueReusableCell(withIdentifier: "DeviceDetailTableViewCell", for: indexPath) as? DeviceDetailTableViewCell
                if cell == nil {
                    cell = DeviceDetailTableViewCell()
                }
                cell?.titleLabel?.text = NSLocalizedString("DELETE THIS GROUP ", comment: "Delete Action")
                cell?.titleLabel?.textColor = UIColor.red
                cell?.makeAccessible(Accessibility.GroupingScreen.deleteGroup)
                cell?.accessoryType = .none
                return cell!
            }
        } else {
            return UITableViewCell()
        }
    }

    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        if section == 0 {
            return NSLocalizedString("GROUP NAME", comment: "GROUP NAME")
        } else if section == 1 {
			return NSLocalizedString("SPEAKERS IN GROUP", comment: "SPEAKERS IN GROUP")
        } else {
			return ""
        }
    }
    
    func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
        return CGFloat.leastNormalMagnitude
    }
    
    func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
        // Return false if you do not want the specified item to be editable.
        return false
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: false)
        if let pg = playerGroup {
            if indexPath.section == 0 {
                performSegue(withIdentifier: Segues.showChooseName, sender: self)
            } else if indexPath.section == 1 {
                let player = playerChoices[indexPath.row]
                let cell = tableView.cellForRow(at: indexPath) as! PlayerCheckBoxTableViewCell
                if (!pg.isConvenienceGroup &&
                    pg.members.contains(where: { $0.playerID == player.id })) {
                    // It's currently in the playerGroup
                    if playersToRemoveFromPlayerGroup.contains(player) {
                        // We previously asked to remove the player from the playerGroup
                        playersToRemoveFromPlayerGroup.remove(player)
                        cell.checkbox?.isHighlighted = true
                    } else {
                        // We want to remove it
                        playersToRemoveFromPlayerGroup.insert(player)
                        cell.checkbox?.isHighlighted = false
                    }
                } else {
                    // It's not in the playerGroup.
                    if playersToAddToPlayerGroup.contains(player) {
                        // we previously asked for it to be added
                        playersToAddToPlayerGroup.remove(player)
                        cell.checkbox?.isHighlighted = false
                    } else {
                        // It's not in the current playerGroup and we are asking to adding
                        playersToAddToPlayerGroup.insert(player)
                        cell.checkbox?.isHighlighted = true
                    }
                }
                updateSaveButton()
            } else {
                let alert = UIAlertController(title: NSLocalizedString("Delete Group", comment: "Delete Group"),
                                              message: NSLocalizedString("Are you sure you want to delete the group?\n\nThis action cannot be undone.", comment: "Delete Group Message"),
                                              preferredStyle: .alert)
                let cancelAction = UIAlertAction(title: NSLocalizedString("Cancel", comment: "Cancel"),
                                                 style: .cancel) { _ in
                }
                let deleteAction = UIAlertAction(title: NSLocalizedString("Delete Group", comment: "Delete Group"),
                                                 style: .destructive) { [weak self] _ in
                                                    guard
                                                        let self = self,
                                                        let groupID = self.playerGroup?.groupID else {
                                                        	return
                                                    }
                                                    self.playerController.delete(playerGroupID: groupID,
                                                                                       completion: { [weak self] (success, error) in
                                                                                        guard let self = self else {
                                                                                            return
                                                                                        }
                                                                                        if success {
                                                                                            self.navigationController?.popToRootViewController(animated: true)
                                                                                        }
                                                        }, queue: DispatchQueue.main)
                }
                alert.addAction(cancelAction)
                alert.addAction(deleteAction)
                present(alert, animated: true, completion: nil)
            }
        }
    }
}
