//
//  EditorVideoControlView.swift
//  HXPhotoPicker
//
//  Created by Silence on 2023/5/13.
//

import UIKit
import AVFoundation

public enum ExportPreset {
    case lowQuality
    case mediumQuality
    case highQuality
    case ratio_640x480
    case ratio_960x540
    case ratio_1280x720
    
    public var name: String {
        switch self {
        case .lowQuality:
            return AVAssetExportPresetLowQuality
        case .mediumQuality:
            return AVAssetExportPresetMediumQuality
        case .highQuality:
            return AVAssetExportPresetHighestQuality
        case .ratio_640x480:
            return AVAssetExportPreset640x480
        case .ratio_960x540:
            return AVAssetExportPreset960x540
        case .ratio_1280x720:
            return AVAssetExportPreset1280x720
        }
    }
}

struct Video {
    
    /// Video export resolution
    /// 视频导出的分辨率
    public var preset: ExportPreset = .ratio_960x540
    
    /// Quality of video export [0-10]
    /// 视频导出的质量[0-10]
    public var quality: Int = 6
    
    /// Autoplay after loading is complete
    /// 加载完成后自动播放
    public var isAutoPlay: Bool = true
    
    /// Clipping duration configuration
    /// 裁剪时长配置
    public var cropTime: CropTime = .init()
    

    public init() { }
    
    public struct CropTime {
        
        /// Video maximum cropping duration
        /// > 0 The video must be cropped when it is longer than
        /// = 0 for no clipping
        /// 视频最大裁剪时长
        /// > 0 视频时长超过时必须裁剪
        /// = 0 可不裁剪
        public var maximumTime: TimeInterval = 0
        
        /// Video minimum cropping duration, minimum 1
        /// 视频最小裁剪时长，最小1
        public var minimumTime: TimeInterval = 1
        
        /// The color of the left and right arrows in normal state
        /// 左右箭头正常状态下的颜色
        public var arrowNormalColor: UIColor = .white
        
        /// The color of the left and right arrows when they are highlighted
        /// 左右箭头高亮状态下的颜色
        public var arrowHighlightedColor: UIColor = .black
        
        /// The color of the highlighted border
        /// 边框高亮状态下的颜色
        public var frameHighlightedColor: UIColor = "#FDCC00".color
        
        public init() { }
    }
}


protocol EditorVideoControlViewDelegate: AnyObject {
    func controlView(_ controlView: EditorVideoControlView, didPlayAt isSelected: Bool)
    func controlView(_ controlView: EditorVideoControlView, leftDidChangedValidRectAt time: CMTime)
    func controlView(_ controlView: EditorVideoControlView, leftEndChangedValidRectAt time: CMTime)
    func controlView(_ controlView: EditorVideoControlView, rightDidChangedValidRectAt time: CMTime)
    func controlView(_ controlView: EditorVideoControlView, rightEndChangedValidRectAt time: CMTime)
    func controlView(_ controlView: EditorVideoControlView, progressLineDragBeganAt time: CMTime)
    func controlView(_ controlView: EditorVideoControlView, progressLineDragChangedAt time: CMTime)
    func controlView(_ controlView: EditorVideoControlView, progressLineDragEndAt time: CMTime)
    func controlView(_ controlView: EditorVideoControlView, didScrollAt time: CMTime)
    func controlView(_ controlView: EditorVideoControlView, endScrollAt time: CMTime)
}

// swiftlint:disable type_body_length
class EditorVideoControlView: UIView {
    // swiftlint:enable type_body_length
    
    weak var delegate: EditorVideoControlViewDelegate?
    let config: Video.CropTime
    
//    private var textSize:CGFloat = !
    
    private var playView: UIVisualEffectView!
    private var playButton: UIButton!
    private var frameMaskView: EditorVideoControlMaskView!
    private var flowLayout: UICollectionViewFlowLayout!
    private var bgView: UIVisualEffectView!
    private var collectionView: UICollectionView!
    private var beginLineFrame: CGRect = .zero
    private var progressLineView: UIView!
    private var currentLineView: UIView!
    private var currentTimeView: UIVisualEffectView!
    private var currentTimeLb: UILabel!
    private var startLineView: UIView!
    private var endLineView: UIView!
    private var startTimeView: UIVisualEffectView!
    private var startTimeLb: UILabel!
    private var endTimeView: UIVisualEffectView!
    private var endTimeLb: UILabel!
    private var totalTimeView: UIVisualEffectView!
    private var totalTimeLb: UILabel!
    
    var isPlaying: Bool {
        get {
            playButton.isSelected
        }
        set {
            playButton.isSelected = newValue
            if newValue {
                startLineAnimation()
            }else {
                stopLineAnimation()
            }
        }
    }
    
    init(config: Video.CropTime) {
        self.config = config
        super.init(frame: .zero)
        initViews()
    }
    
