# Source: core/application.js

    /**
     * @module core/application
     * @requires event/event-manager
     * @requires template
     * @requires ui/popup/popup
     * @requires ui/popup/alert
     * @requires ui/popup/confirm
     * @requires ui/loading
     * @requires ui/popup/growl
     * @requires ui/slot
     */

    var Target = require("./target").Target,
        Template = require("./template"),
        MontageWindow = require("../window-loader/montage-window").MontageWindow,
        Slot;

    require("./dom");

    var FIRST_LOAD_KEY_SUFFIX = "-is-first-load";

    /**
     * The application is a singleton, it initially loads and oversees the running program.
     * It is also responsible for window management.
     * The behavior of the application can be modified by implementing a delegate
     * {@link Application#delegate}.
     * It is also possible to subclass the application by specifying an
     * `applicationPrototype"` in the `package.json`.
     *
     * @class Application
     * @extends Target
     */
    var Application = exports.Application = Target.specialize( /** @lends Application.prototype # */ {

        /**
         * Provides a reference to the Montage event manager used in the
         * application.
         *
         * @property {EventManager} value
         * @default null
         */
        eventManager: {
            value: null
        },

        /**
         * Provides a reference to the parent application.
         *
         * @property {Application} value
         * @default null
         */
        parentApplication: {
            value: null
        },

        name: {
            value: null
        },

        _isFirstLoad: {
            value: null
        },

        isFirstLoad: {
            get: function () {
                return this._isFirstLoad;
            }
        },

        /**
         * Provides a reference to the main application.
         *
         * @type {Application}
         * @default this
         */
        mainApplication: {
            get: function () {
                // JFD TODO: We should cache the result, would need to update it
                // when the window is detached or attached
                var mainApplication = this;
                while (mainApplication.parentApplication) {
                    mainApplication = mainApplication.parentApplication;
                }
                return mainApplication;
            }
        },

        /**
         * possible values: "z-order", "reverse-z-order", "z-order", "reverse-open-order"
         * @private
         * @property {String} value
         */
        _windowsSortOrder: {
            value: "reverse-z-order"
        },

        /**
         * Determines the sort order for the Application.windows array.
         * Possible values are: z-order, reverse-z-order, open-order,
         * reverse-open-order
         *
         * @returns {string}
         * @default "reverse-z-order"
         */
        windowsSortOrder: {
            get: function () {
                if (this.parentApplication === null) {
                    return this._windowsSortOrder;
                } else {
                    return this.mainApplication.windowsSortOrder;
                }
            },

            set: function (value) {
                if (this.parentApplication === null) {
                    if (["z-order", "reverse-z-order", "z-order", "reverse-open-order"].indexOf(value) !== -1) {
                        this._windowsSortOrder = value;
                    }
                } else {
                    this.mainApplication.windowsSortOrder = value;
                }
            }
        },

        /**
         * Provides a reference to all the windows opened by the main application
         * or any of its descendents, including the main window itself.
         * The list is kept sorted, the sort order is determined by the
         * `Application.windowsSortOrder` property
         *
         * @returns {Array<MontageWindow>}
         */
        windows: {
            get: function () {
                if (this.parentApplication === null) {
                    if (!this._windows) {
                        var theWindow = new MontageWindow();
                        theWindow.application = this;
                        theWindow.window = window;
                        this.window = theWindow;

                        this._windows = [this.window];
                        this._multipleWindow = true;
                    }
                    return this._windows;
                } else {
                    return this.mainApplication.windows;
                }
            }
        },

        _window: {
            value: null
        },

        /**
         * Provides a reference to the MontageWindow associated with the
         * application.
         *
         * @returns {MontageWindow}
         */
        window: {
            get: function () {
                if (!this._window && this === this.mainApplication) {
                    var theWindow = new MontageWindow();
                    theWindow.application = this;
                    theWindow.window = window;
                    this._window = theWindow;
                }
                return this._window;
            },

            set: function (value) {
                if (!this._window) {
                    this._window = value;
                }
            }
        },

        /**
         * An array of the child windows attached to the application.
         * @type {Array<MontageWindow>}
         * @default {Array} []
         */
        attachedWindows: {
            value: []
        },

        /**
         * Returns the event manager for the specified window object.
         * @function
         * @param {Window} aWindow The browser window whose event manager object should be returned.
         * @returns aWindow.defaultEventMananger
         */
        eventManagerForWindow: {
            value: function (aWindow) {
                return aWindow.defaultEventMananger;
            }
        },

        /**
         * Return the top most window of any of the Montage Windows.
         * @type {MontageWindow}
         * @default document.defaultView
         */
        focusWindow: {
            get: function () {
                var windows = this.windows,
                    sortOrder = this.windowsSortOrder;

                if (sortOrder === "z-order") {
                    return windows[0];
                } else if (sortOrder === "reverse-z-order") {
                    return windows[windows.length - 1];
                } else {
                    for (var i in windows) {
                        if (windows[i].focused) {
                            return windows[i];
                        }
                    }
                }
            }
        },

        /**
         * The application's delegate object, it can implement a
         * `willFinishLoading` method that will be called right after the
         * index.html is loaded.
         * The application delegate is also the next event target after the
         * application.
         * @type {Object}
         * @default null
         */
        delegate: {
            value: null
        },

        nextTarget: {
            get: function () {
                return this.delegate;
            }
        },

        /**
         * Opens a component in a new browser window, and registers the window with
         * the Montage event manager.
         *
         * The component URL must be in the same domain as the calling script. Can
         * be relative to the main application
         *
         * @function
         * @param {string} component, the path to the reel component to open in the
         * new window.
         * @param {string} name, the component main class name.
         * @param {Object} parameters, the new window parameters (accept same
         * parameters than window.open).
         *
         * @example
         * var app = document.application;
         * app.openWindow("docs/help.reel", "Help", "{width:300, height:500}");
         */
        openWindow: {
            value: function (component, name, parameters) {

                var self = this,
                    childWindow = new MontageWindow(),
                    childApplication,
                    event,
                    windowParams = {
                        location: false,
                        // height: <pixels>,
                        // width: <pixels>,
                        // left: <pixels>,
                        // top: <pixels>,
                        menubar: false,
                        resizable: true,
                        scrollbars: true,
                        status: false,
                        titlebar: true,
                        toolbar: false
                    };

                var loadInfo = {
                    module: component,
                    name: name,
                    parent: window,
                    callback: function (aWindow, aComponent) {
                        var sortOrder;

                        // Finishing the window object initialization and let the consumer knows the window is loaded and ready
                        childApplication = aWindow.document.application;
                        childWindow.window = aWindow;
                        childWindow.application = childApplication;
                        childWindow.component = aComponent;
                        childApplication.window = childWindow;

                        self.attachedWindows.push(childWindow);

                        sortOrder = self.mainApplication.windowsSortOrder;
                        if (sortOrder === "z-order" || sortOrder === "reverse-open-order") {
                            self.windows.unshift(childWindow);
                        } else {
                            self.windows.push(childWindow);
                        }

                        event = document.createEvent("CustomEvent");
                        event.initCustomEvent("load", true, true, null);
                        childWindow.dispatchEvent(event);
                    }
                };

                // If this is the first time we open a window, let's install a focus listener and make sure the body element is focusable
                // Applicable only on the main application
                if (this === this.mainApplication && !this._multipleWindow) {
                    var montageWindow = this.window;    // Will cause to create a Montage Window for the mainApplication and install the needed event handlers
                }

                var param, value, separator = "", stringParamaters = "";
                if (typeof parameters === "object") {

                    // merge the windowParams with the parameters
                    for (param in parameters) {
                        if (parameters.hasOwnProperty(param)) {
                            windowParams[param] = parameters[param];
                        }
                    }
                }

                // now convert the windowParams into a string
                var excludedParams = ["name"];
                for (param in windowParams) {
                    if (excludedParams.indexOf(param) === -1) {
                        value = windowParams[param];
                        if (typeof value === "boolean") {
                            value = value ? "yes" : "no";
                        } else {
                            value = String(value);
                            if (value.match(/[ ,"]/)) {
                                value = '"' + value.replace(/"/g, "\\\"") + '"';
                            }
                        }
                        stringParamaters += separator + param + "=" + value;
                        separator = ",";
                    }
                }

                global.require.loadPackage({name: "montage"}).then(function (require) {
                    var newWindow = window.open(require.location + "window-loader/index.html", "_blank", stringParamaters);
                    newWindow.loadInfo = loadInfo;
                });

                return childWindow;
            }
        },

        /**
         * Attach a window to a parent application.
         * When a window open, it is automatically attached to the Application used
         * to create the window.
         * @function
         * @param {MontageWindow} window to detach.
         */
        attachWindow: {
            value: function (montageWindow) {
                var parentApplicaton = montageWindow.application.parentApplication,
                    sortOrder;

                if (parentApplicaton !== this) {
                    if (parentApplicaton) {
                        parentApplicaton.detachWindow(montageWindow);
                    }

                    montageWindow.parentApplication = this;
                    this.attachedWindows.push(montageWindow);

                    sortOrder = this.mainApplication.windowsSortOrder;
                    if (sortOrder === "z-order" || sortOrder === "reverse-open-order") {
                        this.windows.unshift(montageWindow);
                    } else {
                        this.windows.push(montageWindow);
                    }
                    montageWindow.focus();
                }
                return montageWindow;
            }
        },

        /**
         * Detach the window from its parent application.
         * If no montageWindow is specified, the current application's windows will
         * be detached.
         * @function
         * @param {MontageWindow} window to detach.
         */
        detachWindow: {
            value: function (montageWindow) {
                var index,
                    parentApplicaton,
                    windows = this.windows;

                if (montageWindow === undefined) {
                    montageWindow = this.window;
                }
                parentApplicaton = montageWindow.application.parentApplication;

                if (parentApplicaton === this) {
                    index = this.attachedWindows.indexOf(montageWindow);
                    if (index !== -1) {
                        this.attachedWindows.splice(index, 1);
                    }
                    index = windows.indexOf(montageWindow);
                    if (index !== -1) {
                        windows.splice(index, 1);
                    }
                    montageWindow.application.parentApplication = null;
                } else if (parentApplicaton) {
                    parentApplicaton.detachWindow(montageWindow);
                }
                return montageWindow;
            }
        },

        constructor: {
            value: function Application() {
                if (
                    typeof window !== "undefined" &&
                        window.loadInfo && !this.parentApplication
                ) {
                    this.parentApplication = window.loadInfo.parent.document.application;
                }
            }
        },

        _load: {
            value: function (applicationRequire, callback) {
                var rootComponent,
                    self = this;

                this.name = applicationRequire.packageDescription.name;
                this._loadApplicationContext();

                // assign to the exports so that it is available in the deserialization of the template
                exports.application = self;

                return require.async("ui/component").then(function(exports) {

                    rootComponent = exports.__root__;

                    if (typeof document !== "undefined") {
                        rootComponent.element = document;
                        return Template.instantiateDocument(document, applicationRequire);
                    }

                }).then(function (part) {
                    self.callDelegateMethod("willFinishLoading", self);
                    rootComponent.needsDraw = true;
                    if (callback) {
                        callback(self);
                    }
                    return self;
                });
            }
        },

        _loadApplicationContext: {
            value: function () {
                if (this._isFirstLoad === null) {

                    var hasAlreadyBeenLoaded,
                        alreadyLoadedLocalStorageKey = this.name + FIRST_LOAD_KEY_SUFFIX;

                    if (typeof localStorage !== "undefined") {
                        localStorage.getItem(alreadyLoadedLocalStorageKey);

                        if (hasAlreadyBeenLoaded === null) {
                            try {
                                localStorage.setItem(alreadyLoadedLocalStorageKey, true);
                            } catch (error) {
                                //console.log("Browser is in private mode.");
                            }
                        }
                    }

                    this._isFirstLoad = !hasAlreadyBeenLoaded;
                }
            }
        },

        /**
         * @private
         */
        _alertPopup: {value: null, enumerable: false},
        /**
         * @private
         */
        _confirmPopup: {value: null, enumerable: false},
        /**
         * @private
         */
        _notifyPopup: {value: null, enumerable: false},
        /**
         * @private
         */
        _zIndex: {value: null},

        /**
         * @private
         */
        _isSystemPopup: {value: function (type) {
            return (type === 'alert' || type === 'confirm' || type === 'notify');
        }},

        /**
         * @private
         */
        _createPopupSlot: {value: function (zIndex) {
            var slotEl = document.createElement('div');
            document.body.appendChild(slotEl);
            slotEl.style.zIndex = zIndex;
            slotEl.style.position = 'absolute';

            var popupSlot = new Slot();
            popupSlot.element = slotEl;
            popupSlot.attachToParentComponent();
            return popupSlot;
        }},

        getPopupSlot: {
            value: function (type, content, callback) {

                var self = this;
                require.async("ui/slot.reel/slot")
                .then(function (exports) {
                    Slot = Slot || exports.Slot;
                    type = type || "custom";
                    var isSystemPopup = self._isSystemPopup(type), zIndex, popupSlot;
                    self.popupSlots = self.popupSlots || {};

                    if(isSystemPopup) {
                        switch (type) {
                            case "alert":
                                zIndex = 19004;
                                break;
                            case "confirm":
                                zIndex = 19003;
                                break;
                            case "notify":
                                zIndex = 19002;
                                break;
                        }
                    } else {
                        // custom popup
                        if(!self._zIndex) {
                            self._zIndex = 17000;
                        } else {
                            self._zIndex = self._zIndex + 1;
                        }
                        zIndex = self._zIndex;
                    }

                    popupSlot = self.popupSlots[type];
                    if (!popupSlot) {
                        popupSlot = self.popupSlots[type] = self._createPopupSlot(zIndex);
                    }
                    // use the new zIndex for custom popup
                    if(!isSystemPopup) {
                        popupSlot.element.style.zIndex = zIndex;
                    }

                    popupSlot.content = content;
                    callback.call(this, popupSlot);

                });
            }
        },

        returnPopupSlot: {value: function (type) {
            var self = this;
            if(self.popupSlots && self.popupSlots[type]) {
                var popupSlot = self.popupSlots[type];
                popupSlot.content = null;
                // is there a way to remove the Slot
                // OR should we remove the slotEl from the DOM to clean up ?
            }

        }},

        /**
         * @private
         */
        _getActivePopupSlots: {
            value: function () {
                var arr = [];
                if(this.popupSlots) {
                    var keys = Object.keys(this.popupSlots);
                    if(keys && keys.length > 0) {
                        var i, len = keys.length, slot;
                        for(i=0; i< len; i++) {
                            slot = this.popupSlots[keys[i]];
                            if(slot && slot.content !== null) {
                                arr.push(slot);
                            }

                        }
                    }
                }
                return arr;
            }
        }
    });