(function() {
 
/**
 * Ext.space.Window is an API that allows you to create child web views and do
 * simple management with them (e.g., open, close, navigate).
 *
 * To create a child web view:
 *
 *      var child = window.open("http://example.com/");
 *
 *      child.open("http://another.example.com/somewhere/somehow.html");
 *      // ... later:
 *      child.close();
 *
 *      // close all open children
 *      Ext.space.Window.closeAll();
 *
 */
Ext.define("Ext.space.Window", {
    singleton: true,
 
    /**
     * Internal collection of child web views, by name
     *
     * @type {Object}
     * @private
     */
    namedWindows: null,
 
    /**
     * Internal collection of child web views, by ID
     *
     * @type {Object}
     * @private
     */
    windows: null,
 
    /**
     * @private
     */
    _ready: null,
 
    /**
     * @private
     */
    constructor: function() {
        this.namedWindows = {};
        this.windows = {};
        this._ready = new Ext.space.Promise();
        Ext.onSpaceReady().then(this.init.bind(this));
    },
 
    /**
     * @private
     */
    init: function() {
        // populate our list, in case we reloaded the app after creating some children 
        this.getChildren().then(function(children) {
            this._ready.fulfill();
        }.bind(this));
 
        return this._ready;
    },
 
    /**
     * Transform from a window.open()-style comma separated string of features to
     * a regular object hash.
     *
     * It turns something like "foo=bar,spam=eggs" into {foo: "bar", spam: "eggs"},
     * but note that it doesn't normalize different styles of boolean values, so,
     * e.g., "true", "TRUE", "on", 1, etc. don't all get turned to a boolean true
     * or anything like that.
     *
     * @private
     * @param {String|Object} featuresString Comma separated features string, or an
     *                                       object hash that will be passed through unmodified.
     * @return {Object} Object hash version of the features specified.
     */
    _featuresToObject: function(featuresString) {
        var features, vars;
 
        if (!featuresString) {
            // default features is just empty 
            features = {};
 
        } else if (typeof featuresString == "string") {
            // convert it to an object; note that we currently don't bother 
            // normalizing variations like "on", "true", 1, etc., into standardized 
            // boolean values 
            features = {};
            vars = featuresString.split(",").map(function(s) { return s.split("="); });
            vars.forEach(function(prop) {
                features[prop[0]] = (prop.length > 1) ? prop[1] : true;
            });
 
        } else {
            // actually a hash already 
            features = featuresString;
        }
 
        return features;
    },
 
    /**
     * Open a child web view.
     *
     * In Sencha Web Application Client, window.open() is aliased to this function,
     * so while you can call the explicitly namespaced version here, typically you
     * just do something like:
     *
     *     var child = window.open("http://example.com/");
     *
     * @param {String} url URL to open
     * @param {String} name (optional) Application-specific name to give the child
     *                      web view. Just like in a regular browser context, calling
     *                      window.open twice with the same name will reuse the same
     *                      child web view. Similarly, specifying the name as "_blank"
     *                      will always open a new child web view.
     * @param {String|Object} featuresStringOrObject Comma separated features string of the
     *                      form normally expected by window.open ("foo=bar,spam=eggs", etc...),
     *                      or an object hash containing the same information ({foo: "bar", spam: "eggs"}).
     * @return {Ext.space.window.Child} Child web view, or null if the URL was invalid.
     */
    open: function(url, name, featuresStringOrObject) {
        // 
        // TODO: support animations, and document them above 
        // 
        var child, features, isNamed = false;
 
        if (url && typeof url == "string") {
            features = this._featuresToObject(featuresStringOrObject);
            isNamed = !!name && name != "_blank";
 
            if (isNamed && this.namedWindows[name]) {
                // window so named exists; reuse it 
                child = this.namedWindows[name];
                this.navigate(child, url);
            } else {
                // create a new one 
                child = new Ext.space.window.Child(url, isNamed ? name : null, features);
 
                // cache by name, if necessary 
                if (isNamed) {
                    this.namedWindows[name] = child;
                }
 
                // cache by id, once we have it 
                child.getId().then(function() {
                    this.windows[child.id] = child;
                }.bind(this));
            }
 
            return child;
 
        } else {
            return null;
        }
    },
 
    /**
     * Fetch an array of child web views currently open.
     *
     * @return {Ext.space.Promise} Promise that resolves with an array of Ext.space.window.Child objects
     */
    getChildren: function() {
        var result = new Ext.space.Promise();
        var self = this;
        Ext.space.Communicator.send({
            command: "ChildView#getChildWebViews",
            callbacks: {
                onSuccess: function(webviews) {
                    result.fulfill(webviews.map(function(webview) {
                        var id = ""+webview.tabId;
                        if (!this.windows[id]) {
                            // isn't cached; could be due to app reload or something; 
                            // we need to recreate the child 
                            var child = this.windows[id] = new Ext.space.window.Child(webview.url, webview.name, {id: id}, true);
                            if (webview.name) {
                                this.namedWindows[webview.name] = child;
                            }
                        } else {
                            // cached; update the URL to reflect the latest 
                            this.windows[id].url = webview.url;
                        }
                        return this.windows[id];
                    }.bind(self)));
                },
                onError: function(error) {
                    result.reject(error);
                }
            }
        });
        return result;
    },
 
    /**
     * Navigate the provided child web view to the given URL.
     *
     * @param {String|Ext.space.window.Child} childOrHandle Child web view, or ID thereof
     * @param {String} url URL to open
     * @return {Ext.space.Promise} Promise that resolves once the navigation operation is
     *                       dispatched; note that this is not the same thing as the
     *                       URL being completely loaded. For that, you need to add
     *                       a listener for the child view's 'loadstop' event.
     */
    navigate: function(childOrHandle, url) {
        var result = new Ext.space.Promise();
        var self = this;
        var handle = childOrHandle.hasOwnProperty("id") ? childOrHandle.id : childOrHandle;
        if (url) {
            Ext.space.Communicator.send({
                command: "ChildView#navigate",
                id: handle,
                url: url,
                callbacks: {
                    onSuccess: function() {
                        // if the tab isn't in our window registry, attempt to load it 
                        if (typeof self.windows[handle] === "undefined") {
                            self.getChildren().then(function(children) {
                                if (typeof self.windows[handle] === "undefined") {
                                    result.reject("Tab not found");
                                } else {
                                    self.windows[handle].url = url;
                                    result.fulfill();
                                }
                            });
                        } else {
                            self.windows[handle].url = url;
                            result.fulfill();
                        }
                    },
                    onError: function(error) {
                        result.reject(error);
                    }
                }
            });
        } else {
            result.reject("Missing URL");
        }
        return result;
    },
 
    /**
     * Close the given child web view.
     *
     * @param {String|Ext.space.window.Child} childOrHandle Child web view, or ID thereof
     * @return {Ext.space.Promise} Promise that resolves when the child view is closed
     */
    close: function(childOrHandle) {
        var result = new Ext.space.Promise();
        var self = this;
        var handle = childOrHandle.hasOwnProperty("id") ? childOrHandle.id : childOrHandle;
        Ext.space.Communicator.send({
            id: handle,
            command: "ChildView#close",
            callbacks: {
                onSuccess: function() {
                    var name = self.windows[handle].name;
                    if (self.namedWindows[name]) {
                        delete self.namedWindows[name];
                    }
                    delete self.windows[handle];
                    result.fulfill();
                },
                onError: function(error) {
                    result.reject(error);
                }
            }
        });
        return result;
    },
 
    /**
     * Close all open child web views.
     *
     * @return {Ext.space.Promise} Promise that resolves when the child views are all closed
     */
    closeAll: function() {
        var result = new Ext.space.Promise();
        var self = this;
        Ext.space.Communicator.send({
            command: "ChildView#closeAll",
            callbacks: {
                onSuccess: function() {
                    self.namedWindows = {};
                    self.windows = {};
                    result.fulfill();
                },
                onError: function(error) {
                    result.reject(error);
                }
            }
        });
        return result;
    }
});
 
 
/*
 * Override window.open to use Sencha Web Application Client's API
 *
 * (we currently just obliterate the original since we don't need it)
 */
window.open = Ext.space.Window.open.bind(Ext.space.Window);
 
})();