/*
 *  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 stands for the logic of a tab of the ribbon that handle show/hide:
 *   * depending of the current selection
 *   * depending of the currently focused tool
 * If both are supplyed, a OR is operated.
 */
Ext.define(
	"Ametys.ribbon.element.tab.TabController",
	{
		extend: "Ametys.ribbon.element.RibbonTabController",

		/**
		 * @cfg {String} selection-target-id Specify this configuration to obtain a tab that show/hide depending on the current selection type. The string is a regexp that have to match the current selection type. A leading '!' will reverse the regexp condition. See #cfg-subtarget-id. 
		 */
		/**
		 * @cfg {Object} selection-target-parameter Use this configuration in addition to #cfg-selection-target-id in order to be more specific. This allow to check a target parameter.
		 * @cfg {String} selection-target-parameter.name The name of the parameter to check. The string is a regexp that have to match the current selection type. A leading '!' will reverse the regexp condition. 
		 * @cfg {String} selection-target-parameter.value The value of the parameter to check. The string is a regexp that have to match the current selection type. A leading '!' will reverse the regexp condition. If the parameter is an array, it will check if the value is part of the array (using Ext.Array.contains)
		 */
		/**
		 * @cfg {String} selection-subtarget-id When specified as the same time as #cfg-selection-target-id is, the tab will be show/hide only if the selection target is matching #cfg-selection-target-id AND if there is a subtarget that matched this regexp. A leading '!' will reverse the regexp condition. See #cfg-subtarget-id.
		 */
		/**
		 * @cfg {Object} selection-subtarget-parameter Same as #cfg-selection-target-parameter but applying to #cfg-selection-subtarget-id
		 */
		/**
		 * @cfg {String} selection-subsubtarget-id Same as #cfg-subtarget-id at a third level.
		 */
		/**
		 * @cfg {Object} selection-subsubtarget-parameter Same as #cfg-selection-target-parameter but applying to #cfg-selection-subsubtarget-id
		 */
		/**
		 * @cfg {String} selection-subsubsubtarget-id Same as #cfg-subtarget-id at a fourth level.
		 */
		/**
		 * @cfg {Object} selection-subsubsubtarget-parameter Same as #cfg-selection-target-parameter but applying to #cfg-selection-subsubsubtarget-id
		 */
		/**
		 * @cfg {Boolean/String} only-first-level-target Specify to true to restrict the depth for filtering the selection target to the first level only. Otherwise it will search in all subtargets.
		 */
		
		/**
		 * @property {Boolean} _selection See #cfg-selection-target-id. True means the tab takes care of the selection
		 * @private
		 */
		/**
		 * @property {Ametys.message.MessageTarget[]} _matchingTargets The array of currently selected target matching the desired target type. See {@ link#cfg-selection-target-id}.
		 * @private
		 */
		/**
		 * @property {RegExp} _selectionTargetId See #cfg-selection-target-id converted as a regexp. The leading '!' is transmitted to {@link #_reversedSelectionTargetId}
		 * @private
		 */
		/**
		 * @property {Boolean} _reversedSelectionTargetId The leading '!' from {@link #cfg-selection-target-id} converted to true.
		 * @private
		 */
		/**
		 * @property {RegExp} _selectionSubtargetId See #cfg-selection-subtarget-id converted as a regexp. The leading '!' is transmitted to #_selectionReversedSubtargetId
		 * @private
		 */
		/**
		 * @property {Boolean} _selectionReversedSubtargetId The leading '!' from #cfg-subtarget-id converted to true.
		 * @private
		 */	
		/**
		 * @property {RegExp} _selectionSubsubtargetId See #cfg-selection-subsubtarget-id converted as a regexp. The leading '!' is transmitted to #_selectionReversedSubsubtargetId
		 * @private
		 */
		/**
		 * @property {Boolean} _selectionReversedSubsubtargetId The leading '!' from #cfg-subsubtarget-id converted to true.
		 * @private
		 */	
		/**
		 * @property {RegExp} _selectionSubsubsubtargetId See #cfg-selection-subsubsubtarget-id converted as a regexp. The leading '!' is transmitted to #_selectionReversedSubsubsubtargetId
		 * @private
		 */
		/**
		 * @property {Boolean} _selectionReversedSubsubsubtargetId The leading '!' from #cfg-subsubsubtarget-id converted to true.
		 * @private
		 */	
		/**
		 * @property {Boolean} _onlyFirstLevelTarget The internal property corresponding to #cfg-only-first-level-target
		 * @private
		 */
		
		/**
		 * @cfg {String} tool-id When specified, the tab will only be visible if a tool with this id is focused. This is a regexp. A leading '!' will reverse the regexp condition.
		 */
        /**
         * @cfg {String} tool-property When specified, the button will only be enabled if a tool with this (non-false) property is focused/activated or opened. This is the name of a property. Can be a comma-separated list of properties that must all exists.
         */
		/**
		 * @property {RegExp} _toolId The #cfg-tool-id converted as a regexp. The '!' condition is available in #_toolReveredRole.
		 * @private
		 */
		/**
		 * @property {Boolean} _toolReveredRole The #cfg-tool-id converted as a regexp and this boolean stands for the '!' condition.
		 * @private
		 */
        /**
         * @property {String[]} _toolProperties The #cfg-tool-property
         * @private
         */
		/**
		 * @property {Boolean} _toolFocused When using #cfg-tool-id/#cfg-tool-property this boolean reflects the focus state of the associated tool.
		 */
		
		constructor: function(config)
		{
			this.callParent(arguments);
			
			var targetId = this.getInitialConfig("selection-target-id") || this.getInitialConfig("target-id"); 
			this._matchingTargets = [];
			
			if (targetId)
			{
				this._selection = true;
				Ametys.message.MessageBus.on(Ametys.message.Message.SELECTION_CHANGED, this._onSelectionChanged, this);
				
				this._onlyFirstLevelTarget = String(this.getInitialConfig("only-first-level-target")) == "true";
				
				var i = targetId.indexOf('!');
				if (i == 0)
				{
					this._selectionTargetId = new RegExp(targetId.substring(1));
					this._reversedSelectionTargetId = true;
				}
				else
				{
					this._selectionTargetId = new RegExp(targetId);
					this._reversedSelectionTargetId = false;
				}
				
				// Has an associated target-parameter check?
				var targetParameter = this.getInitialConfig("selection-target-parameter");
				if (targetParameter)
				{
					var i = targetParameter.name.indexOf('!');
					if (i == 0)
					{
						this._selectionTargetParameterName = new RegExp(targetParameter.name.substring(1));
						this._reversedSelectionTargetParameterName = true;
					}
					else
					{
						this._selectionTargetParameterName = new RegExp(targetParameter.name);
						this._reversedSelectionTargetParameterName = false;
					}
					i = targetParameter.value.indexOf('!');
					if (i == 0)
					{
						this._selectionTargetParameterValue = new RegExp(targetParameter.value.substring(1));
						this._reversedSelectionTargetParameterValue = true;
					}
					else
					{
						this._selectionTargetParameterValue = new RegExp(targetParameter.value);
						this._reversedSelectionTargetParameterValue = false;
					}
				}
				
				var subtargetId = this.getInitialConfig("selection-subtarget-id") || this.getInitialConfig("subtarget-id"); 
				if (subtargetId)
				{
					var i = subtargetId.indexOf('!');
					if (i == 0)
					{
						this._selectionSubtargetId = new RegExp(subtargetId.substring(1));
						this._selectionReversedSubtargetId = true;
					}
					else
					{
						this._selectionSubtargetId = new RegExp(subtargetId);
						this._selectionReversedSubtargetId = false;
					}
					
					// Has an associated subtarget-parameter check?
					var subtargetParameter = this.getInitialConfig("selection-subtarget-parameter");
					if (subtargetParameter)
					{
						var i = subtargetParameter.name.indexOf('!');
						if (i == 0)
						{
							this._selectionSubtargetParameterName = new RegExp(subtargetParameter.name.substring(1));
							this._reversedSelectionSubTargetParameterName = true;
						}
						else
						{
							this._selectionSubtargetParameterName = new RegExp(subtargetParameter.name);
							this._reversedSelectionSubTargetParameterName = false;
						}
						i = subtargetParameter.value.indexOf('!');
						if (i == 0)
						{
							this._selectionSubtargetParameterValue = new RegExp(subtargetParameter.value.substring(1));
							this._reversedSelectionSubTargetParameterValue = true;
						}
						else
						{
							this._selectionSubtargetParameterValue = new RegExp(subtargetParameter.value);
							this._reversedSelectionSubTargetParameterValue = false;
						}
					}
					 
					var subsubtargetId = this.getInitialConfig("selection-subsubtarget-id") || this.getInitialConfig("subsubtarget-id"); 
					if (subsubtargetId)
					{
						var i = subsubtargetId.indexOf('!');
						if (i == 0)
						{
							this._selectionSubsubtargetId = new RegExp(subsubtargetId.substring(1));
							this._selectionReversedSubsubtargetId = true;
						}
						else
						{
							this._selectionSubsubtargetId = new RegExp(subsubtargetId);
							this._selectionReversedSubsubtargetId = false;
						}
						
						// Has an associated subsubtarget-parameter check?
						var subsubtargetParameter = this.getInitialConfig("selection-subsubtarget-parameter");
						if (subsubtargetParameter)
						{
							var i = subsubtargetParameter.name.indexOf('!');
							if (i == 0)
							{
								this._selectionSubsubTargetParameterName = new RegExp(subsubtargetParameter.name.substring(1));
								this._reversedSelectionSubsubTargetParameterName = true;
							}
							else
							{
								this._selectionSubsubTargetParameterName = new RegExp(subsubtargetParameter.name);
								this._reversedSelectionSubsubTargetParameterName = false;
							}
							i = subsubtargetParameter.value.indexOf('!');
							if (i == 0)
							{
								this._selectionSubsubTargetParameterValue = new RegExp(subsubtargetParameter.value.substring(1));
								this._reversedSelectionSubsubTargetParameterValue = true;
							}
							else
							{
								this._selectionSubsubTargetParameterValue = new RegExp(subsubtargetParameter.value);
								this._reversedSelectionSubsubTargetParameterValue = false;
							}
						}
						
						var subsubsubtargetId = this.getInitialConfig("selection-subsubsubtarget-id") || this.getInitialConfig("subsubsubtarget-id"); 
						if (subsubsubtargetId)
						{
							var i = subsubsubtargetId.indexOf('!');
							if (i == 0)
							{
								this._selectionSubsubsubtargetId = new RegExp(subsubsubtargetId.substring(1));
								this._selectionReversedSubsubsubtargetId = true;
							}
							else
							{
								this._selectionSubsubsubtargetId = new RegExp(subsubsubtargetId);
								this._selectionReversedSubsubsubtargetId = false;
							}
							
							// Has an associated subsubsubtarget-parameter check?
							var subsubsubtargetParameter = this.getInitialConfig("selection-subsubsubtarget-parameter");
							if (subsubsubtargetParameter)
							{
								var i = subsubsubtargetParameter.name.indexOf('!');
								if (i == 0)
								{
									this._selectionSubsubsubTargetParameterName = new RegExp(subsubsubtargetParameter.name.substring(1));
									this._reversedSelectionSubsubsubTargetParameterName = true;
								}
								else
								{
									this._selectionSubsubsubTargetParameterName = new RegExp(subsubsubtargetParameter.name);
									this._reversedSelectionSubsubsubTargetParameterName = false;
								}
								i = subsubsubtargetParameter.value.indexOf('!');
								if (i == 0)
								{
									this._selectionSubsubsubTargetParameterValue = new RegExp(subsubsubtargetParameter.value.substring(1));
									this._reversedSelectionSubsubsubTargetParameterValue = true;
								}
								else
								{
									this._selectionSubsubsubTargetParameterValue = new RegExp(subsubsubtargetParameter.value);
									this._reversedSelectionSubsubsubTargetParameterValue = false;
								}
							}
						}
					}
				}
			}
			
			var toolId = this.getInitialConfig("tool-id");
            var toolProperty = this.getInitialConfig("tool-property");
			if (toolId || toolProperty)
			{
				this._toolFocused = false;
				
				Ametys.message.MessageBus.on(Ametys.message.Message.TOOL_FOCUSED, this._onAnyToolFocused, this);
				Ametys.message.MessageBus.on(Ametys.message.Message.TOOL_BLURRED, this._onAnyToolBlurred, this);

                if (toolId)
                {
    				var i = toolId.indexOf('!');
    				if (i == 0)
    				{
    					this._toolId = new RegExp(toolId.substring(1));
    					this._toolReveredRole = true;
    				}
    				else
    				{
    					this._toolId = new RegExp(toolId);
    					this._toolReveredRole = false;
    				}
                }
                if (toolProperty)
                {
                    Ametys.message.MessageBus.on(Ametys.message.Message.TOOL_PARAMS_UPDATED, this._onParamsUpdated, this);
                    this._toolProperties = toolProperty.split(',');
                }
			}
		},

		/**
		 * Listener when the selection has changed. Registered only if #cfg-selection-target-id is specified, but can always be called manually. 
		 * Will show or hide the tab effectively upon the current selection.
		 * @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();
			
			this._matchingTargets = this._getMatchingSelectionTargets(message);
			
			if (this._toolFocused === true)
			{
				// this configured tool is already focused, nothing to do with selection
				return;
			}
			
			if (this._selection)
			{
    			var noSelection = message.getTargets().length == 0;
				if (noSelection || this._matchingTargets.length == 0)
				{
					this.hide();
				}
				else
				{
                    var forceSelection = null;
                    if (message.getParameters() && message.getParameters()["creation"])
                    {
                        // we cannot inline this 'if' instruction as null and false are not the same for forceSelection
                        forceSelection = (message.getParameters()["creation"] === this._matchingTargets[0].getId());
                    }
					this.show(forceSelection);
				}
			}
		},
		
		/**
		 * Listener when a tool has been focused. Registered only if #cfg-tool-id is specified. Will enable the buttons effectively.
		 * @param {Ametys.message.Message} message The focus message
		 * @protected
		 */
		_onAnyToolFocused: function(message)
		{
			if (this._getMatchingToolsTarget(message).length > 0)
			{
    			this._toolFocused = true;
				this.show();
			}
		},

		/**
		 * Listener when a tool has been blurred. Registered only if #cfg-tool-id is specified. Will disable the buttons effectively.
		 * @param {Ametys.message.Message} message The focus message
		 * @protected
		 */
		_onAnyToolBlurred: function(message)
		{
			
			if (this._getMatchingToolsTarget(message).length > 0)
			{
			    this._toolFocused = false;
			
                // even if the tool is blurred, the selection may still matching
                if (!this._selection || this._matchingTargets.length == 0)
                {
    				this.hide();
                }
			}
		},
        
        /**
         * Listener when a tool params have been updated. Registered only if #cfg-tool-id is specified. Show or hide tab effectively.
         * @param {Ametys.message.Message} message The updated message
         * @protected
         */
        _onParamsUpdated: function(message)
        {
            var matchingTools = this._getMatchingToolsTarget(message);
            if (matchingTools.length > 0 && matchingTools[0].getParameters().tool.hasFocus())
            {
                this.show();
            }
            else
            {
                if (this._selection && this._matchingTargets.length > 0)
                {
                    // even if the tool is not parametrized, the selection is still matching
                    return;
                }
                
                this.hide();
            }
        },
		
		/**
		 * Get the matching targets in the message
		 * Test if the message if matching upon the #_selectionTargetId, #_selectionSubtargetId and #_selectionSubsubtargetId and #_selectionSubsubsubtargetId.
		 * It also checks for #_selectionTargetParameter, #_selectionSubtargetParameter, #_selectionSubsubTargetParameter and #_selectionSubsubsubTargetParameter
		 * @param {Ametys.message.Message} message The message to test
		 * @return {Ametys.message.MessageTarget[]} The non-null array of matching targets
		 * @private
		 */		
		_getMatchingSelectionTargets: function(message)
		{
			var me = this;
			
			var finalTargets = [];
			if (this._selection)
			{
				var targets = message.getTargets(Ext.bind(this._testTargetLevel0, this, [this._selectionTargetId, this._reversedSelectionTargetId, this._selectionTargetParameterName, this._reversedSelectionTargetParameterName, this._selectionTargetParameterValue, this._reversedSelectionTargetParameterValue], true), this._onlyFirstLevelTarget ? 1 : 0);
				
				if (!me._selectionSubtargetId)
				{
					finalTargets = targets;
				}
				else
				{
					for (var i = 0; i < targets.length; i++)
					{
						var stargets = targets[i].getSubtargets(Ext.bind(this._testTargetLevel1, this, [this._selectionSubtargetId, this._selectionReversedSubtargetId, this._selectionSubtargetParameterName, this._reversedSelectionSubTargetParameterName, this._selectionSubtargetParameterValue, this._reversedSelectionSubTargetParameterValue], true), 1);
						
						if (!me._selectionSubsubtargetId)
						{
							if (stargets.length > 0 || (me._selectionReversedSubtargetId && targets[i].getSubtargets().length == 0))
							{
								finalTargets.push(targets[i]);
							}
						}
						else
						{
							for (var j = 0; j < stargets.length; j++)
							{
								var sstargets = stargets[j].getSubtargets(Ext.bind(this._testTargetLevel2, this, [this._selectionSubsubtargetId, this._selectionReversedSubsubtargetId, this._selectionSubsubTargetParameterName, this._reversedSelectionSubsubTargetParameterName, this._selectionSubsubTargetParameterValue, this._reversedSelectionSubsubTargetParameterValue], true), 1);
								
								if (!me._selectionSubsubsubtargetId)
								{
									if (sstargets.length > 0 || (me._selectionReversedSubsubtargetId && stargets[j].getSubtargets().length == 0))
									{
										finalTargets.push(targets[i]);
									}
								}
								else
								{
									for (var k = 0; k < sstargets.length; k++)
									{
										var ssstargets = sstargets[k].getSubtargets(Ext.bind(this._testTargetLevel3, this, [this._selectionSubsubsubtargetId, this._selectionReversedSubsubsubtargetId, this._selectionSubsubsubTargetParameterName, this._reversedSelectionSubsubsubTargetParameterName, this._selectionSubsubsubTargetParameterValue, this._reversedSelectionSubsubsubTargetParameterValue], true), 1);
										if (ssstargets.length > 0)
										{
											finalTargets.push(targets[i]);
										}
									}
								}
							}
						}					
					}
				}
			}
			
			return finalTargets;
		},
		
		/**
		 * @private
		 * Tests if the target of level 0 matches the configured #cfg-selection-target-id
		 * @return true if the target matches
		 */
		_testTargetLevel0: function (target)
		{
			return this._testTarget(target, this._selectionTargetId, this._reversedSelectionTargetId, this._selectionTargetParameterName, this._reversedSelectionTargetParameterName, this._selectionTargetParameterValue, this._reversedSelectionTargetParameterValue);
		},
		
		/**
		 * @private
		 * Tests if the target of level 1 matches the configured #cfg-selection-subtarget-id
		 * @return true if the target matches
		 */
		_testTargetLevel1: function (target)
		{
			return this._testTarget(target, this._selectionSubtargetId, this._selectionReversedSubtargetId, this._selectionSubtargetParameterName, this._reversedSelectionSubTargetParameterName, this._selectionSubtargetParameterValue, this._reversedSelectionSubTargetParameterValue);
		},
		
		/**
		 * @private
		 * Tests if the target of level 2 matches the configured #cfg-selection-subsubtarget-id
		 * @return true if the target matches
		 */
		_testTargetLevel2: function (target)
		{
			return this._testTarget(target, this._selectionSubsubtargetId, this._selectionReversedSubsubtargetId, this._selectionSubsubTargetParameterName, this._reversedSelectionSubsubTargetParameterName, this._selectionSubsubTargetParameterValue, this._reversedSelectionSubsubTargetParameterValue);
		},
		
		/**
		 * @private
		 * Tests if the target of level 3 matches the configured #cfg-selection-subsubsubtarget-id
		 * @return true if the target matches
		 */
		_testTargetLevel3: function (target)
		{
			return this._testTarget(target, this._selectionSubsubsubtargetId, this._selectionReversedSubsubsubtargetId, this._selectionSubsubsubTargetParameterName, this._reversedSelectionSubsubsubTargetParameterName, this._selectionSubsubsubTargetParameterValue, this._reversedSelectionSubsubsubTargetParameterValue);
		},
		
		/**
		 * @private
		 * Tests if the target of level 0 matches the configured #cfg-selection-target-id
		 * @param {Ametys.message.MessageTarget} target The target to test
		 * @param {RegExp} selectionTypeRegexp The regular expression to pass on targetId
		 * @param {Boolean} selectionTypeReverseRegexp True is the preceding regular expression test shoul be reversed
		 * @param {RegExp} selectionParameterNameRegexp If non-empty, this will test all target's parameters name and a least one must match, with a correct value (see following parameters)
		 * @param {Boolean} selectionParameterNameReverseRegexp True is the preceding regular expression test shoul be reversed
		 * @param {RegExp} selectionParameterValueRegexp Used when a parameter name is matching to test its value.
		 * @param {Boolean} selectionParameterValueReverseRegexp True is the preceding regular expression test shoul be reversed
		 * @return true if the target matches
		 */
		_testTarget: function (target, selectionTypeRegexp, selectionTypeReverseRegexp, selectionParameterNameRegexp, selectionParameterNameReverseRegexp, selectionParameterValueRegexp, selectionParameterValueReverseRegexp)
		{
			function checkParameters()
			{
				if (!selectionParameterNameRegexp)
				{
					return true;
				}
				
				function checkValue(value)
				{
					if (Ext.isArray(value))
					{
						var gotOne = false;
						Ext.each(value, function(v, index, array) {
							if (checkValue(v))
							{
								gotOne = true;
								return false; // stop the iteration
							}
						});
						return gotOne;
					}
					else
					{
						return ((!selectionParameterValueReverseRegexp && selectionParameterValueRegexp.test(value)
								|| selectionParameterValueReverseRegexp && !selectionParameterValueRegexp.test(value)));
					}
				}
				
				var gotOne = false;
				Ext.Object.each(target.getParameters(), function(key, value, parameters) {
					if ((!selectionParameterNameReverseRegexp && selectionParameterNameRegexp.test(key)
							|| selectionParameterNameReverseRegexp && !selectionParameterNameRegexp.test(key))
						&& value != null && checkValue(value))
					{
						gotOne = true;
						return false; // stop the iteration
					}
				});
				return gotOne;
			}
			
			return (!selectionTypeReverseRegexp && selectionTypeRegexp.test(target.getId())
						|| selectionTypeReverseRegexp && !selectionTypeRegexp.test(target.getId()))
					&& checkParameters();
		},
		
		/**
		 * Get the matching targets in the message
		 * Test if the message if matching upon the #_toolId
		 * @param {Ametys.message.Message} message The message to test
		 * @returns {Ametys.message.MessageTarget[]} The non-null array of matching targets
		 * @private
		 */		
		_getMatchingToolsTarget: function(message)
		{
			var me = this;
			
            function _hasAllProperties(tool)
            {
                for (var s = 0; s < me._toolProperties.length; s++)
                {
                    var toolProperty = me._toolProperties[s].trim();
                    var reverseToolProperty = toolProperty.indexOf('!') == 0;
                    if (reverseToolProperty)
                    {
                        toolProperty = toolProperty.substring(1);
                    }
                    
                    if ((!reverseToolProperty && !tool[toolProperty]) || (reverseToolProperty && tool[toolProperty]))
                    {
                        return false;
                    }
                }
                
                return true;
            }

			if (this._toolId || this._toolProperties)
			{
				return message.getTargets(
						function (target)
						{
							return (!me._toolId || (!me._toolReveredRole && me._toolId.test(target.getParameters()['id'])
							                          || me._toolReveredRole && !me._toolId.test(target.getParameters()['id'])))
                                   && (!me._toolProperties || _hasAllProperties(target.getParameters()['tool']));
						}
				);
			}
			else
			{
				return [];
			}
		}
	}
);