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

import UIKit
import PlaygroundSupport
import SPCCore

/// A slider is a control component that lets you select a `Double` value between -0.5 and 0.5 by sliding it between its minimum (-0.5), and maximum (0.5), values.
/// It has properties that let you customize its appearance, and set its initial value.
/// You can use its `valueChanged` output to receive notifications as its value changes.
///
/// - localizationKey: Slider
public class Slider: PlaceableComponent {
    public override class var componentType: String { return String(describing: Slider.self) }
    
    public override var liveComponent: LiveComponent? {
        return liveSlider
    }
    
    private var liveSlider: LiveSlider?
    
    fileprivate enum EventName: ComponentEventName {
        case minimumTrackColorChanged
        case maximumTrackColorChanged
        case sliderColorChanged
        case sliderImageChanged
        case valueChanged
    }
    
    // MARK: Properties
    
    /// The color of the lower-value end of the track that the slider moves along.
    ///
    /// - localizationKey: Slider.minimumTrackColor
    public var minimumTrackColor: Color {
        get {
            if let color = liveSlider?.minimumTrackTintColor {
                return color
            } else {
                return _minimumTrackColor
            }
        }
        set {
            _minimumTrackColor = newValue
            liveSlider?.minimumTrackTintColor = newValue
            updateLiveComponent(.minimumTrackColorChanged, value: newValue.playgroundValue)
        }
    }
    private var _minimumTrackColor = UIColor()
    
    /// The color of the higher-value end of the track that the slider moves along.
    ///
    /// - localizationKey: Slider.maximumTrackColor
    public var maximumTrackColor: Color {
        get {
            if let color = liveSlider?.maximumTrackTintColor {
                return color
            } else {
                return _maximumTrackColor
            }
        }
        set {
            _maximumTrackColor = newValue
            liveSlider?.maximumTrackTintColor = newValue
            updateLiveComponent(.maximumTrackColorChanged, value: newValue.playgroundValue)
        }
    }
    private var _maximumTrackColor = UIColor()
    
    /// The color of the slider control that you move along the track.
    ///
    /// - localizationKey: Slider.sliderColor
    public var sliderColor: Color {
        get {
            if let color = liveSlider?.thumbTintColor {
                return color
            } else {
                return _sliderColor
            }
        }
        set {
            _sliderColor = newValue
            liveSlider?.thumbTintColor = newValue
            updateLiveComponent(.sliderColorChanged, value: newValue.playgroundValue)
        }
    }
    private var _sliderColor = UIColor()
    
    /// The image used for the slider control.
    /// Use this to change the image you use to drag the slider.
    ///
    /// - localizationKey: Slider.sliderImage
    public var sliderImage: Image {
        get {
            guard let image = liveSlider?.thumbImage(for: .normal) else { return _sliderImage }
            return image
        }
        set {
            let resizedImage = newValue.resizedTo(size: LiveSlider.defaultThumbImageSize, using: .aspectFit) ?? newValue
            _sliderImage = resizedImage
            liveSlider?.updateThumbImage(image: resizedImage)
            updateLiveComponent(.sliderImageChanged, value: resizedImage.playgroundValue)
        }
    }
    private var _sliderImage = Image()
    
    /// The current value of the slider. This changes as you drag the slider.
    /// Set this value to place the slider at an initial position.
    /// The default value is 0.0, with the slider in the center of its track.
    ///
    /// - localizationKey: Slider.value
    public var value: Double {
        get {
            guard let liveValue = liveSlider?.value else { return _value }
            return Double(liveValue)
        }
        set {
            _value = newValue
            liveSlider?.value = Float(newValue)
            updateLiveComponent(.valueChanged, value: newValue.playgroundValue)
        }
    }
    private var _value: Double = 0
    
    // MARK: SpacePlaceable
    
    /// The default size of the slider if its size is not set: 200 in width by 30 in height.
    ///
    /// - localizationKey: Slider.intrinsicSize
    public override var intrinsicSize: Size { return Size(width: 200, height: 30) }
    
    // MARK: Outputs
    
    /// An output that sends an event notification of type `Double` each time the position of the slider changes. As you drag the slider, this output will continuously send out updated values.
    ///
    /// You can connect it to an input of type `Input<Double>`, or to a function with a single parameter of type `Double`.
    ///
    /// - localizationKey: Slider.valueChanged
    public let valueChanged = Output<Double>()
    
    // MARK: Initialization
    override func createLiveComponent() {
        liveSlider = LiveSlider(component: self)
    }
    
    // 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 .minimumTrackColorChanged:
            guard let newColor = UIColor.from(event.value) as? UIColor else { return }
            _minimumTrackColor = newColor
            liveSlider?.minimumTrackTintColor = newColor
        case .maximumTrackColorChanged:
            guard let newColor = UIColor.from(event.value) as? UIColor else { return }
            _maximumTrackColor = newColor
            liveSlider?.maximumTrackTintColor = newColor
        case .sliderColorChanged:
            guard let newColor = UIColor.from(event.value) as? UIColor else { return }
            _sliderColor = newColor
            liveSlider?.thumbTintColor = newColor
        case .sliderImageChanged:
            guard let newImage = UIImage.from(event.value) as? UIImage else { return }
            _sliderImage = newImage
            liveSlider?.updateThumbImage(image: newImage)
        case .valueChanged:
            guard case .floatingPoint(let doubleValue) = event.value else { return }
            value = doubleValue
            valueChanged.notifyInputs(value)
        }
    }
}

class LiveSlider: UISlider, LiveComponent {
    weak var component: Component?
    
    static let defaultThumbImageSize = CGSize(width: 30, height: 30)
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    required init(component: Component) {
        self.component = component
        super.init(frame: CGRect.zero)
        minimumValue = -0.5
        maximumValue = 0.5
        value = 0.0
        setAccessibilityInfo()
        addTarget(self, action: #selector(didChangeValue(_:)), for: .valueChanged)
    }
    
    func updateThumbImage(image: UIImage) {
        setThumbImage(image, for: .normal)
        setThumbImage(image, for: .highlighted)
    }
    
    @objc
    func didChangeValue(_ sender: UISlider) {
        guard let component = component as? Slider else { return }
        let event = ComponentEvent(name: Slider.EventName.valueChanged.rawValue, value: .floatingPoint(Double(self.value)))
        if Process.isLiveViewConnectionOpen {
            event.send(to: component, in: Environment.user) // Update remote (user) component
        } else {
            // Not running in a playgroundbook.
            component.receive(event, from: ComponentMessageOrigin.current) // Update local (live) component
        }
    }
}
