//
//  MPMediaCenterManager.swift
//  MusicPlayer
//
//  Created by Mr.Zhou on 2024/4/11.
//

import Foundation
import AVFoundation
import MediaPlayer
///播放器播放事件类型
enum MPPlayerPlayActionType:Int {
    ///正常播放
    case Normal = 0
    ///倒计时播放(用户添加倒计时)
    case CountTime = 1
}
///播放器播放状态
enum MPPlayerStateType:Int {
    ///未播放
    case Null = 0
    ///播放中
    case Playing = 1
    ///暂停
    case Pause = 2
}
///倒计时状态
enum MPTimerType:Int {
    ///未启动
    case UnActivity = 0
    ///运行中
    case Playing = 1
    ///暂停中
    case Suspend = 2
}
///倒计时规格等级
enum MPCountTimerLevel:Int, CaseIterable {
    case OFF = 0
    case _10 = 1
    case _20 = 2
    case _30 = 3
    case _60 = 4
    case _90 = 5
    ///标题
    var title:String{
        switch self {
        case .OFF:
            return "OFF"
        case ._10:
            return "10"
        case ._20:
            return "20"
        case ._30:
            return "30"
        case ._60:
            return "60"
        case ._90:
            return "90"
        }
    }
    ///实际数值（分）
    var mins:Int{
        switch self {
        case .OFF:
            return 0
        case ._10:
            return 10
        case ._20:
            return 20
        case ._30:
            return 30
        case ._60:
            return 60
        case ._90:
            return 90
        }
    }
}


///多媒体控制器（播放控制器，倒计时控制器，麦克风管理器）
class MPMediaCenterManager {
    ///控制器单例
    static let shared = MPMediaCenterManager()
    //MARK: - 各项工具
    //音乐播放器
    private var player:AVPlayer?
    //远程控制中心
    private var center:MPRemoteCommandCenter?
    //GCD倒计时器
    private var countTimer: DispatchSourceTimer?
    //监听器(需要获取麦克风权限)，通过监听器实时获取周遭分贝
    private var monitor:AVAudioRecorder?
    //监听器设置参数字典
    private var monitorSetingsDic:[String : Any]?
    //GCD监听计时器
    private var monitorTimer: DispatchSourceTimer?
    