    private func initViews() {
        flowLayout = UICollectionViewFlowLayout()
        flowLayout.scrollDirection = .horizontal
        flowLayout.minimumLineSpacing = 0
        flowLayout.minimumInteritemSpacing = 0
        collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout)
        collectionView.backgroundColor = .clear
        collectionView.dataSource = self
        collectionView.delegate = self
        collectionView.showsVerticalScrollIndicator = false
        collectionView.showsHorizontalScrollIndicator = false
        if #available(iOS 11.0, *) {
            collectionView.contentInsetAdjustmentBehavior = .never
        }
        collectionView.register(
            EditorVideoControlViewCell.self,
            forCellWithReuseIdentifier: "EditorVideoControlViewCellID"
        )
        
        frameMaskView = EditorVideoControlMaskView()
        frameMaskView.frameHighlightedColor = config.frameHighlightedColor
        frameMaskView.arrowNormalColor = config.arrowNormalColor
        frameMaskView.arrowHighlightedColor = config.arrowHighlightedColor
        frameMaskView.delegate = self
        
        bgView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
        bgView.contentView.addSubview(collectionView)
        bgView.contentView.addSubview(frameMaskView)
        bgView.layer.cornerRadius = 4
        bgView.layer.masksToBounds = true
        addSubview(bgView)
        
        playButton = UIButton(type: .custom)
        playButton.setImage(UIImage(named: "hx_editor_video_control_play"), for: .normal)
        playButton.setImage(UIImage(named: "hx_editor_video_control_pause"), for: .selected)
        playButton.addTarget(self, action: #selector(didPlayButtonClick), for: .touchUpInside)
        playView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
        playView.contentView.addSubview(playButton)
        playView.layer.cornerRadius = 4
        playView.layer.masksToBounds = true
        addSubview(playView)
        
        progressLineView = UIView()
        progressLineView.backgroundColor = .white
        if #available(iOS 11.0, *) {
            progressLineView.cornersRound(radius: 2, corner: .allCorners)
        }
        progressLineView.layer.borderColor = UIColor.black.cgColor
        progressLineView.layer.borderWidth = 0.25
        progressLineView.alpha = 0
        progressLineView.addGestureRecognizer(
            PhotoPanGestureRecognizer(
                target: self,
                action: #selector(progressLinePanGestureClick(pan:))
            )
        )
        addSubview(progressLineView)
        
        startTimeLb = UILabel()
        startTimeLb.font = .systemFont(ofSize: 12)
        startTimeLb.textColor = .white
        startTimeView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
        startTimeView.contentView.addSubview(startTimeLb)
        startTimeView.layer.cornerRadius = 4
        startTimeView.layer.masksToBounds = true
        startTimeView.alpha = 0
        addSubview(startTimeView)
        
        startLineView = UIView()
        startLineView.backgroundColor = .white
        startLineView.size = .init(width: 1, height: 10)
        startLineView.cornersRound(radius: 0.5, corner: .allCorners)
        startLineView.alpha = 0
        addSubview(startLineView)
        
        endTimeLb = UILabel()
        endTimeLb.textAlignment = .right
        endTimeLb.font = .systemFont(ofSize: 12)
        endTimeLb.textColor = .white
        endTimeView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
        endTimeView.contentView.addSubview(endTimeLb)
        endTimeView.layer.cornerRadius = 4
        endTimeView.layer.masksToBounds = true
        endTimeView.alpha = 0
        addSubview(endTimeView)
        
        endLineView = UIView()
        endLineView.backgroundColor = .white
        endLineView.size = .init(width: 1, height: 10)
        endLineView.cornersRound(radius: 0.5, corner: .allCorners)
        endLineView.alpha = 0
        addSubview(endLineView)
        
        currentTimeLb = UILabel()
        currentTimeLb.font = .systemFont(ofSize: 12)
        currentTimeLb.textColor = .white
        currentTimeView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
        currentTimeView.contentView.addSubview(currentTimeLb)
        currentTimeView.layer.cornerRadius = 4
        currentTimeView.layer.masksToBounds = true
        currentTimeView.alpha = 0
        addSubview(currentTimeView)
        
        currentLineView = UIView()
        currentLineView.backgroundColor = .white
        currentLineView.size = .init(width: 1, height: 10)
        currentLineView.cornersRound(radius: 0.5, corner: .allCorners)
        currentLineView.alpha = 0
        addSubview(currentLineView)
        
        totalTimeLb = UILabel.init()
        totalTimeLb.textAlignment = .center
        totalTimeLb.font = .systemFont(ofSize: 12)
        totalTimeLb.textColor = .white
        totalTimeView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
        totalTimeView.contentView.addSubview(totalTimeLb)
        totalTimeView.layer.cornerRadius = 4
        totalTimeView.layer.masksToBounds = true
        totalTimeView.alpha = 0
        addSubview(totalTimeView)
    }
    
    @objc
    private func didPlayButtonClick() {
        playButton.isSelected = !playButton.isSelected
        delegate?.controlView(self, didPlayAt: playButton.isSelected)
        if playButton.isSelected {
            startLineAnimation()
        }else {
            stopLineAnimation()
        }
    }
    
    private let controlWidth: CGFloat = 18
    private var contentWidth: CGFloat = 0
    private var videoFrameCount: Int = 0
    
    private var videoFrameMap: [Int: CGImage] = [:]
    private var videoFrameLock: DispatchQueue = .init(label: "com.hxpicker.editorvideoframelock")
    var videoSize: CGSize = .zero
    /// 一个item代表多少秒
    private var interval: CGFloat = -1
    private var itemWidth: CGFloat = 0
    private var imageGenerator: AVAssetImageGenerator?
    private var assetDuration: TimeInterval = 0
    private weak var timeLabelsTimer: Timer?
    private var avAsset: AVAsset?
    private var margin: CGFloat {
        var leftMargin: CGFloat = 15 + UIDevice.leftMargin
        var rightMargin: CGFloat = UIDevice.rightMargin + 15
        if (width - leftMargin - rightMargin) > 450 {
            let maxWidth = max(width * 0.5, 450)
            leftMargin = (width - maxWidth) * 0.5
            rightMargin = leftMargin
        }
        return leftMargin - UIDevice.leftMargin
    }
    private var bgWidth: CGFloat {
        if UIDevice.isPortrait {
            return width - UIDevice.rightMargin - margin - (margin + UIDevice.leftMargin + playWidth) - 1
        }else {
            return width - UIDevice.rightMargin - margin - (margin + UIDevice.leftMargin + playWidth) - 1
        }
    }
    private var isBeginScrolling: Bool = false
    private var isFirstLoad: Bool = true
    private var playWidth: CGFloat {
        if UIDevice.isPortrait {
            return height
        }else {
            return height * 1.5
        }
    }
    var currentDuration: Double {
        let lineFrame = progressLineView.layer.presentation()?.frame ?? progressLineView.frame
        let scale = (
            lineFrame.minX - bgView.x - frameMaskView.validRect.minX
        ) / (
            frameMaskView.validRect.width - lineFrame.width
        )
        let totalDuration = endDuration - startDuration
        let time = startDuration + totalDuration * scale
        return time
    }
    private var currentTime: CMTime {
        CMTimeMakeWithSeconds(
            Float64(currentDuration),
            preferredTimescale: 1000
        )
    }
    
    func loadData(_ avAsset: AVAsset) {
        self.avAsset = avAsset
        videoSize = PhotoTools.getVideoThumbnailImage(avAsset: avAsset, atTime: 0.1)?.size ?? .zero
        assetDuration = avAsset.duration.seconds
        imageGenerator?.cancelAllCGImageGeneration()
        videoFrameMap.removeAll()
        
        collectionView.contentInset = UIEdgeInsets(
            top: 4,
            left: controlWidth,
            bottom: 4,
            right: controlWidth
        )
        loadItemSize()
        resetValidRect()
        collectionView.reloadData()
        loadVideoFrame(avAsset)
        updateTimeLabels()
        updateTimeLabelsFrame()
        let totalDuration = endDuration - startDuration
        frameMaskView.isShowFrame = totalDuration < assetDuration
        frameMaskView.updateFrameView()
        showLineView(at: startTime)
    }
    
    func loadItemSize() {
        if videoSize == .zero {
            return
        }
        let cellHeight = height - 8
        itemWidth = cellHeight / 16 * 9
        var imgWidth = videoSize.width
        let imgHeight = videoSize.height
        imgWidth = cellHeight / imgHeight * imgWidth
        if imgWidth > itemWidth {
            itemWidth = cellHeight / imgHeight * videoSize.width
            if itemWidth > imgHeight / 9 * 16 {
                itemWidth = imgHeight / 9 * 16
            }
        }
        var videoSecond = assetDuration
        if videoSecond <= 0 {
            videoSecond = 1
        }
        let maxWidth = bgWidth - controlWidth * 2
        var singleItemSecond: CGFloat
        let videoMaximumCropDuration = CGFloat(config.maximumTime)
        if videoSecond <= videoMaximumCropDuration || videoMaximumCropDuration <= 0 {
            let itemCount = maxWidth / itemWidth
            singleItemSecond = videoSecond / itemCount
            
            contentWidth = maxWidth
            videoFrameCount = Int(ceilf(Float(itemCount)))
            interval = singleItemSecond
        }else {
            let singleSecondWidth = maxWidth / videoMaximumCropDuration
            singleItemSecond = itemWidth / singleSecondWidth
            
            contentWidth = singleSecondWidth * videoSecond
            videoFrameCount = Int(ceilf(Float(contentWidth / itemWidth)))
            interval = singleItemSecond
        }
        if round(videoSecond) <= 0 {
            frameMaskView.minWidth = contentWidth
        }else {
            var videoMinimunCropDuration = CGFloat(config.minimumTime)
            if videoMinimunCropDuration < 1 {
                videoMinimunCropDuration = 1
            }
            let scale = videoMinimunCropDuration / videoSecond
            frameMaskView.minWidth = contentWidth * scale
        }
    }
    
    func loadVideoFrame(_ avAsset: AVAsset) {
        if videoFrameCount < 0 {
            return
        }
        imageGenerator = AVAssetImageGenerator(asset: avAsset)
        imageGenerator?.maximumSize = CGSize(width: itemWidth * 2, height: height * 2)
        imageGenerator?.appliesPreferredTrackTransform = true
        imageGenerator?.requestedTimeToleranceAfter = .zero
        imageGenerator?.requestedTimeToleranceBefore = .zero
         
        var times: [NSValue] = []
        for index in 0..<videoFrameCount {
            let time = getVideoCurrentTime(for: index)
            times.append(NSValue.init(time: time))
        }
        var index: Int = 0
        var hasError = false
        var errorIndex: [Int] = []
        imageGenerator?.generateCGImagesAsynchronously(forTimes: times) { (_, cgImage, _, result, _) in
            if result != .cancelled {
                if let cgImage = cgImage {
                    self.videoFrameLock.sync {
                        self.videoFrameMap[index] = cgImage
                    }
                    if hasError {
                        for inde in errorIndex {
                            self.setCurrentCell(image: UIImage(cgImage: cgImage), index: inde)
                        }
                        errorIndex.removeAll()
                        hasError = false
                    }
                    self.setCurrentCell(image: UIImage(cgImage: cgImage), index: index)
                }else {
                    var cg_image: CGImage?
                    self.videoFrameLock.sync {
                        cg_image = self.videoFrameMap[index - 1]
                    }
                    if let cgImage = cg_image {
                        self.setCurrentCell(image: UIImage(cgImage: cgImage), index: index)
                    }else {
                        errorIndex.append(index)
                        hasError = true
                    }
                }
                index += 1
            }
        }
    }
    
    func reloadVideo() {
        guard let avAsset = avAsset else {
            return
        }
        imageGenerator?.cancelAllCGImageGeneration()
        loadItemSize()
        collectionView.reloadData()
        loadVideoFrame(avAsset)
    }
    
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        let lineFrame = progressLineView.layer.presentation()?.frame ?? progressLineView.frame
        let rect = lineFrame.inset(by: .init(top: 4, left: 10, bottom: 4, right: 10))
        if rect.contains(point) {
            return progressLineView
        }
        return super.hitTest(point, with: event)
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        progressLineView.size = .init(width: 4, height: height - 4)
        progressLineView.centerY = height / 2
        if UIDevice.isPortrait {
            let leftMargin: CGFloat = margin + UIDevice.leftMargin
            let rightMargin: CGFloat = UIDevice.rightMargin + margin
            playView.frame = .init(x: leftMargin, y: 0, width: playWidth, height: height)
            bgView.frame = .init(
                x: playView.frame.maxX + 1,
                y: 0,
                width: width - rightMargin - playView.frame.maxX - 1,
                height: height
            )
        }else {
            playView.frame = .init(x: margin + UIDevice.leftMargin, y: 0, width: playWidth, height: height)
            bgView.frame = .init(
                x: playView.frame.maxX + 1,
                y: 0,
                width: width - UIDevice.rightMargin - margin - playView.frame.maxX - 1,
                height: height
            )
        }
        playButton.frame = playView.bounds
        collectionView.frame = bgView.bounds
        frameMaskView.frame = bgView.bounds
        if isFirstLoad {
            resetValidRect()
            isFirstLoad = false
        }
        guard #available(iOS 11.0, *) else {
            progressLineView.cornersRound(radius: 2, corner: .allCorners)
            return
        }
    }
    deinit {
        imageGenerator?.cancelAllCGImageGeneration()
        videoFrameMap.removeAll()
    }
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

