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

import UIKit
import PlaygroundSupport
import SPCCore

/// A placeable component is a component that can be placed in a space.
///
/// `PlaceableComponent` is the base class for all user interface (UI) components such as `Button`, `ImageView`, `Slider`, etc.
/// All these UI components inherit properties and methods from `PlaceableComponent`, for example, `backgroundColor`, `position`, `size`.
///
/// - localizationKey: PlaceableComponent
open class PlaceableComponent: Component, SpacePlaceable {
    private var liveView: UIView? {
        guard Process.isLive else { return nil }
        return liveComponent as? UIView
    }
    
    enum PlaceableComponentEventName: ComponentEventName {
        case alphaChanged
        case backgroundColorChanged
        case borderColorChanged
        case borderWidthChanged
        case contentModeChanged
        case cornerRadiusChanged
        case isHiddenChanged
        case moveTo
        case positionChanged
        case sizeChanged
        case transformChanged
    }
    
    // MARK: Properties
    
    /// How transparent the component is — from 0.0 (totally transparent) to 1.0 (totally opaque).
    ///
    /// - localizationKey: PlaceableComponent.alpha
    public var alpha: Double {
        get {
            if let alpha = liveView?.alpha {
                return Double(alpha)
            } else {
                return _alpha
            }
        }
        set {
            _alpha = newValue
            liveView?.alpha = CGFloat(newValue)
            updateLiveComponent(.alphaChanged, value: newValue.playgroundValue)
        }
    }
    private var _alpha = 1.0
    
    /// The background color of the component, with the default being clear.
    ///
    /// - localizationKey: PlaceableComponent.backgroundColor
    public var backgroundColor: Color {
        get {
            if let color = liveView?.backgroundColor {
                return color
            } else {
                return _backgroundColor
            }
        }
        set {
            _backgroundColor = newValue
            liveView?.backgroundColor = newValue
            updateLiveComponent(.backgroundColorChanged, value: newValue.playgroundValue)
        }
    }
    private var _backgroundColor = UIColor()

    /// The border color of the component, with the default being clear.
    ///
    /// - localizationKey: PlaceableComponent.borderColor
    public var borderColor: Color {
        get {
            if let cgColor = liveView?.layer.borderColor {
                return UIColor(cgColor: cgColor)
            } else {
                return _borderColor
            }
        }
        set {
            _borderColor = newValue
            liveView?.layer.borderColor = newValue.cgColor
            updateLiveComponent(.borderColorChanged, value: newValue.playgroundValue)
        }
    }
    private var _borderColor = UIColor()

    /// The border width of the component, with the default being 0.0.
    ///
    /// - localizationKey: PlaceableComponent.borderWidth
    public var borderWidth: Double {
        get {
            if let borderWidth = liveView?.layer.borderWidth {
                return Double(borderWidth)
            } else {
                return _borderWidth
            }
        }
        set {
            _borderWidth = newValue
            liveView?.layer.borderWidth = CGFloat(newValue)
            updateLiveComponent(.borderWidthChanged, value: newValue.playgroundValue)
        }
    }
    private var _borderWidth: Double = 0.0

    /// The content mode of the component determines how the contents of the component are laid out within the component. The default is `.scaleToFill` — the contents are scaled to fill the component.
    ///
    /// - localizationKey: PlaceableComponent.contentMode
    public var contentMode: ContentMode {
        get {
            if let contentMode = liveView?.contentMode {
                return contentMode
            } else {
                return _contentMode
            }
        }
        set {
            _contentMode = newValue
            liveView?.contentMode = newValue
            updateLiveComponent(.contentModeChanged, value: newValue.rawValue.playgroundValue)
        }
    }
    private var _contentMode: ContentMode = .scaleToFill

