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

import UIKit
import PlaygroundSupport
import SPCCore

/// A component that can be placed within a `ContainerSpace` should conform to `SpacePlaceable`.
///
/// - localizationKey: SpacePlaceable
public protocol SpacePlaceable {
    var position: Point { get set }
    var size: Size { get set }
    var intrinsicSize: Size { get }
}

enum SpaceEventName: ComponentEventName {
    case addComponent
    case removeComponent
    case removeAllComponents
}

/// A component that can have components placed within it should conform to `ContainerSpace`.
///
/// - localizationKey: ContainerSpace
public protocol ContainerSpace: class {
    var subcomponents: [PlaceableComponent] { get set }
    func add(_ component: PlaceableComponent, at position: Point, size: Size?)
    func remove(_ component: PlaceableComponent)
    func removeAll()
    var liveSpaceComponent: (LiveComponent & ContainerSpace)? { get }
    var liveSubcomponents: [PlaceableComponent] { get }
}

// Default implementations
public extension ContainerSpace {
    
    // Default handler for space events.
    func handleSpaceEvent(_ event: ComponentEvent, from origin: ComponentMessageOrigin) {
        //PBLog("event: \(event.name) sent from: \(origin) (\(type(of: self)))")
        guard let eventName = SpaceEventName(rawValue: event.name) else { return }
        switch eventName {
        case .addComponent, .removeComponent:
            guard
                case .dictionary(let dict) = event.value,
                let idValue = dict["id"],
                let id = String.from(idValue) as? String,
                let nameValue = dict["name"],
                let name = String.from(nameValue) as? String
                else { return }
            let componentIdentity = ComponentIdentity(name: name, identifier: id)
            var foundComponent: PlaceableComponent?
            if let component = ComponentManager.shared.component(for: componentIdentity) as? PlaceableComponent {
                foundComponent = component
            }
            guard let placeableComponent = foundComponent else {
                PBLog("event: \(event.name) component: \(componentIdentity) not found.")
                return
            }
            switch eventName {
            case .addComponent:
                guard
                    let positionValue = dict["position"],
                    let position = Point.from(positionValue) as? Point,
                    let sizeValue = dict["size"],
                    let size = Size.from(sizeValue) as? Size
                    else { return }
                liveSpaceComponent?.add(placeableComponent, at: position, size: size)
            case .removeComponent:
                liveSpaceComponent?.remove(placeableComponent)
            default: break
            }
        case .removeAllComponents:
            liveSpaceComponent?.removeAll()
        }
    }
}

// Default implementations for any `Component` that conforms to `ContainerSpace`.
public extension ContainerSpace where Self: Component {
    
    /// Adds a component to the space at a specified position, and with an optional size.
    ///
    /// - Parameter component: The placeable component to be added.
    /// - Parameter position: The position of the center of the component within the space.
    /// - Parameter size: The size of the component (optional). If left out, the size will default to the `intrinsicSize` of the component.
    ///
    /// - localizationKey: ContainerSpace.add(_:at:size)
    func add(_ component: PlaceableComponent, at position: Point, size: Size? = nil) {
        subcomponents.append(component)
        let initialSize = size ?? component.intrinsicSize

        if Process.isUser {
            // Set position and size of local component.
            component.position = position
            component.size = initialSize
            // Update live component by sending an addComponent event.
            var dict = [String : PlaygroundValue]()
            dict["name"] = component.name.playgroundValue
            dict["id"] = component.identity.identifier.playgroundValue
            dict["position"] = component.position.playgroundValue
            dict["size"] = component.size.playgroundValue
            guard let playgroundValue = PlaygroundValueDictionary(dictionary: dict).playgroundValue else { return }
            let event = ComponentEvent(name: SpaceEventName.addComponent.rawValue, value: playgroundValue)
            let message = ComponentMessage.event(origin: ComponentMessageOrigin.current, identity: identity, event: event)
            message.send(to: .live)
        } else {
            guard let liveSpace = liveSpaceComponent else { return }
            component.position = position
            component.size = initialSize
            liveSpace.add(component, at: position, size: initialSize)
        }
    }
    
    /// Removes a component from the space.
    ///
    /// - Parameter component: The placeable component to be removed.
    ///
    /// - localizationKey: ContainerSpace.remove(_:)
    func remove(_ component: PlaceableComponent) {
        subcomponents = subcomponents.filter { $0 != component }
        
        if Process.isUser {
            // Update live component by sending a removeComponent event.
            var dict = [String : PlaygroundValue]()
            dict["name"] = component.name.playgroundValue
            dict["id"] = component.identity.identifier.playgroundValue
            guard let playgroundValue = PlaygroundValueDictionary(dictionary: dict).playgroundValue else { return }
            let event = ComponentEvent(name: SpaceEventName.removeComponent.rawValue, value: playgroundValue)
            let message = ComponentMessage.event(origin: ComponentMessageOrigin.current, identity: identity, event: event)
            message.send(to: .live)
        } else {
            guard let liveSpace = liveSpaceComponent else { return }
            liveSpace.remove(component)
        }
    }
    
    /// Removes all components from the space.
    ///
    /// - localizationKey: ContainerSpace.removeAll()
    func removeAll() {
        subcomponents.removeAll()
        if Process.isUser {
            guard let trueValue = true.playgroundValue else { return }
            let event = ComponentEvent(name: SpaceEventName.removeAllComponents.rawValue, value: trueValue)
            let message = ComponentMessage.event(origin: ComponentMessageOrigin.current, identity: identity, event: event)
            message.send(to: .live)
        } else {
            guard let liveSpace = liveSpaceComponent else { return }
            liveSpace.removeAll()
        }
    }

}