extension EditorVideoControlView {
    @objc
    private func progressLinePanGestureClick(pan: UIPanGestureRecognizer) {
        if isHidden {
            return
        }
        let point = pan.translation(in: progressLineView)
        switch pan.state {
        case .began:
            if playButton.isSelected {
                didPlayButtonClick()
            }
            beginLineFrame = progressLineView.frame
            hideTimeLabels(false)
            updateCurrentTime()
            showCurrentTime()
            delegate?.controlView(self, progressLineDragBeganAt: currentTime)
        case .changed:
            var lineX = beginLineFrame.minX + point.x
            if lineX < bgView.x + frameMaskView.validRect.minX {
                lineX = bgView.x + frameMaskView.validRect.minX
            }
            if lineX > bgView.x + frameMaskView.validRect.maxX - progressLineView.width {
                lineX = bgView.x + frameMaskView.validRect.maxX - progressLineView.width
            }
            progressLineView.x = lineX
            updateCurrentTime()
            delegate?.controlView(self, progressLineDragChangedAt: currentTime)
        case .ended, .cancelled, .failed:
            hideCurrentTime()
            delegate?.controlView(self, progressLineDragEndAt: currentTime)
        default:
            break
        }
    }
    func updateCurrentTime() {
        currentTimeLb.text = currentDuration.time
        currentTimeLb.size = currentTimeLb.textSize
        currentTimeView.size = .init(width: currentTimeLb.width + 10, height: currentTimeLb.size.height + 5)
        currentTimeLb.center = .init(x: currentTimeView.width / 2, y: currentTimeView.height / 2)
        currentLineView.y = -currentLineView.height - 5
        currentLineView.centerX = progressLineView.centerX
        currentTimeView.y = currentLineView.y - currentTimeView.height - 2
        currentTimeView.centerX = currentLineView.centerX
        if currentTimeView.frame.maxX + margin + UIDevice.rightMargin > width {
            currentTimeView.x = width - margin - UIDevice.rightMargin - currentTimeView.width
        }
    }
    func showCurrentTime() {
        if currentLineView.alpha == 1 {
            return
        }
        UIView.animate(withDuration: 0.2) {
            self.currentLineView.alpha = 1
            self.currentTimeView.alpha = 1
        }
    }
    