    /// The radius of the corners of the component if it has rounded corners. If the value is greater than 0.0 (the default), the component has rounded corners.
    ///
    /// - localizationKey: PlaceableComponent.cornerRadius
    public var cornerRadius: Double {
        get {
            if let cornerRadius = liveView?.layer.cornerRadius {
                return Double(cornerRadius)
            } else {
                return _cornerRadius
            }
        }
        set {
            _cornerRadius = newValue
            liveView?.layer.cornerRadius = CGFloat(newValue)
            updateLiveComponent(.cornerRadiusChanged, value: newValue.playgroundValue)
        }
    }
    private var _cornerRadius: Double = 0.0

    
    /// A boolen flag that indicates if the component is hidden. The default value is `false`.
    ///
    /// - localizationKey: PlaceableComponent.isHidden
    public var isHidden: Bool {
        get {
            if let hidden = liveView?.isHidden {
                return hidden
            } else {
                return _isHidden
            }
        }
        set {
            _isHidden = newValue
            liveView?.isHidden = newValue
            updateLiveComponent(.isHiddenChanged, value: newValue.playgroundValue)
        }
    }
    private var _isHidden = false

    /// The position of the center of the component, if it’s been placed in a space.
    ///
    /// - localizationKey: PlaceableComponent.position
    public var position: Point {
        get {
            return _position
        }
        set {
            _position = newValue
            updateLiveViewPositionAndSize()
            updateLiveComponent(.positionChanged, value: newValue.playgroundValue)
        }
    }
    var _position  = Point.zero

    private var _transform = CGAffineTransform.identity
    
    /// The angle — in degrees — of rotation applied to the component. Changing the angle rotates the component counterclockwise around its center. A value of 0.0 (the default) means no rotation. A value of 180 rotates the object 180° and flips it.
    ///
    /// - localizationKey: PlaceableComponent.rotation
    public var rotation: Double {
        get {
            if let transform = liveView?.transform {
                return Double(transform.rotationAngle)
            } else {
                return Double(_rotation)
            }
        }
        set {
            _rotation = CGFloat(newValue)
            updateTransform()
        }
    }
    private var _rotation: CGFloat = 0

    /// The scale factor applied to the component’s size, where 1.0 is the normal (default) size, 0.5 is half size, and 2.0 is twice the normal size.
    ///
    /// - localizationKey: PlaceableComponent.scale
    public var scale: Double {
        get {
            if let transform = liveView?.transform {
                return Double(transform.scaleX)
            } else {
                return Double(_scale)
            }
        }
        set {
            _scale = CGFloat(newValue)
            updateTransform()
        }
    }
    private var _scale: CGFloat = 1.0

    /// The size of the component.
    ///
    /// - localizationKey: PlaceableComponent.size
    public var size: Size {
        get {
            if let cgSize = liveView?.bounds.size {
                return Size(cgSize)
            } else {
                return _size
            }
        }
        set {
            _size = newValue
            updateLiveViewPositionAndSize()
            updateLiveComponent(.sizeChanged, value: newValue.playgroundValue)
            sizeChanged.notifyInputs(newValue)
        }
    }
    private var _size = Size.zero

    // MARK: SpacePlaceable

    /// The default size of the placeable component if its size is not set.
    ///
    /// - localizationKey: PlaceableComponent.intrinsicSize
    open var intrinsicSize: Size { return Size.zero }
    
    // MARK: Outputs
    
    /// An output that sends an event notification of type `Size` each time the size of the component is changed.
    /// This output is useful if you want to respond to size changes, for example, to resize or reposition other components.
    ///
    /// You can connect it to an input of type `Input<Size>`, or to a function with a single parameter of type `Size`.
    ///
    /// - localizationKey: PlaceableComponent.sizeChanged
    public let sizeChanged = Output<Size>()

    // MARK: Methods
    
    /// Moves the component to a new position, animated over duration in seconds.
    ///
    /// - Parameter position: The position to move to.
    ///
    /// - localizationKey: PlaceableComponent.move(to:duration:)
    public func move(to position: Point, duration: Double) {
        guard let positionValue = position.playgroundValue, let durationValue = duration.playgroundValue else { return }
        let event = ComponentEvent(name: PlaceableComponentEventName.moveTo.rawValue, value: .array([positionValue, durationValue]))
        event.send(to: self, in: .live)
    }
    
    // MARK: Private
    
    private func centerInSuperview(for position: Point) -> CGPoint? {
        guard let _ = liveView?.superview, let superViewBounds = liveView?.superview?.bounds.size else { return nil }
        return CoordinateSystem.topLeftOriginCenterFor(centerOriginPosition: position.cgPoint, size: _size.cgSize, within: superViewBounds)
    }
    
