/**
 * Ext.space.Applications is an API that lets applications retrieve information about
 * an organization's other applications.
 *
 * To fetch a list of applications, use the .list() method:
 *
 *      Ext.space.Applications.list().then(function(list) {
 *          list.forEach(function(app) {
 *              // do something with the app object; for example, to open them all:
 *              Ext.space.Applications.open(app);
 *          });
 *      });
 *
 * The API also allows applications to listen for updates to the list of applications
 * and run a callback in response:
 *
 *      Ext.space.Applications.onUpdate(function(reason, operation, list) {
 *          // process the list when it changes (`reason` and `operation` are hints)
 *      });
 *
 * Additionally, the API allows applications to listen for badge/alert/data
 * notifications being pushed to <i>any</i> application in the organization (as
 * opposed to Ext.space.Notification, which listens for notifications for the
 * current application only). This is useful for creating administration dashboards
 * that need to display data from multiple applications simultaneously.
 */
Ext.define("Ext.space.Applications", {
    extend: Ext.space.Observable,
 
    singleton: true,
 
    /**
     * @private
     */
    events: null,
 
    /**
     * @private
     */
    _listeningForNotifications: false,
 
    /**
     * Whether or not the module is watching the list for changes
     * @readonly
     * @private
     * @type {boolean}
     */
    _watchingList: false,
 
    /**
     * @private
     */
    constructor: function() {
        this.events = {
            badge: new Ext.space.Observable(),
            alert: new Ext.space.Observable(),
            data: new Ext.space.Observable()
        };
 
        var args = Array.prototype.slice.call(arguments);
        Ext.onSpaceReady().then(function() {
            Ext.space.Applications.superclass.constructor.apply(this, args);
        }.bind(this));
    },
 
    /**
     * Listen for changes to the applications list
     * @private
     */
    _watchList: function() {
        if (!this._watchingList) {
            Ext.space.Communicator.send({
                command: "Applications#watchList",
                callbacks: {
                    onSuccess: this._onUpdate.bind(this)
                }
            });
 
            this._watchingList = true;
 
            // TODO: when Observable gets the ability to remove listeners, unwatch when 
            //       there are no more listeners left 
        }
    },
 
    /**
     * Callback that fires when the application list changes
     *
     * @private
     * @param {string} reason The thing that changed (categories, applications, etc...)
     * @param {string} operation The type of change (add, remove, update)
     * @param {Array} list List of objects containing the current applications list
     */
    _onUpdate: function(reason, operation, list) {
        this.invokeListeners(reason, operation, list);
    },
 
    /**
     * Retrieve the current organization's applications list
     *
     * @return {Ext.space.Promise} Promise that resolves with the list
     */
    list: function() {
        var result = new Ext.space.Promise();
 
        Ext.space.Communicator.send({
            command: "Applications#list",
            callbacks: {
                onSuccess: function(apps) {
                    // TODO: determine how much else to transform these, if at all 
                    //       (e.g., remove 'deleted', convert dates to objects, etc...) 
                    result.fulfill(apps.map(function(app) {
                        return new Ext.space.applications.Application(app);
                    }));
                },
                onError: function(error) {
                    result.reject(error);
                }
            }
        });
 
        return result;
    },
 
    /**
     * Open an application.
     *
     * @param {Object|string} app The application object or application ID
     * @return {Ext.space.Promise} Promise that resolves when the app is launched
     */
    open: function(app) {
        var result = new Ext.space.Promise();
 
        if (app) {
            Ext.space.Communicator.send({
                command: "Applications#open",
                id: app.id || app,
                callbacks: {
                    onSuccess: function() {
                        result.fulfill();
                    },
                    onError: function(error) {
                        result.reject(error);
                    }
                }
            });
 
        } else {
            result.reject("Cannot open application; no ID provided.");
        }
 
        return result;
    },
 
    /**
     * Register a callback to run when the application list changes in some way.
     *
     * The callback will be passed a three parameters, a string indicating the
     * reason for the callback being invoked ("categories", etc...), a
     * string indicating the operation that happened (such as "add", "remove",
     * "update"), and an array of objects representing the current state of the
     * applications list itself.
     *
     *      function onListUpdate(reason, operation, list) {
     *          Ext.space.Logger.log("Updated: ", reason, operation);
     *          // do something with the items in `list`...
     *      }
     *
     *      Ext.space.Applications.onUpdate(onListUpdate);
     *
     * @param {Function} callback Callback to fire when the application list changes.
     */
    onUpdate: function(callback) {
        var args = Array.prototype.slice.call(arguments);
        Ext.onSpaceReady().then(function() {
            if (!this._watchingList) {
                this._watchList();
            }
            this.addListener.apply(this, args);
        }.bind(this));
    },
 
    /**
     * Start listening for push notifications for all apps in the current organization.
     *
     * @private
     */
    _listenForNotifications: function() {
        this._listeningForNotifications = true;
        Ext.onSpaceReady().then(function() {
            Ext.space.Communicator.send({
                command: "Notification#registerHandler",
                callbacks: {
                    onGlobalBadgeChange: this._onBadgeChange.bind(this),
                    onGlobalAlertReceived: this._onAlert.bind(this),
                    onGlobalDataReceived: this._onData.bind(this),
                    onSuccess: function() { /* no need to do anything */ }
                }
            });
        }.bind(this));
    },
 
    /**
     * Callback that fires when any application's badge has changed.
     *
     * @private
     * @param {Object} change object containing the application ID and the new
     *                        badge's value: {appId: "...", badgeValue: "..."}
     */
    _onBadgeChange: function(change) {
        this.events.badge.invokeListeners(change.appId, change.badgeValue);
    },
 
    /**
     * Register a callback to run when any application's badge has changed, across
     * all applications in the current organization.
     *
     * function onBadgeChanged(appId, badge) {
     *      Ext.space.Logger.log("New Badge (" + appId + "): " + badge);
     * }
     *
     * Ext.space.Applications.onBadgeChange(onBadgeChanged);
     *
     * @param {Function} callback Callback for when any application's badge has changed.
     */
    onBadgeChange: function(callback) {
        if (!this._listeningForNotifications) {
            this._listenForNotifications();
        }
        this.events.badge.addListener(callback);
    },
 
    /**
     * Callback that fires when an alert is received by any application in the
     * current organization.
     *
     * @private
     * @param {Object} change object containing the application ID and the alert
     *                        object: {appId: "...", alert: "..."}
     */
    _onAlert: function(change) {
        this.events.alert.invokeListeners(change.appId, change.alert);
    },
 
    /**
     * Register a callback to run when an alert is received for any application in
     * the current organization.
     *
     * function onAlertReceived(appId, alert) {
     *      Ext.space.Logger.log("New alert (" + appId + "): " + alert.message);
     *      // alert.icon string of the icon
     *      // alert.tags contains an array of tags
     * }
     *
     * Ext.space.Applications.onAlert(onAlertReceived);
     *
     * @param {Function} callback Callback for when an alert is received by any application.
     */
    onAlert: function(callback) {
        if (!this._listeningForNotifications) {
            this._listenForNotifications();
        }
        this.events.alert.addListener(callback);
    },
 
    /**
     * Callback that fires when data is received for any application in the current
     * organization.
     *
     * @private
     * @param {string} change object containing the application ID and the data
     *                        string: {appId: "...", data: "..."}
     */
    _onData: function(change) {
        this.events.data.invokeListeners(change.appId, change.data);
    },
 
    /**
     * Register a callback to run when data is received by any application in the
     * current organization.
     *
     * function onDataReceived(appId, data) {
     *      Ext.space.Logger.log("Data (" + appId + "): " + data);
     * }
     *
     * Ext.space.Applications.onData(onDataReceived);
     *
     * @param {Function} callback Callback for when data is received by any application.
     */
    onData: function(callback) {
        if (!this._listeningForNotifications) {
            this._listenForNotifications();
        }
        this.events.data.addListener(callback);
    }
});