//
//  AudioTool.swift
//  ContactPoster
//
//  Created by TSYH on 2024/1/24.
//

import AVFoundation

typealias AudioCompletionHandler = (URL?, String?) -> Void

class AudioTool {
    lazy var cancelTask = false
    lazy var queue = DispatchQueue(label: "queue_audio_convert")
    
    /*
     剪切开始工作
     大概流程
    1、获得视频总时长，处理时间，数组格式返回音频数据
    2、创建导出会话
    3、设计导出时间范围，淡出时间范围
    4、设计新音频配置数据，文件路径，类型等
    5、开始剪切
     */
    
    /// 裁剪音频
    /// - Parameters:
    ///   - asset: 音频资源
    ///   - startDuration: 开始时间
    ///   - endDuration: 结束时间
    ///   - fadeIn: 淡入时长
    ///   - fadeOut: 淡出时长
    ///   - filePath: 导出保存路径
    ///   - completionHandler: String: 导出文件存储路径，String: 报错信息
    func cutAudio(asset: AVAsset,
                  from startDuration: Double, to endDuration: Double,
                  fadeIn: Double, fadeOut: Double,
                  completionHandler: ((String?, String?) -> Void)?) {
        
        let tracks = asset.tracks(withMediaType: .audio)
        guard let track = tracks.first else {
            // 如果没有数据，跳出
            completionHandler?(nil, "Sorry, Unknow Error, Please try another")
            return
        }
        // 2. 创建导出会话
        guard let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetAppleM4A) else {
            // 创建失败，则跳出
            completionHandler?(nil, "Sorry, Unknow Error, Please try another")
            return
        }
        
        // 导出时间范围
        // create trim time range // CMTimeMake(第几帧， 帧率)
        let startTime = CMTime(value: CMTimeValue(startDuration), timescale: 1)
        let endTime = CMTime(value: CMTimeValue(endDuration), timescale: 1)
        let exportTimeRange = CMTimeRange(start: startTime, end: endTime)
        
        // 创建混音
        let exportMix = AVMutableAudioMix()
        let inputParameters = AVMutableAudioMixInputParameters(track: track)
        
        // 淡入时间
        if fadeIn > 0, fadeIn < (endDuration - startDuration) {
            let startFadeInTime = startTime
            let endFadeInTime = CMTime(value: CMTimeValue(startDuration+fadeIn), timescale: 1)
            let fadeInTimeRange = CMTimeRange(start: startFadeInTime, end: endFadeInTime)
            inputParameters.setVolumeRamp(fromStartVolume: 0.0, toEndVolume: 1.0, timeRange: fadeInTimeRange)
        }
        // 淡出时间
        if fadeOut > 0, fadeOut < (endDuration - startDuration) {
            let startFadeOutTime = CMTime(value: CMTimeValue(endDuration-fadeOut), timescale: 1)
            let endFadeOutTime = endTime
            let fadeOutTimeRange = CMTimeRange(start: startFadeOutTime, end: endFadeOutTime)
            inputParameters.setVolumeRamp(fromStartVolume: 1.0, toEndVolume: 0.0, timeRange: fadeOutTimeRange)
        }
        
        exportMix.inputParameters = [inputParameters]
        
        guard let documentURL = TSBandRingTool.ringDirectory else {
            return
        }
        let outputFilePath = NSString(string: documentURL).appendingPathComponent("ringExportM4a.m4a")
        
        if FileManager.default.fileExists(atPath: outputFilePath) {
            try? FileManager.default.removeItem(atPath: outputFilePath)
        }
        
        // output path 新文件路径
        exportSession.outputURL = URL(fileURLWithPath: outputFilePath)
        exportSession.outputFileType = .m4a
        exportSession.timeRange = exportTimeRange
        // 新的混音音频
        exportSession.audioMix = exportMix
        
