/*
 *  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 plugin injects a multisort toolbar on top on the grid.
 * 
 * A column is adding to the sort by clicking on its header. The last clicked column is always the predominant sorter.
 * The sort fields can be reordered by drag and drop.
 * 
 * By default, the sort toolbar accepts a maximun of 3 sort fields.
 * 
 * Click on a the arrow of a sorter field, change the sort direction.
 */
Ext.define('Ametys.grid.plugin.Multisort', 
{
	extend: 'Ext.AbstractPlugin',
	alias: 'plugin.multisort',
	
	requires: [
	            'Ext.data.*',
	            'Ext.grid.*',
	            'Ext.util.*',
	            'Ext.toolbar.*',
	            'Ext.ux.ToolbarDroppable',
	            'Ext.ux.BoxReorderer'
               ],
	
    /**
   	 * @cfg {Number} [maxNumberOfSortFields=3] the maximum number of accepted sorted fields
   	 */
   	maxNumberOfSortFields: 3,
   	
    /**
     * @private
     * @property {Number} closeItemButtonWidth The width used by buttons to close items
     */
    closeItemButtonWidth: null,
           	
    setCmp: function(gridConfig) 
    {
    	var me = this;
        
        this.callParent(arguments);
        
        var reorderer = Ext.create('Ext.ux.BoxReorderer', {
            listeners: {
                scope: this,
                'drop': function(r, c, container) { //sort on drop
                	this.doSort();
                }
            }
        });
        
        var droppable = Ext.create('Ext.ux.ToolbarDroppable', {
        	
        	destroy: function()
        	{
        		this.dropTarget.destroy();
        		this.dropTarget = null;
        	},
        	
    		// creates the new toolbar item from the header click event if possible
            createItem: function(column) 
            {
            	var columnCt = column.ownerCt,
            		grid = me.getCmp(),
            		toolbar = grid.getDockedItems('toolbar[dock="top"]')[0],
            		changePrimaryCriteria = false;
            	
            	var sortConfig = {
            		text: column.text,	
            		property: column.dataIndex,
                    direction: "ASC"
            	}
            	
            	this._insertSortItem(sortConfig);
            },
            
            /**
             * Inserts a sort item in multisort toolbar
             * @param {Object} sortConfig The sort configuration
             * @param {String} sortConfig.text The text
             * @param {String} sortConfig.property The sort property
             * @param {String} sortConfig.direction The sort direction
             * @private
             */
            _insertSortItem: function (sortConfig)
            {
            	var grid = me.getCmp(),
	        		toolbar = grid.getDockedItems('toolbar[dock="top"]')[0],
	        		changePrimaryCriteria = false;
            	
            	var sorterContainer =  me.createSorterConfig({
                    text: sortConfig.text,
                    sortData: {
                        property: sortConfig.property,
                        direction: sortConfig.direction
                    }
                });
            	
            	for (var i = 1; i < toolbar.items.length; i++)
                {
                	var currentSorterContainer = toolbar.items.get(i),
                	    sortButton = currentSorterContainer.items.get(0);
                	
                	if (sortButton.sortData.property == sortConfig.property)
            		{
                		// the sorter wasn't the primary criteria
            			if (i != 1)
        				{
            				changePrimaryCriteria = true;
        				}
                		
            			me._suspendDoSort = true;
                		me.changeSortDirection(sortButton, changePrimaryCriteria); 
                		me._suspendDoSort = false;
                		
                		toolbar.insert(1, currentSorterContainer);
                		return;
            		}
            	}

                toolbar.insert(1, sorterContainer);
                
                // limited to me.maxNumberOfSortFields search criterion
                if (toolbar.items.length > me.maxNumberOfSortFields + 1)
                {
                	me.destroyContainer(toolbar.items.get(me.maxNumberOfSortFields).getId());
            	}
            },
            
            /**
             * Remove sort items
             * @private
             */
            _removeSortItems: function()
            {
                var grid = me.getCmp(),
                    toolbar = grid.getDockedItems('toolbar[dock="top"]')[0];
                
                while (toolbar.items.length > 1)
                {
                    toolbar.items.get(toolbar.items.length - 1).destroy();
                }
            },
            
            /**
             * Initialize the multisort toolbar with the configured columns
             * @private
             */
            _initializeSortItems: function (grid)
            {
            	function getColumnText (grid, dataIndex) 
				{
					 var columns = grid.headerCt.getGridColumns();
					 for (var i = 0; i < columns.length; i++) 
					 {
						 if (columns[i].dataIndex == dataIndex) 
						 {
							 return columns[i].text;
						 }  
					 }
				}
                
                // Remove old sort items
                this._removeSortItems();
				
				// Draw sort item in multisort toolbar from store's sorters
				var sorters = grid.getStore().getSorters();
				for (var i=sorters.length - 1; i >= 0; i--)
				{
					var columnTxt = getColumnText(grid, sorters.items[i].getProperty());
					if (columnTxt)
					{
						var sortConfig = {
    		            		text: columnTxt,	
    		            		property: sorters.items[i].getProperty(),
    		                    direction: sorters.items[i].getDirection()
    		            	};
    					
    					this._insertSortItem(sortConfig);
					}
				}
            },
            
            /**
            * Custom canDrop implementation which returns true if a column can be added to the toolbar
            * @param {Ext.dd.DragSource} dragSource The drag source
            * @param {Object} event The event
            * @param {Object} data Arbitrary data from the drag source. For a HeaderContainer, it will
            * contain a header property which is the Header being dragged.
            * @return {Boolean} True if the drop is allowed
            */
            canDrop: function(dragSource, event, data) {
            	var sorters = me.getSorters(),
                    header  = data.header,
                    length = sorters.length,
                    entryIndex = this.calculateEntryIndex(event),
                    targetItem = this.toolbar.getComponent(entryIndex),
                    i;
                
                // Group columns have no dataIndex and therefore cannot be sorted
                // If target isn't reorderable it could not be replaced
                if (!header.dataIndex || (targetItem && targetItem.reorderable === false)) {
                    return false;
                }
                
                for (i = 0; i < length; i++) {
                    if (sorters[i].property == header.dataIndex) {
                        return false;
                    }
                }
                return true;
            }
        });
        
        // add the toolbar with the 2 plugins
        gridConfig.dockedItems = Ext.Array.from(gridConfig.dockedItems);
        gridConfig.dockedItems.push({
        	ui: 'ametys-grid-multisort',
        	xtype: 'toolbar',
            itemId: 'grid-multisort',
        	hidden: true,
            border: gridConfig.border,
        	items  : [
	            {
	                xtype: 'tbtext',
	                text: "{{i18n PLUGINS_CORE_UI_MULTISORT_TOOLBAR_TEXT}}",
	                reorderable: false
            	}
        	],
            plugins: [reorderer, droppable]
        });
        
        gridConfig.listeners = gridConfig.listeners || {};
        gridConfig.listeners['columnschanged'] = {
        		scope: this,
        		fn: function(headerCt, eOpts) {
    				Ext.Array.each(headerCt.columnManager.getColumns(), function(column) {column.sort = Ext.bind(me.doSort, me, [droppable, column], false)});
        		}
        };
        
        function initializeSortItems(grid)
        {
            // We need to defer the following because a reconfigured tool (such as AbstractSearchTool, will first reconfigure and then setSorters...)
            window.setTimeout(function() {
    			me._suspendDoSort = true;
				droppable._initializeSortItems(grid);
				me._suspendDoSort = false;
            },1);
        }
        
        gridConfig.listeners['reconfigure'] = {
        		scope: this,
        		fn: function(grid, store, columns) {
                    initializeSortItems(grid);
        		}
        };
        
        gridConfig.viewConfig = gridConfig.viewConfig || {};
        gridConfig.viewConfig.listeners = gridConfig.viewConfig.listeners || {};
        gridConfig.viewConfig.listeners['refresh'] = {
            scope: this,
            fn: function(view) {
                initializeSortItems(view.grid);
            }
        }
        
        // Hide/show button
        gridConfig.listeners['viewready'] = {
        		scope: this,
        		single: true,
        		fn: function(grid, eOpts) {
                    var me  = this;
        			var cmpId = grid.headerCt.getId() + '-' + Ext.id();
                    
                    var original = grid.headerCt.getMenuItems; 
                    grid.headerCt.getMenuItems = function()
                    {
                        var originalMenuItems = original.call(grid.headerCt);
                        
                        var descItem = Ext.Array.findBy(originalMenuItems, function(item) { return item.itemId == 'descItem' });
                        var indexDesc = descItem ? Ext.Array.indexOf(originalMenuItems, descItem) : 0;
                        Ext.Array.insert(originalMenuItems, indexDesc + 1, [{
                            text: "{{i18n PLUGINS_CORE_UI_MULTISORT_SHOW_TOOLBAR_BUTTON_TOOLTIP}}",
                            checked: false,
                            checkHandler: function(item, checked)
                            {
                                me.getCmp().getDockedItems('toolbar[dock="top"]')[0].setVisible(checked);
                            }
                        }]);
                        
                        return originalMenuItems;
                    }
                    

    				// initialize the multisort toolbar
    				droppable._initializeSortItems(grid);
        		}
        }
    },
    
    destroy: function()
    {
    	this.callParent(arguments);
    },
               
    /**
     * Returns an array of sortData from the sorter buttons
     * @return {Array} Ordered sort data from each of the sorter buttons
     */
    getSorters: function () 
    {
    	var sorters = [],
        	toolbar = this.getCmp().getDockedItems('toolbar[dock="top"]')[0];
        
        for (var i = 0; i < toolbar.items.length; i++)
    	{
        	if (toolbar.items.get(i).isXType('container'))
        	{
        		var sortButton = toolbar.items.get(i).items.get(0);
        		sorters.push(sortButton.sortData);
        	}
    	}	
    	
        return sorters;
    },
    
    /**
     * Convenience function for creating Toolbar Buttons that are tied to sorters
     * @param {Object} config Optional config object
     * @param {String} config.text the label of the element to be created
     * @param {Object} config.sortData the sort data
     * @param {String} config.sortData.property the label of the header
     * @param {String} config.sortData. direction equals 'ASC' || 'DESC'
     * @return {Object} The configuration of the Container with the 2 buttons and the label
     */
     createSorterConfig: function (config) 
     {
        var me = this,
        	containerId = Ext.id(),
        	config = config || {};
    	
        var deleteButtonConfig = ({
    		xtype: 'button',
            ui: 'ametys-grid-multisort-toolbar-item-remove',
            iconCls: 'a-grid-multisort-toolbar-item-remove-img',
            width: this.closeItemButtonWidth,
            
        	listeners: {
        		click: function(button, e) {
        			me.destroyContainer(containerId);
        		}
        	}

        });
        
        var sortButtonConfig = ({
            xtype: 'button',
            ui: 'ametys-grid-multisort-toolbar-item-sort',
            iconCls: 'a-grid-multisort-toolbar-item-sort-img',
            enableToggle: true,
            
            reorderable: true,
            sortData: config.sortData,
            pressed: config && config.sortData && config.sortData.direction == "DESC",
            
            listeners: {
                click: function(button, e) {
                    me.changeSortDirection(button, false);
                }
            }
        });
        
        var textConfig = Ext.create('Ext.Component', {
        	html : config.text,
        	cls: 'a-grid-multisort-toolbar-item-text'
        });

        return new Ext.container.Container({
        		id: containerId,
        		layout: {
        	        type: 'hbox',
    	        	defaultMargins: {
    	        		right: 3
    	        	}
        	    },
        	    cls: 'a-grid-multisort-toolbar-items-container',
        	    items: [sortButtonConfig, textConfig, deleteButtonConfig]
        	});
    },
    
    /**
     * Callback handler used when a sorter button is clicked or reordered
     * @param {Ext.Button} button The button that was clicked
     * @param {Boolean} changePrimaryCriteria true if the primary sorting criteria was changed, false otherwise
     */
    changeSortDirection: function (button, changePrimaryCriteria) 
    {
    	var sortData = button.sortData;
    	
        if (sortData)
        {
        	// when changing the primary criteria, the sorting order is ascending by default
        	if (changePrimaryCriteria)
    		{
    			sortData.direction = "ASC";
    		}
        	else
    		{
        		sortData.direction = Ext.String.toggle(sortData.direction, "ASC", "DESC");
    		}
        		
            button.toggle(sortData.direction != "ASC");
        	
    	    this.getCmp().getStore().clearFilter();
    	    
    	    if (!this._suspendDoSort)
    	    {
    	    	this.doSort();
    	    }
        }
    },
    
    /**
     * Sorts the grid and optionally the droppable toolbar createItem method if a grid header was clicked
     * @param {Ext.ux.ToolbarDroppable} droppable the droppable toolbar
     * @param {Ext.grid.column.Column} column the column whose header was clicked
     */
    doSort: function (droppable, column) 
    {
    	// header click
    	if (droppable)
		{
    		droppable.createItem(column);
		}
        var sorters = this.getSorters();
        if (sorters.length == 0)
        {
            // If sorter are empty, the #sort method does not relaunch the search, and so, grapically it is not applied
            this.getCmp().getStore().sorters = null; // There is no other way to clean old sorters... if we do not do that, the next sort call with an empty array, will be ignored
            this.getCmp().getStore().reload({sorters: []});
        }
        else
        {
		    this.getCmp().getStore().sort(sorters);
        }
    },
    
    /**
     * Destroys the container with the id containerId
     * @param {String} containerId the id of the container to destroy
     */
    destroyContainer: function(containerId)
    {
    	Ext.getCmp(containerId).destroy();
    	this.doSort();
    }
});