    func hideCurrentTime() {
        UIView.animate(withDuration: 0.2) {
            self.currentLineView.alpha = 0
            self.currentTimeView.alpha = 0
        }
    }
    func resetLineViewFrsme(at time: CMTime) {
        updateLineViewFrame(at: time)
        if isPlaying {
            startLineAnimation()
        }
    }
    func updateLineViewFrame(at time: CMTime) {
        stopLineAnimation()
        let totalDuration = endDuration - startDuration
        let seconds = (time.seconds - startDuration) / totalDuration
        progressLineView.x = bgView.x + frameMaskView.validRect.minX + (
            frameMaskView.validRect.width - progressLineView.width
        ) * CGFloat(seconds)
    }
    func updateLineViewFrame(at time: TimeInterval) {
        stopLineAnimation()
        let totalDuration = endDuration - startDuration
        let seconds = (time - startDuration) / totalDuration
        progressLineView.x = bgView.x + frameMaskView.validRect.minX + (
            frameMaskView.validRect.width - progressLineView.width
        ) * CGFloat(seconds)
    }
    func showLineView(at time: CMTime? = nil) {
        stopLineAnimation()
        if let time = time {
            updateLineViewFrame(at: time)
        }
        if progressLineView.alpha == 1 {
            return
        }
        UIView.animate(withDuration: 0.2) {
            self.progressLineView.alpha = 1
        }
    }
    func hideLineView(at time: CMTime? = nil) {
        stopLineAnimation()
        if let time = time {
            updateLineViewFrame(at: time)
        }
        if progressLineView.alpha == 0 {
            return
        }
        UIView.animate(withDuration: 0.2) {
            self.progressLineView.alpha = 0
        }
    }
    func startLineAnimation() {
        stopLineAnimation()
        let scale = (
            progressLineView.x - bgView.x - frameMaskView.validRect.minX
        ) / (
            frameMaskView.validRect.width - progressLineView.width
        )
        let totalDuration = endDuration - startDuration
        let duration = totalDuration - totalDuration * scale
        let toX = bgView.x + frameMaskView.validRect.maxX - progressLineView.width
        setLineAnimation(toX: toX, duration: duration)
    }
    func setLineAnimation(toX: CGFloat, duration: TimeInterval) {
        UIView.animate(
            withDuration: duration,
            delay: 0,
            options: [.curveLinear, .allowUserInteraction, .overrideInheritedDuration, .overrideInheritedCurve]
        ) {
            self.progressLineView.x = toX
        } completion: { (isFinished) in
            if isFinished {
                self.stopLineAnimation()
                let startX = self.bgView.x + self.frameMaskView.validRect.minX
                self.progressLineView.x = startX
                let totalDuration = self.endDuration - self.startDuration
                self.setLineAnimation(toX: toX, duration: totalDuration)
            }
        }
    }
    func stopLineAnimation() {
        guard let animationKeys = progressLineView.layer.animationKeys(),
              !animationKeys.isEmpty else {
            return
        }
        let rect = progressLineView.layer.presentation()?.frame ?? progressLineView.frame
        progressLineView.layer.removeAllAnimations()
        progressLineView.frame = rect
    }
    func resetValidRect() {
        frameMaskView.validRect = CGRect(
            x: controlWidth,
            y: 0,
            width: bgView.width - controlWidth * 2,
            height: bgView.height
        )
    }
    func updateTimeLabels() {
        if assetDuration == 0 {
            return
        }
        var endDuration = self.endDuration
        if let duration = Double(String(format: "%.2f", arguments: [endDuration])) {
            endDuration = duration
        }
        var startDuration = self.startDuration
        if let duration = Double(String(format: "%.2f", arguments: [startDuration])) {
            startDuration = duration
        }
        var totalDuration = endDuration - startDuration
        if totalDuration > config.maximumTime && config.maximumTime > 0 {
            totalDuration = config.maximumTime
        }
        if endDuration > startDuration + totalDuration {
            endDuration = startDuration + totalDuration
        }
        endTimeLb.text = endDuration.time
        totalTimeLb.text = totalDuration.time
        startTimeLb.text = startDuration.time
        startTimeLb.size = startTimeLb.textSize
        totalTimeLb.size = totalTimeLb.textSize
        endTimeLb.size = endTimeLb.textSize
    }
    var middleDuration: Double {
        endDuration - startDuration
    }
    var startDuration: Double {
        var offsetX = collectionView.contentOffset.x + collectionView.contentInset.left
        let validX = frameMaskView.validRect.minX - controlWidth
        let maxOfssetX = contentWidth - (collectionView.width - collectionView.contentInset.left * 2.0)
        if offsetX > maxOfssetX {
            offsetX = maxOfssetX
        }
        var second = (offsetX + validX) / contentWidth * assetDuration
        if second < 0 {
            second = 0
        }else if second > assetDuration {
            second = assetDuration
        }
        return second
    }
    var startTime: CMTime {
        CMTimeMakeWithSeconds(
            Float64(startDuration),
            preferredTimescale: 1000
        )
    }
    var endDuration: Double {
        let videoSecond = assetDuration
        let validWidth = frameMaskView.validRect.width
        var second = startDuration + validWidth / contentWidth * videoSecond
        if second > videoSecond {
            second = videoSecond
        }
        return second
    }
    var endTime: CMTime {
        CMTimeMakeWithSeconds(
            Float64(endDuration),
            preferredTimescale: 1000
        )
    }
    func stopScroll() {
        let inset = collectionView.contentInset
        var currentOffset = collectionView.contentOffset
        let maxOffsetX = contentWidth - (collectionView.width - inset.left)
        if currentOffset.x < -inset.left {
            currentOffset.x = -inset.left
        }else if currentOffset.x > maxOffsetX {
            currentOffset.x = maxOffsetX
        }
        collectionView.setContentOffset(currentOffset, animated: false)
    }
    
