//
//  PhotoAssetCollection.swift
//  
//  Copyright © 2016-2019 Apple Inc. All rights reserved.
//

import Foundation
import Photos
import SPCCore

/// A photo asset collection represents a collection of **Photos** photos.
/// It conforms to the Swift `Collection` and `Sequence` protocols so you can use it just like an array. For example:
/// ```
/// let photoCount = photoAssetCollection.count
/// let photo = photoAssetCollection[12]
/// ```
///
/// - localizationKey: PhotoAssetCollection
public class PhotoAssetCollection: NSObject {
    var assetCollection: PHAssetCollection?
    private var _assets: PHFetchResult<PHObject>?
    private var assets: PHFetchResult<PHObject>? {
        if _assets == nil {
            guard let assetCollection = self.assetCollection else { return nil }
            _assets = PHAsset.fetchAssets(in: assetCollection, options: nil) as? PHFetchResult<PHObject>
        }
        return _assets
    }
    
    private var iteratorIndex: Int = 0
    
    /// The identifier of photo asset collection, if it has one.
    ///
    /// - localizationKey: PhotoAssetCollection.identifier
    public var identifier: String?
    
    /// The name of the photo asset collection.
    ///
    /// - localizationKey: PhotoAssetCollection.name
    public var name: String {
        return assetCollection?.localizedTitle ?? ""
    }
    
    // MARK: Outputs
    
    /// An output that sends an event notification of type `Pulse` each time the photo asset collection is updated.
    ///
    /// You can connect it to an input of type `Input<Pulse>`, or to a function with a single parameter of type `Pulse`.
    ///
    /// - localizationKey: PhotoAssetCollection.updated
    public var updated = Output<Pulse>()

    /// Creates an empty photo asset collection.
    ///
    /// - localizationKey: PhotoAssetCollection()
    public override init() {
        super.init()
        
        // Register for change notifications.
        PHPhotoLibrary.shared().register(self)
    }
    
    deinit {
        PHPhotoLibrary.shared().unregisterChangeObserver(self)
    }
    
    /// Creates a photo asset collection to hold a **Photos** photo asset collection with the specified identifier.
    ///
    /// - Parameter identifier: The identifier of the asset collection to be loaded.
    ///
    /// - localizationKey: PhotoAssetCollection(identifier:)
    public init(identifier: String) {
        self.assetCollection = PhotoAssetCollection.getAlbum(identifier: identifier)
        if self.assetCollection == nil {
            PBLog("Photos album not found for identifier: \(identifier)")
        } else {
            self.identifier = identifier
        }
    }
    
    /// The number of photo assets in the photo collection.
    ///
    /// - localizationKey: PhotoAssetCollection.count
    public var count: Int {
        guard let assets = assets else { return 0 }
        return assets.count
    }
    
    /// Adds an image to the photo collection.
    ///
    /// - Parameter image: The image to be added.
    ///
    /// - localizationKey: PhotoAssetCollection.addImage(image:)
    public func addImage(image: Image) {
        addImage(image: image, completion: { _ in
            
        })
    }
    
    func addImage(image: Image, completion: @escaping (_ success: Bool) -> ()) {
        guard let assetCollection = self.assetCollection else {
            completion(false)
            return
        }
        
        PhotosUtilities.addImage(image, to: assetCollection, completion: { success in
            completion(success)
        })
    }
    
    func rehydrate() {
        _assets = nil
        let _ = assets
    }
    
    // MARK: Private

    private static func getAlbum(identifier: String) -> PHAssetCollection? {
        let fetchOptions = PHFetchOptions()
        let collections = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [identifier], options: fetchOptions)
        if collections.count > 0 {
            return collections.firstObject
        }
        return nil
    }
    
}

extension PhotoAssetCollection: Collection {

    public var startIndex: Int {
        return 0
    }
    
    public var endIndex: Int {
        guard let assets = assets else { return 0 }
        return assets.count
    }
    
    public func index(after i: Int) -> Int {
        return i + 1
    }
    
    public subscript(i: Int) -> PhotoAsset {
        guard let assets = assets else { return PhotoAsset() }
        guard (i >= 0) && (i < assets.count) else {
            assertionFailure("Photo collection index (\(i)) is out of range.\nMust be between 0 and \(assets.count - 1).")
            return PhotoAsset()
        }
        let asset = assets[i] as! PHAsset
        return PhotoAsset(asset: asset)
    }
}

extension PhotoAssetCollection: Sequence, IteratorProtocol {
    
    public func next() -> PhotoAsset? {
        if iteratorIndex >= count {
            return nil
        }
        
        defer {
            iteratorIndex += 1
        }
        
        return self[iteratorIndex]
    }
}

extension PhotoAssetCollection: PHPhotoLibraryChangeObserver {
    
    public func photoLibraryDidChange(_ changeInstance: PHChange) {
        guard
            let assets = self.assets
            else { return }        
        // Check for changes to the list of assets (insertions, deletions, moves, or updates).
        if let changes = changeInstance.changeDetails(for: assets) {
            // Update the fetch results.
            _assets = changes.fetchResultAfterChanges
            // Notify updates.
            DispatchQueue.main.async {
                self.updated.notifyInputs(true)
            }
        }
    }
}

