//
//  PQCompositionExporter.swift
//  GPUImage_iOS
//
//  Created by ak on 2020/8/25.
//  Copyright © 2020 Sunset Lake Software LLC. All rights reserved.
//  功能： 导出视频

import AVFoundation
import Foundation
import ObjectMapper
import BFCommonKit

public class PQCompositionExporter {
    public var progressClosure: ((_ currTime: Float, _ duration: Float, _ progress: Float) -> Void)?

    public var completion: ((_ url: URL?) -> Void)?

    public let asset: AVAsset
    public let videoComposition: AVVideoComposition?
    public let audioMix: AVAudioMix?
    public let filters: [ImageProcessingOperation]?
    public var mStickers:[PQEditVisionTrackMaterialsModel]?
    public let animationTool: AVVideoCompositionCoreAnimationTool?
    public let exportURL: URL

    public var input: PQMovieInput?
    public var output: MovieOutput?

    public let tmpExportURL: URL?
    
    public var usedGroupCreateFilter:Bool = false
    
    //最后一次显示的 sticker
    public var lastshowSticker:PQEditVisionTrackMaterialsModel?
    
    //是否显示高斯
    public var showGaussianBlur:Bool = false
    
    // 缓存创建filter 防止 seek 100ms 慢
    @Atomic var cacheFilters: Array<PQBaseFilter> = Array()
    // 缓存个数
    var cacheFiltersMaxCount: Int = 8
  
    /// Use serial queue to ensure that the picture is smooth
    var createFiltersQueue: DispatchQueue!
    
    //导出时是否添加水印
    var isAddWatermark:Bool = false
    
    //是否合成片尾模式
    var isEndMovie:Bool = false

    public init(asset: AVAsset, videoComposition: AVVideoComposition? = nil, audioMix: AVAudioMix? = nil, filters: [ImageProcessingOperation]? = nil,stickers:[PQEditVisionTrackMaterialsModel]? = nil, animationTool: AVVideoCompositionCoreAnimationTool? = nil, exportURL: URL) {
        self.asset = asset
        self.videoComposition = videoComposition
        self.audioMix = audioMix
        self.filters = filters
        self.mStickers = stickers
        self.animationTool = animationTool
        self.exportURL = exportURL
        if self.animationTool != nil {
            tmpExportURL = URL(fileURLWithPath: String.qe.tmpPath() + String.qe.timestamp() + "_tmp.mp4")
        } else {
            tmpExportURL = nil
        }

    }

