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

import Foundation
import PlaygroundSupport

/// A type conforming to `EventSending` can send event notifications of type `EventType`.
///
/// - localizationKey: EventSending
public protocol EventSending {
    associatedtype EventType
    func connect(to input: Input<EventType>)
    func notifyInputs(_ event: EventType)
}

/// An output of type `Output<EventType>` can be connected to an input of type `Input<EventType>`, and can send event notifications to it.
///
/// For example an output of type `Output<Image>` can send `Image` event notifications.
/// You can connect it to:
///
/// * Any input of type `Input<Image>`
///
/// * Any function that takes a single parameter of `EventType`, such as `onPhotoTaken(image: Image) { }`
///
/// - localizationKey: Output<EventType>
public class Output<EventType>: EventSending {
    
    private var inputs: [(Int, Input<EventType>)] = []

    private func removeInputForId(_ id: Int) {
        inputs = inputs.filter { $0.0 != id }
    }
    
    private static func getRandomId() -> Int {
        return Int(arc4random())
    }
    
    // MARK: Public
    
    /// Create an `Output`.
    ///
    /// - localizationKey: Output
    public init() {}

    /// Connect this output to an input of the same event type.
    ///
    /// - Parameter input: The input to be connected to this output.
    ///
    /// - localizationKey: Output<EventType>.connect(to input:)
    public func connect(to input: Input<EventType>) {
        let id = Output<EventType>.getRandomId()
        inputs.append((id, input))
    }
    
    /// Connect this output to a function (handler) that takes a parameter of the same event type.
    ///
    /// - Parameter handler: The handler function to be connected to this output.
    ///
    /// - localizationKey: Output<EventType>.connect(to handler:)
    public func connect(to handler: @escaping ((EventType) -> Void)) {
        let input = Input<EventType>(handler)
        return connect(to: input)
    }
    
    /// Sends an event notification to each of the inputs connected to this output.
    /// Call this if you explictly want to notify the inputs.
    ///
    /// - Parameter event: The event to be notified.
    ///
    /// - localizationKey: Output<EventType>.notifyInputs(_:)
    public func notifyInputs(_ event: EventType) {
        for input in inputs {
            input.1.notifier(event)
        }
    }
    
    /// Disconnect all the inputs connected to this output.
    ///
    /// - localizationKey: Output<EventType>.disconnectAll()
    public func disconnectAll() {
        inputs = []
    }
}
