<commit_before>//
//  UIKitRenderer.swift
//  PortalView
//
//  Created by <NAME> on 2/14/17.
//  Copyright © 2017 <NAME>. All rights reserved.
//

import UIKit

public struct UIKitComponentRenderer<
    MessageType,
    RouteType,
    CustomComponentRendererType: UIKitCustomComponentRenderer
    >: Renderer

    where CustomComponentRendererType.MessageType == MessageType, CustomComponentRendererType.RouteType == RouteType {

    public typealias CustomComponentRendererFactory = () -> CustomComponentRendererType
    public typealias ActionType = Action<RouteType, MessageType>
    
    typealias TableView = PortalTableView<MessageType, RouteType, CustomComponentRendererType>
    typealias CollectionView = PortalCollectionView<MessageType, RouteType, CustomComponentRendererType>
    typealias CarouselView = PortalCarouselView<MessageType, RouteType, CustomComponentRendererType>

    public var isDebugModeEnabled = true
    public var debugConfiguration = RendererDebugConfiguration()

    fileprivate let layoutEngine: LayoutEngine
    fileprivate let rendererFactory: CustomComponentRendererFactory
    
    public init(
        layoutEngine: LayoutEngine = YogaLayoutEngine(),
        rendererFactory: @escaping CustomComponentRendererFactory) {
        self.rendererFactory = rendererFactory
        self.layoutEngine = layoutEngine
    }

    public func render(component: Component<ActionType>, into containerView: UIView) -> Mailbox<ActionType> {
        apply(changeSet: component.fullChangeSet, to: containerView)
        return containerView.getMailbox()
    }
    
    public func apply(changeSet: ComponentChangeSet<ActionType>, to containerView: UIView) {
        let rootView = getOrCreateRootView(from: containerView)
        let renderResult = render(changeSet: changeSet, into: rootView)
        if rootView !== renderResult.view {
            containerView.addSubview(renderResult.view)
            renderResult.mailbox?.forward(to: containerView.getMailbox())
        }
        layoutEngine.executeLayout(for: containerView)
        renderResult.afterLayout?()
        
        if isShowViewFrame {
            renderResult.view.safeTraverse { $0.addDebugFrame() }
        }
    }
    
}

extension UIKitComponentRenderer {
    
    var isShowViewChangeAnimation: Bool {
        return isDebugModeEnabled &&  debugConfiguration.showViewChangeAnimation
    }
    
    var isShowViewFrame: Bool {
        return isDebugModeEnabled &&  debugConfiguration.showViewFrame
    }
    
