<commit_before>//
//  LayoutViewController.swift
//  Layout
//
//  Created by <NAME> on 27/04/2017.
//  Copyright © 2017 <NAME>. All rights reserved.
//

import UIKit

open class LayoutViewController: UIViewController {

    open var layoutNode: LayoutNode? = nil {
        didSet {
            if layoutNode?.viewController == self {
                // TODO: should this use case be allowed at all?
                return
            }
            oldValue?.unmount()
            if let layoutNode = layoutNode {
                do {
                    try layoutNode.mount(in: self)
                    _dismissError()
                    layoutDidLoad()
                } catch {
                    layoutError(LayoutError(error, for: layoutNode))
                }
            }
        }
    }

    fileprivate var _loader: LayoutLoader?
    private var _state: Any = ()
    private var _errorNode: LayoutNode?
    private var _error: LayoutError? = nil

    private var isReloadable: Bool {
        return layoutNode != nil || _loader != nil
    }

    private func merge(_ dictionaries: [[String: Any]]) -> [String: Any] {
        var result = [String: Any]()
        for dict in dictionaries {
            for (key, value) in dict {
                result[key] = value
            }
        }
        return result
    }

    public func loadLayout(
        named: String? = nil,
        bundle: Bundle = Bundle.main,
        relativeTo: String = #file,
        state: Any = (),
        constants: [String: Any]...)
    {
        assert(Thread.isMainThread)
        let name = named ?? "\(type(of: self))".components(separatedBy: ".").last!
        guard let xmlURL = bundle.url(forResource: name, withExtension: nil) ??
            bundle.url(forResource: name, withExtension: "xml") else {
            layoutError(.message("No layout XML file found for `\(name)`"))
            return
        }
        loadLayout(
            withContentsOfURL: xmlURL,
            relativeTo: relativeTo,
            state: state,
            constants: merge(constants)
        )
    }

    public func loadLayout(
        withContentsOfURL xmlURL: URL,
        relativeTo: String? = #file,
        state: Any = (),
        constants: [String: Any]...,
        completion: ((LayoutError?) -> Void)? = nil)
    {
        if _loader == nil {
            _loader = LayoutLoader()
        }
        _loader?.loadLayout(
            withContentsOfURL: xmlURL,
            relativeTo: relativeTo,
            state: state,
            constants: merge(constants)
        ) { layoutNode, error in
            if let layoutNode = layoutNode {
                self.layoutNode = layoutNode
            }
            if let error = error {
                self.layoutError(error)
            }
            completion?(error)
        }
    }

    @objc private func _reloadLayout() {

        // Pass message up the chain to the root LayoutViewController
        var responder: UIResponder = self
        while let nextResponder = responder.next {
            if let layoutController = nextResponder as? LayoutViewController {
                layoutController._reloadLayout()
                return
            }
            responder = nextResponder
        }

        reloadLayout(withCompletion: nil)
    }

    public func reloadLayout(withCompletion completion: ((LayoutError?) -> Void)? = nil) {
        if let loader = _loader {
            loader.reloadLayout { layoutNode, error in
                if let layoutNode = layoutNode {
                    self.layoutNode = layoutNode
                }<commit_msg>Reload no longer propagates up the chain<commit_after>//
//  LayoutViewController.swift
//  Layout
//
//  Created by <NAME> on 27/04/2017.
//  Copyright © 2017 <NAME>. All rights reserved.
//

import UIKit

open class LayoutViewController: UIViewController {

    open var layoutNode: LayoutNode? = nil {
        didSet {
            if layoutNode?.viewController == self {
                // TODO: should this use case be allowed at all?
                return
            }
            oldValue?.unmount()
            if let layoutNode = layoutNode {
                do {
                    try layoutNode.mount(in: self)
                    _dismissError()
                    layoutDidLoad()
                } catch {
                    layoutError(LayoutError(error, for: layoutNode))
                }
            }
        }
    }

    fileprivate var _loader: LayoutLoader?
    private var _state: Any = ()
    private var _errorNode: LayoutNode?
    private var _error: LayoutError? = nil

    private var isReloadable: Bool {
        return layoutNode != nil || _loader != nil
    }

    private func merge(_ dictionaries: [[String: Any]]) -> [String: Any] {
        var result = [String: Any]()
        for dict in dictionaries {
            for (key, value) in dict {
                result[key] = value
            }
        }
        return result
    }

    public func loadLayout(
        named: String? = nil,
        bundle: Bundle = Bundle.main,
        relativeTo: String = #file,
        state: Any = (),
        constants: [String: Any]...)
    {
        assert(Thread.isMainThread)
        let name = named ?? "\(type(of: self))".components(separatedBy: ".").last!
        guard let xmlURL = bundle.url(forResource: name, withExtension: nil) ??
            bundle.url(forResource: name, withExtension: "xml") else {
            layoutError(.message("No layout XML file found for `\(name)`"))
            return
        }
        loadLayout(
            withContentsOfURL: xmlURL,
            relativeTo: relativeTo,
            state: state,
            constants: merge(constants)
        )
    }

    public func loadLayout(
        withContentsOfURL xmlURL: URL,
        relativeTo: String? = #file,
        state: Any = (),
        constants: [String: Any]...,
        completion: ((LayoutError?) -> Void)? = nil)
    {
        if _loader == nil {
            _loader = LayoutLoader()
        }
        _loader?.loadLayout(
            withContentsOfURL: xmlURL,
            relativeTo: relativeTo,
            state: state,
            constants: merge(constants)
        ) { layoutNode, error in
            if let layoutNode = layoutNode {
                self.layoutNode = layoutNode
            }
            if let error = error {
                self.layoutError(error)
            }
            completion?(error)
        }
    }

    @objc private func _reloadLayout() {
        print("Reloading \(type(of: self))")
        reloadLayout(withCompletion: nil)
    }

    public func reloadLayout(withCompletion completion: ((LayoutError?) -> Void)? = nil) {
        if let loader = _loader {
            loader.reloadLayout { layoutNode, error in
                if let layoutNode = layoutNode {
                    self.layoutNode = layoutNode
                }
{'fraction_non_alphanumeric': 0.09591422776006954, 'fraction_numerical': 0.0034772529701535787, 'mean_word_length': 1.578632797908106, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 10}, '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': '2258908', 'n_tokens_mistral': 1839, 'n_tokens_neox': 1737, 'n_words': 502}