    func showTimeLabels() {
        if assetDuration == 0 {
            return
        }
        timeLabelsTimer?.invalidate()
        if startLineView.alpha == 1 {
            return
        }
        UIView.animate(withDuration: 0.2) {
            self.startLineView.alpha = 1
            self.startTimeView.alpha = 1
            self.endLineView.alpha = 1
            self.endTimeView.alpha = 1
            self.totalTimeView.alpha = 1
        }
    }
    
    func hideTimeLabels(_ isDelay: Bool = true) {
        timeLabelsTimer?.invalidate()
        if isDelay {
            timeLabelsTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: { [weak self] _ in
                guard let self = self else { return }
                UIView.animate(withDuration: 0.2) {
                    self.startLineView.alpha = 0
                    self.startTimeView.alpha = 0
                    self.endLineView.alpha = 0
                    self.endTimeView.alpha = 0
                    self.totalTimeView.alpha = 0
                }
            })
        }else {
            if startLineView.alpha == 0 {
                return
            }
            UIView.animate(withDuration: 0.2) {
                self.startLineView.alpha = 0
                self.startTimeView.alpha = 0
                self.endLineView.alpha = 0
                self.endTimeView.alpha = 0
                self.totalTimeView.alpha = 0
            }
        }
    }
    
    func updateTimeLabelsFrame() {
        startTimeLb.size = startTimeLb.textSize
        startTimeView.size = .init(width: startTimeLb.width + 10, height: startTimeLb.size.height + 5)
        startTimeLb.center = .init(x: startTimeView.width / 2, y: startTimeView.height / 2)
        totalTimeLb.size = totalTimeLb.textSize
        totalTimeView.size = .init(width: totalTimeLb.width + 10, height: totalTimeLb.size.height + 5)
        totalTimeLb.center = .init(x: totalTimeView.width / 2, y: totalTimeView.height / 2)
        endTimeLb.size = endTimeLb.textSize
        endTimeView.size = .init(width: endTimeLb.width + 10, height: endTimeLb.size.height + 5)
        endTimeLb.center = .init(x: endTimeView.width / 2, y: endTimeView.height / 2)
        
        startLineView.y = -startLineView.height - 5
        startLineView.centerX = bgView.x + frameMaskView.validRect.minX
        
        var startTimeX = startLineView.centerX - startTimeView.width / 2
        startTimeView.y = startLineView.y - startTimeView.height - 2
        
        endLineView.y = -endLineView.height - 5
        endLineView.centerX = bgView.x + frameMaskView.validRect.maxX
        
        var endTimeX = endLineView.centerX - endTimeView.width / 2
        endTimeView.y = endLineView.y - endTimeView.height - 2
        
        if endTimeX + endTimeView.width + margin + UIDevice.rightMargin > width {
            endTimeX = width - margin - UIDevice.rightMargin - endTimeView.width
        }
        if startTimeX + startTimeView.width + 5 > endTimeX {
            startTimeX = endTimeX - 5 - startTimeView.width
        }
        if startTimeX < UIDevice.leftMargin + margin {
            startTimeX = UIDevice.leftMargin + margin
        }
        if endTimeX < startTimeX + startTimeView.width + 5 {
            endTimeX = startTimeX + startTimeView.width + 5
        }
        startTimeView.x = startTimeX
        endTimeView.x = endTimeX
        
        totalTimeView.centerX = bgView.x + frameMaskView.validRect.minX + frameMaskView.validRect.width * 0.5
        if totalTimeView.frame.maxX > width - UIDevice.rightMargin - margin {
            totalTimeView.x = width - UIDevice.rightMargin - margin - totalTimeView.width
        }
        totalTimeView.y = height + 5
    }
}
extension EditorVideoControlView: UICollectionViewDataSource,
                               UICollectionViewDelegate,
                               UICollectionViewDelegateFlowLayout {
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        videoFrameCount
    }
    func collectionView(
        _ collectionView: UICollectionView,
        cellForItemAt indexPath: IndexPath
    ) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(
            withReuseIdentifier: "EditorVideoControlViewCellID",
            for: indexPath
        ) as! EditorVideoControlViewCell
        return cell
    }
    func collectionView(
        _ collectionView: UICollectionView,
        layout collectionViewLayout: UICollectionViewLayout,
        sizeForItemAt indexPath: IndexPath
    ) -> CGSize {
        if indexPath.item < videoFrameCount - 1 {
            return CGSize(width: itemWidth, height: height - 8)
        }
        let itemW = contentWidth - CGFloat(indexPath.item) * itemWidth
        return CGSize(width: itemW, height: height - 8)
    }
    func collectionView(
        _ collectionView: UICollectionView,
        willDisplay cell: UICollectionViewCell,
        forItemAt indexPath: IndexPath
    ) {
        let myCell = cell as! EditorVideoControlViewCell
        self.videoFrameLock.sync {
            if videoFrameMap.keys.contains(indexPath.item),
               let cgImage = videoFrameMap[indexPath.item] {
                myCell.image = UIImage(cgImage: cgImage)
            }else {
                if let result = videoFrameMap.first {
                    myCell.image = UIImage(cgImage: result.value)
                }
            }
        }
    }
    func setCurrentCell(image: UIImage, index: Int) {
        DispatchQueue.main.async {
            let cell = self.collectionView.cellForItem(
                at: IndexPath(item: index, section: 0)
            ) as? EditorVideoControlViewCell
            cell?.image = image
        }
    }
    func getVideoCurrentTime(for index: Int) -> CMTime {
        var second: CGFloat
        let maxIndex = videoFrameCount - 1
        if index == 0 {
            second = 0.1
        }else if index >= maxIndex {
            if assetDuration < 1 {
                second = CGFloat(assetDuration - 0.1)
            }else {
                second = CGFloat(assetDuration - 0.5)
            }
        }else {
            if assetDuration < 1 {
                second = 0
            }else {
                second = CGFloat(index) * interval + interval * 0.5
            }
        }
        let time = CMTimeMakeWithSeconds(Float64(second), preferredTimescale: 1000)
        return time
    }
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        if videoFrameCount > 0 {
            if !scrollView.isTracking && !scrollView.isDragging && !scrollView.isDecelerating {
                return
            }
            isBeginScrolling = true
            delegate?.controlView(self, didScrollAt: startTime)
            updateTimeLabels()
            updateTimeLabelsFrame()
            if scrollView.isTracking {
                showTimeLabels()
                hideLineView()
                stopLineAnimation()
            }
        }
    }
    func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        if !decelerate && isBeginScrolling {
            showLineView(at: startTime)
            delegate?.controlView(self, endScrollAt: startTime)
            updateTimeLabels()
            updateTimeLabelsFrame()
            hideTimeLabels()
        }
        isBeginScrolling = false
    }
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        if !isBeginScrolling {
            return
        }
        showLineView(at: startTime)
        delegate?.controlView(self, endScrollAt: startTime)
        updateTimeLabels()
        updateTimeLabelsFrame()
        hideTimeLabels()
        isBeginScrolling = false
    }
}