    func updateLiveViewPositionAndSize() {
        liveView?.bounds = CGRect(origin: CGPoint.zero, size: _size.cgSize)
        guard let newCenter = centerInSuperview(for: _position) else { return }
        liveView?.center = newCenter
    }
    
    func moveLiveView(to position: Point, duration: Double) {
        guard Process.isLive else { return }
        guard let newCenter = centerInSuperview(for: position) else { return }
        liveView?.move(to: newCenter, duration: duration, completion: {
            self._position = position
            if let playgroundValue = position.playgroundValue {
                let event = ComponentEvent(name: PlaceableComponentEventName.positionChanged.rawValue, value: playgroundValue)
                event.send(to: self, in: .user)
            }
        })
    }

    private func updateTransform() {
        let rotationTransform = CGAffineTransform(rotationAngle: degrees2radians(_rotation))
        _transform = rotationTransform.scaledBy(x: _scale, y: _scale)
        liveView?.transform = _transform
        updateLiveComponent(.transformChanged, value: _transform.playgroundValue)
    }
    
    private func updateLiveComponent(_ eventName: PlaceableComponentEventName, value: PlaygroundValue?) {
        guard Process.isUser, let playgroundValue = value else { return }
        let event = ComponentEvent(name: eventName.rawValue, value: playgroundValue)
        event.send(to: self, in: .live)
    }
    
    // Default implementation.
    public override func receive(_ event: ComponentEvent, from origin: ComponentMessageOrigin) {
        super.receive(event, from: origin)
        //PBLog("event: \(event.name) sent from: \(origin) received by: \(name) (\(type(of: self))) in PlaceableComponent")
        guard let eventName = PlaceableComponentEventName(rawValue: event.name) else { return }
        switch eventName {
        case .alphaChanged:
            guard let newAlpha = Double.from(event.value) as? Double else { return }
            _alpha = newAlpha
            liveView?.alpha = CGFloat(newAlpha)
        case .backgroundColorChanged:
            guard let newBackgroundColor = UIColor.from(event.value) as? UIColor else { return }
            _backgroundColor = newBackgroundColor
            liveView?.backgroundColor = newBackgroundColor
        case .borderColorChanged:
            guard let newBorderColor = UIColor.from(event.value) as? UIColor else { return }
            _borderColor = newBorderColor
            liveView?.layer.borderColor = newBorderColor.cgColor
        case .borderWidthChanged:
            guard case .floatingPoint(let newValue) = event.value else { return }
            _borderWidth = newValue
            liveView?.layer.borderWidth = CGFloat(newValue)
            liveView?.setNeedsLayout()
        case .contentModeChanged:
            guard let value = Int.from(event.value) as? Int, let newContentMode = ContentMode(rawValue: value)  else { return }
            _contentMode = newContentMode
            liveView?.contentMode = newContentMode
        case .cornerRadiusChanged:
            guard case .floatingPoint(let newValue) = event.value else { return }
            _cornerRadius = newValue
            liveView?.layer.cornerRadius = CGFloat(newValue)
        case .isHiddenChanged:
            guard case .boolean(let newValue) = event.value else { return }
            _isHidden = newValue
            liveView?.isHidden = newValue
        case .moveTo:
            guard
                case let .array(playgroundValues) = event.value,
                playgroundValues.count > 1,
                let newPosition = Point.from(playgroundValues[0]) as? Point,
                let duration = Double.from(playgroundValues[1]) as? Double
            else { return }
            moveLiveView(to: newPosition, duration: duration)
        case .positionChanged:
            guard let newPosition = Point.from(event.value) as? Point else { return }
            _position = newPosition
            updateLiveViewPositionAndSize()
        case .sizeChanged:
            guard let newSize = Size.from(event.value) as? Size else { return }
            _size = newSize
            updateLiveViewPositionAndSize()
            sizeChanged.notifyInputs(newSize)
        case .transformChanged:
            guard let newTransform = CGAffineTransform.from(event.value) as? CGAffineTransform else { return }
            liveView?.transform = newTransform
        }
    }
}
