/*
 *  Copyright 2017 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.
 */
 /**
  * Tree for displaying a hierarchy of simple contents 
  * @private
  */
Ext.define('Ametys.plugins.cms.content.tree.HierarchicalReferenceTablesTree', {
    extend: 'Ext.tree.Panel',
    
    /**
     * @cfg {Object} rootCfg The configuration of the root node
     */
    
    /**
     * @cfg {String} [referenceTableId] The content type of the leaves of this tree. Not mandatory, can be set later with {@link #setReferenceTableId} method.
     */
    
    /**
     * @cfg {Boolean} checkMode To select contents with checkboxes. Defaults to 'false'.
     */
    checkMode : false,
    
    /**
     * @property {Boolean} true if this tool supports candidates.
     */
    allowCandidates: false,
    
    /**
     * @cfg {String/String[]} [values] If {@link #cfg-checkMode}=true, the list of contents to initially check
     */
    values: [],
    /**
     * @private
     * @property {String[]} _valuesToBeChecked The values to be checked
     */
    
    /**
     * @private
     * @property {String} _referenceTableId The simple content type the tree is displaying
     */
    
    /**
     * @cfg {Boolean} [allowToggleAutoposting=false] Set to `true` to allow autoposting during search. 
     */
    
    /**
     * @cfg {Boolean} [activeAutoposting=false] Set to `true` to allow autoposting during search. 
     */
    /**
     * @property {Boolean} [_activeAutoposting=false] See {@link #cfg-activeAutoposting}
     * @private
     */
    _activeAutoposting: false,
    
    /**
     * @property {Boolean} [_activeSelectAllChildren=false] Enabled if {@link #_activeAutoposting} is true.
     * @private
     */
    _activeSelectAllChildren: false,
    
    /**
     * @cfg {String} buttonAutopostingEnabledIconCls The separated CSS classes to apply to button for autoposting enabled
     */
    buttonAutopostingEnabledIconCls: 'ametysicon-text decorator-ametysicon-check34 referencetable-autoposting referencetable-autoposting-enabled',
    /**
     * @cfg {String} buttonAutopostingDisabledIconCls The separated CSS classes to apply to button for autoposting disabled
     */
    buttonAutopostingDisabledIconCls: 'ametysicon-text decorator-ametysicon-delete30 referencetable-autoposting referencetable-autoposting-disabled',
    
    /**
     * @cfg {Boolean} [checkSelection=false] Set to true to disable selection if node is not 'selectable'
     */
    checkSelection: false,
    
    statics: {
		/**
		 * The default filter.
		 */
		DEFAULT_FILTER: function(node)
		{
			return node ? true : false;
		}
	},
    
    initComponent: function ()
    {
    	this.on('checkchange', this._onCheckChange, this);
        this.on('beforeselect', this._onBeforeSelect, this);
        
    	this.callParent();
    },

    constructor: function(config)
    {
        this.contenttypesIds = config.rootCfg.contenttypesIds;
        
        Ext.apply(this, {
            store: this._getStoreCfg(config.rootCfg || {}, config.checkSelection)
        });
        
        // Docked items
        var dockedItems = [];
        
        dockedItems.push(this._getTopToolbarCfg(config));
        dockedItems.push(this._getNoResultPanelCfg());
        
        config.dockedItems = dockedItems;
        
        this._counter = {};
        this._filterConfig = config.filter || Ametys.plugins.cms.content.tree.HierarchicalReferenceTablesTree.DEFAULT_FILTER;
		this._filterValue = null;
		
        this._referenceTableId = config.referenceTableId;
        
        this.values = [];
        var values = config.values ? Ext.Array.from(config.values) : [];

        this.callParent(arguments);
        this.on('itemmouseenter', this._createQtip, this);
        
        if (this.checkMode)
        {
            this.store.on('load', this._updateCheckboxes, this);
        }
    },
    
    /**
     * Sets the id of reference table of the leaves of this tree
     * @param {String} referenceTableId The content type of the leaves of this tree
     */
    setReferenceTableId: function(referenceTableId)
    {
        this._referenceTableId = referenceTableId;
    },
    
    /**
     * Initialize the tree
     * @param {String} cTypeId The id of content type to initialize the tree's root node
     * @param {Boolean} [allowCandidates] Set to true to allow candidates
     */
    initializeTree: function(cTypeId, allowCandidates)
    {
        var cType = Ametys.cms.content.ContentTypeDAO.getContentType(cTypeId);
        
        // Set the root node
        this.setRootNode({
            title: cType.getLabel(),
            text: cType.getLabel(),
            contenttypesIds: [cType.getId()],
            contentId: 'root',
            id: 'root',
            allowDrag : false,
            allowDrop : true,
            iconGlyph: cType.getIconGlyph(),
            iconDecorator: cType.getIconDecorator(),
            iconSmall: cType.getIconSmall(),
            iconMedium: cType.getIconMedium(),
            iconLarge: cType.getIconLarge()
        });
        
        this.allowCandidates = allowCandidates || false;
        
        // Initialize no filter result action
        this.down("button[itemId='no-result']").handler = this.allowCandidates ? this._createCandidateFromFilter : this._clearSearchFilter;
        var noResultText = "{{i18n PLUGINS_CMS_REFERENCE_TABLE_TREE_FILTER_NO_MATCH}}";
        noResultText += this.allowCandidates ? "{{i18n PLUGINS_CMS_REFERENCE_TABLE_TREE_FILTER_NO_MATCH_CREATE_CANDIDATE_ACTION}}" : "{{i18n PLUGINS_CMS_REFERENCE_TABLE_TREE_FILTER_NO_MATCH_ACTION}}";
        this.down("button[itemId='no-result']").setText(noResultText);
        
        // Load the tree
        var node = this.getRootNode();
        this.getStore().load({
            node: node,
            callback: function () {
                this.expandNode (node, false);
            },
            scope: this
        });
    },
    
    /**
     * @protected
     * Gets the configuration of the store
     * @param {Object} rootCfg The configuration of the root node
     * @param {Boolean} checkSelection True to check if selection is authorized
     * @return {Object} The config of the store
     */
    _getStoreCfg: function(rootCfg, checkSelection)
    {
        return {
            model: 'Ametys.plugins.cms.content.tree.HierarchicalReferenceTablesTree.HierarchicalReferenceTablesTreeEntry',
            autoLoad: false,
            proxy: {
                type: 'ametys',
                plugin: 'cms',
                url: 'hierarchical-reference-tables/tree.json',
                reader: {
                    type: 'json',
                    rootProperty: 'children'
                },
		        extraParams: {
		        	checkSelection: checkSelection === true
		        }
            },
            root: Ext.apply(rootCfg, {
                id: 'root',
                contentId: 'root',
                name: 'root'
            }),
            rootCandidate: {
            	id: 'rootCandidate',
	            contentId: 'rootCandidate',
	            name: 'rootCandidate',
                cls: 'root-candidate'
            },
            folderSort: true,
            sorters: [{
                property: 'type',
                direction: 'ASC'
            },
            {
                property: 'text',
                direction: 'ASC'
            }],
            listeners: {
            	'beforeload': {fn: this._onBeforeLoad, scope: this},
            	'load': {fn: this._onLoad, scope: this, single: true}
            }
        };
    },
    
    /**
	 * @protected
     * @param {Object} config The configuration passed to the constructor
	 * Retrieves the top toolbar config object.
	 */
	_getTopToolbarCfg: function(config)
	{
        var items = [{
                        // Filter input
                        xtype: 'textfield',
                        cls: 'ametys',
                        flex: 1,
                        maxWidth: 300,
                        itemId: 'search-filter-input',
                        emptyText: "{{i18n PLUGINS_CMS_REFERENCE_TABLE_TREE_FILTER}}",
                        enableKeyEvents: true,
                        minLength: 3,
                        minLengthText: "{{i18n PLUGINS_CMS_REFERENCE_TABLE_TREE_FILTER_INVALID}}",
                        msgTarget: 'qtip',
                        listeners: {change: Ext.Function.createBuffered(this._searchFilter, 500, this)},
                        style: {
                            marginRight: '0px'
                        }
                    }, 
                    {
                        // Clear filter
                        tooltip: "{{i18n PLUGINS_CMS_REFERENCE_TABLE_TREE_TREE_CLEAR_FILTER}}",
                        handler: Ext.bind (this._clearSearchFilter, this),
                        iconCls: 'a-btn-glyph ametysicon-eraser11 size-16',
                        cls: 'a-btn-light'
                    }, 
                    {
                        xtype: 'tbspacer',
                        flex: 0.0001
                    },
                    {
                        // Collapse all
                        tooltip: "{{i18n PLUGINS_CMS_REFERENCE_TABLE_TREE_COLLAPSE_ALL}}",
                        handler: Ext.bind (this.collapseAll, this, [function(){this.expandNode(this.getRootNode())}], false),
                        iconCls: 'a-btn-glyph ametysicon-minus-sign4 size-16',
                        cls: 'a-btn-light'
                    }, 
                    {
                        // Refresh node
                        tooltip: "{{i18n PLUGINS_CMS_REFERENCE_TABLE_TREE_REFRESH_PAGE}}",
                        handler: Ext.bind (this.refreshNode, this, [], false),
                        iconCls: 'a-btn-glyph ametysicon-arrow123 size-16',
                        cls: 'a-btn-light'
                    }
            ];
            
        var allowToggleAutoposting = config.allowToggleAutoposting === true || config.allowToggleAutoposting == 'true';
        if (allowToggleAutoposting)
        {
            this._activeAutoposting = config.activeAutoposting === true || config.activeAutoposting == 'true';
            
            // Button to activate / deactivate autoposting
            items.push(Ext.create('Ext.button.Button', {
                itemId: 'toggle-autoposting',
                text: '',
                iconCls: this._activeAutoposting ? this.buttonAutopostingEnabledIconCls : this.buttonAutopostingDisabledIconCls,
                tooltip: this._activeAutoposting ? "{{i18n WIDGET_REFERENCE_TABLE_AUTOPOSTING_ENABLED}}" : "{{i18n WIDGET_REFERENCE_TABLE_AUTOPOSTING_DISABLED}}",
                enableToggle: true,
                pressed: this._activeAutoposting,
                toggleHandler: function (btn, state) {
                    this._activeAutoposting = state;
                    
                    if (this._activeAutoposting)
                    {
                        btn.setIconCls(this.buttonAutopostingEnabledIconCls);
                        btn.setTooltip("{{i18n WIDGET_REFERENCE_TABLE_AUTOPOSTING_ENABLED}}")
                    }
                    else
                    {
                        btn.setIconCls(this.buttonAutopostingDisabledIconCls);
                        btn.setTooltip("{{i18n WIDGET_REFERENCE_TABLE_AUTOPOSTING_DISABLED}}");
                    }
                    
                    if (this.checkMode)
                    {
                        var childrenBtn = this.down("button[itemId='select-children']");
                        childrenBtn.setDisabled(this._activeAutoposting);
                        if (this._activeAutoposting)
                        {
                            childrenBtn.toggle(false);
                        }
                    }
                },
                scope: this
            }));
            
            if (config.checkMode)
            {
                this._activeSelectAllChildren = false;
                
                // Button to automatically select children in the tree
                items.push({
                    iconCls: 'a-btn-glyph ametysicon-blank32 size-16',
                    itemId: 'select-children',
                    tooltip: "{{i18n WIDGET_REFERENCE_TABLE_SELECT_ALL_CHILDREN_INACTIVE}}",
                    pressed: false,
                    disabled: this._activeAutoposting,
                    enableToggle: true,
                    toggleHandler: function (btn, state) {
                        this._activeSelectAllChildren = state;
                        
                        if (this._activeSelectAllChildren)
                        {
                            btn.setIconCls('a-btn-glyph ametysicon-check51 size-16');
                            btn.setTooltip("{{i18n WIDGET_REFERENCE_TABLE_SELECT_ALL_CHILDREN_ACTIVE}}");
                        }
                        else
                        {
                            btn.setIconCls('a-btn-glyph ametysicon-blank32 size-16');
                            btn.setTooltip("{{i18n WIDGET_REFERENCE_TABLE_SELECT_ALL_CHILDREN_INACTIVE}}");
                        }
                    },
                    scope: this
                });
            }
        }
        
		return {
			dock: 'top',
            xtype: 'toolbar',
            itemId: 'top-toolbar',
            layout: { 
                type: 'hbox',
                align: 'stretch'
            },
            defaultType: 'button',
			items: items
		};
	},
    
	/**
	 * @protected
	 * Retrieves the 'no result' panel config
	 */
	_getNoResultPanelCfg: function()
	{
        var text = "{{i18n PLUGINS_CMS_REFERENCE_TABLE_TREE_FILTER_NO_MATCH}}";
        text += "{{i18n PLUGINS_CMS_REFERENCE_TABLE_TREE_FILTER_NO_MATCH_ACTION}}"
        
        return {
            dock: 'top',
            xtype: 'button',
            hidden: true,
            itemId: 'no-result',
            ui: 'tool-hintmessage',
            text: text,
            scope: this,
            handler: this._clearSearchFilter
        };
	},
	
	/**
	 * This listener is called on 'change' event on filter input field.
	 * Filters the tree by text input.
	 * @param {Ext.form.Field} field The field
	 * @private
	 */
	_searchFilter: function (field)
	{
		var value = new String(field.getValue()).trim();
		this._filterField = field;
		
		if (this._filterValue == value)
		{
			// Do nothing
			return;
		}
		
		this._filterValue = value;
		
		if (value.length > 2)
		{	
			var rootNode = this.getRootNode();
			this._getFilteredReferenceTable(value, rootNode);
		}
		else
		{
			this._hideNoResultPanel();
			this.clearFilter();
		}
	},
	
	/**
	 * Clear all filters
	 */
	clearFilter: function()
	{
        this.getStore().clearFilter();
	},
	
	/**
	 * Filters by a function. The specified function will be called for each Record in this Store. 
	 * If the function returns true the Record is included, otherwise it is filtered out.
	 * @param {Function} filterFn A function to be called.
	 */
	filterBy: function (filterFn)
	{
        this.clearFilter();
        this.getStore().filterBy(filterFn);
	},
	
	/**
	 * Hide the panel showing there is no result.
	 * @private
	 */
	_hideNoResultPanel: function ()
	{
		this.down("button[itemId='no-result']").setVisible(false);
	},
	
	/**
	 * Show the panel showing there is no result.
	 * @private
	 */
	_showNoResultPanel: function ()
	{
		this.down("button[itemId='no-result']").setVisible(true);
	},
	
	/**
	 * Get the reference tables the name matches the given value
	 * @param {String} value The value to match
	 * @param {Ext.data.Model} rootNode The root node 
	 * @param {Ext.data.Model} node The initial node to search from. Can be null. If null start from root node.
	 * @param {Function} [callback] The function called on server response. Transmited to #_filterTermsCb.
	 * @private
	 */
	_getFilteredReferenceTable: function (value, rootNode, node, callback)
	{
		node = node || rootNode;
		
		if (node.childNodes.length == 0)
		{
			// There is no reference table yet, do not filter
			return;
		}
		
		var leafContentType = this.contenttypesIds ? this.contenttypesIds[0]: rootNode.get("contenttypesIds")[0];
		var nodeId = node.get('contentId');
		
		Ametys.data.ServerComm.callMethod({
			role: "org.ametys.cms.content.referencetable.HierarchicalReferenceTablesHelper", 
			methodName: 'filterReferenceTablesByRegExp', 
			parameters: [value, nodeId, leafContentType],
			errorMessage: "{{i18n PLUGINS_CMS_REFERENCE_TABLE_TREE_SEARCH_ERROR}}",
			callback: {
				handler: this._filterTermsCb,
				scope: this,
				arguments: {
					node: node,
					callback: callback
				}
			},
            cancelCode: this.self.getName() + '$' + this.getId()
		});
	},
	
	/**
	 * @private
	 * Callback function after searching reference tables
	 * @param {Object} result The result object
	 * @param {Object[]} args The callback arguments
	 */
	_filterTermsCb: function(result, args) 
	{
		var hasResult = false;
		var node = args.node || this.getRootNode();
		var callback = args.callback;
		
		if (!result)
		{
			return;
		}
		
		
		if (result.length == 0)
		{
			this.filterBy (function () {return false});
		}
		else
		{
			hasResult = true;
			this._expandAndFilter (result, this.getRootNode(), node);
		}
		
		if (!hasResult)
		{
			this._showNoResultPanel();
			this.getSelectionModel().deselectAll();
			Ext.defer (this._filterField.markInvalid, 100, this._filterField, ["{{i18n PLUGINS_CMS_REFERENCE_TABLE_TREE_FILTER_NO_MATCH}}"]);
		}
		else
		{
			this._hideNoResultPanel();
			this._filterField.clearInvalid();
		}
		
		if (Ext.isFunction(callback))
		{
			callback();
		}
	},
	
	/**
	 * Expand the tree to the given paths. Then filter nodes matching the given paths by calling the #_filterPaths method
	 * @param {Object[]} paths The paths to expand
	 * @param {Ext.data.Model} rootNode The concerned root node
	 * @param {Ext.data.Model} node The node from which apply filter
	 * @private
	 */
	_expandAndFilter: function(paths, rootNode, node)
	{
		node = node || rootNode;
		
		this._counter[rootNode.getId()] = paths.length;
		for (var i=0; i < paths.length; i++)
		{
			var path = paths[i];
			var options = {
				field: 'name',
				separator: null,
				callback: Ext.bind (this._filterPaths, this, [paths, rootNode, node], 2)
			};
			this.expandPath (rootNode.getPath('name') + '/' + paths[i], options);
		}	
	},
	
	/**
	 * Filter nodes by path once the last expand has been processed
	 * @param {Boolean} success true if the expand was successful
	 * @param {Ext.data.Model} lastNode the last node that was expanded
	 * @param {String[]} paths The path to filter by
	 * @param {Ext.data.Model} rootNode The concerned root node
	 * @param {Ext.data.Model} node The node from which apply filter
	 * @private
	 */
	_filterPaths: function (success, lastNode, paths, rootNode, node)
	{
		// only execute the filterBy after the last expandPath()
		if (--this._counter[rootNode.getId()] == 0)
		{
			var filterFn = Ext.bind (this._filterByPath, this, [paths, rootNode], true);
			
			// FIXME Ensure that expand is complete by deferring the filterBy function ...
			Ext.defer(this.filterBy, 50, this, [filterFn, node]);
		}
		
	},
	
	/**
	 * Returns true if the node path is a part of given paths
	 * @param {Ext.data.Model} node The node to test
	 * @param {String[]} paths The paths
	 * @param {Ext.data.Model} rootNode The root node to build the complete paths
	 * @private
	 */
	_filterByPath: function (node, paths, rootNode)
	{
		var currentPath = node.getPath('name');
		for (var i=0; i < paths.length; i++)
		{
			var path = rootNode.getPath('name') + '/' + paths[i];
			if (path.indexOf(currentPath + '/') == 0)
			{
				return true;
			}
			
			if (path == currentPath)
			{
				return this._filterConfig(node);
			}
		}
		return false;
	},
	
	/**
	 * Clear the filter search
	 * @param {Ext.Button} btn The button
	 * @private
	 */
	_clearSearchFilter: function(btn)
	{
		this.clearFilter();
		
        var filterFd = this.down("textfield[itemId='search-filter-input']");
		filterFd.reset();
		
		this._filterValue = null;
		
		this._hideNoResultPanel();
	},
	
	/**
	 * Create a new candidate from filter value
	 * @param {Ext.Button} btn The clicked button
	 * @private
	 */
	_createCandidateFromFilter: function (btn)
	{
		var filterFd = this.down("textfield[itemId='search-filter-input']");
		var filterValue = filterFd.getValue();
		
		var me = this;
		
		this._clearSearchFilter();
        
        Ametys.plugins.cms.content.actions.CandidatesActions.open({
            mode: 'new',
            contentType: this._referenceTableId,
            contentLanguage: Ametys.cms.language.LanguageDAO.getCurrentLanguage(),
            contentTitle: filterValue,
            workflowName: 'reference-table' // FIME Hard coded
        });
	},
	
    /**
     * @private
     * Single listener invoked at first load
     * Select the current values
     * @param {Ext.data.TreeStore} store The tree store
     * @param {Ext.data.TreeModel[]} records The records
     * @param {Boolean} successful True if the operation was successful.
     * @param {Ext.data.Operation} operation The operation that triggered this load.
     * @param {Ext.data.NodeInterface} node The loaded node
     */
    _onLoad: function (store, records, successful, operation, node)
    {
    	if (!Ext.isEmpty(this.values))
    	{
            Ametys.data.ServerComm.callMethod({
                role: "org.ametys.cms.content.referencetable.HierarchicalReferenceTablesHelper",
                methodName: "getPathInHierarchy",
                parameters: [this.values],
                callback: {
                    handler: this._getReferenceTablePathsCb,
                    scope: this
                },
                waitMessage: false
            });
    	}
    },
    
    /**
     * @private
     * Listener invoked when loading a node
     * Update the checkbox state.
     * @param {Ext.data.TreeStore} store The tree store
     * @param {Ext.data.TreeModel[]} records The records
     * @param {Boolean} successful True if the operation was successful.
     * @param {Ext.data.Operation} operation The operation that triggered this load.
     * @param {Ext.data.NodeInterface} node The loaded node
     */
    _updateCheckboxes: function(store, records, successful, operation, node)
    {
        this._setCheckboxes(node);
    },
    
    /**
     * Iterate through the children nodes, and call #_setCheckbox
     * @param {Ext.data.Model} node The node to iterate
     * @private
     */
    _setCheckboxes: function(node)
    {
        var childNodes = node.childNodes;
        for (var i=0; i < childNodes.length; i++)
        {
            this._setCheckbox(childNodes[i]);
            this._setCheckboxes(childNodes[i]);
        }
    },
    
    /**
     * Put a checkbox on nodes that can be checked, with the corresponding state. Does not verify any rights. 
     * @param {Ext.data.Model} node The node 
     * @private
     */
    _setCheckbox: function(node)
    {
        if (this.checkSelection && node.get('disabled'))
        {
            node.set('checked', null);
        }
        else if (!node.get('disabled'))
        {
            node.set('checked', Ext.Array.contains(this.values, node.get('contentId')));
        }
    },
    
    
    /**
     * @private
     * Callback function invoked after getting path in tree of contents
     * Selects or checks nodes.
     * @param {Object} paths The paths for each content
     */
    _getReferenceTablePathsCb: function(paths)
    {
    	var me = this;

        Ext.Object.eachValue(paths, function(path)
        {
            me.expandNodeByPath(path, function (sucessfull, node) {
                if (sucessfull)
                {
                    if (me.checkMode) 
                    {
                        node.set('checked', true);
                    }
                    else
                    {
                        me.getSelectionModel().select(node);
                    }
                }
            });
        }); 
    },
    
    /**
     * Get the root node where all the candidates are referenced
     */
    getRootCandidate: function()
    {
    	var index = this.getStore().findExact('contentId', 'rootCandidate');
        return this.getStore().getAt(index);
    },
    
    /**
     * Expand the tree to the given path. 
     * @param {String[]} path The path to expand
     * @param {Function} callback The callback function to call after expand. Can be null.
     */
    expandNodeByPath: function(path, callback) 
    {
        var rootNode = this.getRootNode();
        this.expandPath (rootNode.getPath('name') + "/" + path, 'name', null, callback);
    },
    
    /**
	 * This function reload the given node
	 * @param {String} id The id of the node to reload
	 * @param {Function} callback The callback function to call after reload. Can be null. Has the following parameters:
	 * @param {Ext.data.Model} callback.node The refreshed node
	 */
	refreshNode: function (id, callback)
	{
		var node;
		if (id == null)
		{
			var selection = this.getSelectionModel().getSelection();
			node = selection.length > 0 ? selection[0] : null;
			
			// Workaround - Refresh selection in case node is not existing anymore (deleted by another user for example).
			if (node != null)
			{
				this.getSelectionModel().deselect(node);
				this.getSelectionModel().select(node);
			}
		}
		else
		{
			node = this.getStore().getNodeById(id);
		}
		
		if (node != null)
		{
			// Set leaf to false, to allow children to be added during the load. Leaf will be set to true again if needed after the load.
			node.set('leaf', false);
			
			this.getStore().load({
				node: node,
				callback: function() {
					
					if (this._filterValue && this._filterValue.length > 2)
					{
						this._getFilteredReferenceTable (this._filterValue, this.getRootNode(), node, callback ? Ext.bind(callback, null, [node]) : null);
					}
					else
					{
						Ext.defer(this._expandNode, 200, this, [node, callback]);
					}
					
					
				},
				scope: this
			});
		}
	},
	
	/**
	 * Expand the given node
	 * @param {String} node The node to expand.
	 * @param {Function} callback The callback function to call after reload. Can be null. Has the following parameters:
	 * @param {Ext.data.Model} callback.node The expended node
	 * @private
	 */
	_expandNode: function (node, callback)
	{
		if (node == null)
		{
			var selection = this.getSelectionModel().getSelection();
			node = selection.length > 0 ? selection[0] : null;
		}
		
		if (node != null)
		{
			if (!node.hasChildNodes())
			{
				node.set('leaf', true);
			}
			
			callback = callback ? Ext.bind(callback, null, [node]) : null;
			this.expandNode (node, false, callback);
		}
		this.expandNode (node, false, callback);
	},
	
    /**
     * @private
     * Listener before a request is made
     * @param {Ext.data.TreeStore} store The store
     * @param {Ext.data.operation.Operation} operation The Ext.data.operation.Operation object that will be passed to the Proxy to load the Store
     */
    _onBeforeLoad: function(store, operation)
    {
        operation.setParams(Ext.apply(operation.getParams() || {}, {
            leafContentType: this._referenceTableId,
            contentId: operation.node.get('contentId')
        }));
    },
    
    /**
     * Verify if a node can be checked. If it can, his value is updated
     * @param {Ext.data.Model} node The node which state was updated
     * @param {Boolean} checked The new node state
     * @private
     */
    _onCheckChange: function(node, checked)
    {
        if (this.checkSelection === true && node.get('disabled'))
        {
            // cancel check
            node.set('checked', false); 
        }
        else if (checked === true && Ext.Array.contains(this.values, node.get('contentId')) === false)
        {
            this.values.push(node.get('contentId'));
            
            if (!this._activeAutoposting && this._activeSelectAllChildren)
            {
                this._checkChildren(node);
            }
        }
        else if (checked === false && Ext.Array.contains(this.values, node.get('contentId')) === true)
        {
            Ext.Array.remove(this.values, node.get('contentId'));
        }
    },
    
    /**
     * @private
     * Expand and check recursively all children of the given node
     * @param {Ext.data.NodeInterface} node The current node
     */
    _checkChildren: function(node) 
    {
        node.expand(false, function()
        {
            node.eachChild(function(childNode) 
            {
                childNode.set('checked', true);
                this.values.push(childNode.get('contentId'));
                this._checkChildren(childNode);
            }, this);
        }, this);
    },
    
    /**
     * @private
     * Listener before selecting node in the tree
     * @param {Ext.selection.Model} sm the selection model
     * @param {Ext.data.Model} record The selected record
     * @return {Boolean} return false if the selection is cancelled
     */
    _onBeforeSelect: function(sm, record)
    {
        // Cancel selection is node is disabled
        return !this.checkSelection || !record.get('disabled');
    },
    
    /**
     * Destroy and create the node tooltip when the mouse enters the node
     * @param {Ext.view.View} view The tree view
     * @param {Ext.data.Model} node The tree node
     * @param {HTMLElement} el The node's element
     */
    _createQtip: function (view, node, el)
    {
        Ext.QuickTips.unregister(el);
        var tip = this._getTooltip(node);
        Ext.QuickTips.register(Ext.apply({target: el, id: el.id + '-tooltip', renderTo: Ext.getBody()}, tip));
    },
    
    /**
     * Get the tooltip configuration
     * @param {Ext.data.Model} node The tree node
     * @returns {Object} The tooltip configuration. See Ametys.ui.fluent.Tooltip.
     * @private
     */
    _getTooltip: function(node)
    {
		return {
		    title: node.get('title'),
            glyphIcon: node.get('iconCls'),
            text: node.get('tooltip'),
            imageWidth: 48,
            imageHeight: 48,
            inribbon: false
		};
    },
    
    /**
     * Return true if the autoposting button is toggled, otherwise false.
     */
    isActiveAutoposting: function()
    {
        return this._activeAutoposting;
    }
});