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

import Foundation
import SPCComponents
import SPCCore

/// You use a camera component to take pictures. It has:
/// * A viewfinder for seeing the live video stream from the iPad camera.
/// * A shutter button that you can press to take a photo.
/// * A front/rear button you can press to switch between the front and rear cameras on your iPad.
///
/// SwiftyCamera comes in several styles including `plain`, `cloud`, `kitty`, `rocket` and `shark`.
///
/// - localizationKey: SwiftyCamera
public class SwiftyCamera: Space {
    
    /// Style options for SwiftyCamera.
    ///
    /// - localizationKey: SwiftyCamera.Style
    public enum Style {
        /// The kitty camera style.
        /// - localizationKey: SwiftyCamera.Style.kitty
        case kitty
        /// The cloud camera style.
        /// - localizationKey: SwiftyCamera.Style.cloud
        case cloud
        /// The plain camera style.
        /// - localizationKey: SwiftyCamera.Style.plain
        case plain
        /// The rocket camera style.
        /// - localizationKey: SwiftyCamera.Style.rocket
        case rocket
        /// The shark camera style.
        /// - localizationKey: SwiftyCamera.Style.shark
        case shark
    }
    
    /// The current style of camera.
    ///
    /// - localizationKey: SwiftyCamera.style
    public var style: Style = .plain {
        didSet {
            customizeFor(style: style)
        }
    }
    
    // Create the components.
    
    /// The shutter button for the camera. Press this button to take a photo.
    ///
    /// After taking a photo, the `photoTaken` output sends an event notification with the new image.
    ///
    /// - localizationKey: SwiftyCamera.shutterButton
    public let shutterButton = Button()
    
    /// The front/rear button for the camera. Press this button to switch between the front and rear cameras on your iPad.
    ///
    /// - localizationKey: SwiftyCamera.frontBackButton
    public let frontBackButton = Button()
    
    /// The viewfinder that displays the live video stream from the selected iPad camera.
    ///
    /// - localizationKey: SwiftyCamera.cameraView
    public let cameraView = CameraView()
    
    /// An image view that (optionally) contains an image that it places over the viewfinder.
    /// The image has transparent areas through which you can see the viewfinder.
    ///
    /// - localizationKey: SwiftyCamera.coverView
    public let coverView = ImageView()
    
    // Variables for the size and position of each component.
    var coverViewPosition = Point.zero
    var coverViewSize = Size.zero
    var cameraViewPosition = Point.zero
    var cameraViewSize = Size(width: 200, height: 200)
    var shutterButtonPosition = Point.zero
    var shutterButtonSize = Size.zero
    var frontBackButtonPosition = Point.zero
    var frontBackButtonSize = Size.zero
    
    /// The default size of the camera if its size is not set.
    /// Each style of camera has its preferred default size.
    ///
    /// - localizationKey: SwiftyCamera.intrinsicSize
    public override var intrinsicSize: Size {
        switch style {
        case .kitty:
            return Size(width: 400, height: 400)
        case .cloud:
            return Size(width: 300, height: 400)
        case .plain:
            return Size(width: 480, height: 280)
        case .rocket:
            return Size(width: 400, height: 400)
        case .shark:
            return Size(width: 400, height: 400)
        }
    }
    
    // MARK: Outputs
    
    /// An output that sends an event notification of type `Image` each time you take a photo.
    ///
    /// You can connect it to an input of type `Input<Image>`, or to a function with a single parameter of type `Image`. For example, you could connect this output to the `input` input of an `ImageView` so that when you take a photo, the image appears in the image view.
    ///
    /// - localizationKey: SwiftyCamera.photoTaken
    public var photoTaken = Output<Image>()
    
    // MARK: Initialization
    
