/*
 *  Copyright 2015 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 implementation of Ametys.ui.tool.ToolsLayout is a complex one.
 * It put the tools into several Ext.tab.Panel that are devided into locations.
 * It supports drag'n'drop between locations and save the new default location for tools into the user preferences.
 * Regions supported are : center (left and right) left, right, top and bottom.
 */
Ext.define("Ametys.ui.tool.layout.ZonedTabsToolsLayout", 
	{
		extend: "Ametys.ui.tool.ToolsLayout",

        /**
         * @property {boolean} initialized When the tools manager is ready, this var goes to true
         */
        
        statics:
        {
            /**
             * @property {Object} __REGION_MINSIZE The minimum size of regions of this layout 
             * @property {Object} __REGION_MINSIZE.width The minimum width in pixels for a region 
             * @property {Object} __REGION_MINSIZE.height The minimum height in pixels for a region 
             * @private
             * @readonly
             */         
            __REGION_MINSIZE: {width: 200, height: 130},
            
            /**
             * @property {Object} __ADDITIONNAL_ZONE_CONFIG_LEFT Properties applyed to the configuration of the left zone
             * @private
             * @readonly
             */
            __ADDITIONNAL_ZONE_CONFIG_LEFT: {},
            
            /**
             * @property {Object} __ADDITIONNAL_ZONE_CONFIG_RIGHT Properties applyed to the configuration of the left zone
             * @private
             * @readonly
             */
            __ADDITIONNAL_ZONE_CONFIG_RIGHT: {}
        },
        
        /** @cfg {String} focusCls A css classname added to the zone, when one of its tools has the focus */
        focusCls: "a-tool-layout-zoned-focused",
        /** @cfg {String} nofocusCls A css classname added to the zone, when none of its tools has the focus */
        nofocusCls: "a-tool-layout-zoned-notfocused",
        
        /** @cfg {String} toolPrefixCls A css classname prefix. Added with the type value, it leads to a classname added to the zone corresponding to the type of the active tool. */
        toolPrefixCls: "a-tool-layout-zoned-panel-",
        
		/**
		 * @property {Ext.container.Container} _panel The big main panel
		 * @private
		 */
		/**
		 * @property {Ametys.ui.tool.ToolPanel} _focusedTool The currently focused tool
		 * @private
		 */

		/**
		 * @property {Object} _panelHierarchy The Ametys.ui.tool.layout.ZonedTabsToolsLayout pointed by location ('cl', 'cr, 'l', 't', 'r' or 'b').
		 * @private
		 */
	
		/**
		 * @property {Object[]} _lastUse The last date of use of each location. A use means have a focused tab.
		 * @property {String} _lastUse.location The location
		 * @property {String} _lastUse.date The date of last use
		 * @private
		 */	
		_lastUse: [],

        /**
         * @property {String} _tabPolicy The current tab policy for new tools
         * @private
         */
		
		constructor: function(config)
		{
            this._tabPolicy = this.getSupportedToolPolicies()[0];
            
            this.callParent(arguments);
            
            Ext.getDoc().on("click", this._onAnyMouseDown, this);
            Ext.getDoc().on("contextmenu", this._onAnyMouseDown, this);
		},
        
        /**
         * Listener for preventing right click on the ui
         * @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
         * @param {HTMLElement} t The target of the event.
         * @param {Object} eOpts The options object passed to Ext.util.Observable.addListener
         */
        _onAnyMouseDown: function(e, t, eOpts) 
        {
            // We want to cancel right-click
            if (e.button != 0)
            {
                if (this._panel && (Ext.fly(t).findParent("#" + this._panel.getId())))
                {
                    if (!/^(input|textarea)$/i.test(t.tagName))
                    {
                        e.preventDefault();
                    }
                    return;
                }       
                
                // As tabpanels may not be nested, we need to test on each directly
                var locs = this.getSupportedLocations();
                for (var i = 0; i < locs.length; i++)
                {
                    var el = this._panelHierarchy && this._panelHierarchy[locs[i]] && this._panelHierarchy[locs[i]].getEl();
                    if (el && Ext.fly(t).findParent("#" + el.getId()))
                    {
                        if (!/^(input|textarea)$/i.test(t.tagName))
                        {
                            e.preventDefault();
                        }
                        return;
                    }
                }
            }                
        },
		
		getSupportedLocations: function()
		{
			return ['cl', 'cr', 'l', 't', 'r', 'b'];
		},
        
        getNearestSupportedLocation: function(desiredLocation)
        {
            // No location specified
            if (!desiredLocation)
            {
                return this.getSupportedLocations()[0];
            }
            
            // Matching location
            if (Ext.Array.indexOf(this.getSupportedLocations(), desiredLocation) != -1)
            {
                return desiredLocation;
            }
            
            // Let's shorten the desired location to see if its matching now
            desiredLocation = desiredLocation.substring(0, desiredLocation.length - 1);
            return this.getNearestSupportedLocation(desiredLocation);
        },
		
		getToolsAtLocation: function(location)
		{
			var tools = [];
            
			var zoneTabToolsPanel = this._panelHierarchy[location];
			if (zoneTabToolsPanel != null)
			{
				zoneTabToolsPanel.items.each(function (tool) {
					tools.push(tool);
				});
			}
			
			return tools;
		},
	
		createLayout: function()
		{
			var panels = this._createRegionPanels();

			this._panel = new Ext.Container ({
                ui: 'tool-layout',
                
				border: false,
				layout: {
                    type: 'vbox',
                    align: 'stretch'
                },

                items: panels
			});
			
			return this._panel;
		},
		
		/**
		 * Creates the panels for the hierarchy of panels in the hmi
		 * @param {String} offset The location offset.
		 * @return {Ext.Panel[]} The created panels
		 * @private
		 */
		_createRegionPanels: function()
		{
			this._panelHierarchy = {};
			
			var panelClass = "Ametys.ui.tool.layout.ZonedTabsToolsLayout.ZoneTabsToolsPanel";
            var placeholderClass = "Ametys.ui.tool.layout.ZonedTabsToolsLayout.ZoneTabsToolsPanelPlaceHolder";
            var containerClass = "Ametys.ui.tool.layout.ZonedTabsToolsLayout.Container";
			
			var panels = [
                Ext.create(placeholderClass, {
                    stateful: true,
                    stateId: placeholderClass + "$t",
                    split: this._createSplitConfig('top'),
                    minHeight: this.self.__REGION_MINSIZE.height,
                    items: [ this._panelHierarchy['t'] = Ext.create(panelClass, Ext.apply({ location: 't', toolsLayout: this, collapsible: true, collapseMode: "header", collapseDirection: "top", floating: true, autoShow: true, shadow: false, floatingSplit: this._createFloatingSplit('top') }, this.self.__ADDITIONNAL_ZONE_CONFIG_OTHER)) ]
                }),
                Ext.create(containerClass, {
                    stateful: true,
                    stateId: containerClass + "$",
                    split: this._createSplitConfig('bottom'),
                    items: [
                        Ext.create(placeholderClass, {
                            stateful: true,
                            stateId: placeholderClass + "$l",
                            split: this._createSplitConfig('left'),
                            minWidth: this.self.__REGION_MINSIZE.width,
                            items: [ this._panelHierarchy['l'] = Ext.create(panelClass, Ext.apply({ location: 'l', toolsLayout: this, collapsible: true, collapseMode: "header", collapseDirection: "left", floating: true, autoShow: true, shadow: false, floatingSplit: this._createFloatingSplit('left') }, this.self.__ADDITIONNAL_ZONE_CONFIG_LEFT)) ]
                        }),
                        Ext.create(containerClass, {
                            stateful: true,
                            stateId: containerClass + "$c",
                            split: this._createSplitConfig('right'),
                            items: [
        				        this._panelHierarchy['cl'] = Ext.create(panelClass, Ext.apply({ location: 'cl', toolsLayout: this, stateful: true, stateId: panelClass + "$cl", minHeight: this.self.__REGION_MINSIZE.height, minWidth: this.self.__REGION_MINSIZE.width }, this.self.__ADDITIONNAL_ZONE_CONFIG_OTHER)),
        				        this._panelHierarchy['cr'] = Ext.create(panelClass, Ext.apply({ location: 'cr', toolsLayout: this, stateful: true, stateId: panelClass + "$cr", minHeight: this.self.__REGION_MINSIZE.height, minWidth: this.self.__REGION_MINSIZE.width }, this.self.__ADDITIONNAL_ZONE_CONFIG_OTHER))
                            ]
                        }),
                        Ext.create(placeholderClass, {
                            stateful: true,
                            stateId: placeholderClass + "$r",
                            minWidth: this.self.__REGION_MINSIZE.width,
                            items: [ this._panelHierarchy['r'] = Ext.create(panelClass, Ext.apply({ location: 'r', toolsLayout: this, collapsible: true, collapseMode: "header", collapseDirection: "right", floating: true, autoShow: true, shadow: false, floatingSplit: this._createFloatingSplit('right') }, this.self.__ADDITIONNAL_ZONE_CONFIG_RIGHT)) ]
                        })
                    ]
                }),
                Ext.create(placeholderClass, {
                    stateful: true,
                    stateId: placeholderClass + "$b",
                    minHeight: this.self.__REGION_MINSIZE.height,
                    items: [ this._panelHierarchy['b'] = Ext.create(panelClass, Ext.apply({ location: 'b', toolsLayout: this,collapsible: true, collapseMode: "header", collapseDirection: "bottom", floating: true, autoShow: true, shadow: false, floatingSplit: this._createFloatingSplit('bottom') }, this.self.__ADDITIONNAL_ZONE_CONFIG_OTHER)) ]
                })
			];
			
			return panels;
		},
        
        /**
         * @private
         * Create the split configuration
         * @param {String} direction The collapse direction between 'left', 'right', 'top' and 'bottom'.
         * @return {Object} A split configuration  
         */
        _createSplitConfig: function(direction)
        {
            return { 
                ui: 'tool-layout', 
                performCollapse: false,
                renderData: { 
                    collapsible: true, 
                    collapseDir: direction 
                },
                listeners: {
                    'click': {
                        element: 'collapseEl',
                        fn: this._splitClickListener,
                        args: [ direction ],
                        scope: this
                    }
                }
            };
        },
        
        /**
         * @private
         * Create the split configuration for the floating panel
         * @param {String} direction The collapse direction between 'left', 'right', 'top' and 'bottom'.
         * @return {Ext.resizer.Splitter} The splitter
         */
        _createFloatingSplit: function(direction)
        {
            return Ext.create({
                xtype: 'splitter',
                ui: 'tool-layout',
                hidden: true,
                performCollapse: false,
                renderData: { 
                    collapsible: true, 
                    collapseDir: direction == 'top' ? 'bottom' : direction == 'bottom' ? 'top' : direction == 'left' ? 'right' : 'left' 
                },
                tracker: {
                    xclass: 'Ametys.ui.tool.layout.ZonedTabsToolsLayout.FloatingSplitterTracker'
                },
                listeners: {
                    'click': {
                        element: 'collapseEl',
                        fn: this._splitClickListener,
                        args: [ direction ],
                        scope: this
                    }
                }
            });
        },
        
        /**
         * @private
         * Click on the splitter
         * @param {String} direction The direction used during #_createSplitConfig
         * @param {Ext.event.Event} event The click event
         * @param {HTMLElement} target The click target
         * @param {Object} options The listener options
         */
        _splitClickListener: function(direction, event, target, options)
        {
            this._panelHierarchy[direction.substring(0, 1)]._expandOrCollapse()
        },
		
		focusTool: function(tool)
		{
            this.callParent(arguments);
            
            if (tool != null)
            {
    			var zoneTabsToolsPanel = tool.ownerCt;
    			
    			if (zoneTabsToolsPanel.getActiveTab() != tool)
    			{
    				zoneTabsToolsPanel.setActiveTab(tool);
    			}
    			
    			this._onToolFocused(tool);
            }
		},
		
		/**
		 * @private
		 * Will slide in a zone and slide out the others
		 * @param {Ametys.ui.tool.layout.ZonedTabsToolsLayout.ZoneTabsToolsPanel} zone The zone to show. Can be null to slide out all zones
		 */
		_slideInZone: function(zone)
		{
			for (var loc in this._panelHierarchy)
			{
				var otherZone = this._panelHierarchy[loc];
				if (otherZone == zone)
				{
    				zone._slideIn();
				}
				else if (!otherZone._isMainArea())
				{
					otherZone._slideOut();
				}
			}
		},
		
		getFocusedTool: function()
		{
			return this._focusedTool;
		},
		
		/**
		 * @private
		 * Listener when a tool is shown (activated or expanded). 
		 * @param {Ametys.ui.tool.ToolPanel} tool The tool that is shown
		 */
		_onToolShown: function(tool)
		{
			// Launch the listener
			tool.fireEvent("toolshow", tool);
		},
        
        /**
         * @private
         * Listener when a tool is activated (the first of its zone). Different from #_onToolFocused.
         * @param {Ametys.ui.tool.ToolPanel} tool The tool that is activated
         */
        _onToolActivated: function(tool)
        {
            // Graphically set the color on the zone
            var zoneTabsToolsPanel = tool.ownerCt;
            zoneTabsToolsPanel.addCls(this.toolPrefixCls + tool.getType());
            
            // Launch the listener
            tool.fireEvent("toolactivate", tool);
        },
		
		/**
		 * Listener when a tool is deactivated (not the first of its zone). Different from #_onToolBlurred.
		 * Internal call by listeners
		 * @param {Ametys.ui.tool.ToolPanel} tool The tool that is deactivated
		 */
		_onToolDeactivated: function(tool)
		{
			// Graphically remove the color on the zone
			var zoneTabsToolsPanel = tool.ownerCt;
			zoneTabsToolsPanel.removeCls(this.toolPrefixCls + tool.getType());
			
			// Launch the listener
            tool.fireEvent("tooldeactivate", tool);
		},

		/**
		 * Listener when a tool receive the focus.
		 * Internal call by listeners
		 * @param {Ametys.ui.tool.ToolPanel} tool The tool that has focus
		 * @param {Boolean} [noevent=false] Prevent to fire toolfocus event on the tool
		 */
		_onToolFocused: function(tool, noevent)
		{
			// Set the active tool
			if (tool == this.getFocusedTool())
			{
				return;
			}
            
            Ext.suspendLayouts();
            try
            {
    			if (this.getFocusedTool())
    			{
    				this._onToolBlurred(this.getFocusedTool());
    			}
    			this._focusedTool = tool;
    			
    			// Focus the element
    			if (document.activeElement != null && !this._hasForParent(document.activeElement, tool.getId()))    
    			{
    				tool.getEl().dom.tabIndex = "1";
    				tool.el.focus();
    			}
    			
    			// Graphically set the focus
    			var zoneTabsToolsPanel = tool.ownerCt;
    			zoneTabsToolsPanel.addCls(this.focusCls);
                zoneTabsToolsPanel.removeCls(this.nofocusCls);
    
    			if (this.initialized)
    			{
        			// If not timeouted, a moved tool in a retracted zone will lead to graphical issues CMS-5912
    			    window.setTimeout(Ext.bind(this._slideInZone, this, [zoneTabsToolsPanel]), 1);
                    
                    // Moreover a tool added in a collapsed zone may have layout issues (especially grids)
                    if (zoneTabsToolsPanel.collapsed && tool.getWidth() < 10)
                    {
                        zoneTabsToolsPanel.on('expand', tool.updateLayout, tool, { single: true});
                    }
    			}
    
    
    			// Store the last use of this zone
    			Ext.Array.remove(this._lastUse, Ext.Array.findBy(this._lastUse, function(item) { return item.location == zoneTabsToolsPanel._location; }));
    			Ext.Array.insert(this._lastUse, 0, [{ location: zoneTabsToolsPanel._location, date: new Date() }]);
    			
    			// Launch the listener
    			if (!noevent)
    			{
    				tool.fireEvent("toolfocus", tool);
    			}
            }
            finally
            {
                Ext.resumeLayouts(true);
            }
		},
		
		/**
		 * @private
		 * Determine if element has a parent with id
		 * @param {HTMLElement} element The element to test
		 * @param {String} parentId The id of a parent
		 */
		_hasForParent: function(element, parentId)
		{
			if (element == null)
			{
				return false;
			}
			else if (element.id == parentId)
			{
				return true;
			}
			
			return this._hasForParent(element.parentNode, parentId)
		},
		
		/**
		 * Listener when a tool lost the focus.
		 * @param {Ametys.ui.tool.ToolPanel} tool The tool that lost focus. Can be null.
		 * @param {Boolean} [noevent=false] Prevent to fire the toolblur event on the panel
		 */
		_onToolBlurred: function(tool, noevent)
		{
            if (tool == null || this.getFocusedTool() != tool)
            {
                return;
            }
            
			// Graphically set the focus
			var zoneTabsToolsPanel = tool.ownerCt;
			if (zoneTabsToolsPanel)
			{
				zoneTabsToolsPanel.removeCls(this.focusCls);
                zoneTabsToolsPanel.addCls(this.nofocusCls);
			}
			
			if (this._focusedTool == tool)
			{
				this._focusedTool = null;
			}

			// Launch the listener
			if (!noevent)
			{
                tool.fireEvent("toolblur", tool);
			}
            
            if (Ext.isFunction(this.titleChangedCallback))
            {
                this.titleChangedCallback(null);
            }
		},
		
		onToolInfoChanged: function(tool)
		{
			var zoneTabsToolsPanel = tool.ownerCt;
			
			var index = zoneTabsToolsPanel.items.indexOf(tool);
			var tabEl = zoneTabsToolsPanel.getTabBar().items.get(index);
			
            tabEl.toggleCls("ametys-tab-dirty", tool.getDirtyState());
			tabEl.setText(tool.getTitle());
			
			if (tool.getGlyphIcon() != null)
			{
                tabEl.setIcon(null);
				tabEl.setIconCls(tool.getGlyphIcon() + (tool.getIconDecorator() != null ? ' ' + tool.getIconDecorator() : '') + ' ametys-decoratortype-' + (tool.getIconDecoratorType() || 'action-default'));
			}
			else
			{
                tabEl.setIconCls(null);
				tabEl.setIcon(Ametys.CONTEXT_PATH + tool.getMediumIcon());
			}
			
			tabEl.setTooltip({
				title: tool.getTitle(),
				glyphIcon: tool.getGlyphIcon(),
				iconDecorator: tool.getIconDecorator(),
                iconDecoratorType: tool.getIconDecoratorType(),
				image: !tool.getGlyphIcon() && (tool.getLargeIcon() || tool.getMediumIcon() || tool.getSmallIcon()) ? (Ametys.CONTEXT_PATH + (tool.getLargeIcon() || tool.getMediumIcon() || tool.getSmallIcon())) : null,
				imageWidth: tool.getGlyphIcon() || tool.getLargeIcon() ? 48 : (tool.getMediumIcon() ? 32 : 16),
				imageHeight: tool.getGlyphIcon() || tool.getLargeIcon() ? 48 : (tool.getMediumIcon() ? 32 : 16),
				text: tool.getDescription(),
				help: tool.getHelp(),
				inribbon: false
			});
			
            if (tool.ownerCt.hasCls(this.focusCls) && tool.ownerCt.getActiveTab() == tool && Ext.isFunction(this.titleChangedCallback))
            {
                this.titleChangedCallback(tool.getTitle());
            }
		},
		
		addTool: function(tool, location)
		{
            location = this.getNearestSupportedLocation(location);
            tool._toolsLayout = this;
			Ext.suspendLayouts();
            try
            {
            	this._addToolToUI(tool, location);
    			tool.fireEvent("toolopen", tool);
            }
            finally
            {
    			Ext.resumeLayouts(true);
            }
		},
        
        /**
         * @private
         * Add a tool to the correct location
         * @param {Ametys.ui.tool.ToolPanel} tool The rendered tool to add
         * @param {String} location The location where to add the tool
         */
        _addToolToUI: function(tool, location)
        {
            var zonedTabsToolPanel = this._panelHierarchy[location];
            
            var newToolPriority = tool.getPriority();

            var insertBeforeTabIndex = null;
            zonedTabsToolPanel.items.each( function(item) {
                if ((this._tabPolicy == 'color' && item.getPriority() == newToolPriority) // Tab policy color mean, a single tool per color 
                        || (this._tabPolicy == 'tool' && item.getPriority() == newToolPriority && item.getPriority() != 0)) // Tab policy tool mean, a single tool per color if color is different from default
                {
                    item.close();
                }
                else if (item.getPriority() > newToolPriority)
                {
                    insertBeforeTabIndex = zonedTabsToolPanel.items.indexOf(item);
                    return false;
                }
            }, this);
            
            if (insertBeforeTabIndex != null)
            {
                zonedTabsToolPanel.insert(insertBeforeTabIndex, tool);
            }
            else
            {
                zonedTabsToolPanel.add(tool);
            }
            
            // effectively add
            this.onToolInfoChanged(tool);
        },        
		
		moveTool: function(tool, location)
		{
            location = this.getNearestSupportedLocation(location);
                        
			var oldZoneTabsToolsPanel = tool.ownerCt;
			if (this._panelHierarchy[location] == oldZoneTabsToolsPanel)
			{
                // Tool moved to its current location... let's discard this call
				return;
			}
			
			Ext.suspendLayouts();
			
            try
            {
    			if (this.getLogger().isDebugEnabled())
    			{
    				this.getLogger().debug("Moving tool '" + tool.getId() + "' to location '" + location + "' (from location '" + oldZoneTabsToolsPanel.location + "')");
    			}
    			
    			
                this._onToolBlurred(this.getFocusedTool());
                if (oldZoneTabsToolsPanel.getActiveTab() == tool)
                {
                    this._onToolDeactivated(tool);
                }
    			
    			// moves the panel
    			var zoneTabsToolPanel = this._panelHierarchy[location];
    			
    			this._addToolToUI(tool, location);
    			
    			// activates the tool in its new location
    			zoneTabsToolPanel.setActiveTab(tool);
    
    			// fix a bug from extjs
    			// when the old location replaces this tab by a new activated tab, the hidden status is incorrect and the tool invisible in the new tab (when this new tab was empty)
    			tool.hidden = true
    			tool.show();
                tool.tab.show();
    
                // activate the tool ?
                
    			// focus the tool
    			this.focusTool(tool);
            }
            finally
            {
    			Ext.resumeLayouts(true);
            }
            
            tool.fireEvent("toolmoved", tool, location);
		},
		
		removeTool: function(tool)
		{
			Ext.suspendLayouts();

            try
            {
    			var zoneTabsToolsPanel = tool.ownerCt;
    			
                if (zoneTabsToolsPanel.getActiveTab() == tool)
                {
                    this._onToolDeactivated(tool);
                }
                
    			var wasFocused = this.getFocusedTool() == tool; 
    			if (wasFocused)
    			{
    				this._onToolBlurred(tool);
    			}
    			
    			tool.fireEvent('toolclose', tool, wasFocused);
    			
    			tool._toolsLayout = null; // remove tool panel reference to the layout
    			zoneTabsToolsPanel.remove(tool);
    			
    			if (this.getFocusedTool() == null)
    			{
    				var me = this;
    		
    				// Search for another zone to focus
    				Ext.Array.each(this._lastUse, function(item) {
    					var location = item.location;
    					var zone = me._panelHierarchy[location];
    					if (zone.isVisible())
    					{
                            me.focusTool(zone.getActiveTab());
    						return false;
    					}
    				});
                    
                    if (this.getFocusedTool() == null)
                    {
                        this.focusTool(null);
                    }
    			}
            }
            finally
            {
    			Ext.resumeLayouts(true);
            }
		},
        
        getSupportedToolPolicies: function()
        {
            return ['all', 'color', 'tool'];
        },
        
        setToolPolicy: function(policy)
        {
            if (Ext.Array.contains(this.getSupportedToolPolicies(), policy))
            {
                this._tabPolicy = policy;
            }
            else
            {
                this._tabPolicy =  this.getSupportedToolPolicies()[0];
            }
        }

	}
);