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

import UIKit
import Photos
import SPCCore

class PhotoAssetCollectionViewCell: UICollectionViewCell {
    
    private var imageView: UIImageView!
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        initialize()
    }
    
    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        initialize()
    }
    
    private func initialize() {
        imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: frame.width, height: frame.height))
        imageView.contentMode = .scaleAspectFit
        contentView.addSubview(imageView)
        backgroundColor = .clear
    }
    
    var photoAsset: PhotoAsset? {
        didSet {
            self.imageView.image = nil
            guard let photoAsset = photoAsset else { return }
            photoAsset.fetchImage(targetSize: Size(imageView.bounds.size), completion: { image in
                //print("image: \(photoAsset.identifier ?? "") \(image.size)")
                self.imageView.image = image
            })
        }
    }
    
    var image: UIImage? {
        get {
            return imageView.image
        }
        set(newImage) {
            imageView.image = newImage
        }
    }
    
    var selectedBorderColor: UIColor = .clear {
        didSet {
            updateBorderColor()
        }
    }
    
    override open var isSelected: Bool {
        didSet {
            updateBorderColor()
        }
    }
    
    override public func layoutSubviews() {
        
        super.layoutSubviews()
        imageView.frame = self.bounds
    }
    
    private func updateBorderColor() {
        imageView.layer.borderWidth = isSelected ? 1.5 : 0.0
        imageView.layer.borderColor = isSelected ? selectedBorderColor.cgColor : nil
    }
}


class PhotoAssetCollectionView: UIView {
    private static let photoCellReuseIdentifier = "photoCell"
    
    private var collectionView: UICollectionView!
    
    private var assetCollection: PHAssetCollection? {
        didSet {
            refresh()
        }
    }
    private var _albumAssets: PHFetchResult<PHObject>?
    private var albumAssets: PHFetchResult<PHObject>? {
        if _albumAssets == nil {
            guard let assetCollection = self.assetCollection else { return nil }
            _albumAssets = PHAsset.fetchAssets(in: assetCollection, options: nil) as? PHFetchResult<PHObject> //as
        }
        return _albumAssets
    }
    
    public var photoCollection = PhotoCollection() {
        didSet {
            assetCollection = photoCollection.assetCollection
        }
    }

    public var photoAssetCollection = PhotoAssetCollection() {
        didSet {
            assetCollection = photoAssetCollection.assetCollection
        }
    }
    
    public var selectedBorderColor: UIColor = #colorLiteral(red: 0.2392156869, green: 0.6745098233, blue: 0.9686274529, alpha: 1) {
        didSet {
            collectionView.visibleCells.forEach {
                ($0 as? PhotoAssetCollectionViewCell)?.selectedBorderColor = selectedBorderColor
            }
        }
    }
    
    public var didSelectItemAt: ((_ index: Int) -> Void)?
    
    public var minimumPhotoCellSize = CGSize(width: 80, height: 80) {
        didSet { setNeedsLayout() }
    }
    
    public var interCellSpacing: CGFloat = 4 {
        didSet {
            guard let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return }
            layout.minimumInteritemSpacing = interCellSpacing
            layout.minimumLineSpacing = interCellSpacing
            setNeedsLayout()
        }
    }
    
    public var photoCellInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) {
        didSet {
            guard let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return }
            layout.sectionInset = photoCellInsets
//            setNeedsLayout()
        }
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        initialize()
    }
    
    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        initialize()
    }
    
    convenience init(collection: PhotoAssetCollection) {
        self.init(frame: CGRect.zero)
        assetCollection = collection.assetCollection
        self.collectionView.reloadData()
    }
    
    private func initialize() {
        let layout = UICollectionViewFlowLayout()
        layout.scrollDirection = .horizontal
        layout.itemSize = minimumPhotoCellSize
        layout.sectionInset = photoCellInsets
        layout.minimumInteritemSpacing = interCellSpacing
        layout.minimumLineSpacing = interCellSpacing

        collectionView = UICollectionView(frame: bounds, collectionViewLayout: layout)
        collectionView.backgroundColor = .clear
        collectionView.register(PhotoAssetCollectionViewCell.self,
                                          forCellWithReuseIdentifier: PhotoAssetCollectionView.photoCellReuseIdentifier)
        collectionView.delegate = self
        collectionView.dataSource = self
        collectionView.isAccessibilityElement = false
        addSubview(collectionView)
        
        // Register for change notifications.
        PHPhotoLibrary.shared().register(self)
    }
    
    deinit {
        PHPhotoLibrary.shared().unregisterChangeObserver(self)
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        let borderInsets = UIEdgeInsets(top: layer.borderWidth, left: layer.borderWidth, bottom: layer.borderWidth, right: layer.borderWidth)
        collectionView.frame = bounds.inset(by: borderInsets)
        guard let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return }
        
        // Compute optimal cell size.
        var suggestedCellSize = minimumPhotoCellSize
        var useMinimumSize = false
        let availableBounds = collectionView.bounds.inset(by: photoCellInsets)
        guard availableBounds.size.width > 0, availableBounds.size.height > 0 else {
            layout.itemSize = CGSize.zero
            return
        }
        if bounds.size.width >= bounds.size.height {
            // Scrolls horizontally => work out number of rows that fit within available height.
            layout.scrollDirection = .horizontal
            let rows = Int((availableBounds.height + interCellSpacing)/(minimumPhotoCellSize.height + interCellSpacing))
            var cellHeight = availableBounds.height
            if rows > 0 {
                cellHeight = (availableBounds.height - (CGFloat(rows - 1) * interCellSpacing)) / CGFloat(rows)
            } else {
                useMinimumSize = true
            }
            suggestedCellSize.height = cellHeight
        } else {
            // Scrolls vertically => work out number of columns that fit within available width.
            layout.scrollDirection = .vertical
            let columns = Int((availableBounds.width + interCellSpacing)/(minimumPhotoCellSize.width + interCellSpacing))
            var cellWidth = availableBounds.width
            if columns > 0 {
                cellWidth = (availableBounds.width - (CGFloat(columns - 1) * interCellSpacing)) / CGFloat(columns)
            } else {
                useMinimumSize = true
            }
            suggestedCellSize.width = cellWidth
        }
        
        var newCellSize = max(suggestedCellSize.width, suggestedCellSize.height)
        if useMinimumSize {
            newCellSize = min(suggestedCellSize.width, suggestedCellSize.height)
        }
        layout.itemSize = CGSize(width: newCellSize, height: newCellSize)
    }

    // MARK: Custom methods
    
    public func refresh() {
        refreshAlbumAssets()
        DispatchQueue.main.async {
            self.collectionView.reloadData()
        }
    }
    
    public func scrollToItem(at index: Int, animated: Bool = false) {
        guard let albumAssets = self.albumAssets, index < albumAssets.count else { return }
        DispatchQueue.main.async {
            let indexPath = IndexPath(item: index, section: 0)
            var scrollPosition: UICollectionView.ScrollPosition = .right
            if let layout = self.collectionView.collectionViewLayout as? UICollectionViewFlowLayout,
                layout.scrollDirection == .vertical {
                scrollPosition = .bottom
            }
            self.collectionView.scrollToItem(at: indexPath, at: scrollPosition, animated: animated)
        }
    }
    
    public func scrollToStart() {
        scrollToItem(at: 0)
    }
    
    public func scrollToEnd() {
        guard let albumAssets = self.albumAssets, albumAssets.count > 0 else { return }
        scrollToItem(at: albumAssets.count - 1)
    }
    
    public var photoCount: Int {
        guard let albumAssets = self.albumAssets else { return 0 }
        return albumAssets.count
    }
    
    // MARK: Private
    func refreshAlbumAssets() {
        _albumAssets = nil
        let _ = albumAssets
    }
    
}

