//
//  ZLPhotoPreviewCell.swift
//  ZLPhotoBrowser
//
//  Created by long on 2020/8/21.
//
//  Copyright (c) 2020 Long Zhang <495181165@qq.com>
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.

import UIKit
import Photos
import PhotosUI

class ZLPreviewBaseCell: UICollectionViewCell {
    
    var singleTapBlock: (() -> Void)?
    
    var currentImage: UIImage? {
        return nil
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        NotificationCenter.default.addObserver(self, selector: #selector(previewVCScroll), name: ZLPhotoPreviewController.previewVCScrollNotification, object: nil)
    }
    
    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    @objc func previewVCScroll() {}
    
    func resetSubViewStatusWhenCellEndDisplay() {}
    
    func resizeImageView(imageView: UIImageView, asset: PHAsset) {
        let size = CGSize(width: asset.pixelWidth, height: asset.pixelHeight)
        var frame: CGRect = .zero
        
        let viewW = bounds.width
        let viewH = bounds.height
        
        var width = viewW
        
        // video和livephoto没必要处理长图和宽图
        if UIApplication.shared.statusBarOrientation.isLandscape {
            let height = viewH
            frame.size.height = height
            
            let imageWHRatio = size.width / size.height
            let viewWHRatio = viewW / viewH
            
            if imageWHRatio > viewWHRatio {
                frame.size.width = floor(height * imageWHRatio)
                if frame.size.width > viewW {
                    frame.size.width = viewW
                    frame.size.height = viewW / imageWHRatio
                }
            } else {
                width = floor(height * imageWHRatio)
                if width < 1 || width.isNaN {
                    width = viewW
                }
                frame.size.width = width
            }
        } else {
            frame.size.width = width
            
            let imageHWRatio = size.height / size.width
            let viewHWRatio = viewH / viewW
            
            if imageHWRatio > viewHWRatio {
                frame.size.height = floor(width * imageHWRatio)
            } else {
                var height = floor(width * imageHWRatio)
                if height < 1 || height.isNaN {
                    height = viewH
                }
                frame.size.height = height
            }
        }
        
        imageView.frame = frame
        
        if UIApplication.shared.statusBarOrientation.isLandscape {
            if frame.height < viewH {
                imageView.center = CGPoint(x: viewW / 2, y: viewH / 2)
            } else {
                imageView.frame = CGRect(origin: CGPoint(x: (viewW - frame.width) / 2, y: 0), size: frame.size)
            }
        } else {
            if frame.width < viewW || frame.height < viewH {
                imageView.center = CGPoint(x: viewW / 2, y: viewH / 2)
            }
        }
    }
    
    func animateImageFrame(convertTo view: UIView) -> CGRect {
        return .zero
    }
    
}

// MARK: local image preview cell

class ZLLocalImagePreviewCell: ZLPreviewBaseCell {
    
    override var currentImage: UIImage? {
        return preview.image
    }
    
    lazy var preview: ZLPreviewView = {
        let view = ZLPreviewView()
        view.singleTapBlock = { [weak self] in
            self?.singleTapBlock?()
        }
        return view
    }()
    
    var image: UIImage? {
        didSet {
            preview.image = image
            preview.resetSubViewSize()
        }
    }
    
    var longPressBlock: (() -> Void)?
    
    deinit {
        zl_debugPrint("ZLLocalImagePreviewCell deinit")
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupUI()
    }
    
    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        preview.frame = bounds
    }
    
    private func setupUI() {
        contentView.addSubview(preview)
        
        let longGes = UILongPressGestureRecognizer(target: self, action: #selector(longPressAction(_:)))
        longGes.minimumPressDuration = 0.5
        addGestureRecognizer(longGes)
    }
    
    override func resetSubViewStatusWhenCellEndDisplay() {
        preview.scrollView.zoomScale = 1
    }
    
    @objc func longPressAction(_ ges: UILongPressGestureRecognizer) {
        guard currentImage != nil else {
            return
        }
        
        if ges.state == .began {
            longPressBlock?()
        }
    }
    
}

// MARK: net image preview cell

class ZLNetImagePreviewCell: ZLLocalImagePreviewCell {
    
