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

import UIKit
import PlaygroundSupport
import SPCCore

/// A photo collection view is a component that presents a collection of photos in a scrolling display.
/// Depending on the view’s aspect ratio, the photos scroll horizontally or vertically.
///
/// You give it a photo collection to display by setting its `photoCollection` property.
///
/// You can add images to the photo collection by:
/// * Calling its `addImage(image:)` method.
/// * Connecting its `imageInput` input to an output of type `Output<Image>`.
///
/// Outputs `itemSelected` and `imageSelected` allow you to respond when someone touches a photo.
///
/// - localizationKey: PhotoCollectionView
public class PhotoCollectionView: PlaceableComponent {
    public override class var componentType: String { return String(describing: PhotoCollectionView.self) }
    
    public override var liveComponent: LiveComponent? {
        return livePhotoCollectionView
    }
    
    var livePhotoCollectionView: LivePhotoCollectionView?
    
    fileprivate enum EventName: ComponentEventName {
        case itemSelected
        case photoCollectionChanged
        case refresh
        case scrollToStart
        case scrollToEnd
        case scrollToItemAtIndex
        case selectedBorderColorChanged
    }
    
    // MARK: Properties
    
    /// The photo collection to be displayed.
    ///
    /// - localizationKey: PhotoCollectionView.photoCollection
    public var photoCollection: PhotoCollection {
        get {
            guard let photoCollection = livePhotoCollectionView?.photoCollection else { return _photoCollection }
            return photoCollection
        }
        set {
            _photoCollection = newValue
            livePhotoCollectionView?.photoCollection = newValue
            
            // playgroundValue is name of the album?
            // if so it might be better to rename PhotoAssetCollection to PhotoCollection
            // or have PhotoCollection as a subclass of PhotoAssetCollection
            updateLiveComponent(.photoCollectionChanged, value: newValue.playgroundValue)
        }
    }
    private var _photoCollection = PhotoCollection()
    
    /// The color of the border displayed around a selected photo. The default is clear (none).
    ///
    /// - localizationKey: PhotoCollectionView.selectedBorderColor
    public var selectedBorderColor: Color {
        get {
            return livePhotoCollectionView?.selectedBorderColor ?? _selectedBorderColor
        }
        set {
            _selectedBorderColor = newValue
            livePhotoCollectionView?.selectedBorderColor = newValue
            updateLiveComponent(.selectedBorderColorChanged, value: newValue.playgroundValue)
        }
    }
    private var _selectedBorderColor = UIColor()
    
    // SpacePlaceable
    
    /// The default size of the photo collection view if its size is not set: 600 in width by 200 in height.
    ///
    /// - localizationKey: PhotoCollectionView.intrinsicSize
    public override var intrinsicSize: Size { return Size(width: 600, height: 200) }
    
    // MARK: Inputs

    /// An input that accepts event notifications of type `Image`. When the input receives an event, the image is added as a new photo to the photo collection.
    ///
    /// You can connect an output of type `Output<Image>` to this input.
    ///
    /// - localizationKey: PhotoCollectionView.imageInput
    public private(set) lazy var imageInput = Input<Image>({ [weak self] image in
        self?.addImage(image: image)
    })
    
    // MARK: Outputs
    
    /// An output that sends an event notification of type `Int` when a photo is selected (touched). The integer value is the index of the selected photo in the photo collection.
    ///
    /// You can connect it to an input of type `Input<Int>`, or to a function with a single parameter of type `Int`.
    ///
    /// - localizationKey: PhotoCollectionView.itemSelected
    public var itemSelected = Output<Int>()
    
    /// An output that sends an event notification of type `Image` when a photo is selected (touched). The image value is the selected photo in the photo collection.
    ///
    /// You can connect it to an input of type `Input<Image>`, or to a function with a single parameter of type `Image`.
    ///
    /// - localizationKey: PhotoCollectionView.imageSelected
    public var imageSelected = Output<Image>()
    
    // MARK: Initialization
    override func createLiveComponent() {
        livePhotoCollectionView = LivePhotoCollectionView(component: self)
    }
    
    // MARK: Methods
        