    /// Creates a SwiftyCamera with the specified style.
    ///
    /// - Parameter style: The style of camera to create.
    ///
    /// - localizationKey: SwiftyCamera(style:)
    public init(style: Style) {
        self.style = style
        super.init()
        coverView.isHidden = true
        
        // Add the components.
        add(cameraView, at: Point(x: 0, y: 0))
        add(coverView, at: Point(x: 0, y: 0))
        add(shutterButton, at: Point(x: 100, y: -100))
        add(frontBackButton, at: Point(x: 100, y: 100))
        
        // Connect the sizeChanged event handler.
        sizeChanged.connect(to: onSizeChanged)

        // Customize.
        customizeFor(style: style)
    }
    
    // MARK: Public Methods
    
    /// Starts the video stream from the selected iPad camera.
    ///
    /// - localizationKey: SwiftyCamera.start()
    public func start() {
        cameraView.isUsingFrontCamera = true
        // Connect the camera components.
        shutterButton.pressed.connect(to: cameraView.trigger)
        frontBackButton.pressed.connect(to: onCameraSelectButtonPressed)
        cameraView.photoTaken.connect(to: onPhotoTaken)
        // Start the camera.
        cameraView.start()
    }
    
    // MARK: Internal Methods
    
    // Camera select button event handler.
    func onCameraSelectButtonPressed(pulse: Pulse) {
        cameraView.isUsingFrontCamera = !cameraView.isUsingFrontCamera
    }
    
    // Photo taken event handler.
    func onPhotoTaken(image: Image) {
        photoTaken.notifyInputs(image)
    }
    
    // Size changed event handler.
    func onSizeChanged(size: Size) {
        updateLayoutFor(cameraSize: size)
    }

    // Returns a position scaled for the size of the camera space relative to its intrinsic size.
    func scaledPositionFor(_ position: Point, within spaceSize: Size) -> Point {
        let scale = intrinsicSize.scaleToFit(within: spaceSize)
        return Point(x: position.x * scale, y: position.y * scale)
    }
    
    // Returns a size scaled for the size of the camera space relative to its intrinsic size.
    func scaledSizeFor(_ size: Size, within spaceSize: Size) -> Size {
        let scale = intrinsicSize.scaleToFit(within: spaceSize)
        return Size(width: size.width * scale, height: size.height * scale)
    }
    