    private lazy var progressView: ZLProgressView = {
        let view = ZLProgressView()
        view.isHidden = true
        return view
    }()
    
    var progress: CGFloat = 0 {
        didSet {
            progressView.progress = progress
            progressView.isHidden = progress >= 1
        }
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        contentView.addSubview(progressView)
    }
    
    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        bringSubviewToFront(progressView)
        progressView.frame = CGRect(x: bounds.width / 2 - 20, y: bounds.height / 2 - 20, width: 40, height: 40)
    }
    
    override func resetSubViewStatusWhenCellEndDisplay() {
        progressView.isHidden = true
        preview.scrollView.zoomScale = 1
    }
    
}

// MARK: static image preview cell

class ZLPhotoPreviewCell: ZLPreviewBaseCell {
    
    override var currentImage: UIImage? {
        return preview.image
    }
    
    private lazy var preview: ZLPreviewView = {
        let view = ZLPreviewView()
        view.singleTapBlock = { [weak self] in
            self?.singleTapBlock?()
        }
        return view
    }()
    
    var model: ZLPhotoModel! {
        didSet {
            preview.model = model
        }
    }
    
    deinit {
        zl_debugPrint("ZLPhotoPreviewCell deinit")
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupUI()
    }
    
    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        preview.frame = bounds
    }
    
    private func setupUI() {
        contentView.addSubview(preview)
    }
    
    override func resetSubViewStatusWhenCellEndDisplay() {
        preview.scrollView.zoomScale = 1
    }
    
    override func animateImageFrame(convertTo view: UIView) -> CGRect {
        let rect = preview.scrollView.convert(preview.containerView.frame, to: self)
        return convert(rect, to: view)
    }
    
}

// MARK: gif preview cell

class ZLGifPreviewCell: ZLPreviewBaseCell {
    
    override var currentImage: UIImage? {
        return preview.image
    }
    
    private lazy var preview: ZLPreviewView = {
        let view = ZLPreviewView()
        view.singleTapBlock = { [weak self] in
            self?.singleTapBlock?()
        }
        return view
    }()
    
    var model: ZLPhotoModel! {
        didSet {
            preview.model = model
        }
    }
    
    deinit {
        zl_debugPrint("ZLGifPreviewCell deinit")
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupUI()
    }
    
    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        preview.frame = bounds
    }
    
    private func setupUI() {
        contentView.addSubview(preview)
    }
    
    override func previewVCScroll() {
        preview.pauseGif()
    }
    
    func resumeGif() {
        preview.resumeGif()
    }
    
    func pauseGif() {
        preview.pauseGif()
    }
    
    /// gif图加载会导致主线程卡顿一下，所以放在willdisplay时候加载
    func loadGifWhenCellDisplaying() {
        preview.loadGifData()
    }
    
    override func resetSubViewStatusWhenCellEndDisplay() {
        preview.scrollView.zoomScale = 1
    }
    
    override func animateImageFrame(convertTo view: UIView) -> CGRect {
        let rect = preview.scrollView.convert(preview.containerView.frame, to: self)
        return convert(rect, to: view)
    }
    
}

// MARK: live photo preview cell

class ZLLivePhotoPreviewCell: ZLPreviewBaseCell {
    
    private lazy var imageView: UIImageView = {
        let view = UIImageView()
        view.contentMode = .scaleAspectFit
        return view
    }()
    
    private var imageRequestID = PHInvalidImageRequestID
    
    private var livePhotoRequestID = PHInvalidImageRequestID
    
    private var onFetchingLivePhoto = false
    
    private var fetchLivePhotoDone = false
    
    var model: ZLPhotoModel! {
        didSet {
            loadNormalImage()
        }
    }
    
    lazy var livePhotoView: PHLivePhotoView = {
        let view = PHLivePhotoView()
        view.contentMode = .scaleAspectFit
        return view
    }()
    
