/*
 *  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.
 */

/**
 * {@link Ext.tree.Panel} for sitemap
 * 
 * 		var tree = Ext.create('Ametys.web.sitemap.SitemapTree', {
 *                  
 * 		     siteContext: Ametys.web.helper.ContextToolbar.SITE_CONTEXT_CURRENT,
 * 		     sitemapContext: Ametys.web.helper.ContextToolbar.SITEMAP_CONTEXT_ALL,
 * 		     defaultSitemapName: null,
 * 		 	 defaultSiteName: Ametys.getAppParameter('siteName'),
 *        		    
 * 		     allowCreation: true,
 * 		     values: ["page://xxxx", "page://yyyy"],
 * 		            
 * 		     checkMode: true
 * 		 });
 * 
 * 		 tree.initialize (Ext.bind(this._initializeCb, this));
 */
Ext.define('Ametys.web.sitemap.SitemapTree', {
	extend: 'Ext.tree.Panel',
	
	/**
     * @cfg {String} defaultSiteName The default site name. Defaults to the current site name
     */
	
	/**
     * @cfg {String} [defaultSitemapName=null] The default language.
     */
    
    /**
     * @cfg {Boolean} [showSelectDecorators=true] When true a button will be displayed to select which decorator to display 
     */
    /**
     * @property {Boolean} _showSelectDecorators The value of #cfg-showSelectDecorators.
     * @private
     */
	
	/**
     * @cfg {String} [siteContext=Ametys.web.helper.ContextToolbar.SITE_CONTEXT_CURRENT] The site context. 
     * Valid contexts are: 
     * <ul>
     * 		<li>Ametys.web.helper.ContextToolbar.SITE_CONTEXT_CURRENT for current site only</li>
     * 		<li>Ametys.web.helper.ContextToolbar.SITE_CONTEXT_ALL for all sites</li>
     * </ul>
     */
	
	/**
     * @cfg {String} [sitemapContext=Ametys.web.helper.ContextToolbar.SITEMAP_CONTEXT_ALL] The sitemap context.
     * Valid contexts are: 
     * <ul>
     * 		<li>Ametys.web.helper.ContextToolbar.SITEMAP_CONTEXT_ALL for all languages</li>
     * 		<li>a language code such as 'fr', 'en', .. for using a specifying language.</li>
     * </ul>
     */
	
	/**
     * @cfg {Boolean} [checkMode=false] Set to true to select pages by check boxes.
     */
	

	/**
     * @cfg {String[]} [rights] Array of rights to check (AND condition) on pages
     */
	/**
	 * @private
	 * @property {String[]} _rights See #cfg-rights
	 */
	
	/**
	 * @private
	 * @property {Object[]} _decorators Array of decorators
	 */
	_decorators: [],
		
	/**
	 * @private
	 * @property {Boolean} _isRefreshingRootNode To prevent multiple requests to refresh the root node.
	 */
	_isRefreshingRootNode: false,
	
	constructor: function(config)
	{
		this._decorators = config.indicators || [];
		this._activeDecorators = config.activeDecorators || [];

		Ext.applyIf (config, {
			rootVisible: true,
			
			siteContext: Ametys.web.helper.ContextToolbar.SITE_CONTEXT_CURRENT,
			sitemapContext: Ametys.web.helper.ContextToolbar.SITEMAP_CONTEXT_ALL,
			defaultSiteName: Ametys.getAppParameter("siteName"),
			defaultSitemapName: null,
			
			cls: 'sitemap-tree',
			border: false,
			scrollable: true,
            
            showSelectDecorators: true,
			
			store: Ext.create('Ext.data.TreeStore', {
				model: config.checkMode ? 'Ametys.web.sitemap.SitemapTree.CheckablePage' : 'Ametys.web.sitemap.SitemapTree.Page',
				checkMode: config.checkMode || false,
				autoLoad : false,
				
				root: {
					expanded: false,
					text: "{{i18n PLUGINS_WEB_SITEMAP_TREE_ROOT_LABEL}}",
					title: "{{i18n PLUGINS_WEB_SITEMAP_TREE_ROOT_LABEL}}",
					iconCls: "a-tree-glyph ametysicon-world91", 
					path: '',
					leaf: true
				},			
				
		        proxy: {
		        	type: 'ametys',
					plugin: 'web',
					url: 'repository/sitemap.json',
		        	reader: {
		        		type: 'json',
						rootProperty: 'pages'
		        	},
		        	extraParams: {
						rights: config.rights || []
					}
		        },
		        
		        listeners: {
					'beforeload': Ext.bind(this._onBeforeLoad, this)
				}
			})
		});
		
		this._rights = config.rights || [];
		
		this._counter = {};
		
		var dockedItems = [];
		
		if (config.siteContext == Ametys.web.helper.ContextToolbar.SITE_CONTEXT_ALL || config.sitemapContext == Ametys.web.helper.ContextToolbar.SITEMAP_CONTEXT_ALL)
		{
			var toolbarCfg = {
				onSelectSiteFn: Ext.bind(this._onSelectSite, this),
				onSelectSitemapFn: Ext.bind(this._onSelectSitemap, this),
				siteContext: config.siteContext,
				sitemapContext: config.sitemapContext,
				defaultSiteName: config.defaultSiteName
			}
			
			// Add a toolbar to select site and/or language in combo boxes
			dockedItems.push(Ametys.web.helper.ContextToolbar.getToolbar(toolbarCfg));
		}
		
        this._showSelectDecorators = config.showSelectDecorators;
        
		// Add toolbar for tools
		dockedItems.push(this._getToolBarConfig());
		dockedItems.push(this._getNoResultPanel());
		
		config.dockedItems = dockedItems;

        this.callParent(arguments);
		
		this.on('beforeselect', this._onBeforeSelect, this);
		this.on('beforeitemdblclick', this._onBeforeSelect, this);
        
        var me = this;
        var view = this.view.lockedView || this.view;
        
        var tpl = new Ext.XTemplate(view.cellTpl.html.replace('</div></td>', 
            '<tpl for="(values.column.getItemId().startsWith(\'tree\') && record.data.decorators) ? record.data.decorators.split(\',\') : []">' // startsWith(\'tree\') to apply to the main column only
                + '<tpl if="this.isActive(arguments[1])">'
                    + '<tpl if="this.getDecorator(arguments[1]).iconGlyph">'
                        + '<span class="sitemap-decorator-glyph {% out.push(this.getDecorator(arguments[1]).iconGlyph) %}" title="{% out.push(this.getDecorator(arguments[1]).label) %}"></span>'
                    + '<tpl else>'
                        + '<img class="sitemap-decorator-icon" src="{% out.push(Ametys.CONTEXT_PATH +this.getDecorator(arguments[1]).icon) %}" title="{% out.push(this.getDecorator(arguments[1]).label) %}" alt="[{% out.push(this.getDecorator(arguments[1]).label) %}]"/>'
                    + '</tpl>'
                 + '</tpl>' 
            + '</tpl>'
            + '</div></td>'), 
            {
                priority: view.cellTpl.priority,
                
                /**
                 * Get the decorator data if available
                 */
                getDecorator: function(id)
                {
                    return Ext.Array.findBy(me._decorators, function(v) { return v.id == id;});
                },
				
                /**
                 * Return true if decorator is active
                 */
				isActive: function(id)
				{
					return me._activeDecorators.includes(id);
				}
        
            });
        if (this.view.lockedView)
        {
            this.view.lockedView.cellTpl = tpl;
        }
        else
        {
            this.view.cellTpl = tpl;
        }            
	},
	
	applyState: function (state)
    {
        this.callParent(arguments);
        
        var me = this;
        this._activeDecorators = state.activeDecorators || [];
        
        var indicatorMenu = this.down('toolbar > button[itemId="indicators-menu"]');
        if (indicatorMenu)
        {
            indicatorMenu.getMenu().items.each(function(item) {
                item.setChecked(Ext.Array.contains(me._activeDecorators, item.name), true);
            });
        }
    },
    
    getState: function ()
    {
        var state = this.callParent(arguments) || {};
        // save indicators 
        state.activeDecorators = this._activeDecorators
        return state;
    },
	
	/**
	 * @private
	 * Get configuration for toolbar
	 * @return {Object} the config object
	 */
	_getToolBarConfig: function ()
	{
		var me = this;
        var menuItems = [];
        Ext.Array.forEach(this._decorators, function(indicator) {
        	menuItems.push({
                xtype: 'menucheckitem',
                iconCls: indicator.iconGlyph,
                text: indicator.label,
                name: indicator.id,
                checkHandler: me.selectDecorators,
                checked: Ext.Array.contains(me._activeDecorators, indicator.id),
                scope: me
            })
        });
        
		return {
			dock: 'top',
			xtype: 'toolbar',
            layout: { 
                type: 'hbox',
                align: 'stretch'
            },
			defaultType: 'button',
			items: [{
						// Filter input
						xtype: 'textfield',
						cls: 'ametys',
						flex: 1,
                        maxWidth: 300,
						itemId: 'search-filter-input',
						emptyText: "{{i18n PLUGINS_WEB_UITOOL_SITEMAP_TREE_FILTER}}",
						minLength: 3,
						minLengthText: "{{i18n PLUGINS_WEB_UITOOL_SITEMAP_TREE_FILTER_INVALID}}",
						msgTarget: 'qtip',
						listeners: {change: Ext.Function.createBuffered(this._searchFilter, 500, this)},
						style: {
                            marginRight: '0px'
                        }
					}, 
					{
						// Clear filter
						tooltip: "{{i18n PLUGINS_WEB_UITOOL_SITEMAP_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_WEB_UITOOL_SITEMAP_TREE_COLLAPSE_ALL}}",
						handler: Ext.bind (this.collapseNode, this, [], false),
						iconCls: 'a-btn-glyph ametysicon-minus-sign4 size-16',
						cls: 'a-btn-light'
					}, 
					{
						// Refresh node
						tooltip: "{{i18n PLUGINS_WEB_UITOOL_SITEMAP_TREE_REFRESH_PAGE}}",
						handler: Ext.bind (this.refreshNode, this, [], false),
						iconCls: 'a-btn-glyph ametysicon-arrow123 size-16',
						cls: 'a-btn-light'
					},
					{
						// Decorators
						hidden: !this._showSelectDecorators || this._decorators.length == 0,
						tooltip: "{{i18n PLUGINS_WEB_UITOOL_SITEMAP_TREE_ICONS}}",
						iconCls: 'a-btn-glyph ametysicon-puzzle33 size-16',
						cls: 'a-btn-light',
						itemId: 'indicators-menu',
		                menu: {
		                    xtype: 'menu',
		                    items: menuItems
		                }
					}
			]
		};
	},
	
	/**
	 * @private
	 * Get the 'no result' button configuration. This button is shown when filter matches no result.
	 * @return {Object} the button configuration
	 */
	_getNoResultPanel: function ()
	{
		return {
			dock: 'top',
			xtype: 'button',
			hidden: true,
			itemId: 'no-result',
			ui: 'tool-hintmessage',
			text: "{{i18n PLUGINS_WEB_UITOOL_SITEMAP_TREE_FILTER_NO_MATCH}}" + "{{i18n PLUGINS_WEB_UITOOL_SITEMAP_TREE_FILTER_NO_MATCH_ACTION}}",
			scope: this,
			handler: this._clearSearchFilter
		};
	},
	
	/**
	 * @private
	 * Show or hide the 'no result' button.
	 * @param {Boolean} show true to show the button, false to hide it.
	 */
	_showHideNoResultPanel: function (show)
	{
		this.down("button[itemId='no-result']").setVisible(show);
	},
	
	/**
	 * This listener is called on 'keyup' 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._getFilteredPages (value, rootNode);
		}
		else
		{
			this._showHideNoResultPanel(false);
			this.clearFilter();
		}
	},
	
	/**
     * Get the tags the name matches the given value
     * @param {String} value The value to match
     * @param {Ext.data.Model} node The node where starting search
     * @param {Boolean} [childNodesOnly] set to 'true' to filter the child nodes only. 
     * @private
     */
	_getFilteredPages: function (value, node, childNodesOnly)
    {
        Ametys.data.ServerComm.callMethod({
            role: "org.ametys.web.repository.page.SitemapDAO", 
            methodName: 'filterPagesByRegExp', 
            parameters: [node.getId(), value],
            errorMessage: "{{i18n PLUGINS_WEB_UITOOL_SITEMAP_TREE_SEARCH_ERROR}}",
            callback: {
                handler: this._filterPagesCb,
                scope: this,
                arguments: {
                	node: node,
                	childNodesOnly: childNodesOnly
                }
            }
        });
    },
    
    /**
     * @private
     * Callback function after searching pages
     * @param {Object} paths The paths of matching pages
     * @param {Object[]} args The callback arguments
     */
    _filterPagesCb: function(paths, args) 
    {
        var hasResult = false;
        var node = args.node || this.getRootNode();
        
        if (!paths)
        {
            return;
        }
        
        if (paths.length == 0)
        {
        	if (args.childNodesOnly)
        	{
        		// There is no child nodes matching but some other nodes are matching.
        		hasResult = true;
        		for (var i=0; i < node.childNodes.length; i++)
        		{
        			this.filterBy (function () {return false}, node.childNodes[i]);
        		}
        	}
        	else
        	{
        		this.filterBy (function () {return false}, node);
        	}
        }
        else
        {
            hasResult = true;
            var childs = node.childNodes;
            this._expandAndFilter (paths, this.getRootNode(), node);
        }
        
        if (!hasResult)
        {
        	this._showHideNoResultPanel(true);
            if (this._filterField)
            {
                Ext.defer (this._filterField.markInvalid, 100, this._filterField, ["{{i18n PLUGINS_WEB_UITOOL_SITEMAP_TREE_FILTER_NO_MATCH}}"]);
            }
        }
        else
        {
        	this._showHideNoResultPanel(false)
            if (this._filterField)
            {
                this._filterField.clearInvalid();
            }
        }
    },
    
    /**
     * Expand the tree to the given paths.
     * @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].substr(0, paths[i].lastIndexOf("/"));
            this.expandPath (rootNode.getPath('name') + '/' + path, 'name', null, Ext.bind (this._filterPaths, this, [paths, rootNode, node], false));
        }   
    },
    
    /**
     * Filter nodes by path once the last expand has been processed
     * @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 (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;
            }
        }
        return false;
    },
    
    /**
     * 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);
    },
    
	/**
	 * Clear the filter search
	 * @param {Ext.Button} btn The button
	 * @private
	 */
	_clearSearchFilter: function(btn)
	{
		this.clearFilter();
		
		if (this._filterField)
        {
			this._filterField.reset();
        }
		
		this._filterValue = null;
		this._showHideNoResultPanel(false);
		
		var selection = this.getSelectionModel().getSelection()[0];
		if (selection)
		{
			this.ensureVisible(selection.getPath('name'), {field: 'name'});
		}
	},
	
	/**
	 * Clear all filters
	 */
	clearFilter: function (node)
	{
        this.getStore().clearFilter();
		
		// TODO checked mode?
		
		var selection = this.getSelectionModel().getSelection()[0];
		if (selection)
		{
			this.ensureVisible(selection.getPath());
		}
	},
	
	/**
	 * Expand the tree and select a node by its complete path
	 * @param {String} path The complete path from the root node
	 */
	selectByPath: function (path)
	{
		this.selectPath(path, 'name');
	},
	
	/**
	 * Collapse recursively the children of the node, then select the collapsed node.
	 * @param {Ext.data.NodeInterface} [node] The node the collapse. Can be null to collapse the whole tree.
	 */
	collapseNode: function (node)
	{
		node = node || this.getRootNode();
		node.collapseChildren(true);
		this.getSelectionModel().select(node);
	},
	
	/**
	 * 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)
	{
        callback = Ext.isFunction(callback) ? callback : Ext.EmptyFn;
        
		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).
			this.getSelectionModel().deselect(node, true);
			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);
			
			var me = this;
			this.getStore().load({
				node: node,
				callback: function () {
					if (me._filterValue && me._filterValue.length > 2)
					{
						// Filter child nodes only
						me._getFilteredPages (this._filterValue, node, true);
					}
                    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 expanded node
     * @private
     */
    _expandNode: function (node, callback)
    {
        if (node == null)
        {
            var selection = this.getSelectionModel().getSelection();
            node = selection.length > 0 ? selection[0] : null;
        }
        
        if (node != null)
        {
            callback = callback ? Ext.bind(callback, null, [node]) : null;
            this.expandNode (node, false, callback);
        }
    },
    
    /**
     * @private
     * Listener when an indicator is checked/unchecked
     * @param {Ext.menu.CheckItem} item the item
     * @param {Boolean} checked the checked status
     */
	selectDecorators: function (item, checked)
	{
		var hasChanges = false;
        if (checked && !Ext.Array.contains(this._activeDecorators, item.name))
        {
            this._activeDecorators.push(item.name);
            hasChanges = true;
        }
        else if (!checked && Ext.Array.contains(this._activeDecorators, item.name))
        {
            Ext.Array.remove(this._activeDecorators, item.name);
            hasChanges = true;
        }
        
        if (hasChanges)
        {
            this.saveState();
            this.view.refresh();
        }
	},
	
	/**
	 * Update the node data. This will updates the node UI (icon, decorators)
	 * @param id The node id
	 * @param data The data to update
	 */
	updateNodeData: function (id, data)
	{
		var node = this.getStore().getNodeById(id);
		if (node)
		{
			node.beginEdit();
			for (var i in data)
			{
				node.set(i, data[i]);
			}
			node.endEdit();
			node.commit();
            
            this.view.refreshNode(node);
		}
	},
	
	/**
	 * @private
	 * Listener on site selection
	 * @param {Ext.form.field.ComboBox} combo The sites' ComboBox 
	 * @param {Ext.data.Model} record the selected record
	 */
	_onSelectSite: function (combo, record)
	{
		var value = combo.getValue();
		
		if (this._siteName == value)
		{
			// No change
			return;
		}
		
		this._siteName = value;
		
        if (this.down("combobox[itemId='sitemaps-combo']"))
        {
            // Load available languages
            this.down("combobox[itemId='sitemaps-combo']").getStore().load();
        }
		
		// Reload tree with current sitemap
		this.refreshRootNode();
	},
	
	/**
	 * @private
	 * Listener on sitemap selection
	 * @param {Ext.form.field.ComboBox} combo The sitemaps' ComboBox 
	 * @param {Ext.data.Model} record the selected record
	 */
	_onSelectSitemap: function (combo, record)
	{
		var value = combo.getValue();
		
		if (this._sitemapName == value)
		{
			// No change
			return;
		}
		
		this._sitemapName = value;
		
		// Reload tree
		this.refreshRootNode();
	},
	
	/**
	 * Returns the current sitemap name
	 * @return the current sitemap name
	 */
	getCurrentSitemapName: function ()
	{
		return this._sitemapName || this.defaultSitemapName;
	},
	
	/**
	 * Returns the current site name
	 * @return the current site name
	 */
	getCurrentSiteName: function ()
	{
		return this._siteName || this.defaultSiteName;
	},
	
	/**
     * 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);
    },
	
    /**
     * @private
     * Listener before node selection.
     * Cancel the selection if the property 'isAllowed' is equals to false.
     * @param {Ext.selection.Model} sm the selectioon model
     * @param {Ext.data.Model} record The selected record
     * @param {Number} index The row index selected
     */
    _onBeforeSelect: function (sm, record, index)
    {
    	if (record.get('isAllowed') === false)
    	{
    		return false;
    	}
    },
    
	/**
	 * @private
	 * Listener before loading tree node
	 * @param {Ext.data.TreeStore} store The tree store
	 * @param {Ext.data.operation.Operation} operation The operation object that will be passed to the Proxy to load the Store
	 */
	_onBeforeLoad: function(store, operation)
    {
		var node = operation.node;
		if (node != null)
		{
			operation.setParams(Ext.apply(operation.getParams() || {}, {
				id: node.getId()
			}));
		}
    },
    
    /**
     * @private
     * Callback function called after loading available sites
     * @param {Function} [initCb] function to call after initialization process
     */
    _loadSitesCb: function (initCb)
    {
    	// Set default value
    	this.down("combobox[itemId='sites-combo']").setValue(this.defaultSiteName);
    	
    	// Then refresh sitemaps or page tree
    	if (this.down("combobox[itemId='sitemaps-combo']"))
    	{
    		this.down("combobox[itemId='sitemaps-combo']").getStore().load({callback: Ext.bind(this._loadSitemapsCb, this, [initCb])});
    	}
    	else
    	{
    		this.refreshRootNode(initCb);
    	}
    },
    
    /**
     * @private
     * Callback function called after loading available sitemaps
     * @param {Function} [initCb] function to call after initialization process
     */
    _loadSitemapsCb: function (initCb)
    {
    	var store = this.down("combobox[itemId='sitemaps-combo']").getStore();
    	
    	if (this.defaultSitemapName == null)
    	{
    		var userLocale = Ametys.getAppParameter('user').locale;
    		
    		if (store.find('name', userLocale) != -1)
    		{
    			this.defaultSitemapName = userLocale;
    		}
    		else if (store.find('name', 'en') != -1)
    		{
    			this.defaultSitemapName = 'en';
    		}
    		else
    		{
    			this.defaultSitemapName = store.getAt(0).get('name');
    		}
    	}
    	
    	// Set default value
    	this.down("combobox[itemId='sitemaps-combo']").setValue(this.defaultSitemapName);
    	this._sitemapName = this.defaultSitemapName;
    	
    	// Then refresh node
    	this.refreshRootNode(initCb);
    },
    
    /**
     * Initialize the tree
     * @param {Function} [initCb] function to call after initialization process
     */
    initialize: function (initCb)
    {
    	if (this.siteContext == Ametys.web.helper.ContextToolbar.SITE_CONTEXT_ALL)
    	{
    		// Load available sites
    		this.down("combobox[itemId='sites-combo']").getStore().load({callback: Ext.bind(this._loadSitesCb, this, [initCb])});
    	}
    	else if (this.sitemapContext == Ametys.web.helper.ContextToolbar.SITEMAP_CONTEXT_ALL)
    	{
    		// Load available sitemaps
    		this.down("combobox[itemId='sitemaps-combo']").getStore().load({callback: Ext.bind(this._loadSitemapsCb, this, [initCb])});
    	}
    	else
    	{
    		this.refreshRootNode(initCb);
    	}
    },
    
    /**
     * Set the language name and reload the sitemap tree if language was changed.<br/>
     * This should ONLY be used when languages and sites combobox are not displayed.
     * @param lang The language to set 
     * @param {Function} [callback] function to call after reloading the tree
     */
    setSitemapName: function (lang, callback)
    {
    	if (this._sitemapName != lang)
    	{
    		this._sitemapName = lang;
    	}
    	this.refreshRootNode (callback);
    },
    
    /**
     * Refresh the whole tree
     * @param {Function} [callback] function to call after refreshing
     */
    refreshRootNode: function (callback)
    {
		if (this._isRefreshingRootNode)
		{
			return;
		}

    	var siteName = this._siteName || this.defaultSiteName;
    	var lang = this._sitemapName || this.defaultSitemapName;
		
		this._isRefreshingRootNode = true;

    	Ametys.data.ServerComm.callMethod({
			role: "org.ametys.web.repository.page.SitemapDAO",
			methodName: "getSitemapProperties",
			parameters: [siteName, lang, this._rights],
			callback: {
				scope: this,
				handler: this._getRootNodeCb,
				arguments: {
					callback: callback
				}
			},
			errorMessage: {
				category: this.self.getName(),
				msg: "{{i18n PLUGINS_WEB_UITOOL_SITEMAP_ERROR}}"
			},
			waitMessage: true // FIXME target: this ?
		});
    },
    
    /**
     * @private
     * Callback function after retrieving root node
     * @param {Object} response The server response
     * @param {Object} args the callback arguments
     */
    _getRootNodeCb: function (response, args)
    {
		this._isRefreshingRootNode = false;
		
    	this.setRootNode({
    		id: response.id,
    		name: response.lang,
    		expanded: true,
			text: "{{i18n PLUGINS_WEB_SITEMAP_TREE_ROOT_LABEL}}",
			title: "{{i18n PLUGINS_WEB_SITEMAP_TREE_ROOT_LABEL}}",
			iconCls: "a-tree-glyph ametysicon-world91", 
			path: '',
			isAllowed: response.isAllowed,
			editable : false,
            allowDrag : false,
            allowDrop : true
    	});
        
        if (Ext.isFunction (args.callback))
        {
            this.store.getRoot().on('expand', function() { args.callback (response.id); }, this, { single: true });
        }
    }
});