/*
 *  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 represents the point of a relation: move, copy or reference operation (start or end).
 * It does contains a list of resources being operated and a kind of operation.
 * 
 * The resources are described using Ametys.message.MessageTarget.
 */
Ext.define("Ametys.relation.RelationPoint", 
	{
		/**
		 * @cfg {Object/Object[]/Ametys.message.MessageTarget/Ametys.message.MessageTarget[]} targets (required) The targets configurations, or the targets themselves that will be part of the operation.
		 */
		/**
		 * @property {Ametys.message.MessageTarget[]} targets The targets instances associated to the point. See #cfg-targets.
		 */
		/**
		 * @cfg {String/String[]} [relationTypes=Ametys.relation.Relation.REFERENCE] The supported relations for this point. One of the constants Ametys.relation.Relation.MOVE, Ametys.relation.Relation.COPY or Ametys.relation.Relation.REFERENCE.
		 */ 
		/**
		 * @property {String[]} relationTypes See #cfg-relationTypes.
		 */
		/**
		 * @cfg {Number} [positionInTargets=-1] When the relation point is a destination point, set this configuration to specify the index where to insert the sources in the #cfg-targets. For example, 0 means to insert as the first child and -1 means at the end. 
		 */
		/**
		 * @property {Number} positionInTargets See #cfg-positionInTargets.
		 */
		
		/**
		 * @private
		 * @property {Function[]} callbacks Callback registered through #waitForTargets during asynchronous process. 
		 */ 
		
		/**
		 * @property {Boolean} _isReady See #isReady
		 * @private
		 */
		_isReady: false,

		/**
		 * Creates a relation point.
		 * @param {Object} config The configuration object
		 */
		constructor: function(config)
		{
			// Handle relations
			this.relationTypes = config.relationTypes || [Ametys.relation.Relation.REFERENCE];
			if (!Ext.isArray(this.relationTypes))
			{
				this.relationTypes = [this.relationTypes];
			}
			
			// Handle position
			this.positionInTargets = config.positionInTargets;
			if (this.positionInTargets == null)
			{
				this.positionInTargets = -1;
			}
			
			// Handle message targets
			this.targets = config.targets || []; 
			if (!Ext.isArray(this.targets))
			{
				this.targets = [this.targets];
			}
			
			this.callbacks = [];
			
			var me = this;
			function cb()
			{
				me._isReady = true;
				for (var i = 0; i < me.callbacks.length; i++)
				{
					me.callbacks[i]();
				}
			}
			
			if (this.targets.length == 0 || this.targets[0].self)
			{
				// Targets are already fine
		        Ext.defer(cb, 1, this);
			}
			else
			{
				// Targets need to be prepared
				function prep(targets)
				{
					me.targets = targets;
			        Ext.defer(cb, 1, me);
				}
				Ametys.message.MessageTargetFactory.createTargets(this.targets, prep);
			}
		},
		
		/**
		 * Since Ametys.message.MessageTarget creation is an asynchronous process, this method allows to ensure that message targets are ready to use
		 * @param {Function} callback This function is called when the targets are ready (so is directly called if ready)
		 */
		waitForTargets: function(callback)
		{
			if (this._isReady)
			{
				Ext.defer(callback, 1, this);
			}
			else
			{
				this.callbacks.push(callback);
			}
		},
		
		/**
		 * As the creation process of a message target is an asynchronous process, this let you know if it is done.
		 * @returns {Boolean} True if the message target have been created 
		 */
		isReady: function()
		{
			return this._isReady;
		},
		
		/**
		 * 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 following 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 since it is not ready";
				this.getLogger().warn(message);
				throw new Error(message);
			}
			
			return Ametys.message.MessageTargetHelper.findTarget(this.targets, filter, depth);
		},
		
		/**
		 * 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 uppon the target type. If the filter is a function, it must return a boolean true to match, and it has the following 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 since it is not ready";
				this.getLogger().warn(message);
				throw new Error(message);
			}

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