/*
 *  Copyright 2019 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 tool displays a list of caches
 */
Ext.define('Ametys.plugins.admin.cache.CachesTool', {
	extend: 'Ametys.tool.Tool',

	/**
	 * @private
	 * @property {Ext.grid.Panel} _cachesGrid The caches grid
	 */
	constructor: function (config) 
	{
		this.callParent(arguments);
		Ametys.message.MessageBus.on(Ametys.message.Message.MODIFIED, this._onMessageModified, this);
	},

	getMBSelectionInteraction: function () 
	{
		return Ametys.tool.Tool.MB_TYPE_ACTIVE;
	},

	createPanel: function () 
	{
		this._cachesGrid = this._drawCachesPanel();
		return this._cachesGrid;
	},

	setParams: function (params)
	{
		this.callParent(arguments);
		this.refresh();
	},

	/**
	 * Refreshes the tool
	 */
	refresh: function ()
	{
		this.showRefreshing();
		this._cachesGrid.getStore().load({ callback: this.showRefreshed, scope: this });
	},

	/**
	 * @private
	 * Draw the panel displaying the caches
	 */
	_drawCachesPanel: function ()
	{
		var store = Ext.create('Ext.data.Store', {
			model: 'Ametys.plugins.admin.tool.CachesTool.Cache',
			proxy: {
				type: 'ametys',
				role: 'org.ametys.core.cache.CacheManager',
				methodName: 'getCachesAsJSONMap',
				methodArguments: [],
				reader: {
					type: 'json'
				}
			},
            groupField: 'type',
            sorters: ['label']
		});
		return Ext.create('Ext.grid.Panel', {
			region: 'center',
			stateful: true,
			stateId: this.self.getName() + "$grid",
			store: store,

            // Filter box
            dockedItems: [{
                dock: 'top',
                xtype: 'toolbar',
                layout: {
                    type: 'hbox',
                    align: 'stretch'
                },
                
                border: false,
                defaults : {
                    cls: 'ametys',
                    labelWidth: 55,
                    labelSeparator: ''
                },
                
                items: [{
                    // Search input
                    xtype: 'textfield',
                    itemId: 'search-filter-input',
                    cls: 'ametys',
                    flex: 1,
                    maxWidth: 300,
                    emptyText: "{{i18n PLUGINS_ADMIN_TOOL_CLEAR_CACHE_FILTER_EMPTY_TEXT}}",
                    enableKeyEvents: true,
                    msgTarget: 'qtip',
                    listeners: {change: Ext.Function.createBuffered(this._filterByTitle, 300, this)}
                }, {
                    // Clear filter
                    tooltip: "{{i18n PLUGINS_ADMIN_TOOL_CLEAR_CACHE_FILTER_CLEAR}}",
                    handler: Ext.bind (this._clearFilter, this),
                    iconCls: 'a-btn-glyph ametysicon-eraser11 size-16',
                    cls: 'a-btn-light'
                }]
            }],
            
            // Grouping by cache type
            features: [
                {
                    ftype: 'grouping',
                    enableGroupingMenu: false,
                    groupHeaderTpl: [
                        '{name:this.description}',
                        {
                            description: function(name) {
                                switch (name) {
                                    case "REQUEST":
                                        return "{{i18n PLUGINS_ADMIN_TOOL_REQUEST_CACHE}}";
                                    case "MEMORY":
                                    default:
                                        return "{{i18n PLUGINS_ADMIN_TOOL_MEMORY_CACHE}}";
                                }
                            }
                        }
                    ]
                } 
            ],
            
			selModel: {
				mode: 'MULTI'
			},

			columns: [
				{ stateId: 'grid-id', header: "{{i18n PLUGINS_ADMIN_TOOL_CACHE_COL_ID}}", hidden: true, sortable: true, flex: 1, dataIndex: 'id' },
				{ stateId: 'grid-label', header: "{{i18n PLUGINS_ADMIN_TOOL_CACHE_COL_NAME}}", sortable: true, flex: 1, dataIndex: 'label' },
				{ stateId: 'grid-description', header: "{{i18n PLUGINS_ADMIN_TOOL_CACHE_COL_DESCRIPTION}}", sortable: true, flex: 3, dataIndex: 'description', renderer: this._renderWithTooltip },
				{ stateId: 'grid-access', header: "{{i18n PLUGINS_ADMIN_TOOL_CACHE_COL_ACCESS}}", sortable: true, width: 140, dataIndex: 'access' },
				{ stateId: 'grid-hit', header: "{{i18n PLUGINS_ADMIN_TOOL_CACHE_COL_HIT}}", hidden: true, sortable: true, width: 100, dataIndex: 'hit' },
				{ stateId: 'grid-hit-rate', header: "{{i18n PLUGINS_ADMIN_TOOL_CACHE_COL_HIT_RATE}}", sortable: true, width: 100, dataIndex: 'hitRate', renderer: this._renderPercent },
				{ stateId: 'grid-miss', header: "{{i18n PLUGINS_ADMIN_TOOL_CACHE_COL_MISS}}", hidden: true, sortable: true, width: 100, dataIndex: 'miss' },
				{ stateId: 'grid-miss-rate', header: "{{i18n PLUGINS_ADMIN_TOOL_CACHE_COL_MISS_RATE}}", sortable: true, width: 100, dataIndex: 'missRate', renderer: this._renderPercent },
				{ stateId: 'grid-nb-elements', header: "{{i18n PLUGINS_ADMIN_TOOL_CACHE_COL_NB_ELEMENTS}}", sortable: true, width: 140, dataIndex: 'nbElement', renderer: this._renderIfMemory },
				{ stateId: 'grid-nb-evictions', header: "{{i18n PLUGINS_ADMIN_TOOL_CACHE_COL_NB_EVICTIONS}}", hidden: true, sortable: true, width: 140, dataIndex: 'nbEviction' },
				{ stateId: 'grid-current-size', header: "{{i18n PLUGINS_ADMIN_TOOL_CACHE_COL_CURRENT_SIZE}}", sortable: true, width: 140, dataIndex: 'complexCurrentSize', renderer: this._renderComplexSize },
                { stateId: 'grid-max-size', header: "{{i18n PLUGINS_ADMIN_TOOL_CACHE_COL_MAX_SIZE}}", hidden: true, sortable: true, width: 140, dataIndex: 'complexMaxSize', renderer: this._renderComplexSize }
			],
			listeners: {
				'selectionchange': Ext.bind(this.sendCurrentSelection, this)
			}
		})
	},

    /**
     * @private
     * Filters queries by input field value.
     * @param {Ext.form.Field} field The field
     */
    _filterByTitle: function (field)
    {
        var value = Ext.String.trim(field.getValue());
        
        var regexFilter = new RegExp(value, 'i');
        
        var currentSelection = this.getContentPanel().getSelection();
       
        this.getContentPanel().getStore().clearFilter();
        this.getContentPanel().getStore().filterBy(function(record){
            return regexFilter.test(record.data.label) || regexFilter.test(record.data.description);
        });
        
        if (currentSelection.length > 0)
        {
            var me = this;
            Ext.Array.each(currentSelection, function (sel) {
                if (me.getContentPanel().getStore().findExact(me.getContentPanel().getStore().getModel().idProperty, sel.getId()) == -1)
                {
                    // The current selection is not visible, clear the selection
                    me.getContentPanel().getSelectionModel().deselect([sel]);
                }
            })
            
        }
    },
    
    /**
     * Clear the current filter
     */
    _clearFilter: function()
    {
        this.getContentPanel().down("#search-filter-input").reset();
        
        this.getContentPanel().getStore().clearFilter();
    },
    
	/**
	 * Renders a value, editing the metadata to display the value as tooltip.
	 * @param {Object} value The data value for the current cell.
	 * @param {Object} metadata A collection of metadata about the current cell.
	 * @private
	 */
	_renderWithTooltip: function (value, metadata) 
	{
		metadata.tdAttr = 'data-qtip="' + value + '"';
		return value;
	},

    /**
     * Renders a percentage value.
     * If there is no hit/miss, display " - " instead, as a percentage wouldn't make sense
     * @param {Object} value The data value for the current cell.
     * @param {Object} metadata A collection of metadata about the current cell.
     * @param {Object} record The record for the current row.
     * @private
     */
    _renderIfMemory: function (value, metadata, record)
    {
        if (record.get('type') == "MEMORY")
        {
            return value;
        }
        else
        {
            return "{{i18n PLUGINS_ADMIN_TOOL_CACHE_SIZE_NOT_AVAILABLE}}";
        }
    },
    
    /**
     * Renders a percentage value.
     * If there is no hit/miss, display " - " instead, as a percentage wouldn't make sense
     * @param {Object} value The data value for the current cell.
     * @param {Object} metadata A collection of metadata about the current cell.
     * @param {Object} record The record for the current row.
     * @private
     */
    _renderPercent: function (value, metadata, record)
    {
        return (record.get('hit') != 0 || record.get('miss') != 0) ? Ext.util.Format.number(value * 100, '0.## %') : " - ";
    },
	
	/**
	 * Renders a value in bytes, sensible to i18n. 
	 * The size may not be computable, in this case, a specific text is displayed
	 * If size is equal to Java Long.MaxValue (2^63-1), return a text to tell infinite size is reached
	 * @param {Object} complexSize The data value for the current cell.
	 * @param {Boolean} complexSize.computableSize true if the size of the cache can be computed
	 * @param {Boolean} complexSize.size the size of the cache in bytes
	 * @private
	 */
	_renderComplexSize: function (complexSize)
	{
		if (complexSize.computableSize)
		{
			if (complexSize.size == 9223372036854775807)
			{
				return " - ";
			}
			else
			{
				return Ext.util.Format.fileSize(complexSize.size);
			}
		}
		else
		{
			return "{{i18n PLUGINS_ADMIN_TOOL_CACHE_SIZE_NOT_AVAILABLE}}";
		}

	},

    /**
     * Fires a event of selection on message bus, from the selected contents in the grid.
     * @protected
     */
	sendCurrentSelection: function ()
	{
		var selection = this._cachesGrid.getSelectionModel().getSelection();
		var targets = [];
		for (var i = 0; i < selection.length; i++)
		{
			targets.push({
				id: Ametys.message.MessageTarget.CACHE,
				parameters: {
                    id: selection[i].id,
                    type: selection[i].data.type
				}
			});
		}

		Ext.create('Ametys.message.Message', {
			type: Ametys.message.Message.SELECTION_CHANGED,
			targets: targets
		});
	},

	/**
     * Listener on creation or edition message.
     * @param {Ametys.message.Message} message The edition message.
     * @private
     */
	_onMessageModified: function (message)
	{
		var targets = message.getTargets(Ametys.message.MessageTarget.CACHE);
		if (targets.length > 0)
		{
			this.showOutOfDate();
		}
	},

});