    // swiftlint:disable cyclomatic_complexity function_body_length
    fileprivate func render(changeSet: ComponentChangeSet<ActionType>, into view: UIView?) -> Render<ActionType> {
        switch changeSet {
            
        case .button(let buttonChangeSet):
            let button = castOrRelease(view: view, to: UIButton.self)
            return button.apply(changeSet: buttonChangeSet, layoutEngine: layoutEngine)
            
        case .label(let labelChangeSet):
            let label = castOrRelease(view: view, to: UILabel.self)
            return label.apply(changeSet: labelChangeSet, layoutEngine: layoutEngine)
            
        case .mapView(let mapViewChangeSet):
            let mapView = castOrRelease(view: view, to: PortalMapView.self)
            return mapView.apply(changeSet: mapViewChangeSet, layoutEngine: layoutEngine)
            
        case .imageView(let imageViewChangeSet):
            let imageView = castOrRelease(view: view, to: UIImageView.self)
            return imageView.apply(changeSet: imageViewChangeSet, layoutEngine: layoutEngine)
            
        case .container(let containerChangeSet):
            let containerView = view ?? UIView()
            containerView.managedByPortal = true
            return apply(changeSet: containerChangeSet, to: containerView)
            
        case .table(let tableChangeSet):
            let table = castOrRelease(view: view, to: TableView.self) { TableView(renderer: self) }
            return table.apply(changeSet: tableChangeSet, layoutEngine: layoutEngine)
            
        case .collection(let collectionChangeSet):
            let collection = castOrRelease(view: view, to: CollectionView.self) { CollectionView(renderer: self) }
            return collection.apply(changeSet: collectionChangeSet, layoutEngine: layoutEngine)
            
        case .carousel(let carouselChangeSet):
            let carousel = castOrRelease(view: view, to: CarouselView.self) { CarouselView(renderer: self) }
            return carousel.apply(changeSet: carouselChangeSet, layoutEngine: layoutEngine)
            
        case .touchable(let touchableChangeSet):
            let result = render(changeSet: touchableChangeSet.child, into: view)
            
            switch touchableChangeSet.gesture {
                
            case .change(to: .tap(let message)):
                let mailbox: Mailbox<ActionType> = result.view.getMailbox()
                let dispatcher = MessageDispatcher(mailbox: mailbox, message: message)
                result.view.register(dispatcher: dispatcher)
                result.view.gestureRecognizers?.forEach { result.view.removeGestureRecognizer($0) }
                let recognizer = UITapGestureRecognizer(target: dispatcher, action: dispatcher.selector)
                result.view.addGestureRecognizer(recognizer)
            
            case .noChange:
                break
                
            }
            
            return result
            
        case .segmented(let segmentedChangeSet):
            let segmented = castOrRelease(view: view, to: UISegmentedControl.self)
            return segmented.apply(changeSet: segmentedChangeSet, layoutEngine: layoutEngine)
            
        case .progress(let progressChangeSet):
            let progress = castOrRelease(view: view, to: UIProgressView.self)
            return progress.apply(changeSet: progressChangeSet, layoutEngine: layoutEngine)
            
        case .textField(let textFieldChangeSet):
            let textField = castOrRelease(view: view, to: UITextField.self)
            return textField.apply(changeSet: textFieldChangeSet, layoutEngine: layoutEngine)
            
        case .custom(let customComponentChangeSet):
            let containerView = view ?? UIView()
            containerView.managedByPortal = true
            layoutEngine.apply(changeSet: customComponentChangeSet.layout, to: containerView)
            containerView.apply(changeSet: customComponentChangeSet.baseStyleSheet)
            let mailbox: Mailbox<ActionType> = containerView.getMailbox()
            return Render(view: containerView, mailbox: mailbox) {
                let renderer = self.rendererFactory()
                renderer.apply(changeSet: customComponentChangeSet, inside: containerView, dispatcher: mailbox.dispatch)
            }
            
        case .spinner(let spinnerChangeSet):
            let spinner = castOrRelease(view: view, to: UIActivityIndicatorView.self)
            return spinner.apply(changeSet: spinnerChangeSet, layoutEngine: layoutEngine)
            
        case .textView(let textViewChangeSet):
            let textView = castOrRelease(view: view, to: UITextView.self)
            return textView.apply(changeSet: textViewChangeSet, layoutEngine: layoutEngine)
            
        }
    }
    // swiftlint:enable cyclomatic_complexity function_body_length
    
