/*
 *  Copyright 2014 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 override Ametys.message.Message (which only contains statics) and add methods
 */
(function ()
{
    Ext.override(Ametys.message.Message,
	{
		config: 
		{
			/*
             * @member Ametys.message.Message
			 * @cfg {String} type (required) The message type. A non null and non empty string. You must use a constant defined in this class.
			 */
			type: null,
			/*
             * @member Ametys.message.Message
			 * @cfg {Object} parameters The parameter associated to the message, the value to set here depends on the specification of the constant you are using for the type. Cannot be null, but may be empty.
			 * Be careful, theses parameters are the one to specify the message, not the targets. To specify parameters on the target, consider the target class.
			 */
			parameters: {},
			/*
             * @member Ametys.message.Message
			 * @cfg {Object/Object[]/Ametys.message.MessageTarget/Ametys.message.MessageTarget[]} targets (required) The targets configuration, or the targets themselves, associated to the message, the value to set here depends on the specification of the constant you are using for the type (see Ametys.message.MessageFactory). Cannot be null, but may be empty.
			 */
			targets: [],
			/*
             * @member Ametys.message.Message
			 * @cfg {Function} beforeFireCallback The function called when the message is going to fired. Has the following parameters:
			 * @cfg {Function} beforeFireCallback.msg The message created
			 */
			beforeFireCallback: null,
            /*
             * @member Ametys.message.Message
             * @cfg {Function} callback The function called when the message has been fired. Has the following parameters:
             * @cfg {Function} callback.msg The message created
             */
            callback: null
		},

		/*
         * @member Ametys.message.Message
		 * @property {Boolean} _isReady See #isReady
		 * @private
		 */
		_isReady: false,
        
        /*
         * @member Ametys.message.Message
         * @private
         * @property {Number} _num The number of the message.
         */

		/*
         * @member Ametys.message.Message
		 * @property {Date} _creationDate The date the message was created
		 * @private
		 */

		/*
         * @member Ametys.message.Message
		 * @property {String} _stack The call stack used to build the message
		 * @private
		 */
		
		/**
         * @member Ametys.message.Message
         * @method constructor
		 * Creates a message. Be careful, the message is not ready to use, as aynchronous called are done. See #cfg-callback to get the message when its ready.
		 * In 99% of the time, you do not have to keep this is as a variable. Creating a message will fire it automatically.
		 * @param {Object} config See configuration parameters.
		 */
		constructor: function(config)
		{
            config.targets = config.targets || []; 
            if (!Ext.isArray(config.targets))
            {
                config.targets = [config.targets];
            }
            var targetsAreReady = config.targets.length == 0 || config.targets[0].self != null;
            
			// If the event is a SELECTION CHANGED, let's prepare the message bus
            var isSelectionChangedEvent = config.type == Ametys.message.Message.SELECTION_CHANGED
			if (isSelectionChangedEvent)
			{
				var targetsTypes = [];
				function addTargetsType(targets)
				{
					for (var i = 0; i < targets.length; i++)
					{
						if (Ext.isObject(targets[i]))
						{
                            // Target can either be a config or a Target object
							targetsTypes.push(targets[i].id || targets[i].getId());
							addTargetsType(Ext.Array.from(targets[i].subtargets));
						}
					}
				}
				addTargetsType(Ext.Array.from(config.targets));
				
				Ext.create(this.self.getName(), {
					type: Ametys.message.Message.SELECTION_CHANGING,
					parameters: { targets: targetsTypes, ready: targetsAreReady },
					targets: []
				});
			}
			
			this._creationDate = new Date();
			this._num = this.self._num++;
			try
			{
				throw new Error("get trace");
			}
			catch (e)
			{
				this._stack = e.stack;
			}
			
			if (this.getLogger().isDebugEnabled())
			{
				this.getLogger().debug("Creating message " + this._num + " of type '" + config.type + "'");
			}
			
			// CREATE EVENT
			this.initConfig(config);
			
			// FIRE EVENT
			var me = this;
			function cb()
			{
				if (this.getLogger().isDebugEnabled())
				{
					this.getLogger().debug("Firing message " + this._num + " of type '" + config.type + "'");
				}

				me._isReady = true;
                if (Ext.isFunction(config.beforeFireCallback))
                {
                    config.beforeFireCallback(me);
                }
				Ametys.message.MessageBus.fire(me);
				if (Ext.isFunction(config.callback))
				{
					config.callback(me);
				}
			}
			
			if (targetsAreReady)
			{
				// Targets are already fine
		        Ext.defer(cb, 1, this);
			}
			else
			{
				// Targets need to be prepared
				function prep(targets)
				{
					me.setTargets(targets);
			        Ext.defer(cb, 1, me);
				}
				Ametys.message.MessageTargetFactory.createTargets(config.targets, prep, isSelectionChangedEvent ? this.$className + "$" + Ametys.message.Message.SELECTION_CHANGED : undefined);
			}
		},
		
		/**
         * @member Ametys.message.Message
         * @method isReady
		 * As the creation process of a message is an asynchronous process, this let you know if it is done.
		 * @returns {Boolean} True if the message has been created 
		 */
		isReady: function()
		{
			return this._isReady;
		},
		
		/**
         * @member Ametys.message.Message
         * @method getCreationDate
		 * Get the date the message was created
		 * @return {Date} The date the constructor was called. Cannot be null.
		 */
		getCreationDate: function()
		{
			return this._creationDate;
		},
		
		/**
         * @member Ametys.message.Message
         * @method getNumber
		 * Get the unique number of the message
		 * @return {Number} The number of the message. Cannot be null.
		 */
		getNumber: function()
		{
			return this._num;
		},
		
		/**
         * @member Ametys.message.Message
         * @method getCallStack
		 * Get the callstack used to create the message. For debug purposes
		 * @return {String} The callstack.
		 * @private
		 */
		getCallStack: function()
		{
			return this._stack;
		},
		
		/**
         * @member Ametys.message.Message
         * @method getTarget
		 * Get the first target matching the filter. If no filter is provided, get the first target (if available)
		 * @param {String/RegExp/Function} [filter] The filter upon the target type. If the filter is a function, it must return a boolean true to match, and it has the target as parameter.
		 * @param {Ametys.message.MessageTarget} filter.target The target to test. 
		 * @param {Number} [depth=0] The depth for filtering. 0 means it will dig all subtargets what ever the level is. 1 means it will only seek in the first level targets. And so on.
		 * @returns {Ametys.message.MessageTarget} The matching target, or the array of type hierarchy. Can be null.
		 * 
		 * The following examples will return a content target or null
		 * 		msg.getTarget("content");
		 * 		msg.getTarget(/^content$/);
		 * 		msg.getTarget(function (target) { return target.getId() == 'content' });
		 */
		getTarget: function(filter, depth)
		{
			if (!this.isReady())
			{
				var message = "Cannot get target on message " + this.getType() + " since it is not ready";
				this.getLogger().warn(message);
				throw new Error(message);
			}
			
			return Ametys.message.MessageTargetHelper.findTarget(this._targets, filter, depth);
		},
		
		/**
         * @member Ametys.message.Message
         * @method getTargets
		 * Same as #getTarget, but will return all the matching targets. When a target also have subtargets that would match the filter, only the parent target is returned.
		 * Get the targets matching the filter. If no filter is provided, get all the targets available
		 * @param {String/RegExp/Function} [filter] The filter upon the target type. If the filter is a function, it must return a boolean true to match, and it has the target as parameter.
		 * @param {Ametys.message.MessageTarget} filter.target The target to test. 
		 * @param {Number} [depth=0] The depth for filtering. 0 means it will dig all subtargets what ever the level is. 1 means it will only seek in the first level targets. And so on.
		 * @returns {Ametys.message.MessageTarget[]} The non-null array of matching targets.
		 * 
		 * The following examples will return an array of PageTarget or an empty array
		 * 		msg.getTargets("page");
		 * 		msg.getTargets(/^page$/);
		 * 		msg.getTargets(function (target) { return target.getId() == 'page' });
		 */
		getTargets: function(filter, depth)
		{
			if (!this.isReady())
			{
				var message = "Cannot get targets on message " + this.getType() + " since it is not ready";
				this.getLogger().warn(message);
				throw new Error(message);
			}

			return Ametys.message.MessageTargetHelper.findTargets(this._targets, filter, depth);
		}
	});
})();