        // 开始导出
        exportSession.exportAsynchronously {
            if let error = exportSession.error {
                debugPrint(error)
            }
            switch exportSession.status {
            case .completed:
                completionHandler?(outputFilePath, nil)
            case .failed:
                completionHandler?(nil, "Sorry, Export Session Failure")
            default:
                completionHandler?(nil, "Sorry, Export Session Failure")
            }
        }
    }
    
    /*
     音频格式转换
     1. 裁剪后获得m4a
     1. m4a 转 wav
     2. wav 转 mp3
     */
    func startTansformAudio(asset: AVAsset,
                  from startDuration: Double, to endDuration: Double,
                  fadeIn: Double, fadeOut: Double,
                  savePath filePath: String,
                  completionHandler: ((String?, String?) -> Void)?) {
        
        cutAudio(asset: asset, from: startDuration, to: endDuration, fadeIn: fadeIn, fadeOut: fadeOut) { outputFilePath, errMsg in
            
            guard let outputFilePath = outputFilePath,
                  let documentURL = TSBandRingTool.ringDirectory else {
                completionHandler?(nil, errMsg)
                return
            }
            let wavFilePath = NSString(string: documentURL).appendingPathComponent("ringExportWav.wav")
                
            AudioConverter.convertM4a(toWav: outputFilePath, outPath: wavFilePath) { path, errMsg in
                
                guard let path = path else {
                    completionHandler?(nil, errMsg)
                    return
                }
                AudioConverter.convenrtToMp3(withResult: path, outPath: filePath) { mp3Path, errMsg in
                    completionHandler?(mp3Path, errMsg)
                }
            }
        }
    }
    
    /// 创建用于分享库乐队的band文件
    func convertToBand(from fileURL: URL, to filePath: String,
                       bandFileURL: URL,
                       completion: ((URL?) -> Void)?) {
        
        let copyBandURL = URL(fileURLWithPath: filePath).deletingPathExtension().appendingPathExtension("band")
        // 步骤3：复制band文件
        try? FileManager.default.copyItem(atPath: bandFileURL.path, toPath: copyBandURL.path)
        
        guard FileManager.default.fileExists(atPath: copyBandURL.path) else {
            // 复制失败
            completion?(nil)
            return
        }
        
        // 步骤4：格式转换为aiff
        let converter = ExtAudioConverter()
        converter.inputFile = fileURL.path
        converter.outputFile = copyBandURL.path.appending("/Media/ringtone.aiff")
        converter.outputFileType = kAudioFileAIFFType
        let success = converter.convert()
        
        guard success,
              FileManager.default.fileExists(atPath: copyBandURL.path) else {
            // 转换失败
            completion?(nil)
            return
        }
        let url = URL(fileURLWithPath: copyBandURL.path)
        debugPrint("===bandPath: \(url.absoluteString)")
        completion?(url)
    }
    
    /*
     视频转mp3音频
     1. 提取视频音频获取m4a文件
     2. m4a 转 wav
     3. wav 转 mp3
     */
    func convertToMp3FromVideo(fileURL: URL, outputURL: URL,
                               completion: ((URL?, String?) -> Void)?) {
        
        queue.async {
            let m4aURL = outputURL.deletingPathExtension().appendingPathExtension("m4a")
            
            self.extractAudioFromVideo(fileURL: fileURL, outputURL: m4aURL) { m4aURL, errMsg in
                
                try? FileManager.default.removeItem(atPath: fileURL.path)
                
                guard let m4aURL = m4aURL else {
                    completion?(nil, errMsg)
                    return
                }
                let wavURL = m4aURL.deletingPathExtension().appendingPathExtension("wav")
                    
                AudioConverter.convertM4a(toWav: m4aURL.path, outPath: wavURL.path) { wavPath, errMsg in
                    
                    try? FileManager.default.removeItem(atPath: m4aURL.path)
                    
                    guard let wavPath = wavPath else {
                        completion?(nil, errMsg)
                        return
                    }
                    AudioConverter.convenrtToMp3(withResult: wavPath, outPath: outputURL.path) { [weak self] mp3Path, errMsg in
                        
                        try? FileManager.default.removeItem(atPath: wavPath)
                        
                        // 中途取消，删除文件
                        if let self = self, self.cancelTask,
                            let mp3Path = mp3Path {
                            let url = URL(fileURLWithPath: mp3Path)
                            if FileManager.default.fileExists(atPath: url.path) {
                                try? FileManager.default.removeItem(atPath: url.path)
                            }
                            return
                        }
                        
                        if let mp3Path = mp3Path {
                            completion?(URL(fileURLWithPath: mp3Path), nil)
                        } else {
                            completion?(nil, errMsg)
                        }
                    }
                }
            }
        }
    }
    
    // 从视频中提取 m4a
    func extractAudioFromVideo(fileURL: URL, outputURL: URL,
                               completion: ((URL?, String?) -> Void)?) {
        
        if FileManager.default.fileExists(atPath: outputURL.path) {
            try? FileManager.default.removeItem(atPath: outputURL.path)
        }
        
        let asset = AVAsset(url: fileURL)
        let tracks = asset.tracks(withMediaType: .audio)
        guard let track = tracks.first else {
            // 如果没有数据，跳出
            completion?(nil, "Sorry, Unknow Error, Please try another")
            return
        }
        // 2. 创建导出会话
        guard let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetAppleM4A) else {
            // 创建失败，则跳出
            completion?(nil, "Sorry, Unknow Error, Please try another")
            return
        }
        
        // 创建混音
        let exportMix = AVMutableAudioMix()
        let inputParameters = AVMutableAudioMixInputParameters(track: track)
        exportMix.inputParameters = [inputParameters]
        
        // output path 新文件路径
        exportSession.outputURL = URL(fileURLWithPath: outputURL.path)
        exportSession.outputFileType = .m4a
        // 新的混音音频
        exportSession.audioMix = exportMix
        
        // 开始导出
        exportSession.exportAsynchronously {
            if let error = exportSession.error {
                debugPrint(error)
            }
            switch exportSession.status {
            case .completed:
                completion?(outputURL, nil)
            case .failed:
                completion?(nil, "Sorry, Extract Audio Failure")
            default:
                completion?(nil, "Sorry, Extract Audio Failure")
            }
        }
    }
}
