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

/**
 * Tool which displays a grid with the assignments for users and groups on the profiles of the application, depending on a context object.
 * @private
 */
Ext.define('Ametys.plugins.coreui.profiles.ProfileAssignmentsTool', {
    extend: "Ametys.tool.Tool",
    
    statics: {
        /**
         * @readonly
         * @property {String} ACCESS_TYPE_ALLOW Type of access for an allowed access
         */
        ACCESS_TYPE_ALLOW: 'allow',
        
        /**
         * @readonly
         * @property {String} ACCESS_TYPE_DENY Type of access for an denied access
         */
        ACCESS_TYPE_DENY: 'deny',
        
        /**
         * @readonly
         * @property {String} ACCESS_TYPE_INHERITED_ALLOW Type of access for an allowed access by inheritance
         */
        ACCESS_TYPE_INHERITED_ALLOW: 'inherited_allow',
        
        /**
         * @readonly
         * @property {String} ACCESS_TYPE_DENIED Type of access for an denied access by inheritance
         */
        ACCESS_TYPE_INHERITED_DENY: 'inherited_deny',
        
        /**
         * @readonly
         * @property {String} ACCESS_TYPE_ALLOW_BY_GROUP Type of access for an allowed access through groups (client-side only)
         */
        ACCESS_TYPE_ALLOW_BY_GROUP: 'allow_by_group',
        
        /**
         * @readonly
         * @property {String} ACCESS_TYPE_DENY_BY_GROUP Type of access for a denied access through groups (client-side only)
         */
        ACCESS_TYPE_DENY_BY_GROUP: 'deny_by_group',
        
        /**
         * @readonly
         * @property {String} ACCESS_TYPE_ALLOW_BY_ANONYMOUS Type of access for an allowed access through anonymous (client-side only)
         */
        ACCESS_TYPE_ALLOW_BY_ANONYMOUS: 'allow_by_anonymous',
        
        /**
         * @readonly
         * @property {String} ACCESS_TYPE_ALLOW_BY_ANYCONNECTED Type of access for an allowed access through any connected user (client-side only)
         */
        ACCESS_TYPE_ALLOW_BY_ANYCONNECTED: 'allow_by_anyconnected',
        
        /**
         * @readonly
         * @property {String} ACCESS_TYPE_DENY_BY_ANYCONNECTED Type of access for a denied access through any connected user (client-side only)
         */
        ACCESS_TYPE_DENY_BY_ANYCONNECTED: 'deny_by_anyconnected',
        
        /**
         * @readonly
         * @property {String} ACCESS_TYPE_UNKNOWN Type of access for a undetermined access
         */
        ACCESS_TYPE_UNKNOWN: 'unknown',
        
        /**
         * Function called when an assignment is clicked in order to change its value
         * @param {String} recordId The id of the record
         * @param {String} profileId The profile id (id of the column)
         * @param {String} toolId The id of the tool
         */
        onCellClick: function(recordId, profileId, toolId)
        {
            var tool = Ametys.tool.ToolsManager.getTool(toolId);
            if (tool != null)
            {
                tool.onCellClick(recordId, profileId);
            }
        },
        
        /**
         * Compute the tooltip for a given record and access type
         * @param {Ext.data.Model} record The record
         * @param {String} accessType The access type
         * @return The tooltip text
         */
        computeTooltip: function (record, accessType)
        {
        	var type = record.get('targetType');
        	
        	if (type == Ametys.plugins.coreui.profiles.PermissionTargetStore.TARGET_TYPE_ANONYMOUS)
        	{
        		return this._computeTooltipForAnonymous(record, accessType);
        	}
        	else if (type == Ametys.plugins.coreui.profiles.PermissionTargetStore.TARGET_TYPE_ANYCONNECTEDUSER)
        	{
        		return this._computeTooltipForAnyconnectedUser(record, accessType);
        	}
        	else if (type == Ametys.plugins.coreui.profiles.PermissionTargetStore.TARGET_TYPE_USER)
        	{
        		return this._computeTooltipForUser(record, accessType);
        	}
        	else if (type == Ametys.plugins.coreui.profiles.PermissionTargetStore.TARGET_TYPE_GROUP)
        	{
        		return this._computeTooltipForGroup(record, accessType);
        	}
        },
        
        /**
         * @private
         * Compute the tooltip for the Anonymous record and access type
         * @param {Ext.data.Model} record The Anonymous record
         * @param {String} accessType The access type
         * @return The tooltip text
         */
        _computeTooltipForAnonymous: function (record, accessType)
        {
        	switch (accessType) 
            {
                case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW:
                	// Anonymous is locally allowed 
                	return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_ANONYMOUS_LOCAL_ALLOWED}}";
                case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_INHERITED_ALLOW:
                	// Anonymous is allowed by inheritance
                	return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_ANONYMOUS_INHERIT_ALLOWED}}";
                case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_DENY:
                	// Anonymous is locally denied 
                	return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_ANONYMOUS_LOCAL_DENIED}}";
                case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_INHERITED_DENY:
                	// Anonymous is denied by inheritance
                	return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_ANONYMOUS_INHERIT_DENIED}}";
                case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_UNKNOWN:
                default:
                	return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_UNKNOWN}}";
            }
        },
        
        /**
         * @private
         * Compute the tooltip for the anyconnected user record and access type
         * @param {Ext.data.Model} record The anyconnected user record
         * @param {String} accessType The access type
         * @return The tooltip text
         */
        _computeTooltipForAnyconnectedUser: function (record, accessType)
        {
        	switch (accessType) 
            {
            	case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW_BY_ANONYMOUS:
            		// Any connected users are allowed because Anonymous is allowed
            		return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_ANYCONNECTED_DISABLED}}";
            	case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW:
                	// Any connected users are locally allowed 
                	return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_ANYCONNECTED_LOCAL_ALLOWED}}";
            	case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_INHERITED_ALLOW:
                	// Any connected users are allowed by inheritance
                	return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_ANYCONNECTED_INHERIT_ALLOWED}}";
            	case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_DENY:
                	// Any connected users are locally denied 
                	return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_ANYCONNECTED_LOCAL_DENIED}}";
            	case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_INHERITED_DENY:
                	// Any connected users are denied by inheritance
                	return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_ANYCONNECTED_INHERIT_DENIED}}";
            	case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_UNKNOWN:
                default:
                	return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_UNKNOWN}}";
            }
        },
        
        /**
         * @private
         * Compute the tooltip for a user record and access type
         * @param {Ext.data.Model} record The user record
         * @param {String} accessType The access type
         * @return The tooltip text
         */
        _computeTooltipForUser: function (record, accessType)
        {
        	switch (accessType) 
            {
            	case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW_BY_ANONYMOUS:
        			// The user is allowed because Anonymous is allowed
                    return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_USER_ALLOWED_BY_ANONYMOUS}}";
                    
            	case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW:
                	// The user is locally allowed 
                	return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_USER_LOCAL_ALLOWED}}";
                	
                case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW_BY_GROUP:
                	// The user is allowed because he belongs to a allowed group locally
                    return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_USER_ALLOWED_BY_GROUP}}";
                    
                case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW_BY_ANYCONNECTED:
                	// The user is allowed because any connected users are allowed
                    return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_USER_ALLOWED_BY_ANYCONNECTED}}";
                    
                case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_INHERITED_ALLOW:
                	// The user is allowed by inheritance
                	return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_USER_INHERIT_ALLOWED}}";
                	
                case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_DENY:
                	// The user is denied 
                	return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_USER_LOCAL_DENIED}}";
                	
                case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_DENY_BY_GROUP:
                	// The user is denied because he belongs to a denied group locally
                    return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_USER_DENIED_BY_GROUP}}";
                    
                case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_DENY_BY_ANYCONNECTED:
                	// The user is denied because any connected users are denied
                	return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_USER_DENIED_BY_ANYCONNECTED}}";
                	
                case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_INHERITED_DENY:
                	// The user is denied by inheritance
                	return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_USER_INHERIT_DENIED}}";
                	
                case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_UNKNOWN:
                default:
                	return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_UNKNOWN}}";
            }
        },
        
        /**
         * @private
         * Compute the tooltip for a group record and access type
         * @param {Ext.data.Model} record The group record
         * @param {String} accessType The access type
         * @return The tooltip text
         */
        _computeTooltipForGroup: function (record, accessType)
        {
        	switch (accessType) 
            {
	            case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW_BY_ANONYMOUS:
	    			// The group is allowed because Anonymous is allowed
	                return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_GROUP_ALLOWED_BY_ANONYMOUS}}";
	                
	        	case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW:
	            	// The group is locally allowed 
	            	return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_GROUP_LOCAL_ALLOWED}}";
	            	
	            case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW_BY_ANYCONNECTED:
	            	// The group is allowed because any connected users are allowed
	                return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_GROUP_ALLOWED_BY_ANYCONNECTED}}";
	                
	            case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_INHERITED_ALLOW:
	            	// The group is allowed by inheritance
	            	return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_GROUP_INHERIT_ALLOWED}}";
	            	
	            case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_DENY:
	            	// The group is denied 
	            	return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_GROUP_LOCAL_DENIED}}";
	            	
	            case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_DENY_BY_ANYCONNECTED:
	            	// The group is denied because any connected users are denied
	            	return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_GROUP_DENIED_BY_ANYCONNECTED}}";
	            	
	            case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_INHERITED_DENY:
	            	// The group is denied by inheritance
	            	return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_GROUP_INHERIT_DENIED}}";
	            	
	            case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_UNKNOWN:
	            default:
	            	return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_UNKNOWN}}";
            }
        }
    },
    
    /**
     * @private
     * @property {Ext.form.field.ComboBox} _contextCombobox The combobox displaying the right assignment contexts
     */
    
    /**
     * @private
     * @property {Ext.panel.Panel} _contextPanel The panel on the left of the tool, with a card layout, showing the context panel corresponding to the currently selected right assignment context in the combobox
     */
    
    /**
     * @private
     * @property {Object} _contextComponents An object containing the context {@link Ext.Component}s (the key is the right assignment context id)
     */
    
    /**
     * @private
     * @property {Ext.grid.Panel} _assignmentsGrid The grid panel on the right of the tool, showing the assignment on current object context.
     */
    
    /**
     * @private
     * @property {Ext.data.Store} _gridStore The store of the grid
     */
    
    /**
     * @private
     * @property {Object|Object[]} _objectContext The current object context(s). Must be up-to-date before loading the grid store. If multiple (more than 1), the grid store is not displayed.
     */
    
    /**
     * @private
     * @property {Object[]} _profiles The profiles of the application.
     * @property {String} _profiles.id The id of the profile
     * @property {String} _profiles.label The label of the profile
     * @property {String[]} _profiles.rights The ids of the rights this profile contains
     */
    _profiles: [],
    
    /**
     * @private
     * @property {Object[]} The unsaved assignments induced by the removal of records 
     */
    _removedAssignments: [],
    
    /**
     * @private
     * @property {Object} _storedValues The stored assignments by profile (server-side) 
     * The key is the profile id, the value is an object (where its key is the record id, the value is the server-side value (with no induced local changes)
     */
    
    /**
     * @cfg {String} [rightContextHintPrefix="PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_HINT1"] The prefix text to use for right context hint
     */
    /**
     * @private 
     * @property {String} _rightContextHintPrefix. See #cfg-rightContextHintPrefix
     */
    
    /**
     * @cfg {String} [profilesPluginName="core"] The name of the plugin used to get the list of profiles.
     */
    /**
     * @private
     * @property {String} _profilesPluginName See #cfg-profilesPluginName
     */
    
    /**
     * @cfg {String} [profilesUrl="rights/profiles.json"] The plugin url used to get the the list of profiles.
     */
    /**
     * @private
     * @property {String} See #cfg-profilesUrl
     */
    
    constructor: function (config)
    {
    	this._profilesPluginName = config.profilesPluginName || 'core';
        this._profilesUrl = config.profilesUrl || 'rights/profiles.json';
        
        this._rightContextHintPrefix = config.rightContextHintPrefix || "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_HINT1}}";
        this._initializing = true;
        
        Ametys.message.MessageBus.on(Ametys.message.Message.CREATED, this._onMessage, this);
        Ametys.message.MessageBus.on(Ametys.message.Message.MODIFIED, this._onMessage, this);
        Ametys.message.MessageBus.on(Ametys.message.Message.DELETED, this._onMessage, this);
        
        this.callParent(arguments);
    },
    
    createPanel: function()
    {
        this._contextCombobox = Ext.create('Ext.form.field.ComboBox', this._getContextComboCfg());
        
        this._contextPanel = Ext.create('Ext.panel.Panel', {
            minWidth: 300,
            flex: 1,
            scrollable: false,
            split: true,
            layout: 'card',
            cls: 'context-panel',
            
            dockedItems: [{
                xtype: 'toolbar',
                cls: 'context-toolbar',
                layout: { 
                    type: 'vbox',
                    align: 'stretch'
                },
                dock: 'top',
                
                items: [{
                		xtype: 'component',
                		html: "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_CONTEXT}}",
                		style: {
                			paddingTop: '5px',
                			paddingBottom: '5px'
                		}
                	}, 
                	this._contextCombobox
                ]
            }],
            
            listeners: {
                'objectcontextchange': Ext.bind(this._onObjectContextChange, this)
            }
        });
        
        this._createContextPanels();
        
        // Hide if only one context and it is hideable
        var rightAssignmentContexts = this.getFactory()._rightAssignmentContexts,
            rightAssignmentContextIds = Object.keys(rightAssignmentContexts);
        if (rightAssignmentContextIds.length == 1 && rightAssignmentContexts[rightAssignmentContextIds[0]].isHideable())
        {
            this._contextPanel.hide();
        }
        
        var mainPanel = Ext.create("Ext.container.Container", {
            layout: {
                type: 'hbox',
                align: 'stretch'
            },
            cls: 'uitool-profile-assignment',
            items: [
                this._contextPanel, 
                {
                	xtype: 'container',
                	itemId: 'right-card-container',
	            	layout: 'card',
	    			activeItem: 0,
	    			split: true,
	    			flex: 3,
	            	items: [{
							xtype: 'component',
							cls: 'a-panel-text-empty',
							border: false,
							html: "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_NO_OBJECT_CONTEXT}}"
						}, 
						{
							xtype: 'container',
							itemId: 'grid-wrapper',
							layout: 'fit',
							html: ''
						}
					]
                }
            ]
        });
        
        return mainPanel;
    },
    
    /**
     * @private 
     * Create the grid for assignments
     * @param {Object[]} columns The columns
     * @return the created grid
     */
    _createGrid: function (columns)
    {
    	this._gridStore = Ext.create('Ametys.plugins.coreui.profiles.PermissionTargetStore', {
            proxy: {
                type: 'ametys',
                plugin: 'core-ui',
                url: 'profileAssignments.json',
                reader: {
                    type: 'json',
                    rootProperty: 'assignments'
                }
            },
            
            listeners: {
                'beforeload': Ext.bind(this._onBeforeLoadGrid, this),
                'update': Ext.bind(this._onUpdateOrLoadGrid, this),
                'load': Ext.bind(this._onUpdateOrLoadGrid, this)
            }
        });
        
        return Ext.create('Ametys.plugins.coreui.profiles.PermissionTargetGrid', {
            dockedItems: this._getGridDockedItemsCfg(),
            
            enableColumnMove: true,
            
            store: this._gridStore,
            listeners: {
                'selectionchange': Ext.bind(this.sendCurrentSelection, this)
            },
            
            stateful: true,
            stateId: this.self.getName() + "$grid",
            
            selModel: {
                mode: 'MULTI'
            },
            
            columns: columns
        });
    },
    
    /**
     * @private
     * Gets the configuration of the combobox for assignment contexts
     * @return {Object} The config object
     */
    _getContextComboCfg: function()
    {
        var data = [];
        Ext.Object.each(this.getFactory()._rightAssignmentContexts, function(id, rightAssignmentContext) {
            data.push({
                value: id,
                displayText: rightAssignmentContext.getLabel()
            });            
        });
        
        return {
            store: {
                fields: ['value', {name: 'displayText', type: 'string'}],
                data: data,
                sorters: [{property: 'displayText', direction: 'ASC'}]
            },
            autoSelect: false,
            editable: false,
            
            listeners: {
                'change': Ext.bind(this._onComboboxChange, this)
            },
            
            queryMode: 'local',
            allowBlank: false,
            forceSelection: true,
            triggerAction: 'all',
            
            valueField: 'value',
            displayField: 'displayText',
            
            hideLabel: true,
            flex: 0.5
        };
    },
    
    /**
     * @private
     * Function called when the current object context has changed.
     * @param {Object|Object[]} object The new object(s) context(s)
     * @param {String} hintTextContext The hint text to update
     * @param {Boolean} [readOnly] true if no modification is allowed on object context
     * @param {Boolean} [inheritanceNotAvailable] true if inheritance is not available on object context
     */
    _onObjectContextChange: function(object, hintTextContext, readOnly, inheritanceNotAvailable)
    {
        if (this.isDirty())
        {
            Ametys.Msg.show({
                title: "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_CHANGE_CONTEXT_BOX_TITLE}}",
                msg: "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_CHANGE_CONTEXT_BOX_MESSAGE}}",
                buttons: Ext.Msg.YESNO,
                icon: Ext.MessageBox.QUESTION,
                fn: Ext.bind(callback, this, [object, hintTextContext, readOnly, inheritanceNotAvailable], 1)
            });
        }
        // Only change context if the grid is initialized
        else if (this._gridStore != undefined)
        {
            this._internalChangeObjectContext(object, hintTextContext, readOnly, inheritanceNotAvailable);
        }
        
        function callback(btn, object, parentObjects, hintTextContext, readOnly, inheritanceNotAvailable)
        {
            if (btn == 'yes')
            {
                this._saveChanges(this._contextCombobox.getValue(), Ext.bind(this._internalChangeObjectContext, this, [object, hintTextContext, readOnly, inheritanceNotAvailable]));
            }
            else
            {
                this._internalChangeObjectContext(object, hintTextContext, readOnly, inheritanceNotAvailable);
            }
            
        }
    },
    
    /**
     * @private
     * Changes the internal representation of the object context, update the hint text of the grid and updates the grid.
     * @param {Object|Object[]} object The new object(s) context(s)
     * @param {String} hintTextContext The hint text to update
     * @param {Boolean} [readOnly=false] true if no modification is allowed on object context
     * @param {Boolean} [inheritanceNotAvailable=false] true if the inheritance of assignments is not supported on this context
     */
    _internalChangeObjectContext: function(object, hintTextContext, readOnly, inheritanceNotAvailable)
    {
        this._clearFilters(); // avoid bugs in the grid store before loading it
        
        this.getLogger().info("Right assignment context has changed to : " + object);
        
        if (!object)
        {
            this._objectContext = null;
            
            // no context selected
            this.getContentPanel().down('#right-card-container').items.get(0).setHtml("{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_NO_OBJECT_CONTEXT}}");
        	this.getContentPanel().down('#right-card-container').getLayout().setActiveItem(0);
            this.sendCurrentSelection();
        }
        else if (Ext.isArray(object) && object.length > 1)
        {
            // multi contexts selected
            
            this._objectContext = [];
            
            Ext.Array.each(object, function(oCtx) {
               this._objectContext.push({
	                context: oCtx,
	                modifiable: !readOnly,
	                inheritanceAvailable: !inheritanceNotAvailable
	            })
            }, this)
            
            this.getContentPanel().down('#right-card-container').items.get(0).setHtml("{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_MULTI_OBJECT_CONTEXT}}")
            this.getContentPanel().down('#right-card-container').getLayout().setActiveItem(0);
            this.sendCurrentSelection();
        }
        else
        {
            object = Ext.isArray(object) ? object[0] : object;
            this._objectContext = {
	            context: object,
	            modifiable: !readOnly,
	            inheritanceAvailable: !inheritanceNotAvailable
	        }
        
            var me = this;
            function callback(inheritanceDisallowed)
            {
                me._objectContext.inheritanceDisallowed = inheritanceDisallowed;
                
                me.getContentPanel().down('#right-card-container').getLayout().setActiveItem(1);
            
                me._assignmentsGrid.getDockedItems('#context-helper-text')[0].update(me._rightContextHintPrefix + hintTextContext);
	            me._updateGrid();
	            
	            me._switchToReadOnlyMode(readOnly);
                me._switchToInheritanceDisallowedMode(inheritanceDisallowed);
                
                me.sendCurrentSelection();
            }
            
            if (!inheritanceNotAvailable)
            {
                // get status of inheritance from server
                var parameters = [
                   this.getFactory()._rightAssignmentContexts[this._contextCombobox.getValue()].getServerId(), 
                   this._objectContext.context 
	            ];
	            this.serverCall('isInheritanceDisallowed', parameters, callback, {waitMessage: false});
            }
            else
            {
                callback(false);
            }
        }
    },
    
    /**
     * @private
     * Enter or leave the read-only mode
     * @param {Boolean} readOnly true to switch to read-only mode
     */
    _switchToReadOnlyMode: function (readOnly)
    {
    	if (this._assignmentsGrid.rendered)
        {
            var lockingGrid = this._assignmentsGrid.items.getRange()[0],
                normalGrid = this._assignmentsGrid.items.getRange()[1];
        	if (readOnly)
            {
        		this._assignmentsGrid.getDockedItems('#context-readonly-text')[0].show();
        		lockingGrid.getView().mask();
        		normalGrid.getView().mask();
            }
            else
            {
            	this._assignmentsGrid.getDockedItems('#context-readonly-text')[0].hide();
                lockingGrid.getView().unmask();
                normalGrid.getView().unmask();
            }
        }
    },
    
    /**
     * @private
     * Enter or leave the "inheritance disallowed" mode
     * @param {Boolean} disallow true to switch to the mode when inheritance is disallowed on the selected context
     */
    _switchToInheritanceDisallowedMode: function (disallow)
    {
        if (disallow)
        {
            this._assignmentsGrid.getDockedItems('#context-inheritance-disallowed-text')[0].show();
        }
        else
        {
            this._assignmentsGrid.getDockedItems('#context-inheritance-disallowed-text')[0].hide();
        }
    },
    
    /**
     * @private
     * Gets the configuration of the docked items of the grid
     * @return {Object[]} the docked items
     */
    _getGridDockedItemsCfg: function()
    {
        return [{
            xtype: 'component',
            itemId: 'context-helper-text',
            ui: 'tool-hintmessage'
        }, {
            xtype: 'component',
            itemId: 'context-inheritance-disallowed-text',
            html: "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_CONTEXT_INHERITANCE_DISALLOWED}}",
            ui: 'tool-hintmessage',
            hidden: true
        }, {
            xtype: 'component',
            itemId: 'context-readonly-text',
            html: "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_CONTEXT_READONLY}}",
            hidden: true,
            ui: 'tool-hintmessage'
        }, {
            dock: 'top',
            xtype: 'toolbar',
            layout: {
                type: 'hbox',
                align: 'stretch'
            },
            border: false,
            defaultType: 'textfield',
            items: [{
                xtype: 'component',
                html: '{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_FILTERS}}'
            }, {
                xtype: 'edition.right',
                itemId: 'profile-filter',
                name: 'profile-filter',
                cls: 'ametys',
                allowBlank: true,
                multiple: false,
                stacked: "false",
                width: 400,
                emptyText: "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_RIGHT_FILTER}}",
                listeners: {change: Ext.bind(this._filterByRight, this)}
            }, {
                itemId: 'user-group-filter',
                name: 'user-group-filter',
                cls: 'ametys',
                width: 400,
                emptyText: "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_USERGROUP_FILTER}}",
                listeners: {change: Ext.Function.createBuffered(this._filterByUserOrGroup, 500, this)}
            }]
        }];
    },
    
    /**
     * @private
     * Filters the columns by right
     * @param {Ametys.form.widget.Right} field The right field
     */
    _filterByRight: function(field)
    {
        Ext.suspendLayouts();
        var rightId = field.getValue();
        if (Ext.isEmpty(rightId))
        {
            Ext.Array.forEach(this._assignmentsGrid.getColumns(), function(column, index) {
                column.setVisible(true);
            }, this);
        }
        else
        {
            // Computes the columns to let visible
            var matchingProfiles = [];
            Ext.Array.forEach(this._profiles, function(profile) {
                if (Ext.Array.contains(profile.rights, rightId))
                {
                    matchingProfiles.push(profile.id);
                }
            }, this);
            
            // Hide the others (except the first column)
            Ext.Array.forEach(this._assignmentsGrid.getColumns(), function(column, index) {
                var visible = index == 0 || Ext.Array.contains(matchingProfiles, column.dataIndex);
                column.setVisible(visible);
            }, this);
        }
        Ext.resumeLayouts(true);
    },
    
    /**
     * @private
     * Filters the records by their user login/label or group id/label
     * @param {Ext.form.field.Text} field The text field
     */
    _filterByUserOrGroup: function(field)
    {
        this._gridStore.clearFilter();
        
        var text = Ext.String.escapeRegex(field.getRawValue());
        if (text.length == 0)
        {
            return;
        }
        
        var fn = function(record, text)
        {
            var regExp = new RegExp('.*' + text + '.*', 'i');
            return regExp.test(record.get('login')) 
                    || regExp.test(record.get('groupId'))
                    || regExp.test(record.get('sortableLabel'));
        };
        
        this._gridStore.filterBy(Ext.bind(fn, this, [text], 1), this);
    },
    
    /**
     * @private
     * Clear the filters
     */
    _clearFilters: function()
    {
        this._gridStore.clearFilter(); // We cannot wait for the 'change' event to be fired after the #setValue("") because we created a 500ms buffer and it lead to bugs
        this._assignmentsGrid.down('#user-group-filter').setValue("");
    },
    
    
    
    /**
     * Function called when an assignment is clicked in order to change its value
     * @param {String} recordId The id of the record
     * @param {String} profileId The profile id (id of the column)
     */
    onCellClick: function(recordId, profileId)
    {
    	function callback (computedValue)
    	{
    		// Re-init other local values to be sure to not have previously induced values
        	this._reinitComputedLocalValues(profileId);
        	
            // Compute and update the induced values on other records of same column
        	var records = this._getUnfilteredRecords();
        	Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.AssignmentHelper.computeAndUpdateLocalInducedAssignments (records, profileId);
    	}
    	
    	// Compute and set the new value
    	var newValue = this._computeNewValue (recordId, profileId, callback, this);
    },
    
    /**
     * @private
     * Compute the new value for assignment and save the new value on record.
     * The value is computed on following rotation : Allow <-> Deny <-> Unknown <-> Allow
     * @param {String} recordId The id of record
     * @param {String} profileId The id of profile
     * @param {Function} callback The callback function invoked after computing the new value. The arguments are:
	 * @param {Function} callback.value The computed value
	 * @param {Object} [scope] The scope of callback function
     */
    _computeNewValue: function(recordId, profileId, callback, scope)
    {
    	var record = this._gridStore.getById(recordId);

    	var newValue;
    	// Local value (client-side)
    	var currentValue = record.get(profileId);
    	// Stored value (server-side)
    	var storedValue = this._storedValues[profileId][recordId] || Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_UNKNOWN;
    	
    	if (currentValue == Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW)
        {
    		this._setComputedValue(Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_DENY, record, profileId, callback, scope);
        }
    	else if (currentValue == Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_DENY)
        {
    		// When go to 'unknown' value, the value is computed from parent contexts
    		var parameters = [
    		       this.getFactory()._rightAssignmentContexts[this._contextCombobox.getValue()].getServerId(), 
    		       this._objectContext.context, 
    		       profileId, 
    		       record.get('targetType'), 
    		       this._getIdentity(record)
    		];
    		this.serverCall('getInheritedAssignment', parameters, Ext.bind(this._setComputedValue, this, [record, profileId, callback, scope], 1), {waitMessage: false});
        }
    	else
        {
    		this._setComputedValue(Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW, record, profileId, callback, scope);
        }
    },
    
    /**
     * @private
     * Function invoked after computing the new assignment value. Set the new value.
     * @param {String} newValue The computed value
     * @param {String} record The record to update
     * @param {String} profileId The id of profile
     * @param {Function} callback The callback function invoked after computing the new value. The arguments are:
	 * @param {Function} callback.value The computed value
	 * @param {Object} [scope] The scope of callback function
     */
    _setComputedValue: function(newValue, record, profileId, callback, scope)
    {
    	var storedValue = this._storedValues[profileId][record.getId()] || Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_UNKNOWN;
    	record.set(profileId, newValue, {dirty: storedValue != newValue});
    	
    	if (Ext.isFunction(callback))
    	{
    		callback.call (scope, newValue);
    	}
    },
    
    /**
     * @private
     * Reinitialize the local values (client-side) of records with the last stored values (server-side) for the given profile (column) except for the given record
     * @param {String} profileId The id of profile
     */
    _reinitComputedLocalValues: function(profileId)
    {
    	var storedValues = this._storedValues[profileId];
    	
    	this._getUnfilteredRecords().each(function(record) {
    		if (!Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.AssignmentHelper.isCellDirty(record, profileId))
    		{
    			var storedValue = storedValues[record.getId()] || Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_UNKNOWN;
        		record.set(profileId, storedValue, {dirty: false})
    		}
    	});
    },
    
    /**
     * Adds user records in the assignment grid.
     * @param {Object[]} users The users to add
     * @param {String} users.login The login of the user
     * @param {String} users.populationId The id of the population of the user
     * @param {String} users.populationName The label of the population of the user
     * @param {String} users.fullName The full name of the user
     */
    addUsers: function(users)
    {
    	var usersToAdd = [],
    		addedRecords = [],
    		records = this._getUnfilteredRecords();
    	
    	Ext.Array.forEach(users, function(user) {
        	if (!Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.AssignmentHelper.findUserRecord(records, user.login, user.populationId))
        	{
        		usersToAdd.push(user);
        	}
        }, this);
    	
    	var total = usersToAdd.length,
    		count = 0;
    	
        function addInStore(groups, user)
        {
        	addedRecords = Ext.Array.merge (addedRecords, this._gridStore.add({
                targetType: Ametys.plugins.coreui.profiles.PermissionTargetStore.TARGET_TYPE_USER,
                login: user.login,
                populationId: user.populationId,
                populationLabel: user.populationName,
                userSortableName: user.fullName,
                groups: groups,
                isNew: true
            }));
        	count++;
        	
        	if (count == total)
            {
        		this._updateAddedRecords(addedRecords);
            }
        }
        
        Ext.Array.forEach(usersToAdd, function(user) {
        	// Need to know the groups the user belongs to
            this.serverCall('getUserGroups', [user.login, user.populationId], Ext.bind(addInStore, this, [user], 1));
        }, this);
        
        
    },
    
    /**
     * Adds group records in the assignment grid.
     * @param {Object[]} groups The groups to add
     * @param {String} groups.id The id of the group
     * @param {String} groups.groupDirectory The id of the group directory of the group
     * @param {String} groups.groupDirectoryName The label of the group directory of the group
     * @param {String} groups.label The label of the group
     */
    addGroups: function(groups)
    {
    	var addedRecords = [],
			records = this._getUnfilteredRecords();
    	
        Ext.Array.forEach(groups, function(group) {
        	if (!Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.AssignmentHelper.findGroupRecord(records, group.id, group.groupDirectory))
        	{
        		addedRecords = Ext.Array.merge (addedRecords, this._gridStore.add({
	                targetType: Ametys.plugins.coreui.profiles.PermissionTargetStore.TARGET_TYPE_GROUP,
	                groupId: group.id,
	                groupDirectory: group.groupDirectory,
	                groupDirectoryLabel: group.groupDirectoryName,
	                groupLabel: group.label,
	                isNew: true
	            }));
        	}
        }, this);
        
        if (addedRecords.length > 0)
        {
        	this._updateAddedRecords(addedRecords);
        }
    },
    
    /**
     * @private
     * This function is invoked when adding manually user or group records
     * Update the assignment for each profile with the inherited assignment
     * @param {Ext.data.Model[]} addedRecords The added records (users or groups)
     */
    _updateAddedRecords: function (addedRecords)
    {
    	var me = this,
    		count = 0;
    		total = addedRecords.length;
    	
		Ext.Array.forEach(addedRecords, function(record) {
			function callback (inheritedValues)
    		{
				for (var i in inheritedValues)
				{
					record.set(i, inheritedValues[i], {dirty: false});
				}
    			count++;
    			
    			if (count == total)
    			{
    				this._onStoreUpdated();
    			}
    		}
			
			// Get the inherited assignment for each profile for the added user or group
    		var parameters = [
    		       me.getFactory()._rightAssignmentContexts[me._contextCombobox.getValue()].getServerId(), 
    		       me._objectContext.context, 
    		       me._getProfileIds(), 
    		       record.get('targetType'), 
    		       me._getIdentity(record)
    		];
    		me.serverCall('getInheritedAssignments', parameters, Ext.bind(callback, me), {waitMessage: false});
    	});
    },
    
    /**
     * @private
     * Returns the id of current profiles
     * @return the id of current profiles
     */
    _getProfileIds: function()
    {
    	var profileIds = [];
    	Ext.Array.forEach(this._profiles, function (profile) {
    		profileIds.push (profile.id)
    	});
    	return profileIds;
    },
    
    /**
     * Removes the given assignments
     * @param {Object[]} assignments The assignments to remove
     * @param {String} assignments.id The record id
     * @param {Object} assignments.context The context
     */
    removeAssignments: function(assignments)
    {
        var me = this;
        Ext.Array.forEach(assignments, function(assignment) {
            var record = this._gridStore.getById(assignment.id);
            if (!record.get('isNew') 
            	&& assignment.context == this._objectContext.context 
                && assignment != null
                && record.get('targetType') != Ametys.plugins.coreui.profiles.PermissionTargetStore.TARGET_TYPE_ANONYMOUS
                && record.get('targetType') != Ametys.plugins.coreui.profiles.PermissionTargetStore.TARGET_TYPE_ANYCONNECTEDUSER)
            {
                // Iterate through the profiles, only keep the ones with local assignments to avoid useless removal
                Ext.Array.forEach(this._profiles, function(profile) {
                    var profileId = profile.id,
                        currentAssignment = record.get(profileId);
                    if (currentAssignment == Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW || currentAssignment == Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_DENY)
                    {
                        var assignmentInfo = {
                            profileId: profileId,
                            targetType: record.get('targetType'),
                            assignment: null
                        };
                        
                        if (record.get('targetType') == Ametys.plugins.coreui.profiles.PermissionTargetStore.TARGET_TYPE_USER)
                        {
                            assignmentInfo.identity = {
                                login: record.get('login'),
                                populationId: record.get('populationId')
                            };
                        }
                        else
                        {
                            assignmentInfo.identity = {
                                groupId: record.get('groupId'),
                                groupDirectory: record.get('groupDirectory')
                            };
                        }
                        me._removedAssignments.push(assignmentInfo);
                    }
                }, this);
            }
            
            // Remove record from the grid
            this._gridStore.remove([record]);
            
            if (this._removedAssignments.length > 0)
            {
            	this.setDirty(true);
            }
            
        }, this);
    },
    
    /**
     * Cancel the changes made in the grid
     */
    cancelChanges: function ()
    {
    	this._gridStore.rejectChanges();
    	this._updateGrid();
    },
    
    /**
     * Saves the changes made in the grid
     */
    saveChanges: function()
    {
        this._saveChanges(this._contextCombobox.getValue(), this._updateGrid);
    },

    /**
     * @private
     * Computes the assignments and make a server call to save changes.
     * @param {String} rightAssignmentId The id of the right assignment context
     * @param {Function} [callback] The callback function to call when the changes are saved.
     */
    _saveChanges: function(rightAssignmentId, callback)
    {
        var assignmentsInfo = [];
        Ext.Array.forEach(this._gridStore.getModifiedRecords(), function(record) {
            Ext.Object.each(record.modified, function(profileId) {
                var assignment = record.get(profileId);
                if (assignment != Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW && assignment != Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_DENY)
                {
                    assignment = Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_UNKNOWN;
                }
                
                assignmentsInfo.push({
                    profileId: profileId,
                    targetType: record.get('targetType'),
                    assignment: assignment, 
                    identity: this._getIdentity (record)
                });
            }, this);
        }, this);
        
        // Add the assignment due to removed records
        assignmentsInfo = Ext.Array.merge(this._removedAssignments, assignmentsInfo);
        
        if (assignmentsInfo.length > 0)
        {
            var parameters = [this.getFactory()._rightAssignmentContexts[rightAssignmentId].getServerId(), this._objectContext.context, assignmentsInfo];
            this.serverCall('saveChanges', parameters, callback);
        }
    },
    
    /**
     * Apply some assignments and inheritance status on given contexts
     * @param {Object} contexts the contexts with:
     * @param {String} contexts.context the JS context
     * @param {Boolean} contexts.inheritanceAvailable true if inheritance is available for the context
     * @param {Object} assignmentsInfo the assignments to apply:
     * @param {Object[]} assignmentsInfo.assignments the assignments
     * @param {Boolean} assignmentsInfo.inheritanceDisallowed the inheritance status to apply (will be applied on context with inheritance available only)
     */
    applyAssignments: function(contexts, assignmentsInfo)
    {
        var assignments = assignmentsInfo.assignments
        if (assignments.length > 0)
        {
            function doApply(btn)
	        {
	            if (btn == 'yes')
	            {
	                var rightAssignmentId = this._contextCombobox.getValue();
		            var parameters = [this.getFactory()._rightAssignmentContexts[rightAssignmentId].getServerId(), contexts, assignments, assignmentsInfo.inheritanceDisallowed];
		            this.serverCall('applyAssignments', parameters, this._applyAssignmentsCb, { arguments: { inheritanceDisallowed: assignmentsInfo.inheritanceDisallowed } });
	            }
	        }
        
            Ametys.Msg.confirm ("{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_PASTE_ASSIGNMENTS_TITLE}}", 
                            "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_PASTE_ASSIGNMENTS_CONFIRM}}", 
                            Ext.bind(doApply, this));
        }
    },
    
    /**
     * @private
     * Callback function invoked after applying assignments on contexts
     * @param {Boolean} success false if something went wrong
     * @param {Object} args additionnal args
     */
    _applyAssignmentsCb: function(success, args)
    {
        if (!success)
        {
            Ametys.Msg.show({
                title: "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_PASTE_ASSIGNMENTS_TITLE}}",
                msg: "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_PASTE_ASSIGNMENTS_ERROR}}",
                buttons: Ext.Msg.OK,
                icon: Ext.MessageBox.ERROR
            });
            return;
        }
        
        // Update inheritance status if assignments were applied on a single selection (inheritance status is ignored in a multi-selection)
        if (!Ext.isArray(this._objectContext))
        {
            this._objectContext.inheritanceDisallowed = args.inheritanceDisallowed
        }
        
        this._updateGrid();
        
        var targets = this._getProfileContextTargetsConfig();
        Ext.create('Ametys.message.Message', {
            type: Ametys.message.Message.MODIFIED,
            targets: targets
        });
    },
    
    /**
     * Get the current assignments stored on current context. All changes should be saved before calling this method.
     * @return {Object[]} the stored assignments
     */
    getCurrentAssignments: function()
    {
        if (this.isDirty())
        {
            // some changes have to be saved
            Ametys.Msg.show({
                title: "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_GET_ASSIGNMENTS_TITLE}}",
                msg: "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_GET_ASSIGNMENTS_NOSAVE_MESSAGE}}",
                buttons: Ext.Msg.OK,
                icon: Ext.MessageBox.ERROR
            });
            return null;
        }
        
        var assignmentsInfo = [];
        
        var me = this;
        var records = this._getUnfilteredRecords();
        records.each(function(record) {
            Ext.Array.each(me._getProfileIds(), function(profileId) {
               var assignment = record.get(profileId);
               if (assignment == Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW || assignment ==  Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_DENY)
               {
                    assignmentsInfo.push({
	                    profileId: profileId,
	                    targetType: record.get('targetType'),
	                    assignment: assignment, 
	                    identity: me._getIdentity (record)
	                });
               }
            });
        });
        
        return assignmentsInfo;
    },
    
    /**
     * Allow or disallow inheritance on current object context
     * @param {Boolean} disallow true to disallow inheritance
     */
    disallowInheritance: function(disallow)
    {
        var me = this;
        
        function callback()
        {
            // update current object context
            me._objectContext.inheritanceDisallowed = disallow;
            // reload the assignments grid
            me._updateGrid();
            // hide/show warning on inheritance
            me._switchToInheritanceDisallowedMode(disallow)
            // update selection message
            var targets = this._getProfileContextTargetsConfig();
	        Ext.create('Ametys.message.Message', {
	            type: Ametys.message.Message.MODIFIED,
	            targets: targets
	        });
        }
        
        var parameters = [
               me.getFactory()._rightAssignmentContexts[me._contextCombobox.getValue()].getServerId(), 
               me._objectContext.context, 
               disallow
        ];
        me.serverCall('disallowInheritance', parameters, Ext.bind(callback, me), {waitMessage: false});
    },
    
    /**
     * @private
     * Function called before loading the grid store
     * @param {Ext.data.Store} store The grid store
     * @param {Ext.data.operation.Operation} operation The object that will be passed to the Proxy to load the store
     */
    _onBeforeLoadGrid: function(store, operation)
    {
        operation.setParams(Ext.apply(operation.getParams() || {}, {
            rightAssignmentContextId: this.getFactory()._rightAssignmentContexts[this._contextCombobox.getValue()].getServerId(),
            context: this._objectContext.context
        }));
    },
    
    /**
     * @private
     * Listener when a Model instance of the grid store has been updated, or when the grid store is loaded
     * @param {Ext.data.Store} store The store
     */
    _onUpdateOrLoadGrid: function(store)
    {
        // store.getModifiedRecords().length > 0 is not sufficient as when adding a record, there is no dirty cell but the record is returned in this array anyway
        var dirty = false;
        Ext.Array.each(store.getModifiedRecords(), function(record) {
            if (!Ext.Object.isEmpty(record.modified))
            {
                dirty = true;
                return false;
            }
        }, this);
        this.setDirty(dirty);
    },
    
    /**
     * @private
     * Listener when the value of the context combobox changes.
     * @param {Ext.form.field.ComboBox} combo The combobox
     * @param {Object} newValue The new value (the selected context id)
     * @param {Object} oldValue The old value
     */
    _onComboboxChange: function(combo, newValue, oldValue)
    {
        if (this.isDirty())
        {
            Ametys.Msg.show({
                title: "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_CHANGE_CONTEXT_BOX_TITLE}}",
                msg: "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_CHANGE_CONTEXT_BOX_MESSAGE}}",
                buttons: Ext.Msg.YESNO,
                icon: Ext.MessageBox.QUESTION,
                fn: Ext.bind(callback, this, [combo, newValue, oldValue], 1)
            });
        }
        else
        {
            this._changeContextPanel(newValue, oldValue);
        }
        
        function callback(btn, combo, newValue, oldValue)
        {
            // Force dirty state to false to avoid a second dialog box if event 'objectcontextchange' is fired too soon
            this.setDirty(false);
            
            if (btn == 'yes')
            {
                this._saveChanges(oldValue, Ext.bind(this._changeContextPanel, this, [newValue, oldValue]));
            }
            else
            {
                this._changeContextPanel(newValue, oldValue);
            }
        }
    },
    
    /**
     * @private
     * Changes the active context panel
     * @param {String} rightAssignmentContextId The id of the right assignment context to display
     * @param {String} oldRightAssignmentContextId The id of the old right assignment context to display
     */
    _changeContextPanel: function(rightAssignmentContextId, oldRightAssignmentContextId)
    {
        // Dispose old assigment context id if exist
        var oldRightAssignmentContext = this.getFactory()._rightAssignmentContexts[oldRightAssignmentContextId];
        if (oldRightAssignmentContext)
        {
            oldRightAssignmentContext.dispose();
        }
        
        // Clear filters
        this._clearFilters();
        
        // Change the current panel displayed in the context panel
        this._contextPanel.getLayout().setActiveItem(this._contextComponents[rightAssignmentContextId]);
        
        // Call its initialize() method
        var rightAssignmentContext = this.getFactory()._rightAssignmentContexts[rightAssignmentContextId];
        if (rightAssignmentContext)
        {
            rightAssignmentContext.initialize();
        }
    },
    
    /**
     * @private
     * Updates the grid cells from the context.
     * @param {Object} result Result sent by the server
     * @param {Boolean} result.success If every assignment is successfull
     * @param {Object[]} result.errorInfos list of affectations in error
     * @param {String} result.errorInfos.assignment action (allow, deny, unknown, etc...)
     * @param {Object} result.errorInfos.identity User or group impacted or null
     * @param {String} result.errorInfos.targetType user, group, anyconnected_user
     * @param {String} result.errorInfos.profileId id of the profile
     * @param {String} result.errorInfos.profileLabel label of the profile
     * @param {Object[]} result.successInfos list of affectations successfull
     * @param {String} result.successInfos.assignment action (allow, deny, unknown, etc...)
     * @param {Object} result.successInfos.identity User or group impacted or null
     * @param {String} result.successInfos.targetType user, group, anyconnected_user
     * @param {String} result.successInfos.profileId id of the profile
     * @param {String} result.successInfos.profileLabel label of the profile
     */
    _updateGrid: function(result)
    {
        if (result && !result.success)
        {
            var details = "";
            if (result.errorInfos)
            {
                var haveNegativeRights = false;
                var unallowedProfiles = [];
                for (var i = 0; i < result.errorInfos.length; i++)
                {
                    var info = result.errorInfos[i];
                    if (info.assignment != "allow")
                    {
                        haveNegativeRights = true;
                    }
                    else
                    {
                        var profileDisplayed = "- " + Ext.String.escapeHtml(info.profileLabel) + " (" + info.profileId + ")";
                        if (!unallowedProfiles.includes(profileDisplayed))
                        {
                            unallowedProfiles.push(profileDisplayed);
                        }
                    }
                }
                
                if (haveNegativeRights)
                {
                    details += "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_DELEGATION_SAVE_ERROR_ONLY_ADD_PROFILE}}";
                    if (unallowedProfiles.length != 0)
                    {
                        details += "\n";
                    }
                }
                if (unallowedProfiles.length != 0)
                {
                    details += "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_DELEGATION_SAVE_ERROR_UNALLOWED_PROFILES}}\n";
                    details += unallowedProfiles.join('\n');
                }
            }
            
            Ametys.log.ErrorDialog.display({
                title: "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_DELEGATION_SAVE_ERROR_TITLE}}", 
                text: "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_DELEGATION_SAVE_ERROR}}",
                details: details,
                category: this.self.getName()
            });
        }
        
    	this._removedAssignments = [];
        this._gridStore.load({
            callback: this._onStoreUpdated,
            scope: this
        });
    },
    
    /**
     * @private
     * This function has to be invoked after the store is modified 
     * For instance, callback of the store loading, or after adding a new user/group record.
     */
    _onStoreUpdated: function()
    {
    	var me = this;
    	
    	// Update the server-side values
    	this._reinitStoredValues();
    	
    	// Update local assignments for each profiles
    	var records = this._getUnfilteredRecords();
    	Ext.Array.forEach(this._profiles, function(profile) {
    		Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.AssignmentHelper.computeAndUpdateLocalInducedAssignments (records, profile.id);
    	});
    	
    	// Clear the selection
        this._assignmentsGrid.getSelectionModel().deselectAll();
    },
    
    /**
     * @private
     * Create a columns object representing the columns
     */
    _reinitStoredValues: function()
    {
        this._storedValues = {};
        Ext.Array.forEach(this._profiles, function(profile) {
            this._storedValues[profile.id] = {};
        }, this);
        
        var records = this._getUnfilteredRecords();
        records.each(function(record) {
            Ext.Object.each(this._storedValues, function(profileId, assignments) {
            	var assignment = record.get(profileId);
            	
            	if (assignment && (assignment == Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW
            			|| assignment == Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_DENY
            			|| assignment == Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_INHERITED_ALLOW
            			|| assignment == Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_INHERITED_DENY))
            	{
            		assignments[record.getId()] = record.get(profileId)
            	}
            	else
            	{
            		// All other assignment values can not be server-side values
            		assignments[record.getId()] = Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_UNKNOWN;
            	}
            }, this)
        }, this);
    },
    
    setParams: function(params)
    {
        this.callParent(arguments);
        this._objectContext = null;
        
        this.showOutOfDate();
    },
    
    close: function(manual)
    {
        var assigmentContext = this.getFactory()._rightAssignmentContexts[this._contextCombobox.getValue()];
        if (assigmentContext)
        {
            assigmentContext.dispose();
        }
        
        if (this.isDirty())
        {
            Ametys.form.SaveHelper.promptBeforeQuit("{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_CLOSE_BOX_TITLE}}", 
                                                    "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_CLOSE_BOX_MESSAGE}}",
                                                    null,
                                                    Ext.bind(this._closeCb, this));
        }
        else
        {
            this.callParent(arguments);
        }
    },
    
    /**
     * @private
     * Callback function after the user clicked on one of the three choices in the "Prompt before quit" dialog box
     * @param {Boolean} doSave true means the user want to save. false means the user does not want to save. null means the user does not want to save nor quit.
     */
    _closeCb: function(doSave)
    {
        if (doSave === true)
        {
            this._saveChanges(this._contextCombobox.getValue(), Ext.bind(this._closeWithoutPrompt, this));
        }
        else if (doSave === false)
        {
            this._closeWithoutPrompt();
        }
    },
    
    /**
     * @private
     * Calls the close method on superclass
     */
    _closeWithoutPrompt: function()
    {
        Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.superclass.close.call(this);
    },
    
    /**
     * @private
     * Creates the context panels
     */
    _createContextPanels: function()
    {
        this._contextComponents = {};
        Ext.Object.each(this.getFactory()._rightAssignmentContexts, function(contextId, rightAssignmentContext) {
            var cmp = rightAssignmentContext.getComponent();
            this._contextComponents[contextId] = cmp;
            // Add the component in the context panel (which has a card layout)
            this._contextPanel.add(cmp);
            // Give the reference to the context panel
            rightAssignmentContext.setContextPanel(this._contextPanel);
        }, this);
    },
    
    refresh: function()
    {
    	this._initializing = true;
        this.showRefreshing();
        
        // First, retrieve the profiles to reconfigure the grid panel (every profile is a column)
        Ametys.data.ServerComm.send({
            plugin: this._profilesPluginName,
            url: this._profilesUrl,
            parameters: {
                limit: null,
                context: this._getProfilesContext()
            },
            priority: Ametys.data.ServerComm.PRIORITY_MAJOR,
            callback: {
                handler: this._getProfilesCb,
                scope: this,
                arguments: []
            },
            errorMessage: true,
            waitMessage: false,
            responseType: 'text'
        });
    },
    
    /**
     * @protected
     * Get the context to display profiles (additionnaly to null context)
     * @returns {String} the context to display or empty
     */
    _getProfilesContext: function()
    {
        return "";
    },
    
    /**
     * @private
     * Callback function after retrieving the profiles
     * @param {Object} response The server response
     */
    _getProfilesCb: function(response)
    {
        // The server returned a string json, decode it into an array of object
        var stringData = response.firstChild.textContent;
        var data = Ext.decode(stringData, true);
        
        this._profiles = data.profiles;
        
        var profilesColumn = Ametys.plugins.coreui.profiles.ProfilesGridHelper.getProfilesColumns(this._profiles, this._renderAssignment, this);
        this._assignmentsGrid = this._createGrid(profilesColumn);
        
        var newFields = Ext.Array.map(this._profiles, function(profile) {
            return {name: profile.id};
        }, this);
        newFields.push({name: 'isNew', type: 'boolean', defaultValue: false});
        this._gridStore.getModel().addFields(newFields);
        
        this.getContentPanel().down('#grid-wrapper').removeAll();
        this.getContentPanel().down('#grid-wrapper').add(this._assignmentsGrid);
        
    	if (this._contextCombobox.getStore().getCount() == 1)
    	{
    		this._contextPanel.down("toolbar").hide();
    	}
    	
    	if (this._profiles.length < 2)
    	{
    		this.getContentPanel().down('#profile-filter').hide();
    	}
    	
    	// Select the default context
        this._selectDefaultContext();
        
    	this.showRefreshed();
    },
    
    /**
     * @private
     * Initializing the default context from the current selection on the message bus.
     * If several contexts are supported, this of highest priority will be selected.
     */
    _selectDefaultContext: function ()
    {
    	var message = Ametys.message.MessageBus.getCurrentSelectionMessage();
    	
    	var initContextFound = false;
    	
    	// Get the right contexts which support the current selection
    	var supportedContexts = {};
		Ext.Object.each(this.getFactory()._rightAssignmentContexts, function(contextId, rightAssignmentContext) {
            if (rightAssignmentContext.isSupported(message))
            {
            	supportedContexts[contextId] = rightAssignmentContext;
            	initContextFound = true;
            }
        }, this);
		
		if (Ext.Object.isEmpty(supportedContexts))
		{
			// If no contexts support the current selection, re-init with all contexts
			supportedContexts = this.getFactory()._rightAssignmentContexts;
		}
		
		// Select the context of max priority among the supported context
		var maxPriority = -1;
		var contextIdToSelect = null;
		
		Ext.Object.each(supportedContexts, function(contextId, rightAssignmentContext) {
			if (rightAssignmentContext.getPriority() > maxPriority)
            {
            	contextIdToSelect = contextId;
            	maxPriority = rightAssignmentContext.getPriority();
            }
        }, this);
		
		// Clear the value to force the trigger of a change event
		this._contextCombobox.clearValue();
		this._contextCombobox.select(contextIdToSelect);
		
		this._initializing = false;
		
		if (initContextFound)
		{
			this.getFactory()._rightAssignmentContexts[contextIdToSelect].initContext (message);
		}
		else
		{
			this.sendCurrentSelection();
		}
    },
    
    
    getMBSelectionInteraction: function() 
    {
        return Ametys.tool.Tool.MB_TYPE_ACTIVE;
    },
    
    sendCurrentSelection: function()
    {
    	if (this._initializing)
    	{
    		return;
    	}
    	
        var me = this;
        
        function hasLocalAssignments(record)
        {
            var result = false;
            Ext.Array.each(me._profiles, function(profile) {
                var profileId = profile.id,
                    assignment = record.get(profileId);
                if (assignment == Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW || assignment == Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_DENY)
                {
                    result = true;
                    return false;
                }
            }, me);
            
            return result;
        }
        
        var selection = this._assignmentsGrid ? this._assignmentsGrid.getSelection() : [];
        
        var targets = [];
        
        if (this._objectContext != null)
        {
            targets = this._getProfileContextTargetsConfig();
        }
        
        Ext.create('Ametys.message.Message', {
            type: Ametys.message.Message.SELECTION_CHANGED,
            targets: targets
        });
    },
    
    /**
     * Get the configuration of profile context targets from current object context
     * @return {Object[]} the targets' configuration
     */
    _getProfileContextTargetsConfig: function()
    {
        var contexts = Ext.Array.from(this._objectContext);
        
        var targets = [];
        
        var me = this;
        
        Ext.Array.each(contexts, function (oCtx) {
            
            var params = {
                context: oCtx.context,
                modifiable: oCtx.modifiable,
                inheritanceAvailable: oCtx.inheritanceAvailable
            }
            
            var subtargets = [];
            
            if (contexts.length == 1)
            {
                params.inheritanceDisallowed = oCtx.inheritanceDisallowed;
                
		        function hasLocalAssignments(record)
		        {
		            var result = false;
		            Ext.Array.each(me._profiles, function(profile) {
		                var profileId = profile.id,
		                    assignment = record.get(profileId);
		                if (assignment == Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW || assignment == Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_DENY)
		                {
		                    result = true;
		                    return false;
		                }
		            }, me);
		            
		            return result;
		        }
                
	            var selection = me._assignmentsGrid ? me._assignmentsGrid.getSelection() : [];
	            var subtargets = Ext.Array.map(selection, function(record) {
	                var type = record.get('targetType'),
	                    removable = (type == Ametys.plugins.coreui.profiles.PermissionTargetStore.TARGET_TYPE_USER || type == Ametys.plugins.coreui.profiles.PermissionTargetStore.TARGET_TYPE_GROUP)
	                                && hasLocalAssignments(record);
	                return {
	                        id: Ametys.message.MessageTarget.PROFILE_ASSIGNMENT,
	                        parameters: {
	                            id: record.get('id'),
	                            type: type,
	                            context: oCtx.context,
	                            removable: removable
	                        }
	                    };
	            }, me);
            }
            
            targets.push({
                id: Ametys.message.MessageTarget.PROFILE_CONTEXT,
                parameters: params,
                subtargets: subtargets
            });
        });
            
        return targets;
    },
    
    /**
     * @private
     * Renderer for the first column, depending on the type of assignement
     * @param {Object} value The data value (the user sortable name or the group label)
     * @param {Object} metaData A collection of metadata about the current cell
     * @param {Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.Entry} record The record
     * @return {String} The html representation
     */
    _renderWho: function(value, metaData, record)
    {
        var type = record.get('targetType');
        switch (type) {
            case Ametys.plugins.coreui.profiles.PermissionTargetStore.TARGET_TYPE_ANONYMOUS:
                var text = "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TARGET_TYPE_ANONYMOUS}}";
                return '<span class="ametysicon-carnival23"></span> ' + text;
            case Ametys.plugins.coreui.profiles.PermissionTargetStore.TARGET_TYPE_ANYCONNECTEDUSER:
                var text = "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TARGET_TYPE_ANYCONNECTEDUSERS}}";
                return '<span class="ametysicon-key162"></span> ' + text;
            case Ametys.plugins.coreui.profiles.PermissionTargetStore.TARGET_TYPE_USER:
                return Ametys.grid.GridColumnHelper.renderUser.apply(this, arguments);
            case Ametys.plugins.coreui.profiles.PermissionTargetStore.TARGET_TYPE_GROUP:
                var text = value + ' (' + record.get('groupId') + ', ' + record.get('groupDirectoryLabel') + ')';
                return '<span class="ametysicon-multiple25"></span> ' + text;
            default:
                return value;
        }
    },
    
    /**
     * @private
     * Renderer for the assignment cells, which draws a clickable icon representing the assignement
     * @param {Object} value The data value for the current cell
     * @param {Object} metaData A collection of metadata about the current cell
     * @param {Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.Entry} record The record for the current row
     * @return {String} The HTML string to be rendered
     */
    _renderAssignment: function(value, metaData, record)
    {
        var glyph, suffix;
        
        switch (value) 
        {
            case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW_BY_ANONYMOUS:
                glyph = "ametysicon-check";
                suffix = "allowed disabled";
                break;
            case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW:
                glyph = "ametysicon-check-1";
                suffix = "allowed";
                break;
            case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW_BY_GROUP:
            case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW_BY_ANYCONNECTED:
                glyph = "ametysicon-check";
                suffix = "allowed";
                break;
            case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_INHERITED_ALLOW:
                glyph = "ametysicon-check decorator-ametysicon-up-arrow";
                suffix = "allowed";
                break;
            case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_DENY:
                glyph = "ametysicon-cross-1";
                suffix = "denied"; 
                break;
            case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_DENY_BY_GROUP:
            case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_DENY_BY_ANYCONNECTED:
                glyph = "ametysicon-cross"; 
                suffix = "denied"; 
                break;
            case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_INHERITED_DENY:
            	// Denied by inheritance
                glyph = "ametysicon-cross decorator-ametysicon-up-arrow"; 
                suffix = "denied"; 
                break;
            case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_UNKNOWN:
            default:
            	// Undetermined
                glyph = "ametysicon-minus-symbol";
                suffix = "unknown";
        }
        
        var tooltip = Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.computeTooltip(record, value);
        
        metaData.tdAttr = 'data-qtip="' + tooltip + '"';
        
        var onclickFn = "Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.onCellClick('" + record.get('id') + "', '" + metaData.column.dataIndex + "', '" + this.id + "')";
        
        if (value == Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW_BY_ANONYMOUS)
        {
        	// Disable cell (no action available)
        	// We need to reinitialize onclick to empty otherwise the last set onclick function remains attached to the span (strange ...)
            return '<span class="a-grid-assignment-glyph ' + glyph + ' ' + suffix + '" onclick=""/>';
        }
        else
        {
            return '<span class="a-grid-assignment-glyph ' + glyph + ' ' + suffix + '" onclick="' + onclickFn + '"/>';
        }
    },
    
    /**
     * @private
     * Gets all records in the assignment grid store, even the unfiltered items if there is a filter in this store.
     * @return {Ext.util.Collection} All records in the assignment grid store 
     */
    _getUnfilteredRecords: function()
    {
        var collection = this._gridStore.getData(),
            unfilteredItems = collection.getSource();
        if (unfilteredItems != null)
        {
            // there is a filter
            return unfilteredItems;
        }
        else
        {
            return collection;
        }
    },
    
    /**
     * @private
     * Returns a Object representing the identity of the record
     * @param {Ext.data.Model} record The target record
     * @return {Object} record The identity
     */
    _getIdentity: function (record)
    {
    	if (record.get('targetType') == Ametys.plugins.coreui.profiles.PermissionTargetStore.TARGET_TYPE_USER)
        {
    		return {
    			login: record.get('login'),
    			populationId: record.get('populationId')
    		}
        }
        else if (record.get('targetType') == Ametys.plugins.coreui.profiles.PermissionTargetStore.TARGET_TYPE_GROUP)
        {
        	return {
        		groupId: record.get('groupId'),
        		groupDirectory: record.get('groupDirectory')
        	}
        }
    	return null;
    },
    
    /**
     * @private
     * Listener for bus message
     * @param {Ametys.message.Message} message The message received
     */
    _onMessage: function(message)
    {
        var targets = message.getTargets(Ametys.message.MessageTarget.PROFILE);
        if (targets.length > 0 || message.getTarget(Ametys.message.MessageTarget.RIGHT))
        {
            // if it was already on default context, the grid will be bugged, so force it before to null
            this._contextCombobox.select(null);
            this.showOutOfDate();
        }
    }
});

 /**
 * This class is the data model for profile assignment grid entries.
 * @private
 */
