 
 import Photos
 
+public typealias DKImageRequestID = Int32
+
 public func getImageDataManager() -> DKImageDataManager {
 	return DKImageDataManager.sharedInstance
 }

    public func stopCachingForAllAssets() {
        self.manager.stopCachingImagesForAllAssets()
    }
}

	
    private let manager = PHCachingImageManager()
	
	private lazy var imageRequestOptions: PHImageRequestOptions = {
		let options = PHImageRequestOptions()
        options.isNetworkAccessAllowed = true
		
		return options
	}()
	
	private lazy var videoRequestOptions: PHVideoRequestOptions = {
		let options = PHVideoRequestOptions()
		options.deliveryMode = .mediumQualityFormat
        options.isNetworkAccessAllowed = true
		
		return options
	}()
    
    @discardableResult
	public func fetchImage(for asset: DKAsset, size: CGSize, completeBlock: @escaping (_ image: UIImage?, _ info: [AnyHashable: Any]?) -> Void) -> DKImageRequestID {
        return self.fetchImage(for: asset, size: size, options: nil, completeBlock: completeBlock)
	}
	
    @discardableResult
	public func fetchImage(for asset: DKAsset, size: CGSize, contentMode: PHImageContentMode, completeBlock: @escaping (_ image: UIImage?, _ info: [AnyHashable: Any]?) -> Void) -> DKImageRequestID {
        return self.fetchImage(for: asset, size: size, options: nil, contentMode: contentMode, completeBlock: completeBlock)
	}

    @discardableResult
	public func fetchImage(for asset: DKAsset, size: CGSize, options: PHImageRequestOptions?, completeBlock: @escaping (_ image: UIImage?, _ info: [AnyHashable: Any]?) -> Void) -> DKImageRequestID {
        return self.fetchImage(for: asset, size: size, options: options, contentMode: .aspectFill, completeBlock: completeBlock)
    }
    
    @discardableResult
    public func fetchImage(for asset: DKAsset, size: CGSize, options: PHImageRequestOptions?, contentMode: PHImageContentMode,
                           completeBlock: @escaping (_ image: UIImage?, _ info: [AnyHashable: Any]?) -> Void) -> DKImageRequestID {
        return self.fetchImage(for: asset, size: size, options: options, contentMode: contentMode, oldRequestID: nil, completeBlock: completeBlock)
    }
    
    @discardableResult
    private func fetchImage(for asset: DKAsset, size: CGSize, options: PHImageRequestOptions?, contentMode: PHImageContentMode,
                            oldRequestID: DKImageRequestID?,
                            completeBlock: @escaping (_ image: UIImage?, _ info: [AnyHashable: Any]?) -> Void) -> DKImageRequestID {
        let requestID = oldRequestID ?? self.getSeed()
        
        let requestOptions = options ?? self.imageRequestOptions
        let imageRequestID = self.manager.requestImage(for: asset.originalAsset!,
                                                       targetSize: size,
                                                       contentMode: contentMode,
                                                       options: requestOptions,
                                                       resultHandler: { image, info in
                                                        if let info = info, let isCancelled = info[PHImageCancelledKey] as? NSNumber, isCancelled.boolValue {
                                                            completeBlock(image, info)
                                                            return
                                                        }
                                                        
                                                        if let isInCloud = info?[PHImageResultIsInCloudKey] as AnyObject?
                                                            , image == nil && isInCloud.boolValue && !requestOptions.isNetworkAccessAllowed {
                                                            if self.requestIDs[requestID] == nil {
                                                                completeBlock(nil, [PHImageCancelledKey : NSNumber(value: 1)])
                                                                return
                                                            }
                                                            
                                                            let requestCloudOptions = requestOptions.copy() as! PHImageRequestOptions
                                                            requestCloudOptions.isNetworkAccessAllowed = true
                                                            
                                                            self.fetchImage(for: asset, size: size, options: options, contentMode: contentMode, oldRequestID: requestID, completeBlock: completeBlock)
                                                        } else {
                                                            self.update(requestID: requestID, with: nil)
                                                            completeBlock(image, info)
                                                        }
        })
        
        self.update(requestID: requestID, with: imageRequestID)
        return requestID
    }
    
    @discardableResult
    public func fetchImageData(for asset: DKAsset, options: PHImageRequestOptions?, completeBlock: @escaping (_ data: Data?, _ info: [AnyHashable: Any]?) -> Void) -> DKImageRequestID {
        return self.fetchImageData(for: asset, options: options, oldRequestID: nil, completeBlock: completeBlock)
    }
    
    @discardableResult
    private func fetchImageData(for asset: DKAsset, options: PHImageRequestOptions?, oldRequestID: DKImageRequestID?, completeBlock: @escaping (_ data: Data?, _ info: [AnyHashable: Any]?) -> Void) -> DKImageRequestID {
        let requestID = oldRequestID ?? self.getSeed()
        
        let requestOptions = options ?? self.imageRequestOptions
        let imageRequestID = self.manager.requestImageData(for: asset.originalAsset!,
                                                           options: requestOptions) { (data, dataUTI, orientation, info) in
                                                            if let info = info, let isCancelled = info[PHImageCancelledKey] as? NSNumber, isCancelled.boolValue {
                                                                completeBlock(data, info)
                                                                return
                                                            }
                                                            
                                                            if let isInCloud = info?[PHImageResultIsInCloudKey] as AnyObject?
                                                                , data == nil && isInCloud.boolValue && !requestOptions.isNetworkAccessAllowed {
                                                                if self.requestIDs[requestID] == nil {
                                                                    completeBlock(nil, [PHImageCancelledKey : NSNumber(value: 1)])
                                                                    return
                                                                }
                                                                
                                                                let requestCloudOptions = requestOptions.copy() as! PHImageRequestOptions
                                                                requestCloudOptions.isNetworkAccessAllowed = true
                                                                
                                                                self.fetchImageData(for: asset, options: requestCloudOptions, oldRequestID: requestID, completeBlock: completeBlock)
                                                            } else {
                                                                self.update(requestID: requestID, with: nil)
                                                                completeBlock(data, info)
                                                            }
        }
        
        self.update(requestID: requestID, with: imageRequestID)
        return requestID
    }
    
    @discardableResult
	public func fetchAVAsset(for asset: DKAsset, completeBlock: @escaping (_ avAsset: AVAsset?, _ info: [AnyHashable: Any]?) -> Void) -> DKImageRequestID {
        return self.fetchAVAsset(for: asset, options: nil, completeBlock: completeBlock)
	}
	
    @discardableResult
    public func fetchAVAsset(for asset: DKAsset, options: PHVideoRequestOptions?, completeBlock: @escaping (_ avAsset: AVAsset?, _ info: [AnyHashable: Any]?) -> Void) -> DKImageRequestID {
        return self.fetchAVAsset(for: asset, options: options, oldRequestID: nil, completeBlock: completeBlock)
    }
    
    @discardableResult
    private func fetchAVAsset(for asset: DKAsset, options: PHVideoRequestOptions?, oldRequestID: DKImageRequestID?, completeBlock: @escaping (_ avAsset: AVAsset?, _ info: [AnyHashable: Any]?) -> Void) -> DKImageRequestID {
        let requestID = oldRequestID ?? self.getSeed()
        
        let requestOptions = options ?? self.videoRequestOptions
        let imageRequestID = self.manager.requestAVAsset(forVideo: asset.originalAsset!,
                                                         options: requestOptions) { avAsset, audioMix, info in
                                                            if let info = info, let isCancelled = info[PHImageCancelledKey] as? NSNumber, isCancelled.boolValue {
                                                                completeBlock(avAsset, info)
                                                                return
                                                            }

                                                            if let isInCloud = info?[PHImageResultIsInCloudKey] as AnyObject?
                                                                , avAsset == nil && isInCloud.boolValue && !requestOptions.isNetworkAccessAllowed {
                                                                if self.requestIDs[requestID] == nil {
                                                                    completeBlock(nil, [PHImageCancelledKey : NSNumber(value: 1)])
                                                                    return
                                                                }

                                                                let requestCloudOptions = requestOptions.copy() as! PHVideoRequestOptions
                                                                requestCloudOptions.isNetworkAccessAllowed = true
                                                                
                                                                self.fetchAVAsset(for: asset, options: requestCloudOptions, oldRequestID: requestID, completeBlock: completeBlock)
                                                            } else {
                                                                self.update(requestID: requestID, with: nil)
                                                                completeBlock(avAsset, info)
                                                            }
        }
        
        self.update(requestID: requestID, with: imageRequestID)
        return requestID
    }
    
    public func cancelRequest(requestID: DKImageRequestID) {
        objc_sync_enter(self)
        defer { objc_sync_exit(self) }
        
        if let imageRequestID = self.requestIDs[requestID] {
            self.manager.cancelImageRequest(imageRequestID)
        }
        
        self.update(requestID: requestID, with: nil)
    }
    
    public func startCachingAssets(for assets: [PHAsset], targetSize: CGSize, contentMode: PHImageContentMode, options: PHImageRequestOptions?) {
        self.manager.startCachingImages(for: assets, targetSize: targetSize, contentMode: contentMode, options: options)