extension EditorVideoControlView: EditorVideoControlMaskViewDelegate {
    func frameMaskView(leftValidRectDidChanged frameMaskView: EditorVideoControlMaskView) {
        updateTimes(true)
        hideLineView(at: startTime)
        delegate?.controlView(self, leftDidChangedValidRectAt: startTime)
    }
    func frameMaskView(leftValidRectEndChanged frameMaskView: EditorVideoControlMaskView) {
        updateTimes(false)
        showLineView(at: startTime)
        delegate?.controlView(self, leftEndChangedValidRectAt: startTime)
    }
    func frameMaskView(rightValidRectDidChanged frameMaskView: EditorVideoControlMaskView) {
        updateTimes(true)
        hideLineView(at: endTime)
        delegate?.controlView(self, rightDidChangedValidRectAt: endTime)
    }
    func frameMaskView(rightValidRectEndChanged frameMaskView: EditorVideoControlMaskView) {
        updateTimes(false)
        showLineView(at: endTime)
        delegate?.controlView(self, rightEndChangedValidRectAt: endTime)
    }
    
    func updateTimes(_ isShow: Bool) {
        let totalDuration = endDuration - startDuration
        frameMaskView.isShowFrame = totalDuration < assetDuration
        updateTimeLabels()
        updateTimeLabelsFrame()
        if isShow {
            showTimeLabels()
        }else {
            hideTimeLabels()
        }
    }
}