    fileprivate func apply(changeSet: ContainerChangeSet<ActionType>, to view: UIView) -> Render<ActionType> {
        var afterLayoutTasks = [AfterLayoutTask]()
        afterLayoutTasks.reserveCapacity(changeSet.childrenCount)
        let reuseSubviews = view.subviews.count == changeSet.childrenCount
        let subviews: [UIView?]
        
        if reuseSubviews {
            subviews = view.subviews
        } else {
            view.subviews.forEach { $0.removeFromSuperview() }
            subviews = Array(repeating: .none, count: changeSet.childrenCount)
        }
        
        let mailbox: Mailbox<ActionType> = view.getMailbox()
        for (subview, childChangeSet) in zip(subviews, changeSet.children) {
            let result = render(changeSet: childChangeSet, into: subview)
            
            if !reuseSubviews || result.view !== subview {
                result.view.managedByPortal = true
                // TODO This should not be added it should replace the current
                // view if needed
                view.addSubview(result.view)
                result.mailbox?.forward(to: mailbox)
            }<commit_msg>Extract change set application logic.<commit_after>//
//  UIKitRenderer.swift
//  PortalView
//
//  Created by <NAME> on 2/14/17.
//  Copyright © 2017 <NAME>. All rights reserved.
//

import UIKit

public struct UIKitComponentRenderer<
    MessageType,
    RouteType,
    CustomComponentRendererType: UIKitCustomComponentRenderer
    >: Renderer

    where CustomComponentRendererType.MessageType == MessageType, CustomComponentRendererType.RouteType == RouteType {

    public typealias CustomComponentRendererFactory = () -> CustomComponentRendererType
    public typealias ActionType = Action<RouteType, MessageType>
    
    typealias TableView = PortalTableView<MessageType, RouteType, CustomComponentRendererType>
    typealias CollectionView = PortalCollectionView<MessageType, RouteType, CustomComponentRendererType>
    typealias CarouselView = PortalCarouselView<MessageType, RouteType, CustomComponentRendererType>

    public var isDebugModeEnabled = true
    public var debugConfiguration = RendererDebugConfiguration()

    fileprivate let layoutEngine: LayoutEngine
    fileprivate let rendererFactory: CustomComponentRendererFactory
    
    public init(
        layoutEngine: LayoutEngine = YogaLayoutEngine(),
        rendererFactory: @escaping CustomComponentRendererFactory) {
        self.rendererFactory = rendererFactory
        self.layoutEngine = layoutEngine
    }

    public func render(component: Component<ActionType>, into containerView: UIView) -> Mailbox<ActionType> {
        apply(changeSet: component.fullChangeSet, to: containerView)
        return containerView.getMailbox()
    }
    
    public func apply(changeSet: ComponentChangeSet<ActionType>, to containerView: UIView) {
        let rootView = getOrCreateRootView(from: containerView)
        let renderResult = render(changeSet: changeSet, into: rootView)
        if rootView !== renderResult.view {
            containerView.addSubview(renderResult.view)
            renderResult.mailbox?.forward(to: containerView.getMailbox())
        }
        layoutEngine.executeLayout(for: containerView)
        renderResult.afterLayout?()
        
        if isShowViewFrame {
            renderResult.view.safeTraverse { $0.addDebugFrame() }
        }
    }
    
}

extension UIKitComponentRenderer {
    
    var isShowViewChangeAnimation: Bool {
        return isDebugModeEnabled &&  debugConfiguration.showViewChangeAnimation
    }
    
    var isShowViewFrame: Bool {
        return isDebugModeEnabled &&  debugConfiguration.showViewFrame
    }
    
    // swiftlint:disable cyclomatic_complexity function_body_length
    fileprivate func render(changeSet: ComponentChangeSet<ActionType>, into view: UIView?) -> Render<ActionType> {
        switch changeSet {
            
        case .button(let buttonChangeSet):
            let button = castOrRelease(view: view, to: UIButton.self)
            return button.apply(changeSet: buttonChangeSet, layoutEngine: layoutEngine)
            
        case .label(let labelChangeSet):
            let label = castOrRelease(view: view, to: UILabel.self)
            return label.apply(changeSet: labelChangeSet, layoutEngine: layoutEngine)
            
        case .mapView(let mapViewChangeSet):
            let mapView = castOrRelease(view: view, to: PortalMapView.self)
            return mapView.apply(changeSet: mapViewChangeSet, layoutEngine: layoutEngine)
            
        case .imageView(let imageViewChangeSet):
            let imageView = castOrRelease(view: view, to: UIImageView.self)
            return imageView.apply(changeSet: imageViewChangeSet, layoutEngine: layoutEngine)
            
        case .container(let containerChangeSet):
            let containerView = view ?? UIView()
            containerView.managedByPortal = true
            return apply(changeSet: containerChangeSet, to: containerView)
            
        case .table(let tableChangeSet):
            let table = castOrRelease(view: view, to: TableView.self) { TableView(renderer: self) }
            return table.apply(changeSet: tableChangeSet, layoutEngine: layoutEngine)
            
        case .collection(let collectionChangeSet):
            let collection = castOrRelease(view: view, to: CollectionView.self) { CollectionView(renderer: self) }
            return collection.apply(changeSet: collectionChangeSet, layoutEngine: layoutEngine)
            
        case .carousel(let carouselChangeSet):
            let carousel = castOrRelease(view: view, to: CarouselView.self) { CarouselView(renderer: self) }
            return carousel.apply(changeSet: carouselChangeSet, layoutEngine: layoutEngine)
            
        case .touchable(let touchableChangeSet):
            return apply(changeSet: touchableChangeSet, to: view)
            
        case .segmented(let segmentedChangeSet):
            let segmented = castOrRelease(view: view, to: UISegmentedControl.self)
            return segmented.apply(changeSet: segmentedChangeSet, layoutEngine: layoutEngine)
            
        case .progress(let progressChangeSet):
            let progress = castOrRelease(view: view, to: UIProgressView.self)
            return progress.apply(changeSet: progressChangeSet, layoutEngine: layoutEngine)
            
        case .textField(let textFieldChangeSet):
            let textField = castOrRelease(view: view, to: UITextField.self)
            return textField.apply(changeSet: textFieldChangeSet, layoutEngine: layoutEngine)
            
        case .custom(let customComponentChangeSet):
            return apply(changeSet: customComponentChangeSet, to: view ?? UIView())
            
        case .spinner(let spinnerChangeSet):
            let spinner = castOrRelease(view: view, to: UIActivityIndicatorView.self)
            return spinner.apply(changeSet: spinnerChangeSet, layoutEngine: layoutEngine)
            
        case .textView(let textViewChangeSet):
            let textView = castOrRelease(view: view, to: UITextView.self)
            return textView.apply(changeSet: textViewChangeSet, layoutEngine: layoutEngine)
            
        }
    }
    // swiftlint:enable cyclomatic_complexity function_body_length
    
    fileprivate func apply(changeSet: TouchableChangeSet<ActionType>, to view: UIView?) -> Render<ActionType> {
        let result = render(changeSet: changeSet.child, into: view)
        
        switch changeSet.gesture {
            
        case .change(to: .tap(let message)):
            let mailbox: Mailbox<ActionType> = result.view.getMailbox()
            let dispatcher = MessageDispatcher(mailbox: mailbox, message: message)
            result.view.register(dispatcher: dispatcher)
            result.view.gestureRecognizers?.forEach { result.view.removeGestureRecognizer($0) }
            let recognizer = UITapGestureRecognizer(target: dispatcher, action: dispatcher.selector)
            result.view.addGestureRecognizer(recognizer)
            
        case .noChange:
            break
            
        }
        
        return result
    }
    
    fileprivate func apply(changeSet: CustomComponentChangeSet, to containerView: UIView) -> Render<ActionType> {
        containerView.managedByPortal = true
        layoutEngine.apply(changeSet: changeSet.layout, to: containerView)
        containerView.apply(changeSet: changeSet.baseStyleSheet)
        let mailbox: Mailbox<ActionType> = containerView.getMailbox()
        return Render(view: containerView, mailbox: mailbox) {
            let renderer = self.rendererFactory()
            renderer.apply(changeSet: changeSet, inside: containerView, dispatcher: mailbox.dispatch)
        }
    }
    
    fileprivate func apply(changeSet: ContainerChangeSet<ActionType>, to view: UIView) -> Render<ActionType> {
        var afterLayoutTasks = [AfterLayoutTask]()
        afterLayoutTasks.reserveCapacity(changeSet.childrenCount)
        let reuseSubviews = view.subviews.count == changeSet.childrenCount
        let subviews: [UIView?]
        
        if reuseSubviews {
            subviews = view.subviews
        } else {
            view.subviews.forEach { $0.removeFromSuperview() }
            subviews = Array(repeating: .none, count: changeSet.childrenCount)
        }
        
        let mailbox: Mailbox<ActionType> = view.getMailbox()
        for (subview, childChangeSet) in zip(subviews, changeSet.children) {
            let result = render(changeSet: childChangeSet, into: subview)
            
            if !reuseSubviews || result.view !== subview {
                result.view.managedByPortal = true
                // TODO This should not be added it should replace the current
                // view if needed
                view.addSubview(result.view)
                result.mailbox?.forward(to: mailbox)
            }
{'fraction_non_alphanumeric': 0.07688326624496837, 'fraction_numerical': 0.0013801035077630822, 'mean_word_length': 2.681414055884843, 'pattern_counts': {'":': 0, '<': 40, '<?xml version=': 0, '>': 50, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4107434', 'n_tokens_mistral': 4436, 'n_tokens_neox': 4364, 'n_words': 1003}