/*
 *  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 is the class for all tools who take care of the current selection.
 */
Ext.define('Ametys.tool.SelectionTool', {
	extend: "Ametys.tool.Tool",
	
    statics:
    {
        /**
         * @readonly
         * @property {Object} DeclineReason Enumerator of reason to decline the selection 
         */
        DeclineReason:
        {
            /**
             * @readonly
             * @property {String} EMPTY There is no selection
             */
            EMPTY: "empty",
            
            /**
             * @readonly
             * @property {String} NO_MATCH There is no matching selection
             */
            NO_MATCH: "noMatch",

            /**
             * @readonly
             * @property {String} MULTIPLE There is multiple matched in the selection
             */
            MULTIPLE: "multiple"
        }
    },

	/**
	 * @cfg {String} selection-target-id Specify this configuration to obtain a tool that take care of the current selection type. The string is a regexp that have to match the current selection type. See #cfg-subtarget-id.
	 */
	/**
	 * @cfg {String} selection-subtarget-id When specified as the same time as #cfg-selection-target-id is, the tool will be enable/disabled only if the selection target is matching #cfg-selection-target-id AND if there is a subtarget that matched this regexp. See #cfg-subsubtarget-id.
	 */
	/**
	 * @cfg {String} selection-subsubtarget-id Same as #cfg-subtarget-id at a third level.
	 */
	/**
	 * @cfg {String} [selection-enable-multiselection=false] If 'false' the tool will be in a "no match selection state" as soon as the are many elements selected. Works only when #cfg-selection-target-id is specified.
	 */
	/**
	 * @cfg {String} selection-description-empty The description when the selection is empty
	 */
	/**
	 * @cfg {String} selection-description-nomatch The description when the selection does not match the awaited #cfg-selection-target-id
	 */
	/**
	 * @cfg {String} selection-description-multiselectionforbidden The description when the selection is multiple but #cfg-selection-enable-multiselection is false.
	 */
	
	/**
	 * @property {Ametys.message.MessageTarget[]} _currentSelectionTargets The current selection targets matching to the tool configuration. See #cfg-selection-enable-multiselection. See #cfg-selection-target-id. See #cfg-selection-subtarget-id. See #cfg-selection-subsubtarget-id.
	 * @private
	 */
	/**
	 * @property {Boolean} _selection See #cfg-selection-target-id. True means the tool takes care of the selection
	 * @private
	 */
	/**
	 * @property {RegExp} _selectionTargetType See #cfg-selection-target-id converted as a regexp.
	 * @private
	 */
	/**
	 * @property {RegExp} _selectionSubtargetId See #cfg-selection-subtarget-id converted as a regexp.
	 * @private
	 */
	/**
	 * @property {RegExp} _selectionSubsubtargetId See #cfg-selection-subsubtarget-id converted as a regexp.
	 * @private
	 */
	
	constructor: function(config)
	{
		this.callParent(arguments);
		
		var targetId = this.getInitialConfig("selection-target-id"); 
		if (targetId)
		{
			this._selection = true;
			
			Ametys.message.MessageBus.on(Ametys.message.Message.SELECTION_CHANGED, this._onSelectionChanged, this);
			this._selectionTargetType = new RegExp(targetId);
			
			var subtargetId = this.getInitialConfig("selection-subtarget-id"); 
			if (subtargetId)
			{
				this._selectionSubtargetId = new RegExp(subtargetId);
				
				var subsubtargetId = this.getInitialConfig("selection-subsubtarget-id"); 
				if (subsubtargetId)
				{
					this._selectionSubsubtargetId = new RegExp(subsubtargetId);
				}
			}
		}
	},
	
	getMBSelectionInteraction: function()
	{
		return Ametys.tool.Tool.MB_TYPE_LISTENING;
	},
	
	/**
	 * Listener when the selection has changed. Registered only if #cfg-selection-target-id is specified, but can always be called manually. 
	 * @param {Ametys.message.Message} [message] The selection message. Can be null to get the last selection message
	 * @protected
	 */
	_onSelectionChanged: function(message)
	{
		message = message || Ametys.message.MessageBus.getCurrentSelectionMessage();
		
		if (this._isRefreshNeeded (message))
		{
			this.showOutOfDate(true);
		}
	},
	
	setParams: function (params)
	{
		this.callParent(arguments);
		
		var message = Ametys.message.MessageBus.getCurrentSelectionMessage();
		if (this._isRefreshNeeded (message))
		{
			this.showOutOfDate(true);
		}
	},
	
	/**
	 * @protected
	 * This function looks at the given selection message to determine if the tool has to be refreshed.
	 * If the current targets do not match the expected target(s) the function #setNoSelectionMatchState is called and the function will return false.
	 * @param message the selection message. 
	 * @return true if the tool has to be refreshed
	 */
	_isRefreshNeeded: function (message)
	{
        // First check if there is any selection
		if (message.getTargets().length == 0)
		{
			// noselection
			this.setNoSelectionMatchState(this.getInitialConfig("selection-description-empty"), Ametys.tool.SelectionTool.DeclineReason.EMPTY);
			return false;
		}
		
		// From this point only matching targets should be considered.
		var matchingTargets = this._getMatchingSelectionTargets(message);
		// Check that anything is matching
		if (matchingTargets.length == 0)
		{
			this.setNoSelectionMatchState(this.getInitialConfig("selection-description-nomatch") || this.getInitialConfig("selection-description-empty"), Ametys.tool.SelectionTool.DeclineReason.NO_MATCH);
			return false;
		}
		
		// Check the match size
		var singleSelection = matchingTargets.length == 1;
		var multiSelectionEnabled = Ext.isBoolean (this.getInitialConfig("selection-enable-multiselection")) ? this.getInitialConfig("selection-enable-multiselection") : this.getInitialConfig("selection-enable-multiselection") == 'true';
		
		if (!singleSelection && !multiSelectionEnabled)
		{
			this.setNoSelectionMatchState(this.getInitialConfig("selection-description-multiselectionforbidden"), Ametys.tool.SelectionTool.DeclineReason.MULTIPLE);
			return false;
		}
		// Check for selection change
		else if (this._isCurrentSelectionChanged (matchingTargets))
		{
			this._currentSelectionTargets = matchingTargets;
			return true;
		}
		
		return false;
	},
	
	/**
	 * Compares two targets and returns true if the two targets are equals. The default implementation compares the parameters "id" of the targets.
	 * @return true if the tow targets are the same.
	 * @template
	 */
	areSameTargets: function (target1, target2)
	{
		if (target1.getParameters().id && target2.getParameters().id)
		{
			return target1.getParameters().id == target2.getParameters().id;
		}
		return false;
	},
	
	/**
	 * Get the current selection targets matching the tool selection configuration
	 * @return {Ametys.message.MessageTarget[]} the current selection targets concerning the tool. Can be null.
	 */
	getCurrentSelectionTargets: function ()
	{
		return this._currentSelectionTargets || [];
	},
	
	/**
	 * @protected
	 * @template
	 * This function is called when the selection is empty or do not no match the excepted target.
	 * Override this function to handle the display of the message. In your implementation you have to call parent function!
	 * @param {String} message The message to display
     * @param {Ametys.tool.Tool.DeclineReason} reason The reason of the decline
	 */
	setNoSelectionMatchState: function (message, reason)
	{
		this._currentSelectionTargets = null;
		this.showUpToDate();
	},
	
	/**
	 * @protected
	 * Returns the targets from message matching the current selection targets of the tool
	 * @param {Ametys.message.Message} message the message
	 * @return {Ametys.message.MessageTarget[]} The common targets between the current targets of the tool and the targets in message
	 */
	getTargetsInCurrentSelectionTargets: function (message)
	{
		var targets = this._getMatchingSelectionTargets(message)
		
		if (this.getCurrentSelectionTargets() == null || targets == null)
		{
			return [];
		}	
		
		var targetsInSelection = [];
		
		for (var i=0; i < this.getCurrentSelectionTargets().length; i++)
		{
			for (var j=0; j < targets.length; j++)
			{
				if (this.areSameTargets(this.getCurrentSelectionTargets()[i], targets[j]))
				{
					targetsInSelection.push(targets[j]);
				}
			}
		}
		
		return targetsInSelection;
	},
	
	/**
	 * Determine if the current selection has changed from the point of view of tool
	 * @param {Ametys.message.MessageTarget[]} targets The targets to be test
	 * @returns true if targets does not match the registered selection
	 */
	_isCurrentSelectionChanged: function (targets)
	{
		if (this.getCurrentSelectionTargets() == null || targets == null || this.getCurrentSelectionTargets().length != targets.length)
		{
			return true;
		}	
		
		for (var i=0; i < this.getCurrentSelectionTargets().length; i++)
		{
			var found = false;
			for (var j=0; j < targets.length; j++)
			{
				if (this.areSameTargets(this.getCurrentSelectionTargets()[i], targets[j]))
				{
					found = true;
					break;
				}
			}
			
			if (!found)
			{
				return true;
			}
		}
		
		return false;
	},
	
	/**
	 * Get the matching targets in the message
	 * Test if the message if matching upon the #_selectionTargetType, #_selectionSubtargetId and #_selectionSubsubtargetId
	 * @param {Ametys.message.Message} message The message to test
	 * @returns {Ametys.message.MessageTarget[]} The non-null array of matching targets
	 * @protected
	 */		
	_getMatchingSelectionTargets: function(message)
	{
		var me = this;
		
		var finalTargets = [];
		if (this._selection)
		{
			var targets = message.getTargets(
					function (target)
					{
						return me._selectionTargetType.test(target.getId());
					}
			);
			
			if (!me._selectionSubtargetId)
			{
				finalTargets = targets;
			}
			else
			{
				for (var i = 0; i < targets.length; i++)
				{
					var stargets = targets[i].getSubtargets(
							function (target)
							{
								return me._selectionSubtargetId.test(target.getId());
							}
					);
					
					if (!me._selectionSubsubtargetId)
					{
						if (stargets.length > 0)
						{
							finalTargets.push(targets[i]);
						}
					}
					else
					{
						for (var j = 0; j < targets.length; j++)
						{
							var sstargets = stargets[j].getSubtargets(
									function (target)
									{
										return me._selectionSubsubtargetId.test(target.getId());
									}
							);
						}
						
						if (sstargets.length > 0)
						{
							finalTargets.push(targets[i]);
						}
					}					
				}
			}
		}
		
		return finalTargets;
	}
});