    public func prepare(videoSize: CGSize,videoAverageBitRate:Int = 0) -> Bool {
        guard !FileManager.default.fileExists(atPath: exportURL.absoluteString) else {
            return false
        }
        
        
        if #available(iOS 10.0, *) {
            createFiltersQueue = DispatchQueue(label: "PQ.moveFiler.seeking111", qos: .default, attributes: .initiallyInactive, autoreleaseFrequency: .never, target: nil)
        } else {
            createFiltersQueue = DispatchQueue(label: "PQ.moveFiler.seeking111", qos: .userInteractive, attributes: [], autoreleaseFrequency: .inherit, target: nil)
        }
        if #available(iOS 10.0, *) {
            createFiltersQueue.activate()
        }
        
        //        guard let videoTrack = asset.tracks(withMediaType:AVMediaType.video).first else { return false }
        let audioTrack = asset.tracks(withMediaType: AVMediaType.audio).first

        //
        //        Float64 seconds = 3;
        //        int32_t preferredTimeScale = 600;  //处理视频内容事常见的时间刻度为600，这是大部分视频帧率24fps、25fps、30fps的公倍数。音频数据常见的时间刻度就是采样率，譬如44 100(44.1kHZ)或48 000(48kHZ)。
        //        CMTime inTime = CMTimeMakeWithSeconds(seconds, preferredTimeScale);
        //        CMTimeShow(inTime);
        //
        // file:///private/var/mobile/Containers/Data/Application/9FDA94D5-D6E5-4DD7-A3C3-3D1D62318C33/tmp/1600948914.410534.m4a👈
        let audioDecodingSettings = [
            AVFormatIDKey: kAudioFormatLinearPCM,
            AVNumberOfChannelsKey: 1,
//               AVEncoderBitRateKey: 16000
        ]
        var acl = AudioChannelLayout()
        memset(&acl, 0, MemoryLayout<AudioChannelLayout>.size)
        acl.mChannelLayoutTag = kAudioChannelLayoutTag_Mono
        let audioEncodingSettings: [String: Any] = [
            AVFormatIDKey: kAudioFormatMPEG4AAC,
            AVNumberOfChannelsKey: 1,
            AVSampleRateKey: AVAudioSession.sharedInstance().sampleRate,
            AVChannelLayoutKey: NSData(bytes: &acl, length: MemoryLayout<AudioChannelLayout>.size),
            AVEncoderBitRateKey: 32000,
        ]

        do {
            if audioMix != nil {
                try input = PQMovieInput(asset: asset, videoComposition: nil, audioMix: audioMix, playAtActualSpeed: true, loop: false, audioSettings: audioDecodingSettings)
            } else {
                try input = PQMovieInput(asset: asset, videoComposition: nil, audioMix: nil, playAtActualSpeed: false, loop: false, audioSettings: audioDecodingSettings)
            }

            input?.mShowVidoSize = videoSize

        } catch {
            print("Couldn't process movie with error: \(error)")
        }
        BFLog(message: "export mShowVidoSize is \(String(describing: input?.mShowVidoSize))")
    
        let videoEncodingSettings: [String: Any] = [
            AVVideoCompressionPropertiesKey: [
                AVVideoExpectedSourceFrameRateKey: 30,
                AVVideoAverageBitRateKey: videoAverageBitRate == 0 ? Int(input?.mShowVidoSize.width ?? 0) * Int(input?.mShowVidoSize.height ?? 0) * 2 : videoAverageBitRate,
                AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel,
                AVVideoH264EntropyModeKey: AVVideoH264EntropyModeCABAC,
            ],
            //                AVVideoAllowFrameReorderingKey: videoTrack.requiresFrameReordering],
            AVVideoCodecKey: AVVideoCodecH264,
        ] // deprecated: 11.0, renamed: "AVVideoCodecType.h264" 改名

        do {
            try output = MovieOutput(URL: tmpExportURL ?? exportURL, size: Size(width: Float(input?.mShowVidoSize.width ?? 1080), height: Float(input?.mShowVidoSize.height ?? 1080)), fileType: .mp4, liveVideo: false, videoSettings: videoEncodingSettings, videoNaturalTimeScale: 30, audioSettings: audioEncodingSettings, audioSourceFormatHint: nil)
        } catch {
            return false
        }

        if audioTrack != nil {
            input?.audioEncodingTarget = output
        }
        input?.synchronizedMovieOutput = output
        
        if(mStickers == nil){
            var currentTarget: ImageSource = input!

            if filters?.count ?? 0 > 0 {
                filters!.forEach {
                    currentTarget.addTarget($0, atTargetIndex: 0)
                    currentTarget = $0
                }
            }

            currentTarget.addTarget(output!, atTargetIndex: 0)

        }else{

//            configCache(beginTime: mStickers?.first?.timelineIn ?? 0)
        
        }
 
        return true
    }
    
 
   public  func  findShowStikcer(currTime:Float64)  {
         
        if(mStickers?.count ?? 0 == 0){
            BFLog(message: "mStickers data is error")
            return
        }
        var currentSticker:PQEditVisionTrackMaterialsModel?
        for sticker in mStickers! {
            BFLog(message: "sticker in \(sticker.timelineIn) out \(sticker.timelineOut)  currTime is \(currTime)")
            if(sticker.timelineIn <= currTime && sticker.timelineOut >= currTime){
                currentSticker = sticker
                break
            }
        }
        //创建不同的filter
        if(currentSticker == nil){
            BFLog(message: "sticker data is error")
            return
        }
        if(input != nil && lastshowSticker != currentSticker){
            var showFitler:PQBaseFilter?
            if currentSticker!.type == StickerType.VIDEO.rawValue {
                showFitler = PQMovieFilter(movieSticker: currentSticker!)
     

            } else if currentSticker!.type == StickerType.IMAGE.rawValue {
                showFitler = PQImageFilter(sticker: currentSticker!)
                
            }
            input!.removeAllTargets()
            let currentTarget: ImageSource = input!
            
            //XXXXX TODO 这里不是最好的方案，应该使用group filter 目前能确保mStickers每一位： 0是背景视频，1，用户头像，2，用户名
            if isEndMovie {
                //头像
                let avatarFilter = PQImageFilter(sticker: mStickers![1], isExport: true)
                //用户名
                let userNameFitler = PQTextFilter(sticker: mStickers![2])
                currentTarget.addTarget(showFitler!, atTargetIndex: 0)
                showFitler?.addTarget(avatarFilter,atTargetIndex: 0)
                avatarFilter.addTarget(userNameFitler,atTargetIndex: 0)
                userNameFitler.addTarget(output!, atTargetIndex: 0)
            }else{
                
                //是否添加水印
                var weatMaskFilter:PQBaseFilter?
                if(isAddWatermark){
                    //创建水印filter
                    let  weatMaskSticker:PQEditVisionTrackMaterialsModel = PQEditVisionTrackMaterialsModel.init()
                    weatMaskSticker.locationPath = "watermark"
                    weatMaskSticker.timelineIn = mStickers?.first?.timelineIn ?? 0.0
                    weatMaskSticker.timelineOut = mStickers?.last?.timelineOut ?? 0.0
                    /*
                    1080P        最短的一边 ≥1080    100%     距右40，距下30
                    720P         最短的一边 ≥720，＜1080    66.60%    距右27，距下20
                    480P及以下    最短的一边＜720    44.40%    距右18，距下14
                    */
                    let postion:PQEditMaterialPositionModel = PQEditMaterialPositionModel.init()
                    let minSlider = min(input?.mShowVidoSize.width ?? 0, input?.mShowVidoSize.height ?? 0)
                    postion.width = 350
                    postion.height = 100
                    if(minSlider >= 1080){
                        postion.x = Int(input?.mShowVidoSize.width ?? 0) - 40 - postion.width
                        postion.y = Int(input?.mShowVidoSize.height ?? 0) - 30 - postion.height
                    }else if(minSlider >= 720 && minSlider < 1080){
                        postion.width = Int(Float(postion.width) * 0.666)
                        postion.height = Int(Float(postion.height) * 0.666)
                        postion.x = Int(input?.mShowVidoSize.width ?? 0) - 27 - postion.width
                        postion.y = Int(input?.mShowVidoSize.height ?? 0) - 20 - postion.height
                    
                    }else if(minSlider < 720){
                        postion.width = Int(Float(postion.width) * 0.444)
                        postion.height = Int(Float(postion.height) * 0.444)
                        postion.x = Int(input?.mShowVidoSize.width ?? 0) - 18 - postion.width
                        postion.y = Int(input?.mShowVidoSize.height ?? 0) - 14 - postion.height
                        
                    }
                    weatMaskSticker.materialPosition = postion
                   
                    weatMaskFilter = PQImageFilter(sticker: weatMaskSticker, isExport: true)
                    
                }
                
                if(currentSticker?.type == StickerType.IMAGE.rawValue && showGaussianBlur){
                    //高斯层
                    let json = currentSticker?.toJSONString(prettyPrint: false)
                    if json == nil {
                        BFLog(message: "数据转换有问题 跳转")
                        return
                    }
                    let blurStickerModel: PQEditVisionTrackMaterialsModel? = Mapper<PQEditVisionTrackMaterialsModel>().map(JSONString: json!)
                    blurStickerModel?.canvasFillType = stickerContentMode.aspectFillStr.rawValue
                    let showGaussianFitler:PQBaseFilter = PQImageFilter(sticker: blurStickerModel!)
                    
                    let iosb:GaussianBlur = GaussianBlur.init()
                    iosb.blurRadiusInPixels = 20
                    showGaussianFitler.addTarget(iosb)
                    
                    currentTarget.addTarget(showGaussianFitler, atTargetIndex: 0)
                    
                    iosb.addTarget(showFitler!)
                    
                    if(weatMaskFilter != nil){
                        showFitler?.addTarget(weatMaskFilter!, atTargetIndex: 0)
                        weatMaskFilter?.addTarget(output!,atTargetIndex: 0)
                    }else{
                        showFitler?.addTarget(output!, atTargetIndex: 0)
                    }
                  
                }else{
                    if(weatMaskFilter != nil){
                        BFLog(1, message: "find show stricker")
                        currentTarget.addTarget(showFitler!, atTargetIndex: 0)
                        showFitler?.addTarget(weatMaskFilter!, atTargetIndex: 0)
                        weatMaskFilter?.addTarget(output!, atTargetIndex: 0)
                        
                    }else{
                        currentTarget.addTarget(showFitler!, atTargetIndex: 0)
                        showFitler?.addTarget(output!, atTargetIndex: 0)
                    }
     
                }
            }
         

            lastshowSticker = currentSticker
        }
 
    }

    public func start(playeTimeRange:CMTimeRange = CMTimeRange.init()) {
        input?.completion = { [weak self] in
            self?.output?.finishRecording { [weak self] in
                self?.input?.removeAllTargets()
                BFLog(message: "导出视频完成发通知")
                DispatchQueue.main.async {[weak self] in
                    if let url = self?.tmpExportURL {
                        self?.handleCaption(for: AVURLAsset(url: url, options: nil))
                    } else {
                        self?.progressClosure?(0, 0, 1)
                        self?.completion?(self?.exportURL)
                    }
                }
            }
        }

        input?.progress = { [weak self] currTime, duration, progress in
            
            if(self?.mStickers?.count ?? 0 > 0){
                self?.findShowStikcer(currTime: CMTimeGetSeconds(self?.input?.currentTime ?? .zero) )
//                self?.changeFilter(currTime: CMTimeGetSeconds(self?.input?.currentTime ?? .zero) )
            }
            DispatchQueue.main.async {[weak self] in
                self?.progressClosure?(Float(currTime), Float(duration), Float(progress))
            }
        }

        output?.startRecording { [weak self] started, error in
            guard started else {
                NXLog(message: "MovieOutput unable to start writing with error: \(String(describing: error)) ")
                return
            }

            self?.input?.start(isFreeBuffer: false, isExport: true,timeRange: playeTimeRange)
        }
    }

    // 取消输出
    public func cancel() {
        input?.cancel()
        output?.assetWriter.cancelWriting()
        output?.finishRecording()
    }

    public func handleCaption(for asset: AVAsset) {
        guard let animationTool = animationTool else { return }
        guard let assetVideoTrack = asset.tracks(withMediaType: .video).first else { return }
        guard let assetAudioTrack = asset.tracks(withMediaType: .audio).first else { return }
        let mixComposition = AVMutableComposition()
        guard let videoTrack = mixComposition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid) else { return }
        guard let audioTrack = mixComposition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid) else { return }
        do {
            try videoTrack.insertTimeRange(CMTimeRange(start: .zero, duration: asset.duration), of: assetVideoTrack, at: .zero)
            try audioTrack.insertTimeRange(CMTimeRange(start: .zero, duration: asset.duration), of: assetAudioTrack, at: .zero)
        } catch {
            NXLog(message: "error is \(error)")
        }

        videoTrack.preferredTransform = assetVideoTrack.preferredTransform

        let size = videoTrack.naturalSize

        let videoComposition = AVMutableVideoComposition()
        videoComposition.renderSize = size
        videoComposition.animationTool = animationTool
        videoComposition.frameDuration = CMTime(value: 1, timescale: 60)
        let instruction = AVMutableVideoCompositionInstruction()
        instruction.timeRange = CMTimeRange(start: .zero, duration: asset.duration)
        let layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)
        instruction.layerInstructions = [layerInstruction]
        videoComposition.instructions = [instruction]

        let exporter = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetHighestQuality)
        exporter?.outputURL = exportURL
        exporter?.outputFileType = .mp4
        exporter?.shouldOptimizeForNetworkUse = true
        exporter?.videoComposition = videoComposition
        exporter?.exportAsynchronously { [weak self] in
            guard let strongSelf = self else { return }
            BFLog(message: "导出完成 \(strongSelf.exportURL)")
            DispatchQueue.main.async {
                strongSelf.progressClosure?(1, 1, 1)
                strongSelf.completion?(strongSelf.exportURL)
            }
        }
    }
    
    
    
    /// 初始化缓存，默认选创建 cacheFiltersMaxCount 个缓存 filterrs
    /// - Parameter beginTime: 开始缓存的开始时间，用在 seek操作时 老的缓存已经无效不能在使用了
    func configCache(beginTime: Float64 ) {
        cacheFilters.removeAll()
        BFLog(2, message: "原素材 总数：\(mStickers?.count ?? 0) ")
       
        if mStickers?.count ?? 0 > 0 {
            
            for (index , sticker) in mStickers!.enumerated() {
                print( "mStickers timelinein：\(sticker.timelineIn) timelineout: \(sticker.timelineOut) index : \(index)")

            }
            
            for (index, currentSticker) in mStickers!.enumerated() {
               //到达最大缓存数退出
                if cacheFilters.count == cacheFiltersMaxCount {
                    break
                }
                //小于缓存的开始时间继续查找
                if(currentSticker.timelineOut < beginTime){
                    continue
                }
                var showFitler: PQBaseFilter?
                if currentSticker.type == StickerType.VIDEO.rawValue {
                    showFitler = PQMovieFilter(movieSticker: currentSticker)

                } else if currentSticker.type == StickerType.IMAGE.rawValue {
                    showFitler = PQImageFilter(sticker: currentSticker)
                }
                if showFitler != nil {
                    print( " 加入到缓存 的 filter timelinein：\(currentSticker.timelineIn) timelineout: \(currentSticker.timelineOut) in :\(currentSticker.model_in) out: \(currentSticker.out) index : \(index)")
                    cacheFilters.append(showFitler!)
                }

            }
            
            
            for (index, filter) in cacheFilters.enumerated() {
                BFLog(2, message: " 初始化 config create currentSticker timelinein \(String(describing: filter.stickerInfo?.timelineIn)) timelineout \(String(describing: filter.stickerInfo?.timelineOut))  in :\(String(describing: filter.stickerInfo?.model_in)) out \(String(describing: filter.stickerInfo?.out))  index\(index)")
            }
            
            if(cacheFilters.first != nil){
                input?.removeAllTargets()
                let showFilter: PQBaseFilter = cacheFilters.first!
                input?.addTarget(showFilter, atTargetIndex: 0)
                showFilter.addTarget(output!, atTargetIndex: 0)
            }
      
        }

   

  
    }

    //创建下一个filter 数据
    func createNextFilter() {
        BFLog(2, message: "加入前 当前的缓存个数为： \(cacheFilters.count)  maxCount \(cacheFiltersMaxCount) 最后一个显示时间 \(String(describing: cacheFilters.last?.stickerInfo?.timelineIn))")
          if cacheFilters.count <=  cacheFiltersMaxCount {
              let showIndex = mStickers?.firstIndex(where: { (sticker) -> Bool in
                (cacheFilters.last?.stickerInfo == sticker)
              })
                BFLog(2, message: "当前显示的showIndex: \(String(describing: showIndex))")
              if ((showIndex ?? 0) + 1) < (mStickers?.count ?? 0) {
                  let currentSticker = mStickers?[(showIndex ?? 0) + 1]
                  if currentSticker != nil {
                      var showFitler: PQBaseFilter?
                      if currentSticker!.type == StickerType.VIDEO.rawValue {
                          showFitler = PQMovieFilter(movieSticker: currentSticker!)

                      } else if currentSticker!.type == StickerType.IMAGE.rawValue {
                          showFitler = PQImageFilter(sticker: currentSticker!)
                      }
                      if showFitler != nil {

                          cacheFilters.append(showFitler!)
                      }
                  }else{
                    BFLog(2, message: "缓存数据加入不成功!!!!!")
                  }
              }
            
            BFLog(2, message: "加入后 当前的缓存个数为： \(cacheFilters.count)  maxCount \(cacheFiltersMaxCount) 最后一个显示时间 \(String(describing: cacheFilters.last?.stickerInfo?.timelineIn))")
             
          }
        
        
      }
 
    
    /// 按时间从缓存中取出要显示的filter
    /// - Parameter currTime: 当前播放时间
    func changeFilter(currTime: Float64) {
        BFLog(2, message: " 要查找的 currTime is \(currTime)")
        //1,删除已经显示过的 filter
        self.cacheFilters.removeAll(where: {(filter) -> Bool in

            (currTime > (filter.stickerInfo?.timelineOut ?? 0.0))
        })
 
        // 2,找出一个要显示的 fitler
        let showIndex = cacheFilters.firstIndex(where: { (filter) -> Bool in
            (currTime >= (filter.stickerInfo?.timelineIn ?? 0.0) && currTime <= (filter.stickerInfo?.timelineOut ?? 0.0))
        })
  
        if(showIndex == nil){
          
            for (index,bsFilter )in cacheFilters.enumerated() {
                print( "22222缓存操作   没要查找到要查找的显示是为：\(currTime) 缓存数据timeline in :\(bsFilter.stickerInfo?.timelineIn ?? 0.0)) timelineOut:\(bsFilter.stickerInfo?.timelineOut ?? 0.0) 缓存数 \(cacheFilters.count) index: \(index)")
            }
            
            return
        }
    
        let showFilter: PQBaseFilter = cacheFilters[showIndex ?? 0]
        
        print("缓存操作   查找到命中的显示是为：\(currTime) 缓存数据timeline in :\(showFilter.stickerInfo?.timelineIn ?? 0.0)) timelineOut:\(showFilter.stickerInfo?.timelineOut ?? 0.0) 缓存数 \(cacheFilters.count) index: \(String(describing: showIndex))")
        
        if(!(showFilter.isShow)){
            BFLog(2, message: "showIndex当前时间为  \(currTime) showIndex is \(String(describing: showIndex)) 显示 filter timein is: \(String(describing: showFilter.stickerInfo?.timelineIn)) timeout is: \(String(describing: showFilter.stickerInfo?.timelineOut))")
 
            showFilter.isShow = true
            
            input!.removeAllTargets()
        
            if(showFilter.stickerInfo?.type == StickerType.IMAGE.rawValue && showGaussianBlur){
              
//                self.createFiltersQueue.async {
                    //高斯层
                    let json = showFilter.stickerInfo?.toJSONString(prettyPrint: false)
                    if json == nil {
                        BFLog(2, message: "数据转换有问题 跳转")
                        return
                    }

                    let blurStickerModel: PQEditVisionTrackMaterialsModel? = Mapper<PQEditVisionTrackMaterialsModel>().map(JSONString: json!)
                    blurStickerModel?.canvasFillType = stickerContentMode.aspectFillStr.rawValue
                    let showGaussianFitler:PQBaseFilter = PQImageFilter(sticker: blurStickerModel!)
                    
                    let iosb:GaussianBlur = GaussianBlur.init()
                    iosb.blurRadiusInPixels = 20
                    showGaussianFitler.addTarget(iosb)
                    
                    sharedImageProcessingContext.runOperationAsynchronously{ [self] in
        
                        self.input?.addTarget(showGaussianFitler, atTargetIndex: 0)
                        iosb.addTarget(showFilter)
                        showFilter.addTarget(output!, atTargetIndex: 0)
                        
                    }
//                }
            
           
                
            }else{
                input?.addTarget(showFilter, atTargetIndex: 0)
                showFilter.addTarget(output!, atTargetIndex: 0)
            }
//            self.createFiltersQueue.async {
                self.createNextFilter()
//            }

        }
    }
}
