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

import UIKit
import SPCCore

/// The component manager acts as a central repository for component instances.
/// Used as a singleton, there is one instance of `ComponentManager` per process, accessed through the `shared` property.
///
/// - localizationKey: ComponentManager
public class ComponentManager {
    /// Shared instance of ComponentManager.
    ///
    /// - localizationKey: ComponentManager.shared
    public static let shared = ComponentManager()
    
    // Dictionary of component instances keyed on identity.
    private var _components = [ComponentIdentity: AnyObject]()
    
    private var isClearing = false
    
    /// Set to `true` if the component manager is running in a Playgrounds live view.
    /// If `true`, the component manager will hold strong references to components.
    ///
    /// - localizationKey: ComponentManager.isRunningInLiveView
    public var isRunningInLiveView = false {
        didSet {
            if isRunningInLiveView {
                PBLog("ComponentManager is running in the live view.")
            }
        }
    }
    
    // Returns a component instance, whether wrapped in a WeakObject or not.
    private func componentFromAnyObject(_ object: AnyObject) -> Component? {
        if let weakObject = object as? WeakObject<Component> {
            return weakObject.object
        }
        if let node = object as? Component {
            return node
        }
        return nil
    }
    
    /// The current components as an array.
    ///
    /// - localizationKey: ComponentManager.components
    public var components: [Component] {
        return _components.values.compactMap{ componentFromAnyObject($0) }
    }

    // The current live components as an array. These are the underlying objects that the components represent.
    // For example a `Slider` component has a `UISlider` live component.
    var liveComponents: [LiveComponent] {
        return _components.values.compactMap{ componentFromAnyObject($0)?.liveComponent }
    }
    
    // The subset of current live components that are subclasses of `UIView`, as an array.
    var liveComponentViews: [UIView] {
        return _components.values.compactMap{ componentFromAnyObject($0)?.liveComponent as? UIView }
    }
    
    /// Registers a component instance with ComponentManager.
    ///
    /// - Parameter component: The component to be registered.
    ///
    /// - localizationKey: ComponentManager.register(_:component:)
    public func register(_ component: Component) {
        //PBLog("+++++++ register: \(component.identity.name) \(component.identity.identifier)")
        let object: AnyObject = isRunningInLiveView ? component : WeakObject<Component>(component)
        if _components.isEmpty {
            let inLiveView = isRunningInLiveView ? " in live view" : ""
            PBLog("ComponentManager: first component registered\(inLiveView): \(component.description)")
        }
        _components[component.identity] = object
    }
    
    /// Deregisters a component instance from ComponentManager.
    ///
    /// - Parameter identity: The identity of the component to be deregistered.
    ///
    /// - localizationKey: ComponentManager.deregisterComponentWith(identity:)
    public func deregisterComponentWith(identity: ComponentIdentity) {
        guard !isClearing else {
            // Clearing is in progress, so no need to deregister individual components since a bulk register is already in progress.
            return
        }
        _components.removeValue(forKey: identity)
        //PBLog("------ deregister: \(identity.name) \(identity.identifier) components: \(components.count)")
        if _components.values.count == 0 {
            PBLog("ComponentManager: all components deregistered.")
        }
    }
    
    /// Clears all components.
    ///
    /// - localizationKey: ComponentManager.clear()
    public func clear() {
        // When using strong references, emptying of _components will cause component instances
        // to deinit triggering a call to deregister.
        isClearing = true
        _components = [:]
        isClearing = false
        PBLog("ComponentManager: components cleared.")
    }
    
    /// Resets all components and then clears them.
    ///
    /// - localizationKey: ComponentManager.reset()
    public func reset() {
        liveComponents.forEach {
            // Stop the capture session for any camera views.
            if let cameraView = $0.component as? CameraView {
                cameraView.stop()
            }
        }
        liveComponentViews.forEach { $0.removeFromSuperview() }
        PBLog("ComponentManager: reset.")
        clear()
    }
    
    /// Returns the component with a specified identity, or `nil` if not found.
    ///
    /// - Parameter identity: The identity of the component to be found.
    ///
    /// - localizationKey: ComponentManager.component(for:)
    public func component(for identity: ComponentIdentity) -> Component? {
        guard let object = _components[identity] else {
            PBLog("ComponentManager node not found: \(identity.name) \(identity.identifier)")
            return nil
        }
        return componentFromAnyObject(object)
    }
}
