/**
 * The class that represents a child window. It's basically a convenient way to
 * pass a child webview handle around. You normally don't instantiate these objects
 * manually, but rather get them from window.open() or Ext.space.Window.open():
 *
 *      var child = window.open("http://example.com/");
 *      Ext.space.Logger.log(child instanceof Ext.space.window.Child); // true
 *
 *      child.on("loadstop", function(evt) {
 *          // evt.url has finished loading; do something...
 *      });
 *
 *      // ... time passes ...
 *      child.close();
 *
 */
Ext.define("Ext.space.window.Child", {
    /**
     * Internal identifier for this web view
     *
     * @type {String}
     * @private
     */
    id: null,
 
    /**
     * URL specified at creation or when calling navigate(), or when a new URL is
     * successfully loaded into the web view.
     *
     * @type {String}
     * @private
     */
    url: null,
 
    /**
     * Application-defined name for this web view.
     *
     * @type {String}
     * @private
     */
    name: null,
 
    /**
     * Hash of features specified at the time of creation. So far, only openAnimation
     * and closeAnimation are supported.
     *
     * @type {Object}
     * @private
     */
    features: null,
 
    /**
     * Internal hash of web view event listeners, which application code hooks into.
     *
     * @type {Object}
     * @private
     */
    events: null,
 
    /**
     * Internal hash of callbacks for native web view events.
     *
     * @type {Object}
     * @private
     */
    apiListeners: null,
 
    /**
     * Promise which indicates that this object is ready for use (i.e., has been
     * assigned an ID).
     *
     * @type {Ext.space.Promise}
     * @private
     */
    initialized: null,
 
    /**
     * @private
     */
    constructor: function(url, name, features, skipCreate) {
        var me = this;
 
        var events = this.events = {
            loadstart: new Ext.space.Observable(),
            loadstop: new Ext.space.Observable(),
            loaderror: new Ext.space.Observable(),
            close: new Ext.space.Observable()
        };
 
        this.url = url;
        this.name = name;
        this.features = features;
 
        this.initialized = new Ext.space.Promise();
        this.apiListeners = {};
 
        if (!skipCreate) {
            // normal usage; create a native web view 
            var command = {
                command: "ChildView#create",
                url: url,
                callbacks: {
                    onSuccess: function(handle) {
                        me.id = ""+handle;
                        me.initialized.fulfill();
                    },
                    onError: function(error) {
                        me.initialized.reject();
                    }
                }
            };
 
            if (name) {
                command.name = name;
            }
 
            if (features) {
                if (features.openAnimation) {
                    command.openAnimation = features.openAnimation;
                }
                if (features.closeAnimation) {
                    command.closeAnimation = features.closeAnimation;
                }
            }
 
            Ext.space.Communicator.send(command);
 
        } else {
            // skip creation of the native object (useful for rebuilding after a refresh); 
            // in this case, features.id should have the existing web view's ID 
            if (features && features.id) {
                this.id = features.id;
                this.initialized.fulfill();
            } else {
                this.initialized.reject();
            }
        }
 
        this.on("loadstop", function(evt) {
            // try and keep the URL up to date as best we can 
            if (evt && evt.url) {
                this.url = evt.url;
            }
        }.bind(this));
    },
 
    /**
     * Fetch the internal web view ID. Also useful as a guard to make sure the child
     * view is ready for use before doign too much with it.
     *
     * @return {Ext.space.Promise} Promise that resolves with the web view ID
     */
    getId: function() {
        return this.initialized.then(function() {
            return this.id;
        }.bind(this));
    },
 
    /**
     * Alias of Ext.space.window.Child#open
     *
     * Navigate the web view to the given URL.
     *
     * @inheritdoc Ext.space.window.Child#open
     */
    navigate: function(url) {
        return this.initialized.then(function() {
            return Ext.space.Window.navigate(this, url);
        }.bind(this));
    },
 
    /**
     * Navigate the web view to the given URL.
     *
     * @param {String} url The URL to open.
     * @return {Ext.space.Promise} Promise that resolves when the navigate operation is
     *                       dispatched; note that this is different from the URL
     *                       being completely loaded (listen for the 'loadstop' event
     *                       for that).
     */
    open: function(url) {
        return this.navigate(url);
    },
 
    /**
     * Close the web view.
     *
     * @return {Ext.space.Promise} Promise that resolves when the web view is closed.
     */
    close: function() {
        return this.initialized.then(function() {
            return Ext.space.Window.close(this);
        }.bind(this));
    },
 
    /**
     * Wire up event listeners for the web view.
     *
     * There are four events applications can listen for: 'loadstart' (loading from
     * a URL has begun), 'loadstop' (loading from a URL has finished), 'loaderror'
     * (an error occurred while loading), and 'close' (the web view is closing).
     *
     * @param {String} event Event name to listen for (loadstart, loadstop, loaderror, close)
     * @param {Function} callback Callback to invoke when the event fires
     */
    on: function(event, callback) {
        var events = this.events;
        var apiListeners = this.apiListeners;
 
        // allow "exit" as an alias for "close" (PhoneGap compatibility) 
        if (event == "exit") {
            event = "close";
        }
 
        // only register events we support; otherwise just silently swallow it 
        if (events[event]) {
            events[event].addListener(callback);
        }
 
        // make sure to register our own listeners with the native bridge, on demand; 
        // these will invoke the listeners app code has registered in this.events.* 
        function makeListener(registry, eventname) {
            return function(loadEvent) {
                registry[eventname].invokeListeners(loadEvent);
            };
        }
 
        if (!apiListeners[event]) {
            this.getId().then(function(id) {
                var cb = makeListener(events, event);
                Ext.space.Communicator.send({
                    command: "ChildView#addEventListener",
                    id: id,
                    eventname: event,
                    callbacks: {
                        onEvent: cb,
                        onSuccess: function() {
                            // mark that we have it registered 
                            apiListeners[event] = cb;
                        },
                        onError: function() {}
                    }
                });
            });
        }
    },
 
    /**
     * Shorthand method, same as calling .on('loadstart', callback).
     *
     * @param {Function} callback Function to invoke when the loadstart event fires
     */
    onloadstart: function(callback) {
        return this.on("loadstart", callback);
    },
 
    /**
     * Shorthand method, same as calling .on('loadstop', callback).
     *
     * @param {Function} callback Function to invoke when the loadstop event fires
     */
    onloadstop: function(callback) {
        return this.on("loadstop", callback);
    },
 
    /**
     * Shorthand method, same as calling .on('loaderror', callback).
     *
     * @param {Function} callback Function to invoke when the loaderror event fires
     */
    onloaderror: function(callback) {
        return this.on("loaderror", callback);
    },
 
    /**
     * Shorthand method, same as calling .on('close', callback).
     *
     * @param {Function} callback Function to invoke when the close event fires
     */
    onclose: function(callback) {
        return this.on("close", callback);
    },
 
    /**
     * Add a listener for the specified event. This is mostly an alias for the .on()
     * method, except that for compatibility with PhoneGap applications, 'exit' is
     * also supported as an alias for the 'close' event.
     *
     * @param {String} event Event name to listen for (loadstart, loadstop, loaderror, close/exit)
     * @param {Function} callback Callback to invoke when the event fires
     */
    addEventListener: function(event, callback) {
        return this.on.apply(this, arguments);
    }
});