/*
 *  Copyright 2013 Anyware Services
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

/**
 * This class handle the message bus. A message is a way for ui elements to communicate without knwoing each others.
 * One tool will say "I just edit the content X", a concerned button will then refresh. But both elements does not references themselves. 
 * 
 * Use this class to register/unregister your component or to send a message.
 */
Ext.define("Ametys.message.MessageBus",
    {
        singleton: true,
        
        /**
         * @property {Object} _listeners The registered listeners. An association of event type and functions.
         * @property {Object[]} _listeners.MESSAGETYPE The event type list of listeners
         * @property {Function} _listeners.MESSAGETYPE.fn The function to call
         * @property {Object} _listeners.MESSAGETYPE.scope The scope to use
         * @private
         */
        _listeners: {},

        /**
         * @private
         * @property {Boolean} _dispatching=false Is the MessageBus currently firing a message
         */
        _dispatching: false,

        
        /**
         * @property {Ametys.message.Message} _currentSelectionMessage The current selection
         * @private
         */
        /**
         * @property {Ametys.message.Message} _currentSelectionChangingMessage The current changing selection
         * @private
         */
        
        /**
         * Is the MessageBus currently dispatching a message
         * @returns {Boolean} true if a fire is running
         */
        isDispatching: function()
        {
            return this._dispatching;
        },
        
        /**
         * This method adds an function to the registered list of message receivers for a given event type.
         * You must unregister the object before you destroy it. See {@link #un}
         * @param {String} messageType See Ametys.message.Message#cfg-type. Use '*' to register to all messages. Cannot be null.
         * @param {Function} listener The function to call. The function has the following signature:
         * @param {Ametys.message.Message} listener.message The message received
         * @param {Object} scope The scope used to call this function
         */
        on: function(messageType, listener, scope)
        {
            if (messageType == null)
            {
                var msg = "Ametys.message.MessageBus#on cannot be called with a null messageType";
                this.getLogger().error(msg);
                throw new Error(msg);
            }
            
            if (this._listeners[messageType] == null)
            {
                this._listeners[messageType] = [];
            }
            
            this._listeners[messageType].push({
                fn: listener,
                scope: scope
            });
        },

        /**
         * This method removes an object to the registered list of message receivers.
         * @param {String} messageType See Ametys.message.Message#cfg-type
         * @param {Function} listener The function to call
         * @param {Object} scope The scope used to call this function
         */
        un: function(messageType, listener, scope)
        {
            for (var i = this._listeners[messageType].length - 1; i >= 0; i--)
            {
                if (this._listeners[messageType][i].fn == listener && this._listeners[messageType][i].scope == scope)
                {
                    Ext.Array.remove(this._listeners[messageType], this._listeners[messageType][i]);
                    return;
                }
            }
        },
        
        /**
         * This method removes an object to the all registered list of message receivers.
         * @param {Object} scope The scope used to call this function
         */
        unAll: function (scope)
        {
            for (var messageType in this._listeners)
            {
                for (var i = this._listeners[messageType].length - 1; i >= 0; i--)
                {
                    if (this._listeners[messageType][i].scope == scope)
                    {
                        Ext.Array.remove(this._listeners[messageType], this._listeners[messageType][i]);
                    }
                }
            }
        },
        
        /**
         * Get the current selection.
         * @returns {Ametys.message.Message} The last message of type Ametys.message.Message#SELECTION_CHANGED. Cannot be null, be can contains empty targets.
         */
        getCurrentSelectionMessage: function()
        {
            return this._currentSelectionMessage;
        },
        
        /**
         * @private
         * Main method of the bus : send a serie of message to the registered objects.
         * @param {Ametys.message.Message} message An array of message to send.
          */
        fire: function(message)
        {
            Ext.suspendLayouts();
            
            this._dispatching = true;

            try
            {
                // If this is a selection message, discard obsolete message and record the last one
                if (message.getType() == Ametys.message.Message.SELECTION_CHANGING)
                {
                    this._currentSelectionChangingMessage = message;
                    Ext.getCmp('ribbon').getPanel().suspendProcessing();
                }
                else if (message.getType() == Ametys.message.Message.SELECTION_CHANGED)
                {
                    if ((this._currentSelectionMessage && this._currentSelectionMessage.getCreationDate() > message.getCreationDate())
                        || (this._currentSelectionChangingMessage && this._currentSelectionChangingMessage.getCreationDate() > message.getCreationDate()))
                    {
                        // discarding this new selection message that is obsolete
                        
                        if (this.getLogger().isDebugEnabled())
                        {
                            this.getLogger().debug("Discarding obsolete selection message " + message.getNumber() + " with creation date " + message.getCreationDate() + " and associated targets " + message.getTargets() + "\n" + message._stack);
                        }
    
                        return;
                    }
                    
                    this._currentSelectionChangingMessage = null;
                    this._currentSelectionMessage = message;
                    Ext.getCmp('ribbon').getPanel().resumeProcessing();
                }
                
                // Duplicate listener array to avoid synchronization problems
                var listeners = Ext.Array.merge(Ext.Array.clone(this._listeners['*'] || []),
                                                Ext.Array.clone(this._listeners[message.getType()] || []));
                
                // Stops server comm to group all incoming request
                Ametys.data.ServerComm.suspend();
    
                // Loop on listeners
                for (var i = 0; i < listeners.length; i++)
                {
                    var obj = listeners[i];
                    if (typeof obj.fn == "function")
                    {
                        try
                        {
                            obj.fn.apply(obj.scope, [message]);
                        }
                        catch (e)
                        {
                            Ametys.log.ErrorDialog.display({
                                    title: "{{i18n PLUGINS_CORE_UI_MSG_MESSAGEBUS_BUSERROR_TITLE}}",
                                    text: "{{i18n PLUGINS_CORE_UI_MSG_MESSAGEBUS_BUSERROR_DESC}}",
                                    details: e,
                                    category: "Ametys.message.MessageBus"
                            });
                            function throwException(e) 
                            { 
                                throw e; 
                            }
                            
                            Ext.defer(throwException, 1, this, [e]);
                        }
                    }                
                }
                
                // Refresh tools
                try
                {
                    Ametys.tool.ToolsManager.refreshTools();
                }
                catch (e)
                {
                    Ametys.log.ErrorDialog.display({
                            title: "{{i18n PLUGINS_CORE_UI_MSG_MESSAGEBUS_REFRESHERROR_TITLE}}",
                            text: "{{i18n PLUGINS_CORE_UI_MSG_MESSAGEBUS_REFRESHERROR_DESC}}",
                            details: e,
                            category: "Ametys.message.MessageBus"
                    });
    
                    function throwException(e) 
                    { 
                        throw e;
                    }
                    Ext.defer(throwException, 1, this, [e]);
                }
    
                // Finally send all requests
                Ametys.data.ServerComm.restart();
            }
            finally
            {
                this._dispatching = false;
                Ext.resumeLayouts(true);
            }
        },
        
        /**
         * @private
         * This will log the different listeners size. For debug purposes.
         */
        debugListenersSize: function()
        {
            var s = "";
            var size = 0;
            Ext.Object.each(this._listeners, function(messageType, listeners) {
                size += listeners.length;
                s += " - " + messageType + ": " + listeners.length;
            });
            console.log(size + " Listeners. " + s);
            s = null;
        }
    }
);