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

/**
 * <p>This tool displays a tree if log categories, and allows the administrator to perform several operations such as :</p>
 * <ul>
 * 	<li> change the log levels </li>
 * 	<li> force log level inheritance </li>
 * 	<li> inherit log level </li>
 * </ul>
 * @private
 */
Ext.define('Ametys.plugins.admin.logs.LogsLevelTool', {
	extend: 'Ametys.tool.Tool',
	
    /**
     * @cfg {String[]} [expandCategories] A list of categories and select to expand on load
     */
    
	/**
	 * @private
	 * @property {Ext.tree.Panel} _logsTree The logs tree panel
	 */
	
	/**
	 * @private
	 * @property {String[]} _expandCategories paths to expand on load
	 */
	
	constructor: function(config)
	{
		this.callParent(arguments);
		Ametys.message.MessageBus.on(Ametys.message.Message.MODIFIED, this._onModified, this);
	},
		
	getMBSelectionInteraction: function() 
	{
		return Ametys.tool.Tool.MB_TYPE_ACTIVE;
	},
	
	createPanel: function ()
	{
		this._logsTree = this._drawLogsLevelPanel();
		return this._logsTree;
	},
	
	sendCurrentSelection: function()
	{
		var targets = [];
		
		var selectedCategories = this._logsTree.getSelectionModel().getSelection();
		Ext.Array.forEach(selectedCategories, function(selectedCategory) {
			
			target = Ext.create('Ametys.message.MessageTarget', {
				id: Ametys.message.MessageTarget.LOG_CATEGORY,
				parameters: {id: selectedCategory.data.category, level: selectedCategory.data.level}
			});
			
			targets.push(target);
		});
		
		Ext.create('Ametys.message.Message', {
			type: Ametys.message.Message.SELECTION_CHANGED,
			targets: targets
		});
	},

	setParams: function(params)
	{
		this._expandCategories = params.expandCategories;

		this.showOutOfDate();
	},
	
	/**
	 * Refreshes the tool
	 */
	refresh: function ()
	{
		this.showRefreshing();
		this._logsTree.getStore().load({node: this._logsTree.getRootNode()});
	},

    /**
     * Get the tree of the tool
     * @return {Ext.tree.Panel} The main tree component of the tool
     */
	getTree: function()
	{
		return this._logsTree;
	},
	
	/**
	 * @private
	 * Draw the tree panel for the log levels
	 */
	_drawLogsLevelPanel: function()
	{
		var store = Ext.create('Ext.data.TreeStore', {
			model: 'Ametys.plugins.admin.logs.LogsLevelTool.Category',
			
			root: {
				expanded: false
			},		
            
	        proxy: {
	        	type: 'ametys',
				plugin: 'admin',
				url: 'logs-level',
	        	reader: {
	        		type: 'json',
					rootProperty: 'children'
	        	}
	        },
	        
	        listeners: {
	        	'load': Ext.bind(this._onLoad, this)
	        },
	        
	        sorters: [ { property: 'text', direction: "ASC" }]
	        
		});
		
		return new Ext.tree.Panel({
			store: store,
			rootVisible: false,
			
            dockedItems: [
                  this._getFilterToolbarConfig(),
                  {
                      dock: 'top',
                      xtype: 'button',
                      ui: 'tool-hintmessage',
                      hidden: true,
                      itemId: 'no-result',
                      text: "{{i18n PLUGINS_ADMIN_PLUGINS_FILTER_NO_MATCH}}" + "{{i18n PLUGINS_ADMIN_PLUGINS_FILTER_NO_MATCH_ACTION}}",
                      scope: this,
                      handler: this._clearSearchFilter
                  },
                  {
                      xtype: 'component',
                      cls: 'hint',
                      hidden: true,
                      dock: 'top',
                      html: "{{i18n PLUGINS_ADMIN_PLUGINS_CHANGES_PENDING}}"
                  }
            ],            
            
			border: false,
			scrollable: true,
			
			listeners: {
				'selectionchange': Ext.bind(this.sendCurrentSelection, this)
			}
		});
	},
	
        /**
     * @private
     * Get the filter toolbar config
     * @return {Object} The filter toolbar config
     */
    _getFilterToolbarConfig: function()
    {
        return {
            dock: 'top',
            xtype: 'toolbar',
            layout: { 
                type: 'hbox',
                align: 'stretch'
            },
            border: false,
            defaultType: 'button',
            items: [{
                        // Filter input
                        xtype: 'textfield',
                        cls: 'ametys',
                        flex: 1,
                        maxWidth: 400,
                        itemId: 'plugins-filter-input',
                        emptyText: "{{i18n PLUGINS_ADMIN_PLUGINS_FILTER}}",
                        minLength: 3,
                        minLengthText: "{{i18n PLUGINS_ADMIN_PLUGINS_FILTER_INVALID}}",
                        msgTarget: 'qtip',
                        listeners: {change: Ext.Function.createBuffered(this._filter, 500, this)},
                        style: {
                            marginRight: '0px'
                        }
                    }, 
                    {
                        // Clear filter
                        tooltip: "{{i18n PLUGINS_ADMIN_PLUGINS_CLEAR_FILTER}}",
                        handler: Ext.bind (this._clearSearchFilter, this),
                        icon: Ametys.getPluginResourcesPrefix('admin') + '/img/plugins/clear.gif',
                        cls: 'a-btn-light'
                    }, 
                    {
                        xtype: 'tbspacer',
                        flex: 0.0001
                    },
                    {
                        // Expand all
                        tooltip: "{{i18n PLUGINS_ADMIN_PLUGINS_EXPAND_ALL}}",
                        handler: Ext.bind (this._expandAll, this, [], false),
                        icon: Ametys.getPluginResourcesPrefix('admin') + '/img/plugins/expand-all.gif',
                        cls: 'a-btn-light'
                    },
                    {
                        // Collapse all
                        tooltip: "{{i18n PLUGINS_ADMIN_PLUGINS_COLLAPSE_ALL}}",
                        handler: Ext.bind (this._collapseAll, this, [], false),
                        icon: Ametys.getPluginResourcesPrefix('admin') + '/img/plugins/collapse-all.gif',
                        cls: 'a-btn-light'
                    }
            ]
        };
    },
    
    /**
     * @private
     * Handler for the expand amm button
     */
    _expandAll: function ()
    {
        this._logsTree.expandAll(); 
    },
    
    /**
     * @private
     * Handler for the collapse all button
     */
    _collapseAll: function ()
    {
        this._logsTree.collapseAll();   
    },
    
    /**
     * @private
     * Filters the tree nodes by entered text.
     * @param {Ext.form.field.Text} field This text field
     */
    _filter: function(field)
    {
        this._filterField = field;
        this._logsTree.getStore().clearFilter();
        var val = Ext.String.escapeRegex(field.getRawValue());
        
        if (val.length > 2)
        {
            this._regexFilter = new RegExp(val, 'i');
            
            this._logsTree.getStore().filter({
                filterFn: Ext.bind(this._filterByTextAndChildren, this)
            });
        }
        else
        {
            this._regexFilter = null;
        }
        
        this._logsTree.getDockedItems()[1].setVisible(!this._logsTree.getStore().getCount());
    },
    
    /**
     * @private
     * Filter function that check if a node in the tree should be visible or not.
     * @param {Ext.data.Model} record The record to check.
     * @return {boolean} True if the record should be visible.
     */
    _filterByTextAndChildren: function (record)
    {
        var isVisible = this._regexFilter == null || this._regexFilter.test(record.data.text);
        if (!isVisible)
        {
            // if the record does not match, we check if any child is visible. If at least one is, this record should not be hidden.
            // This is efficient because the data is in the store, and is not loaded in the view.
            for (var i = 0; !isVisible && i < record.childNodes.length; i++) {
                isVisible = this._filterByTextAndChildren(record.childNodes[i]);
            }
        }
        
        if (isVisible)
        {
            this._logsTree.expandNode(record);
        }
        
        return isVisible; 
    },    
    
    /**
     * @private
     * Handler for the clear search
     */
    _clearSearchFilter: function ()
    {
        if (this._filterField)
        {
            this._filterField.reset();
        }
        
        this._regexFilter = null;
        this._logsTree.getStore().clearFilter();
        this._logsTree.getDockedItems()[1].setVisible(false);
        
        var selection = this._logsTree.getSelectionModel().getSelection()[0];
        if (selection)
        {
            this._logsTree.ensureVisible(selection.getPath());
        }
        
    },
    
    /**
     * @private
     * Listener when store is loaded
     */
	_onLoad: function()
    {
		var rootNode = this._logsTree.getRootNode();
		
		// Set the parent level field on each node of the tree
        if (rootNode.firstChild)
        {
		  this._setParentLevels(rootNode.firstChild.data.level, rootNode.firstChild.childNodes);
        }

        // Expand first nodes
        this._logsTree.getRootNode().expandChildren(false, this._onRootNodesChangedAndExpanded, this);

		this.showRefreshed();
    },
    
    /**
     * @private
     * Set the parent levels on the tree nodes
     * @param {String} parentLevel the level of the parent node
     * @param {Ext.data.NodeInterface[]} children the list of child nodes
     */
    _setParentLevels: function(parentLevel, children)
    {
    	var me = this;
    	Ext.Array.forEach(children, function(child) {
    		child.set('parentLevel', parentLevel);
    		
    		if (child.childNodes)
			{
    			var subParentLevel = child.data.level == "inherit" ? parentLevel : child.data.level;
    			me._setParentLevels(subParentLevel, child.childNodes);
			}
    	});
    },
    
    /**
     * @private
     * When the newly loaded root nodes are expanded
     */
    _onRootNodesChangedAndExpanded: function()
    {
		if (this._expandCategories)
		{
			var rootNode = this._logsTree.getRootNode().firstChild;
            
			this._expandCategories.forEach(function (path) {
                var node = this._logsTree.getStore().findNode('category', path);
                if (node == null)
                {
                    this.getLogger().warn("The server log category '" + path + "' was not existing at the time the tool was opened");
                }
                else
                {
                    if (path == this._expandCategories[0])
                    {
                        this._logsTree.getSelectionModel().select(node);
                    }
                    while (node = node.parentNode)
                    {
                        node.expand();
                    }
                }
            }, this);
            
            this._logsTree.ensureVisible(this._expandCategories[0], {field: 'text', separator: '.'});
		}
		else
		{
	        // Select first node
    	    this._logsTree.getSelectionModel().select(this._logsTree.getRootNode().firstChild);
		}
	},

    /**
     * @private
     * Listener on the message bus of type modified
     * @param {Ametys.message.Message} message The message
     */
	_onModified: function(message)
	{
		var targets = message.getTargets("log-category");
		if (targets.length > 0)
		{
			var store = this._logsTree.getStore();
			var me = this;
			
			Ext.Array.forEach(targets, function(target) {
				var name = target.getParameters().id;
				var index = store.find("category", name); 
				if (index != -1)
				{
					var level = target.getParameters().level,
					    modifiedCategory = store.getAt(index); 
					
					if (message.getParameters().major)
					{
						var level = modifiedCategory.getResolvedLevel();
                        for (var i = 0; i < modifiedCategory.childNodes.length; i++)
                        {
                            var childNode = modifiedCategory.childNodes[i];
    						me._updateNode(childNode, level, true, true);
                        }
					}
					else if (level == 'INHERIT')
					{
						var level = modifiedCategory.parentNode.getResolvedLevel(); 
						me._updateNode(modifiedCategory, level, true, false);
					}
					else
					{
						me._updateNode(modifiedCategory, level, false, false);
					}
				}
			});
			
		}
	},
	
    /**
     * @private
     * Update node info
     * @param {Ametys.plugins.admin.logs.LogsLevelTool.Category} node The node to update
     * @param {String} level The level to set
     * @param {Boolean} inherited Is the level inherited
     * @param {Boolean} force True to force recursiverly the level inheritance
     */
	_updateNode: function (node, level, inherited, force)
	{
        var inherited = inherited == true;

		node.set('level', inherited ? "inherit" : level);
		
		// Find the first parent containing the appropriate level information
		var parentNode = node.parentNode;
		while (parentNode.data.level.toLowerCase() == "inherit")
		{
			parentNode = parentNode.parentNode;
		}
		node.set('parentLevel', parentNode.data.level);
		
		for (var i = 0; i < node.childNodes.length; i++)
		{
			var childNode = node.childNodes[i];
			if (childNode.get('level').toLowerCase() == "inherit" || force)
			{
				this._updateNode(childNode, level, true, force);
			}
		}
	}
});