extension PhotoAssetCollectionView: PHPhotoLibraryChangeObserver {
    
    func photoLibraryDidChange(_ changeInstance: PHChange) {
        guard
            let albumAssets = self._albumAssets
            else { return }
        // Change notifications may be made on a background queue.
        DispatchQueue.main.sync {
            // Check for changes to the list of assets (insertions, deletions, moves, or updates).
            if let changes = changeInstance.changeDetails(for: albumAssets) {
                // Keep the new fetch result for future use.
                _albumAssets = changes.fetchResultAfterChanges
                if changes.hasIncrementalChanges {
                    // If there are incremental diffs, animate them in the collection view.
                    var indexToScrollTo: Int?
                    collectionView.performBatchUpdates({
                        // For indexes to make sense, updates must be in this order:
                        // delete, insert, reload, move
                        if let removed = changes.removedIndexes, removed.count > 0 {
                            collectionView.deleteItems(at: removed.map { IndexPath(item: $0, section:0) })
                        }
                        if let inserted = changes.insertedIndexes, inserted.count > 0 {
                            collectionView.insertItems(at: inserted.map { IndexPath(item: $0, section:0) })
                            indexToScrollTo = inserted.last
                        }
                        if let changed = changes.changedIndexes, changed.count > 0 {
                            collectionView.reloadItems(at: changed.map { IndexPath(item: $0, section:0) })
                            indexToScrollTo = changed.last
                        }
                        changes.enumerateMoves { fromIndex, toIndex in
                            self.collectionView.moveItem(at: IndexPath(item: fromIndex, section: 0),
                                                    to: IndexPath(item: toIndex, section: 0))
                        }
                    }, completion: { _ in
                        if let indexToScrollTo = indexToScrollTo {
                            self.scrollToItem(at: indexToScrollTo, animated: true)
                        }
                    })
                } else {
                    // Reload the collection view if incremental diffs are not available.
                    collectionView.reloadData()
                }
            }
        }
    }

}

// MARK: UICollectionViewDelegate
extension PhotoAssetCollectionView: UICollectionViewDelegate {
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        didSelectItemAt?(indexPath.row)
    }
    
}

// MARK: UICollectionViewDataSource
extension PhotoAssetCollectionView: UICollectionViewDataSource {
    
    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 1
    }
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        guard let albumAssets = self.albumAssets else { return 0 }
        return albumAssets.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PhotoAssetCollectionView.photoCellReuseIdentifier, for: indexPath as IndexPath) as! PhotoAssetCollectionViewCell
        cell.photoAsset = nil
        cell.selectedBorderColor = selectedBorderColor
        
        if let albumAssets = self.albumAssets,
            let asset = albumAssets[indexPath.row] as? PHAsset {
            cell.photoAsset = PhotoAsset(asset: asset)
        }
        
        return cell
    }
}

