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

import UIKit

enum AudioSettingRowSourceResult {
    case none
    case bool(value: Bool)
    case string(value: String)
    case double(value: Double)
    case checked(value: Bool)
}

protocol AudioSettingRowSource {
    func identifier() -> String
    func title() -> String
    func accessibilityTitle(player: IoTPlayer) -> String
    func viewModel(player: IoTPlayer)->AudioSettingsViewModel?
    func available(player: IoTPlayer) -> Bool
    func value(player: IoTPlayer) -> AudioSettingRowSourceResult
}

class AudioSettingsBaseViewModel: AudioSettingsViewModel {
    var title: String { return "" }
    weak var viewController: AudioSettingsViewController?
    var preferences: Preferences?
    var playerController: PlayerControllerProtocol?
    var playerID: String?
    // MARK: Private Properties
    private weak var timeoutTimer: Timer?
    private let timeoutDuration = 10.0
    private var observers = [ObserverToken] ()

    func toggleChanged(_ row: AudioSettingsRow, value: Bool) {}
    func sliderMoved(_ row: AudioSettingsRow, value: Double) {}
    func optionRowTapped(_ row: AudioSettingsRow) {}
    func submenuTapped(_ row: AudioSettingsRow) {}
    func checkableOptionChecked(_ row: AudioSettingsRow) {}

    func rows() -> [AudioSettingsRow] {
        // Ideally here we would call through to the generic method but we don't know our enum type yet.
        return []
    }

    init() {
        setUpObservers()
    }

    deinit {
        removeObservers()
    }
}

extension AudioSettingsBaseViewModel {
    func reloadState() {
        viewController?.reloadAll()
    }

    func cancelTimeoutTimer() {
        if let t = timeoutTimer {
            t.invalidate()
            timeoutTimer = nil
        }
    }

    func startTimeoutTimer() {
        cancelTimeoutTimer()
        let t = Timer(timeInterval: timeoutDuration,
                      target: self,
                      selector: #selector(timeoutTimerFired(_:)),
                      userInfo: nil,
                      repeats: false)
        RunLoop.current.add(t, forMode: .common)
        timeoutTimer = t
    }

    @objc func timeoutTimerFired(_: Any?) {
        timeoutTimer = nil
        reloadState()
    }

    func rows<T>(rowEnum: T.Type)->[AudioSettingsRow] where T: CaseIterable, T: AudioSettingRowSource {
        let allCases = rowEnum.allCases
        if
            let playerID = playerID,
            let player = playerController?.player(id: playerID) {
            let rows = allCases.compactMap { (rowType) -> AudioSettingsRow? in
                if rowType.available(player: player) {
                    let value = rowType.value(player: player)
                    switch value {
                    case .bool(let value):
                        return AudioSettingsRow(identifier: rowType.identifier(),
                                                type: AudioSettingsRowType.toggle(value: value),
                                                title: rowType.title(),
                                                accessibilityTitle: rowType.accessibilityTitle(player: player))
                    case .string(let value):
                        return AudioSettingsRow(identifier: rowType.identifier(),
                                                type: AudioSettingsRowType.option(value: value),
                                                title: rowType.title(),
                                                accessibilityTitle: rowType.accessibilityTitle(player: player))
                    case .double(let value):
                        return AudioSettingsRow(identifier: rowType.identifier(),
                                                type: AudioSettingsRowType.slider(value: value),
                                                title: rowType.title(),
                                                accessibilityTitle: rowType.accessibilityTitle(player: player))
                    case .checked(let value):
                        return AudioSettingsRow(identifier: rowType.identifier(),
                                                type: AudioSettingsRowType.checkableOption(checked: value),
                                                title: rowType.title(),
                                                accessibilityTitle: rowType.accessibilityTitle(player: player))
                    case .none:
                        // Either submenu or text - usually handled in subclasses
                        return nil
                    }
                } else {
                    return nil
                }
            }
            return rows
        } else {
            return []
        }
    }

    func submenuTapped<T>(_ row: AudioSettingsRow, rowEnum: T.Type) where T: CaseIterable, T: AudioSettingRowSource {
        optionRowTapped(row, rowEnum: rowEnum)
    }

    func optionRowTapped<T>(_ row: AudioSettingsRow, rowEnum: T.Type) where T: CaseIterable, T: AudioSettingRowSource {
        guard
            let playerID = playerID,
            let player = playerController?.player(id: playerID) else {
                return
        }

        let viewModels = rowEnum.allCases.compactMap { (rowType) -> AudioSettingsViewModel? in
            if rowType.identifier() == row.identifier {
                return rowType.viewModel(player: player)
            } else {
                return nil
            }
        }

        if let viewModel = viewModels.first {
            presentViewController(viewModel: viewModel)
        }
    }

    private func setUpObservers() {
        let pgHandler: (PlayerGroupNotification) -> () = { [weak self] notification in
            guard let self = self else { return }

			guard let _ = self.playerController?.player(id: self.playerID ?? "")  else {
				self.reloadState()
                return
            }
        }

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

        let playerHandler: (PlayerNotification) -> () = { [weak self] notification in
            guard let self = self else { return }

            if let _ = self.playerController?.player(id: self.playerID ?? "") {
                if notification.reason == .audioConfigurationChanged {
                    if notification.player.playerID == self.playerID {
                        self.cancelTimeoutTimer()
                        self.reloadState()
                    }
                }
            } else {
                // NO PLAYER!!!
				self.reloadState()
            }
        }

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

        observers = [pgObserver, playerObserver]
    }

    private func removeObservers () {
        observers.forEach {
            NotificationCenter.default.removeObserver($0)
        }
        observers.removeAll()
    }
}