Ext.define('Ametys.plugins.admin.tool.CachesTool.Cache', {
	extend: 'Ext.data.Model',
	fields: [
		{ name: 'id' },
		{ name: 'label' },
		{ name: 'description' },
		{ name: 'computableSize' },
		{ name: 'access' },
		{ name: 'hit' },
		{ name: 'hitRate' },
		{ name: 'miss' },
		{ name: 'missRate' },
		{ name: 'nbElement' },
        { name: 'nbEviction' },
        { name: 'type' },
		{ name: 'currentSize' },
		{ name: 'maxSize' },
		{
			name: 'complexCurrentSize',
			calculate: function (data)
			{
				return {
					computableSize: data.computableSize,
					size: data.currentSize
				};
			},
			sortType: function (value)
			{
				return value.computableSize ? value.size : -1;
			}
		},
		{
			name: 'complexMaxSize',
			calculate: function (data)
			{
				return  {
					computableSize: data.computableSize,
					size: data.maxSize
				};
			},
			sortType: function (value)
			{
				return value.computableSize ? value.size : -1;
			}
		},
	]
});

Ext.define("Ametys.message.CacheMessageTarget", {
	override: "Ametys.message.MessageTarget",
	statics:
	{
		/**
		 * @member Ametys.message.MessageTarget
		 * @readonly
		 * @property {String} CACHE The target of the message is the cache  The expected parameters are: 
         * @property {String} CACHE.id The id of the cache
         * @property {String} CACHE.type The type of the cache
		 */
		CACHE: "cache"
	}
});