    //MARK: - 实体与状态值
    ///音乐实体
    private var music:MusicModel?
    ///获取音乐实体
    func getMusic() -> MusicModel?{
        return music
    }
    ///更改音乐实体
    func setMusic(_ music:MusicModel?) {
        self.music = music
    }
    ///播放器播放方法
    private var playActionType:MPPlayerPlayActionType = .Normal
    ///播放器播放状态（默认未播放）
    private var playerState:MPPlayerStateType = .Null
    ///获取播放器播放状态
    func getPlayerState() -> MPPlayerStateType {
        return playerState
    }
    ///倒计时运行状态（默认未启动）
    private var countTimeType:MPTimerType = .UnActivity
    ///倒计时规格 （默认为OFF）
    private var countTimerLevel:MPCountTimerLevel = .OFF
    ///获取倒计时规格值
    func getCountTimerLevel() -> MPCountTimerLevel {
        return countTimerLevel
    }
    ///监听器计时状态
    private var monitorTimerType:MPTimerType = .UnActivity
    ///监听器是否活跃
    private var isMonitorActivity:Bool = false
    //隐藏管理器初始化方法
    private init() {
        //初始化时获取上次运行app时存入的最后一首音乐（保存的内容为音乐实体的identifier，具备唯一性）
        let lastTitle = UserDefaults.standard.string(forKey: "Last") ?? ""
        let last = MusicModel.fetch(.init(format: "identifier==%@", lastTitle)).first
        //更新音乐实体（可选性）
        music = last
        // 添加观察者，监听播放结束事件
        NotificationCenter.default.addObserver(self, selector: #selector(playerDidFinishPlaying), name: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem)
        
        //初始化监听器字典并添加设置参数
        monitorSetingsDic =
        [
            //音频格式
            AVFormatIDKey:NSNumber(value: kAudioFormatLinearPCM),
            //录音的声道数，立体声为双声道
            AVNumberOfChannelsKey: 2,
            //录音质量
            AVEncoderAudioQualityKey : AVAudioQuality.max.rawValue,
            //采样位数
            AVLinearPCMBitDepthKey:NSNumber(value:16),
            AVEncoderBitRateKey : 320000,
            //录音器每秒采集的录音样本数
            AVSampleRateKey : 44100.0
        ]
    }
    
    //管理器销毁时
    deinit {
        //解除监听
        NotificationCenter.default.removeObserver(self)
        //释放播放器
        player = nil
        //释放倒计时器
        countTimer?.cancel()
        countTimer = nil
        //释放监听器
        monitor = nil
        monitorTimer?.cancel()
        monitorTimer = nil
    }
    //MARK: - 播放器与倒计时器
    /// 启动播放器
    /// - Parameters:
    ///   - music: 音乐实体
    ///   - actionType: 播放方法（正常播放/倒计时播发）
    ///   - countLevel: 如果倒计时播放则填入倒计时规格
    func playerStart(_ music:MusicModel, actionType:MPPlayerPlayActionType, countLevel: MPCountTimerLevel = .OFF) {
        //检索倒计时器状态
        switch countTimeType {
        case .UnActivity://未启动计时器
           break
        case .Playing://计时器运行中
            if countTimer != nil {
                //倒计时器具备实例，处于rsume中，可以直接销毁
                countTimer!.cancel()
                countTimer = nil
            }
        case .Suspend://计时器暂停中，销毁计时器
            if countTimer != nil {
                //倒计时器具备实例，处于suspend中，可以直接销毁
                countTimer!.resume()
                countTimer!.cancel()
                countTimer = nil
            }
        }
        //检索当前音乐播放器状态
        switch playerState {
        case .Null://未播放,跳过
            break
        case .Playing://播放中
            //销毁播放器
            if player != nil {
                player!.pause()
                player = nil
            }
            //发送停止音乐播放通知
            NotificationCenter.notificationKey.post(notificationName: .stop_music)
        case .Pause://暂停中
            //销毁播放器
            if player != nil {
                player = nil
            }
            //发送停止音乐播放通知
            NotificationCenter.notificationKey.post(notificationName: .stop_music)
        }
        //更新播放方法
        playActionType = actionType
        //检索播放方法
        switch playActionType {
        case .Normal://正常播放
            playMusic(music)
        case .CountTime://倒计时播放
            //更新倒计时规格
            countTimerLevel = countLevel
            countTimerStart(Double(countLevel.mins*60), music: music)
        }
    }
    //倒计时播放时触发
    private func countTimerStart(_ totalTimes:TimeInterval, music:MusicModel) {
//        //检索倒计时器状态
//        switch countTimeType {
//        case .UnActivity://未启动计时器
//           break
//        case .Playing://计时器运行中
//            if countTimer != nil {
//                //倒计时器具备实例，处于rsume中，可以直接销毁
//                countTimer!.cancel()
//                countTimer = nil
//            }
//        case .Suspend://计时器暂停中，销毁计时器
//            if countTimer != nil {
//                //倒计时器具备实例，处于suspend中，可以直接销毁
//                countTimer!.resume()
//                countTimer!.cancel()
//                countTimer = nil
//            }
//        }
        //创建倒计时器队列
        let queue = DispatchQueue(label: "com.MPCountTimer.queue")
        //创建倒计时器
        countTimer = DispatchSource.makeTimerSource(queue: queue)
        //设置计时器的起始时间以及触发事件频率为一秒一次
        countTimer!.schedule(deadline: .now(), repeating: .seconds(1))
        //将总时间值赋予times
        var times = totalTimes
        //计时器设置触发事件
        countTimer!.setEventHandler(handler: {
            [weak self] in
            //判断时间值是否归零
            if times > 0 {
                //未归0，倒计时-1
                times -= 1
                //打印倒计时
                print(setTimesToMinSeconds(times))
                //发布时间值变化通知
                NotificationCenter.notificationKey.post(notificationName: .time_times, object: times)
            }else {
                //执行结束事件
                self?.playerStop()
            }
        })
        //启动倒计时器
        countTimeType = .Playing
        countTimer!.resume()
        print("The CountTimer has started.")
        //调用音乐播放器
        playMusic(music)
    }
    
    //启动播放器
    private func playMusic(_ music:MusicModel) {
        //根据音乐实体类型
        self.music = music
        player_play(self.music!.isLocal ? musicLocal():musicDocument())
    }
    //音乐为本地文件
    private func musicLocal() -> URL {
        //本地文件，mp3格式
        let url:URL = .init(fileURLWithPath: Bundle.main.path(forResource: music!.path, ofType: "mp3")!)
        return url
    }
    //音乐为沙盒文件
    private func musicDocument() -> URL {
        var url:URL!
        //用户上传文件，文件路径设置为沙盒
        let directory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
        //获取沙盒中的数据
        let vedioUrl = URL(fileURLWithPath:URL(fileURLWithPath: directory).appendingPathComponent((music!.path)).path)
        //检索路径是否安全授权
        let authozied = vedioUrl.startAccessingSecurityScopedResource()
        if authozied == true {
            //允许安全访问路径，调出文件协调器解码并获取真实文件地址
            let fileCoordinator = NSFileCoordinator()
            fileCoordinator.coordinate(readingItemAt: vedioUrl, options: .withoutChanges, error: nil) { (newUrl) in
                url = newUrl
            }
        }else {
            //未能获取安全授权，强制获取
            url = vedioUrl
        }
        //停止安全访问权限
        vedioUrl.stopAccessingSecurityScopedResource()
        return url
    }
    //播放音乐
    private func player_play(_ url:URL) {
        //检索真实路径对应的文件是否存在
        if FileManager.default.fileExists(atPath: url.path) == true {
            //当前路径的文件正式存在
            //初始化播放器
            let playerItem = AVPlayerItem(url: url)
            //创建AVPlayer
            player = AVPlayer(playerItem: playerItem)
            //正式播放
            player?.play()
            playerState = .Playing
            //启动远程中心并配置相关内容
            setCommandCenter(music!)
            //发送播放器启动通知
            NotificationCenter.notificationKey.post(notificationName: .play_music)
            //更新最后一次播放内容
            music!.lastTime = Date().timeZone()
            MusicModel.save()
            UserDefaults.standard.set(music!.identifier, forKey: "Last")
        }else {
            //文件不存在，通知用户删除该音乐实体
            print("Couldn't find the file.")
            playerState = .Null
            music = nil
            //发送音乐缺失通知
            NotificationCenter.notificationKey.post(notificationName: .null_music)
        }
    }
    ///暂停播放器
    func playerPause() {
        //检索播放状态，是否进行中
        guard playerState == .Playing, let player = player else {
            //未处于播放中
            print("Player is not in playing")
            return
        }
        //处于播放中,检索播放方法
        switch playActionType {
        case .Normal://正常播放
            player_pause(player)
        case .CountTime://倒计时播放
            //检索倒计时器状态，是否进行中
            guard countTimeType == .Playing, let countTimer = countTimer else {
                //倒计时器未运行
                print("CountTimer is not playing")
                return
            }
            //倒计时运行中,暂停倒计时
            countTimer.suspend()
            countTimeType = .Suspend
            //暂停播放
            player_pause(player)
        }
    }
    //暂停播放
    private func player_pause(_ player:AVPlayer) {
        //暂停播放器
        player.pause()
        //切换播放器状态
        playerState = .Pause
        //发布音乐暂停公告
        NotificationCenter.notificationKey.post(notificationName: .pause_music)
    }
    ///继续播放器
    func playerResume() {
        //检索播放状态，是否暂停中
        guard playerState == .Pause, let player = player else {
            //未处于暂停中
            print("Player is not paused")
            return
        }
        //处于暂停中,检索播放方法
        switch playActionType {
        case .Normal://正常播放
            player_resume(player)
        case .CountTime://倒计时播放
            //检索倒计时器状态，是否暂停中
            guard countTimeType == .Suspend, let countTimer = countTimer else {
                //倒计时器未暂停
                print("CountTimer is not paused")
                return
            }
            //倒计时暂停中，运行倒计时
            countTimer.resume()
            countTimeType = .Playing
            //继续播放
            player_resume(player)
        }
    }
    //继续播放
    private func player_resume(_ player:AVPlayer) {
        //继续播放器
        player.play()
        //切换播放器状态
        playerState = .Playing
        //发布音乐继续公告
        NotificationCenter.notificationKey.post(notificationName: .resume_music)
    }
    ///停止播放器
    func playerStop() {
        //检索播放状态，是否已启动
        guard playerState != .Null, let player = player else {
            //未启动
            print("Player is not started")
            return
        }
        //处于启动中,检索播放方法
        switch playActionType {
        case .Normal://正常播放
            player_stop(player)
        case .CountTime://倒计时播放
            //检索倒计时器状态，是否已启动
            guard countTimeType != .UnActivity, let countTimer = countTimer else {
                //倒计时器未启动
                print("CountTimer is not started")
                return
            }
            //倒计时器已启动，终止倒计时
            if countTimeType == .Suspend {
                countTimer.resume()
            }
            countTimer.cancel()
            self.countTimer = nil
            //当计时器终止后，将计时等级回正为off
            countTimerLevel = .OFF
            //停止音乐播放
            player_stop(player)
        }
    }
    //停止播放
    private func player_stop(_ player:AVPlayer) {
        player.pause()
        self.player = nil
        playerState = .Null
        //回正播放类型
        playActionType = .Normal
        //发送停止音乐播放通知
        NotificationCenter.notificationKey.post(notificationName: .stop_music)
    }
    //播放器停止
    @objc private func playerDidFinishPlaying() {
        guard playerState == .Playing, let player = player else {
            return
        }
        // 重置播放时间到开始
        player.seek(to: CMTime.zero)
        // 继续播放
        player.play()
    }
    //MARK: - 远程中心
    private func setCommandCenter(_ music:MusicModel) {
        // 实例化远程控制中心
        center = MPRemoteCommandCenter.shared()
        
        //设置控制中心各项操作
        //播放
        center!.playCommand.addTarget(handler: { [weak self] (event) in
            guard let self = self else { return .noActionableNowPlayingItem}
            if self.music != nil {
                
                return .success
            }else {
                return .noActionableNowPlayingItem
            }
        })
        //暂停
        center!.pauseCommand.addTarget(handler: { [weak self] (event) in
            guard let self = self else { return .noActionableNowPlayingItem}
            if self.music != nil {
                
                return .success
            }else {
                return .noActionableNowPlayingItem
            }
        })
        //设置info字典信息
        var info = [String:Any]()
        //展示标题
        info[MPMediaItemPropertyTitle] = music.title ?? ""
        //设置艺术家
//        info[MPMediaItemPropertyArtist] = ""
        //设置专辑
//        info[MPMediaItemPropertyAlbumTitle] = ""
        //设置歌曲时长
//        info[MPMediaItemPropertyPlaybackDuration] = 0
        //设置歌曲封面
        if let image = UIImage(data: music.cover) {
            info[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: image.size, requestHandler: { size in
                return image
            })
        }
        //更新远程中心
        MPNowPlayingInfoCenter.default().nowPlayingInfo = info
    }
    //MARK: - 麦克风监听器
    ///开启监听器
    func openMonitor(_ decibels:Double) {
        //优先检索倒计时器和播放器状态
        if countTimeType != .UnActivity || playerState != .Null {
            //有一项处于启动中，终止该事件
            playerStop()
        }
        //检索监听器计时状态
        switch monitorTimerType {
        case .UnActivity://未启动监听器计时
            break
        case .Playing://监听器计时运行中
            if monitorTimer != nil {
                //倒计时器具备实例，处于rsume中，可以直接销毁
                monitorTimer!.cancel()
                monitorTimer = nil
            }
        case .Suspend://监听器计时暂停中
            if monitorTimer != nil {
                //倒计时器具备实例，处于suspend中，可以直接销毁
                monitorTimer!.resume()
                monitorTimer!.cancel()
                monitorTimer = nil
            }
        }
        //创建计时器队列
        let queue = DispatchQueue(label: "com.MPMonitorTimer.queue")
        //实例化监听器计时
        monitorTimer = DispatchSource.makeTimerSource(queue: queue)
        //设置计时器的起始时间以及触发事件频率为一秒一次
        monitorTimer!.schedule(deadline: .now(), repeating: .seconds(1))
        //计时器设置触发事件
        monitorTimer!.setEventHandler(handler: {
            [weak self] in
            guard let self = self else { return }
            //每秒触发一次监听器，获取当前麦克风分贝值
            let currentDecibels = checkDecibels()
            //根据分贝值大小，判断是否启动音乐播放器
            if currentDecibels > decibels {
                guard let music = self.music else {
                    return
                }
                //启动十分钟计时器
                playerStart(music, actionType: .CountTime, countLevel: ._10)
                //停止监听器
                stopMonitor()
            }
        })
        //先删除监听器缓存内容，并释放监听器
        monitor?.deleteRecording()
        monitor = nil
        var url:URL?
        let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] + "/play"
        //生成空路径
        if #available(iOS 16.0, *) {
            url = URL(filePath: path)
        } else {
            url = URL(fileURLWithPath: path)
        }
        //重新实例化监听器
        do {
            //初始化监听器
            monitor = try AVAudioRecorder(url: url!, settings: monitorSetingsDic!)
            //开启仪表计数功能
            monitor!.isMeteringEnabled = true
            //准备监听
            monitor!.prepareToRecord()
            //开始监听
            monitor!.record()
            //启动监听器计时
            monitorTimerType = .Playing
            isMonitorActivity = true
            monitorTimer!.resume()
            //发送开启监听通知
            NotificationCenter.notificationKey.post(notificationName: .open_monitor)
            print("The monitor has open.")
        } catch let error {
            print("Monitor initialization failure:\(error.localizedDescription)")
        }
    }
    ///终止监听器运行
    func stopMonitor() {
        guard let timer = monitorTimer, let monitor = monitor else { return }
        //停止并销毁计时器和监听器
        if monitorTimerType == .Suspend {
            timer.resume()
        }
        timer.cancel()
        self.monitorTimer = nil
        monitorTimerType = .UnActivity
        isMonitorActivity = false
        monitor.stop()
        self.monitor = nil
        //发送关闭监听通知
        NotificationCenter.notificationKey.post(notificationName: .stop_monitor)
        print("The monitor has stoped")
        
    }
    //检测麦克风音量
    private func checkDecibels() -> Double{
        // 刷新音量数据
        monitor!.updateMeters()
        let power = monitor!.peakPower(forChannel: 0)
        //获取分贝  基本在0-1之间 可能超过1
        let decibels:Double = pow(Double(10), Double(0.05*power))
        print("Current decibels: \(decibels)")
        return decibels
    }
}