    override var currentImage: UIImage? {
        return imageView.image
    }
    
    deinit {
        zl_debugPrint("ZLLivePhotoPewviewCell deinit")
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupUI()
    }
    
    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        livePhotoView.frame = bounds
        resizeImageView(imageView: imageView, asset: model.asset)
    }
    
    override func previewVCScroll() {
        livePhotoView.stopPlayback()
    }
    
    override func animateImageFrame(convertTo view: UIView) -> CGRect {
        return convert(imageView.frame, to: view)
    }
    
    override func resetSubViewStatusWhenCellEndDisplay() {
        PHImageManager.default().cancelImageRequest(livePhotoRequestID)
    }
    
    private func setupUI() {
        contentView.addSubview(livePhotoView)
        contentView.addSubview(imageView)
    }
    
    private func loadNormalImage() {
        if imageRequestID > PHInvalidImageRequestID {
            PHImageManager.default().cancelImageRequest(imageRequestID)
        }
        if livePhotoRequestID > PHInvalidImageRequestID {
            PHImageManager.default().cancelImageRequest(livePhotoRequestID)
        }
        onFetchingLivePhoto = false
        imageView.isHidden = false
        
        // livephoto 加载个较小的预览图即可
        var size = model.previewSize
        size.width /= 4
        size.height /= 4
        
        resizeImageView(imageView: imageView, asset: model.asset)
        imageRequestID = ZLPhotoManager.fetchImage(for: model.asset, size: size, completion: { [weak self] image, _ in
            self?.imageView.image = image
        })
    }
    
    private func startPlayLivePhoto() {
        imageView.isHidden = true
        livePhotoView.startPlayback(with: .full)
    }
    
    func loadLivePhotoData() {
        guard !onFetchingLivePhoto else {
            if fetchLivePhotoDone {
                startPlayLivePhoto()
            }
            return
        }
        onFetchingLivePhoto = true
        fetchLivePhotoDone = false
        
        livePhotoRequestID = ZLPhotoManager.fetchLivePhoto(for: model.asset, completion: { livePhoto, _, isDegraded in
            if !isDegraded {
                self.fetchLivePhotoDone = true
                self.livePhotoView.livePhoto = livePhoto
                self.startPlayLivePhoto()
            }
        })
    }
    
}

// MARK: video preview cell

class ZLVideoPreviewCell: ZLPreviewBaseCell {
    
    override var currentImage: UIImage? {
        return imageView.image
    }
    
    private var player: AVPlayer?
    
    private var playerLayer: AVPlayerLayer?
    
    private lazy var progressView = ZLProgressView()
    
    private lazy var imageView: UIImageView = {
        let view = UIImageView()
        view.clipsToBounds = true
        view.contentMode = .scaleAspectFill
        return view
    }()
    
    private lazy var playBtn: UIButton = {
        let btn = UIButton(type: .custom)
        btn.setImage(.zl.getImage("zl_playVideo"), for: .normal)
        btn.addTarget(self, action: #selector(playBtnClick), for: .touchUpInside)
        return btn
    }()
    
    private lazy var syncErrorLabel: UILabel = {
        let attStr = NSMutableAttributedString()
        let attach = NSTextAttachment()
        attach.image = .zl.getImage("zl_videoLoadFailed")
        attach.bounds = CGRect(x: 0, y: -10, width: 30, height: 30)
        attStr.append(NSAttributedString(attachment: attach))
        let errorText = NSAttributedString(
            string: localLanguageTextValue(.iCloudVideoLoadFaild),
            attributes: [
                NSAttributedString.Key.foregroundColor: UIColor.white,
                NSAttributedString.Key.font: UIFont.zl.font(ofSize: 12)
            ]
        )
        attStr.append(errorText)
        
        let label = UILabel()
        label.attributedText = attStr
        return label
    }()
    
    private var imageRequestID = PHInvalidImageRequestID
    
    private var videoRequestID = PHInvalidImageRequestID
    
    private var onFetchingVideo = false
    
    private var fetchVideoDone = false
    
    private let operationQueue = DispatchQueue(label: "com.ZLPhotoBrowser.ZLVideoPreviewCell")
    
    var isPlaying: Bool {
        if player != nil, player?.rate != 0 {
            return true
        }
        return false
    }
    
    var model: ZLPhotoModel! {
        didSet {
            configureCell()
        }
    }
    
    deinit {
        zl_debugPrint("ZLVideoPreviewCell deinit")
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupUI()
    }
    
    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        playerLayer?.frame = bounds
        resizeImageView(imageView: imageView, asset: model.asset)
        let insets = deviceSafeAreaInsets()
        playBtn.frame = CGRect(x: 0, y: insets.top, width: bounds.width, height: bounds.height - insets.top - insets.bottom)
        syncErrorLabel.frame = CGRect(x: 10, y: insets.top + 60, width: bounds.width - 20, height: 35)
        progressView.frame = CGRect(x: bounds.width / 2 - 30, y: bounds.height / 2 - 30, width: 60, height: 60)
    }
    