    // Customize the camera for the specified style.
    // Positions and sizes set in this function are all relative to the intrinsic, or natural, size of the camera.
    func customizeFor(style: Style) {
        let standardButtonSize = Size(width: 60, height: 60)
        coverViewPosition = Point(x: 0, y: 0)
        coverViewSize = intrinsicSize
        cameraViewPosition = Point(x: 0, y: 0)
        shutterButtonSize = standardButtonSize
        frontBackButtonPosition = Point(x: 180, y: -80)
        frontBackButtonSize = standardButtonSize
        shutterButton.backgroundColor = .clear
        frontBackButton.backgroundColor = .clear
        cornerRadius = 20.0
        cameraView.cornerRadius = 0.0
        
        switch style {
        case .kitty:
            backgroundColor = .clear
            shutterButton.image = #imageLiteral(resourceName: "catShutterButton")
            frontBackButton.image = #imageLiteral(resourceName: "catArrowButton")
            coverView.image = #imageLiteral(resourceName: "catCamera")
            coverView.isHidden = false
            
            let buttonSize = Size(width: 74, height: 74)
            cameraViewPosition = Point(x: -61, y: -39)
            cameraViewSize = Size(width: 140, height: 140)
            shutterButtonPosition = Point(x: 56, y: -2)
            shutterButtonSize = buttonSize
            frontBackButtonPosition = Point(x: 56, y: -80)
            frontBackButtonSize = buttonSize
            
        case .cloud:
            backgroundImage = #imageLiteral(resourceName: "cloudBackground")
            shutterButton.image = #imageLiteral(resourceName: "cloudShutterButton")
            frontBackButton.image = #imageLiteral(resourceName: "cloudArrowButton")
            cornerRadius = 40.0
            cameraView.cornerRadius = 20.0
            cameraView.borderColor =  #colorLiteral(red: 0.9098039269, green: 0.4784313738, blue: 0.6431372762, alpha: 1)
            cameraView.borderWidth = 4
            
            let buttonSize = Size(width: 50, height: 50)
            cameraViewSize = Size(width: intrinsicSize.width * 0.75, height: intrinsicSize.height * 0.75)
            shutterButtonPosition = Point(x: 65, y: -105)
            shutterButtonSize = buttonSize
            frontBackButtonPosition = Point(x: -65, y: -105)
            frontBackButtonSize = buttonSize

        case .plain:
            backgroundColor =  #colorLiteral(red: 0.4695840478, green: 0.6612184644, blue: 0.6645051241, alpha: 1)
            shutterButton.backgroundColor = .clear
            frontBackButton.backgroundColor = .clear
            shutterButton.image = #imageLiteral(resourceName: "cf_shutter-icon")
            frontBackButton.image = #imageLiteral(resourceName: "cf_front-back-icon")
            
            let w = intrinsicSize.height * 0.85
            cameraViewSize = Size(width: w, height: w)
            shutterButtonPosition = Point(x: 180, y: 80)
            frontBackButtonPosition = Point(x: 180, y: -80)
            
        case .rocket:
            backgroundImage = #imageLiteral(resourceName: "cf_spaceWP")
            coverView.image = #imageLiteral(resourceName: "rocketCamera")
            shutterButton.image = #imageLiteral(resourceName: "rocketStarButton")
            frontBackButton.image = #imageLiteral(resourceName: "rocketArrowButton")
            cameraView.maskImage = #imageLiteral(resourceName: "circle-mask")
            cornerRadius = 10.0
            coverView.isHidden = false
            
            cameraViewPosition = Point(x: 50, y: 50)
            cameraViewSize = Size(width: 124, height: 124)
            shutterButtonPosition = Point(x: 150, y: 150)
            frontBackButtonPosition = Point(x: 51, y: -87)
            
        case .shark:
            backgroundColor = .clear
            coverView.image = #imageLiteral(resourceName: "sharkCamera")
            shutterButton.image = #imageLiteral(resourceName: "sharkShutterButton")
            frontBackButton.image = #imageLiteral(resourceName: "sharkArrowButton")
            cameraView.overlayImage = #imageLiteral(resourceName: "sharkCamera")
            cameraView.isOverlayImageCropped = false
            coverView.isHidden = false

            cameraViewPosition = Point(x: 8, y: 26)
            cameraViewSize = Size(width: 168, height: 168)
            shutterButtonPosition = Point(x: 54, y: -148)
            shutterButtonSize = Size(width: 60, height: 60)
            frontBackButtonPosition = Point(x: -39, y: -148)
            frontBackButtonSize = Size(width: 60, height: 60)
        }
    }
    
    // Set the actual position and size of each component by scaling based on
    // the ratio between the specified current camera size and its intrinsic size.
    func updateLayoutFor(cameraSize: Size) {
        // Scale size and position of components to new camera size.
        coverView.position = scaledPositionFor(coverViewPosition, within: cameraSize)
        coverView.size = scaledSizeFor(coverViewSize, within: cameraSize)
        cameraView.position = scaledPositionFor(cameraViewPosition, within: cameraSize)
        cameraView.size = scaledSizeFor(cameraViewSize, within: cameraSize)
        shutterButton.position = scaledPositionFor(shutterButtonPosition, within: cameraSize)
        shutterButton.size = scaledSizeFor(shutterButtonSize, within: cameraSize)
        frontBackButton.position = scaledPositionFor(frontBackButtonPosition, within: cameraSize)
        frontBackButton.size = scaledSizeFor(frontBackButtonSize, within: cameraSize)
        cameraView.overlayImageScale = coverView.size.width / cameraView.size.width
        cameraView.overlayImageOffset = Point(x: -cameraView.position.x, y: -cameraView.position.y)
    }
}