struct EditorVideoControlInfo: Codable {
    let offsetXScale: CGFloat
    let validXScale: CGFloat
    let validWithScale: CGFloat
}

extension EditorVideoControlView {
    
    var controlInfo: EditorVideoControlInfo {
        let offsetX = collectionView.contentOffset.x
        let validX = frameMaskView.validRect.minX
        let validWidth = frameMaskView.validRect.width
        
        let insert = collectionView.contentInset
        let offsetXScale = (offsetX + insert.left) / contentWidth
        let validInitialX = controlWidth
        let validMaxWidth = bgView.width - validInitialX * 2
        let validXScale = (validX - validInitialX) / validMaxWidth
        let validWithScale = validWidth / validMaxWidth
        return .init(offsetXScale: offsetXScale, validXScale: validXScale, validWithScale: validWithScale)
    }
    
    func setControlInfo(_ info: EditorVideoControlInfo) {
        if avAsset == nil {
            return
        }
        let insert = collectionView.contentInset
        let offsetX = -insert.left + contentWidth * info.offsetXScale
        collectionView.setContentOffset(CGPoint(x: offsetX, y: -insert.top), animated: false)
        let validInitialX = controlWidth
        let validMaxWidth = bgView.width - validInitialX * 2
        let validX = validMaxWidth * info.validXScale + validInitialX
        let vaildWidth = validMaxWidth * info.validWithScale
        frameMaskView.validRect = CGRect(x: validX, y: 0, width: vaildWidth, height: bgView.height)
        let totalDuration = endDuration - startDuration
        frameMaskView.isShowFrame = totalDuration < assetDuration
        frameMaskView.updateFrameView()
        updateTimeLabels()
        updateTimeLabelsFrame()
        showLineView(at: startTime)
    }
}