    override func previewVCScroll() {
        if player != nil, player?.rate != 0 {
            pausePlayer(seekToZero: false)
        }
    }
    
    override func resetSubViewStatusWhenCellEndDisplay() {
        imageView.isHidden = false
        player?.currentItem?.seek(to: CMTimeMake(value: 0, timescale: 1))
    }
    
    override func animateImageFrame(convertTo view: UIView) -> CGRect {
        return convert(imageView.frame, to: view)
    }
    
    private func setupUI() {
        contentView.addSubview(imageView)
        contentView.addSubview(syncErrorLabel)
        contentView.addSubview(progressView)
        contentView.addSubview(playBtn)
        
        NotificationCenter.default.addObserver(self, selector: #selector(appWillResignActive), name: UIApplication.willResignActiveNotification, object: nil)
    }
    
    private func configureCell() {
        imageView.image = nil
        imageView.isHidden = false
        syncErrorLabel.isHidden = true
        playBtn.isEnabled = false
        player = nil
        playerLayer?.removeFromSuperlayer()
        playerLayer = nil
        
        if imageRequestID > PHInvalidImageRequestID {
            PHImageManager.default().cancelImageRequest(imageRequestID)
        }
        if videoRequestID > PHInvalidImageRequestID {
            PHImageManager.default().cancelImageRequest(videoRequestID)
        }
        
        // 视频预览图尺寸
        var size = model.previewSize
        size.width /= 2
        size.height /= 2
        
        resizeImageView(imageView: imageView, asset: model.asset)
        imageRequestID = ZLPhotoManager.fetchImage(for: model.asset, size: size, completion: { image, _ in
            self.imageView.image = image
        })
        
        videoRequestID = ZLPhotoManager.fetchVideo(for: model.asset, progress: { [weak self] progress, _, _, _ in
            self?.progressView.progress = progress
            zl_debugPrint("video progress \(progress)")
            if progress >= 1 {
                zl_debugPrint("video load finished")
                self?.progressView.isHidden = true
            } else {
                self?.progressView.isHidden = false
            }
        }, completion: { [weak self] item, info, isDegraded in
            let error = info?[PHImageErrorKey] as? Error
            let isFetchError = ZLPhotoManager.isFetchImageError(error)
            if isFetchError {
                self?.syncErrorLabel.isHidden = false
                self?.playBtn.setImage(nil, for: .normal)
            }
            if !isDegraded, item != nil {
                self?.fetchVideoDone = true
                self?.configurePlayerLayer(item!)
            }
        })
    }
    
    private func configurePlayerLayer(_ item: AVPlayerItem) {
        playBtn.setImage(.zl.getImage("zl_playVideo"), for: .normal)
        playBtn.isEnabled = true
        
        player = AVPlayer(playerItem: item)
        playerLayer = AVPlayerLayer(player: player)
        playerLayer?.frame = bounds
        layer.insertSublayer(playerLayer!, at: 0)
        NotificationCenter.default.addObserver(self, selector: #selector(playFinish), name: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem)
    }
    
    @objc private func playBtnClick() {
        let currentTime = player?.currentItem?.currentTime()
        let duration = player?.currentItem?.duration
        if player?.rate == 0 {
            if currentTime?.value == duration?.value {
                player?.currentItem?.seek(to: CMTimeMake(value: 0, timescale: 1))
            }
            imageView.isHidden = true
            player?.play()
            playBtn.setImage(nil, for: .normal)
            singleTapBlock?()
        } else {
            pausePlayer(seekToZero: false)
        }
    }
    
    @objc private func playFinish() {
        pausePlayer(seekToZero: true)
    }
    
    @objc private func appWillResignActive() {
        if player != nil, player?.rate != 0 {
            pausePlayer(seekToZero: false)
        }
    }
    
    private func pausePlayer(seekToZero: Bool) {
        player?.pause()
        if seekToZero {
            player?.seek(to: .zero)
        }
        playBtn.setImage(.zl.getImage("zl_playVideo"), for: .normal)
        singleTapBlock?()
        
        operationQueue.async {
            try? AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
        }
    }
    
    func pauseWhileTransition() {
        player?.pause()
        playBtn.setImage(.zl.getImage("zl_playVideo"), for: .normal)
    }
    
}

// MARK: net video preview cell

class ZLNetVideoPreviewCell: ZLPreviewBaseCell {
    
    private var player: AVPlayer?
    
    private var playerLayer: AVPlayerLayer?
    
    private lazy var playBtn: UIButton = {
        let btn = UIButton(type: .custom)
        btn.setImage(.zl.getImage("zl_playVideo"), for: .normal)
        btn.addTarget(self, action: #selector(playBtnClick), for: .touchUpInside)
        return btn
    }()
    
    var isPlaying: Bool {
        if player != nil, player?.rate != 0 {
            return true
        }
        return false
    }
    
    private let operationQueue = DispatchQueue(label: "com.ZLPhotoBrowser.ZLNetVideoPreviewCell")
    
    deinit {
        zl_debugPrint("v deinit")
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupUI()
    }
    
    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        playerLayer?.frame = bounds
        let insets = deviceSafeAreaInsets()
        playBtn.frame = CGRect(x: 0, y: insets.top, width: bounds.width, height: bounds.height - insets.top - insets.bottom)
    }
    
    override func resetSubViewStatusWhenCellEndDisplay() {
        player?.currentItem?.seek(to: CMTimeMake(value: 0, timescale: 1))
    }
    
    private func setupUI() {
        contentView.addSubview(playBtn)
        
        NotificationCenter.default.addObserver(self, selector: #selector(appWillResignActive), name: UIApplication.willResignActiveNotification, object: nil)
    }
    
    @objc private func playBtnClick() {
        let currentTime = player?.currentItem?.currentTime()
        let duration = player?.currentItem?.duration
        if player?.rate == 0 {
            if currentTime?.value == duration?.value {
                player?.currentItem?.seek(to: CMTimeMake(value: 0, timescale: 1))
            }
            player?.play()
            playBtn.setImage(nil, for: .normal)
            singleTapBlock?()
        } else {
            pausePlayer(seekToZero: false)
        }
    }
    
    @objc private func playFinish() {
        pausePlayer(seekToZero: true)
    }
    
    @objc private func appWillResignActive() {
        if player != nil, player?.rate != 0 {
            pausePlayer(seekToZero: false)
        }
    }
    
    override func previewVCScroll() {
        if player != nil, player?.rate != 0 {
            pausePlayer(seekToZero: false)
        }
    }
    
    private func pausePlayer(seekToZero: Bool) {
        player?.pause()
        if seekToZero {
            player?.seek(to: .zero)
        }
        playBtn.setImage(.zl.getImage("zl_playVideo"), for: .normal)
        singleTapBlock?()
        
        operationQueue.async {
            try? AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
        }
    }
    
    func configureCell(videoUrl: URL, httpHeader: [String: Any]?) {
        player = nil
        playerLayer?.removeFromSuperlayer()
        playerLayer = nil
        
        var options: [String: Any] = [:]
        options["AVURLAssetHTTPHeaderFieldsKey"] = httpHeader
        let asset = AVURLAsset(url: videoUrl, options: options)
        let item = AVPlayerItem(asset: asset)
        player = AVPlayer(playerItem: item)
        playerLayer = AVPlayerLayer(player: player)
        playerLayer?.frame = bounds
        layer.insertSublayer(playerLayer!, at: 0)
        NotificationCenter.default.addObserver(self, selector: #selector(playFinish), name: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem)
    }
    
}

// MARK: class ZLPreviewView

class ZLPreviewView: UIView {
    
    private static let defaultMaxZoomScale: CGFloat = 3
    
    private lazy var progressView = ZLProgressView()
    
    private var imageRequestID = PHInvalidImageRequestID
    
    private var gifImageRequestID = PHInvalidImageRequestID
    
    private var imageIdentifier = ""
    
    private var onFetchingGif = false
    
    private var fetchGifDone = false
    
    lazy var scrollView: UIScrollView = {
        let view = UIScrollView()
        view.maximumZoomScale = ZLPreviewView.defaultMaxZoomScale
        view.minimumZoomScale = 1
        view.isMultipleTouchEnabled = true
        view.delegate = self
        view.showsHorizontalScrollIndicator = false
        view.showsVerticalScrollIndicator = false
        view.delaysContentTouches = false
        return view
    }()
    
    lazy var containerView = UIView()
    
    lazy var imageView: UIImageView = {
        let view = UIImageView()
        view.contentMode = .scaleAspectFill
        view.clipsToBounds = true
        return view
    }()
    
    var image: UIImage? {
        get {
            return imageView.image
        }
        set {
            imageView.image = newValue
        }
    }
    
    var singleTapBlock: (() -> Void)?
    
    var doubleTapBlock: (() -> Void)?
    
    var model: ZLPhotoModel! {
        didSet {
            self.configureView()
        }
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupUI()
    }
    
    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        scrollView.frame = bounds
        progressView.frame = CGRect(x: bounds.width / 2 - 20, y: bounds.height / 2 - 20, width: 40, height: 40)
        scrollView.zoomScale = 1
        resetSubViewSize()
    }
    
    private func setupUI() {
        addSubview(scrollView)
        scrollView.addSubview(containerView)
        containerView.addSubview(imageView)
        addSubview(progressView)
        
        let singleTap = UITapGestureRecognizer(target: self, action: #selector(singleTapAction(_:)))
        addGestureRecognizer(singleTap)
        
        let doubleTap = UITapGestureRecognizer(target: self, action: #selector(doubleTapAction(_:)))
        doubleTap.numberOfTapsRequired = 2
        addGestureRecognizer(doubleTap)
        
        singleTap.require(toFail: doubleTap)
    }
    
    @objc private func singleTapAction(_ tap: UITapGestureRecognizer) {
        singleTapBlock?()
    }
    
    @objc private func doubleTapAction(_ tap: UITapGestureRecognizer) {
        let scale: CGFloat = scrollView.zoomScale != scrollView.maximumZoomScale ? scrollView.maximumZoomScale : 1
        let tapPoint = tap.location(in: self)
        var rect = CGRect.zero
        rect.size.width = scrollView.frame.width / scale
        rect.size.height = scrollView.frame.height / scale
        rect.origin.x = tapPoint.x - (rect.size.width / 2)
        rect.origin.y = tapPoint.y - (rect.size.height / 2)
        scrollView.zoom(to: rect, animated: true)
    }
    
    private func configureView() {
        if imageRequestID > PHInvalidImageRequestID {
            PHImageManager.default().cancelImageRequest(imageRequestID)
        }
        if gifImageRequestID > PHInvalidImageRequestID {
            PHImageManager.default().cancelImageRequest(gifImageRequestID)
        }
        
        scrollView.zoomScale = 1
        imageIdentifier = model.ident
        
        if ZLPhotoConfiguration.default().allowSelectGif, model.type == .gif {
            loadGifFirstFrame()
        } else {
            loadPhoto()
        }
    }
    
    private func requestPhotoSize(gif: Bool) -> CGSize {
        // gif 情况下优先加载一个小的缩略图
        var size = model.previewSize
        if gif {
            size.width /= 2
            size.height /= 2
        }
        return size
    }
    
    private func loadPhoto() {
        if let editImage = model.editImage {
            imageView.image = editImage
            resetSubViewSize()
        } else {
            imageRequestID = ZLPhotoManager.fetchImage(for: model.asset, size: requestPhotoSize(gif: false), progress: { [weak self] progress, _, _, _ in
                self?.progressView.progress = progress
                if progress >= 1 {
                    self?.progressView.isHidden = true
                } else {
                    self?.progressView.isHidden = false
                }
            }, completion: { [weak self] image, isDegraded in
                guard self?.imageIdentifier == self?.model.ident else {
                    return
                }
                self?.imageView.image = image
                self?.resetSubViewSize()
                if !isDegraded {
                    self?.progressView.isHidden = true
                    self?.imageRequestID = PHInvalidImageRequestID
                }
            })
        }
    }
    
    private func loadGifFirstFrame() {
        onFetchingGif = false
        fetchGifDone = false
        
        if ZLPhotoConfiguration.default().gifPlayBlock != nil {
            imageView.subviews.forEach { $0.removeFromSuperview() }
        }
        
        imageRequestID = ZLPhotoManager.fetchImage(for: model.asset, size: requestPhotoSize(gif: true), completion: { [weak self] image, _ in
            guard self?.imageIdentifier == self?.model.ident else {
                return
            }
            if self?.fetchGifDone == false {
                self?.imageView.image = image
                self?.resetSubViewSize()
            }
        })
    }
    
    func loadGifData() {
        guard !onFetchingGif else {
            if fetchGifDone {
                resumeGif()
            }
            return
        }
        onFetchingGif = true
        fetchGifDone = false
        imageView.layer.speed = 1
        imageView.layer.timeOffset = 0
        imageView.layer.beginTime = 0
        gifImageRequestID = ZLPhotoManager.fetchOriginalImageData(for: model.asset, progress: { [weak self] progress, _, _, _ in
            self?.progressView.progress = progress
            if progress >= 1 {
                self?.progressView.isHidden = true
            } else {
                self?.progressView.isHidden = false
            }
        }, completion: { [weak self] data, info, isDegraded in
            guard let `self` = self else { return }
            guard self.imageIdentifier == self.model.ident else {
                return
            }
            
            if !isDegraded {
                self.fetchGifDone = true
                if let gifPlayBlock = ZLPhotoConfiguration.default().gifPlayBlock {
                    gifPlayBlock(self.imageView, data, info)
                } else {
                    self.imageView.image = UIImage.zl.animateGifImage(data: data)
                }
                
                self.resetSubViewSize()
            }
        })
    }
    
    func resetSubViewSize() {
        let size: CGSize
        if let model = model {
            if let ei = model.editImage {
                size = ei.size
            } else {
                size = CGSize(width: model.asset.pixelWidth, height: model.asset.pixelHeight)
            }
        } else {
            size = imageView.image?.size ?? bounds.size
        }
        
        var frame: CGRect = .zero
        
        let viewW = bounds.width
        let viewH = bounds.height
        
        var width = viewW
        
        if UIApplication.shared.statusBarOrientation.isLandscape {
            let height = viewH
            frame.size.height = height
            
            let imageWHRatio = size.width / size.height
            let viewWHRatio = viewW / viewH
            
            if imageWHRatio > viewWHRatio {
                frame.size.width = floor(height * imageWHRatio)
                if frame.size.width > viewW {
                    // 宽图
                    frame.size.width = viewW
                    frame.size.height = viewW / imageWHRatio
                }
            } else {
                width = floor(height * imageWHRatio)
                if width < 1 || width.isNaN {
                    width = viewW
                }
                frame.size.width = width
            }
        } else {
            frame.size.width = width
            
            let imageHWRatio = size.height / size.width
            let viewHWRatio = viewH / viewW
            
            if imageHWRatio > viewHWRatio {
                // 长图
                frame.size.width = min(size.width, viewW)
                frame.size.height = floor(frame.size.width * imageHWRatio)
            } else {
                var height = floor(frame.size.width * imageHWRatio)
                if height < 1 || height.isNaN {
                    height = viewH
                }
                frame.size.height = height
            }
        }
        
        // 优化 scroll view zoom scale
        if frame.width < frame.height {
            scrollView.maximumZoomScale = max(ZLPreviewView.defaultMaxZoomScale, viewW / frame.width)
        } else {
            scrollView.maximumZoomScale = max(ZLPreviewView.defaultMaxZoomScale, viewH / frame.height)
        }
        
        containerView.frame = frame
        
        var contenSize: CGSize = .zero
        if UIApplication.shared.statusBarOrientation.isLandscape {
            contenSize = CGSize(width: width, height: max(viewH, frame.height))
            if frame.height < viewH {
                containerView.center = CGPoint(x: viewW / 2, y: viewH / 2)
            } else {
                containerView.frame = CGRect(origin: CGPoint(x: (viewW - frame.width) / 2, y: 0), size: frame.size)
            }
        } else {
            contenSize = frame.size
            if frame.height < viewH {
                containerView.center = CGPoint(x: viewW / 2, y: viewH / 2)
            } else {
                containerView.frame = CGRect(origin: CGPoint(x: (viewW - frame.width) / 2, y: 0), size: frame.size)
            }
        }
        
        ZLMainAsync(after: 0.01) {
            self.scrollView.contentSize = contenSize
            self.imageView.frame = self.containerView.bounds
            self.scrollView.contentOffset = .zero
        }
    }
    
    func resumeGif() {
        guard let m = model else { return }
        guard ZLPhotoConfiguration.default().allowSelectGif, m.type == .gif else { return }
        
        let config = ZLPhotoConfiguration.default()
        
        if config.gifPlayBlock != nil, let resumeGIFBlock = config.resumeGIFBlock {
            resumeGIFBlock(imageView)
            return
        }
        
        guard imageView.layer.speed != 1 else { return }
        
        let pauseTime = imageView.layer.timeOffset
        imageView.layer.speed = 1
        imageView.layer.timeOffset = 0
        imageView.layer.beginTime = 0
        let timeSincePause = imageView.layer.convertTime(CACurrentMediaTime(), from: nil) - pauseTime
        imageView.layer.beginTime = timeSincePause
    }
    
    func pauseGif() {
        guard let m = model else { return }
        guard ZLPhotoConfiguration.default().allowSelectGif, m.type == .gif else { return }
        
        let config = ZLPhotoConfiguration.default()
        
        if config.gifPlayBlock != nil, let pauseGIFBlock = config.pauseGIFBlock {
            pauseGIFBlock(imageView)
            return
        }
        
        guard imageView.layer.speed != 0 else { return }
        
        let pauseTime = imageView.layer.convertTime(CACurrentMediaTime(), from: nil)
        imageView.layer.speed = 0
        imageView.layer.timeOffset = pauseTime
    }
    
}

extension ZLPreviewView: UIScrollViewDelegate {
    
    func viewForZooming(in scrollView: UIScrollView) -> UIView? {
        return containerView
    }
    
    func scrollViewDidZoom(_ scrollView: UIScrollView) {
        let offsetX = (scrollView.frame.width > scrollView.contentSize.width) ? (scrollView.frame.width - scrollView.contentSize.width) * 0.5 : 0
        let offsetY = (scrollView.frame.height > scrollView.contentSize.height) ? (scrollView.frame.height - scrollView.contentSize.height) * 0.5 : 0
        containerView.center = CGPoint(x: scrollView.contentSize.width * 0.5 + offsetX, y: scrollView.contentSize.height * 0.5 + offsetY)
    }
    
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        resumeGif()
    }
    
}