Ext.define('Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.Entry', {
    extend: 'Ext.data.Model',
    
    fields: [
        /* For user entries */
        {name: 'login'},
        {name: 'populationId'},
        {name: 'populationLabel'},
        {name: 'userSortableName'},
        {name: 'groups'},
        
        {name: 'isNew', type: 'boolean', defaultValue: false},
        
        /* For group entries */
        {name: 'groupId'},
        {name: 'groupDirectory'},
        {name: 'groupDirectoryLabel'},
        {name: 'groupLabel'},
        
        /* For grouping feature */
        {name: 'targetType'},
        
        /* For sorting */
        {
            name: 'sortableLabel',
            type: 'string',
            type: 'string',
            
            convert: function(value, record) // using convert and not calculate because for an unknown reason, it doesn't work when closing and reopening the tool
            {
                if (record.get('userSortableName') != null)
                {
                    return record.get('userSortableName'); 
                }
                else if (record.get('groupLabel') != null)
                {
                    return record.get('groupLabel');
                }
                return "";
            }
        }
    ]
});

Ext.define("Ametys.message.ProfileAssignmentMessageTarget",{
    override: "Ametys.message.MessageTarget",
    
    statics: 
    {
        /**
         * @member Ametys.message.MessageTarget
         * @readonly
         * @property {String} PROFILE_ASSIGNMENT The target of the message is a profile assignment
         * @property {String} PROFILE_ASSIGNMENT.id The id of the record
         * @property {String} PROFILE_ASSIGNMENT.type The type of assignment
         * @property {Object} PROFILE_ASSIGNMENT.context The object context of the assignment
         * @property {Boolean} PROFILE_ASSIGNMENT.removable true if the record is removable
         */
        PROFILE_ASSIGNMENT: "profileAssignment",
        /**
         * @member Ametys.message.MessageTarget
         * @readonly
         * @property {String} PROFILE_CONTEXT The target of the message is a profile assignment
         * @property {Object} PROFILE_CONTEXT.context The object context of the assignment
         * @property {Boolean} PROFILE_CONTEXT.modifiable false if it is a read-only context
         * @property {Boolean} PROFILE_CONTEXT.inheritanceAvailable true if inheritance can be allow/disallow on this context 
         * @property {Boolean} [PROFILE_CONTEXT.inheritanceDisallowed] true if inheritance is disallowed on this context
         */
        PROFILE_CONTEXT: "profileContext"
    }
});