fileprivate extension Double {
    var time: String {
        if self < 60 {
            let sec = Double(String(format: "%.2f", arguments: [self])) ?? self
            if sec < 10 {
                return String(format: "00:0%.2f", arguments: [sec])
            }else {
                return String(format: "00:%.2f", arguments: [sec])
            }
        }else {
            var min = Double(Int(self / 60))
            let sec = Double(String(format: "%.2f", arguments: [self - (min * 60)])) ?? self - (min * 60)
            if min < 60 {
                if min < 10 {
                    if sec < 10 {
                        return String(format: "0%.0f:0%.2f", arguments: [min, sec])
                    }else {
                        return String(format: "0%.0f:%.2f", arguments: [min, sec])
                    }
                }else {
                    if sec < 10 {
                        return String(format: "%.0f:0%.2f", arguments: [min, sec])
                    }else {
                        return String(format: "%.0f:%.2f", arguments: [min, sec])
                    }
                }
            }else {
                let hour = Double(Int(min / 60))
                min -= hour * 60
                if hour < 10 {
                    if min < 10 {
                        if sec < 10 {
                            return String(format: "0%.0f:0%.0f:0%.2f", arguments: [hour, min, sec])
                        }else {
                            return String(format: "0%.0f:0%.0f:%.2f", arguments: [hour, min, sec])
                        }
                    }else {
                        if sec < 10 {
                            return String(format: "0%.0f:%.0f:0%.2f", arguments: [hour, min, sec])
                        }else {
                            return String(format: "0%.0f:%.0f:%.2f", arguments: [hour, min, sec])
                        }
                    }
                }
                if min < 10 {
                    if sec < 10 {
                        return String(format: "%.0f:0%.0f:0%.2f", arguments: [hour, min, sec])
                    }else {
                        return String(format: "%.0f:0%.0f:%.2f", arguments: [hour, min, sec])
                    }
                }else {
                    if sec < 10 {
                        return String(format: "%.0f:%.0f:0%.2f", arguments: [hour, min, sec])
                    }else {
                        return String(format: "%.0f:%.0f:%.2f", arguments: [hour, min, sec])
                    }
                }
            }
        }
    }
}