    /// Adds an image to the photo collection.
    ///
    /// - Parameter image: The image to be added.
    ///
    /// - localizationKey: PhotoCollectionView.addImage(image:)
    public func addImage(image: Image) {
        photoCollection.addImage(image: image)
    }

    /// Refreshes the photo collection view.
    ///
    /// - localizationKey: PhotoCollectionView.refresh()
    public func refresh() {
        livePhotoCollectionView?.refresh()
        let event = ComponentEvent(name: EventName.refresh.rawValue, value: .boolean(true))
        event.send(to: self, in: .live)
    }
    
    /// Scrolls to the first photo in the photo collection.
    ///
    /// - localizationKey: PhotoCollectionView.scrollToStart()
    public func scrollToStart() {
        livePhotoCollectionView?.scrollToStart()
        let event = ComponentEvent(name: EventName.scrollToStart.rawValue, value: .boolean(true))
        event.send(to: self, in: .live)
    }
    
    /// Scrolls to the last photo in the photo collection.
    ///
    /// - localizationKey: PhotoCollectionView.scrollToEnd()
    public func scrollToEnd() {
        livePhotoCollectionView?.scrollToEnd()
        let event = ComponentEvent(name: EventName.scrollToEnd.rawValue, value: .boolean(true))
        event.send(to: self, in: .live)
    }
    
    /// Scrolls until the specified photo is visible.
    ///
    /// - Parameter image: The index of the photo.
    ///
    /// - localizationKey: PhotoCollectionView.scrollToItem(at:)
    public func scrollToItem(at index: Int) {
        livePhotoCollectionView?.scrollToItem(at: index)
        let event = ComponentEvent(name: EventName.scrollToItemAtIndex.rawValue, value: .integer(index))
        event.send(to: self, in: .live)
    }
    
    // MARK: Messaging
    private func updateLiveComponent(_ eventName: EventName, value: PlaygroundValue?) {
        guard let playgroundValue = value else { return }
        let event = ComponentEvent(name: eventName.rawValue, value: playgroundValue)
        event.send(to: self, in: .live)
    }
    
    public override func receive(_ event: ComponentEvent, from origin: ComponentMessageOrigin) {
        super.receive(event, from: origin)
        guard let eventName = EventName(rawValue: event.name) else { return }
        switch eventName {
        case .itemSelected:
            guard case .integer(let index) = event.value else { return }
            itemSelected.notifyInputs(index)
            guard index < photoCollection.count else { return }
            imageSelected.notifyInputs(photoCollection[index].image)
        case .photoCollectionChanged:
            guard let newPhotoCollection = PhotoCollection.from(event.value) as? PhotoCollection else { return }
            livePhotoCollectionView?.photoCollection = newPhotoCollection
        case .refresh:
            livePhotoCollectionView?.refresh()
        case .scrollToStart:
            livePhotoCollectionView?.scrollToStart()
        case .scrollToEnd:
            livePhotoCollectionView?.scrollToEnd()
        case .scrollToItemAtIndex:
            guard case .integer(let index) = event.value else { return }
            livePhotoCollectionView?.scrollToItem(at: index)
        case .selectedBorderColorChanged:
            guard let newSelectedBorderColor = UIColor.from(event.value) as? UIColor else { return }
            _selectedBorderColor = newSelectedBorderColor
            livePhotoCollectionView?.selectedBorderColor = newSelectedBorderColor
        }
    }
}

class LivePhotoCollectionView: PhotoAssetCollectionView, LiveComponent {
    weak var component: Component?
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    required init(component: Component) {
        self.component = component
        super.init(frame: CGRect.zero)
        isAccessibilityElement = true
        setAccessibilityInfo()
        
        didSelectItemAt = { index in
            guard let playgroundValue = index.playgroundValue else { return }
            let event = ComponentEvent(name: PhotoCollectionView.EventName.itemSelected.rawValue, value: playgroundValue)
            if Process.isLiveViewConnectionOpen {
                event.send(to: component, in: Environment.user) // User component
            } else {
                // Not running in playgroundbook
                component.receive(event, from: ComponentMessageOrigin.current) // Local component
            }
        }
    }
}
