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

/**
 * This class provides a widget for the mapping of a collection of a synchronizable contents based on an Ametys user population.
 */
Ext.define('Ametys.plugins.user.directory.widget.UserPopulationMapping', {
    extend: 'Ametys.plugins.contentio.widget.AbstractMapping',
    
    /**
     * @cfg {String} [populationField] The relative path of the population id field
     */
    /**
     * @private
     * @property {String} _populationFieldName The property related to {@link #cfg-populationField}
     */
    
    /**
     * @cfg {String} [firstNameField] The relative path of the firstname field
     */
    /**
     * @private
     * @property {String} _firstNameFieldName The property related to {@link #cfg-firstNameField}
     */
    
    /**
     * @cfg {String} [lastNameField] The relative path of the lastname field
     */
    /**
     * @private
     * @property {String} _lastNameFieldName The property related to {@link #cfg-lastNameField}
     */
    
    /**
     * @cfg {String} [emailField] The relative path of the email field
     */
    /**
     * @private
     * @property {String} _emailFieldName The property related to {@link #cfg-emailField}
     */
    
    initComponent: function()
    {
        this._populationFieldName = this.populationField || null;
        this._firstNameFieldName = this.firstNameField || null;
        this._lastNameFieldName = this.lastNameField || null;
        this._emailFieldName = this.emailField || null;
        
        this._createModel([]);
        this._createGrid();
        
        this.items = [this._grid];

        this.form.onRelativeFieldsChange([this._populationFieldName], this, this._onPopulationChanged);
        this.form.onRelativeFieldsChange([this._firstNameFieldName, this._lastNameFieldName, this._emailFieldName], this, this._onMetadataFieldChanged);
        
        this._addColumns = Ext.Function.createInterceptor(this._addColumns, this._isNotDestroyed, this);
        
        this.callParent(arguments);
    },
    
    /**
     * @private
     * Listener called when the value of the population field changes
     * @param {Ext.form.field.Field} field The relative field that has triggered the on change event.
     * @param {Object} newValue The new value
     * @param {Object} oldValue The old value
     */
    _onPopulationChanged: function(field, newValue, oldValue)
    {
        if (this._populationId != newValue)
        {
            // Remove old data in the store
        	if (oldValue != null)
        	{
        		this._grid.getStore().getData().each(function(record) {
                    for (var fieldName in record.data)
                    {
                        if (/^attribute-.*/.test(fieldName))
                        {
                            delete record.data[fieldName];
                        }
                    }
                });
        	}
            
            this._populationId = newValue;
            this._reconfigureColumns();
        }
    },
    
    _isInitialized: function ()
    {
    	return this.callParent() && this._populationId != null;
    },
    
    _initializeGrid: function (callback)
    {
    	this.callParent([Ext.bind(this._reconfigureColumns, this, [callback])]);
    },
    
    /**
     * Configure grid's columns with the selected population
     * @param {Function} [callback] The callback function
     */
    _reconfigureColumns: function (callback)
    {
    	this._populationId = this._populationId == null ? this.form.getRelativeField(this._populationFieldName, this).getValue() : this._populationId;
    	
    	if (this._populationId != null)
    	{
            Ametys.data.ServerComm.callMethod({
                role: "org.ametys.plugins.userdirectory.synchronize.UserPopulationSynchronizableContentsCollectionHelper",
                methodName: "getSupportedUserDirectories",
                parameters: [this._populationId],
                callback: {
                    handler: this._addColumns,
                    arguments: {
                        callback: callback
                    },
                    scope: this
                },
                errorMessage: {
                    category: Ext.getClassName(this),
                    msg: "{{i18n PLUGINS_USER_DIRECTORY_SYNCHRONIZE_USERPOPULATION_GET_SUPPORTED_USERDIRECTORIES_ERROR}}"
                }
            });
    	}
    	else
    	{
    		// No population selected, reconfigure with initial columns without any user directory column
            this._createModel([]);
            this._grid.reconfigure(this._getColumnsCfg());
            
            if (Ext.isFunction(callback))
            {
            	callback();
            }
    	}
    },
    
    /**
     * @private
     * It not destroyed?
     * @return {Boolean} true if the user population mapping is destroyed, false otherwise
     */
    _isNotDestroyed: function()
    {
        return !this.destroyed;
    },
    
    /**
     * @private
     * Reconfigure the columns (add one column by supported user directory)
     * @param {Object[]} userDirectories The supported user directories
     * @param {Object} [args] The additional arguments
     * @param {Function} [args.callback] A function to callback
     */
    _addColumns: function(userDirectories, args)
    {
        var additionalFields = Ext.Array.map(userDirectories, function(userDirectory) {
            return {name: 'attribute-' + userDirectory.id, type: 'string'};
        }, this);
        
        var columnsCfg = this._getColumnsCfg();

        var udColumns = Ext.Array.map(userDirectories, function(userDirectory) {
            var dataIndex = 'attribute-' + userDirectory.id;
            var type = userDirectory.type;
            
            var text = "{{i18n plugin.contentio:PLUGINS_CONTENTIO_WIDGET_MAPPING_MAPPING}}";
            var tooltip = "{{i18n plugin.contentio:PLUGINS_CONTENTIO_WIDGET_MAPPING_MAPPING_DESC}}";
            if (type == "SQL")
            {
            	text = "{{i18n plugin.contentio:PLUGINS_CONTENTIO_WIDGET_MAPPING_SQL_COLUMN}}";
            	tooltip = "{{i18n plugin.contentio:PLUGINS_CONTENTIO_WIDGET_MAPPING_SQL_COLUMN_DESC}}";
            }
            else if (type == "LDAP")
            {
            	text = "{{i18n plugin.contentio:PLUGINS_CONTENTIO_WIDGET_MAPPING_LDAP_ATTRIBUTE}}";
            	tooltip = "{{i18n plugin.contentio:PLUGINS_CONTENTIO_WIDGET_MAPPING_LDAP_ATTRIBUTE_DESC}}";
            }
            return {
            	text: text + " (" + userDirectory.modelLabel + (userDirectory.label ? " \"" + userDirectory.label + "\"" : "") + ")",
            	tooltip: tooltip,
                dataIndex: dataIndex, 
                flex: 1,
                xtype: 'widgetcolumn', 
                widget: {
                    xtype: 'textfield',
                    listeners: {
                        'change': Ext.bind(this._onWidgetChange, this, [dataIndex], 0)
                    }
                }
            };
        }, this);
        Ext.Array.insert(columnsCfg, 1, udColumns);
        
        this._createModel(additionalFields);
        this._grid.reconfigure(columnsCfg);
        
        if (Ext.isFunction(args.callback))
        {
        	args.callback();
        }
    },
    
    /**
     * @private
     * Listener called when the value of the login/firstname/lastname/email field changes
     * @param {Ext.form.field.Field} field The relative field that has triggered the on change event.
     * @param {Object} newValue The new value
     * @param {Object} oldValue The old value
     */
    _onMetadataFieldChanged: function(field, newValue, oldValue)
    {
        var gridStore = this._grid.getStore();
        // Remove in the grid the new value
        if (newValue != null)
        {
            var record = gridStore.findExact('metadata-ref', newValue);
            if (record == -1)
            {
                // This listener may be fired too soon (i.e. store is not set with its data yet)
                // if #setValue is called on login, firstName, lastName, email before called on this widget
                this._excludedRecords.push(newValue);
            }
            else
            {
                gridStore.remove([record]);
            }
        }
        
        // Add in the grid the old value
        if (oldValue != null)
        {
            var fieldStore = field.combobox.getStore(),
                record = fieldStore.findExact('id', oldValue);
            if (record > -1)
            {
                gridStore.add({
                    "metadata-ref": oldValue
                });
            }
        }
    },
    
    /**
     * @private
     * Creates the model for the store of the grid
     * @param {Object[]} additionalFields The additonnal fields to define in the model
     */
    _createModel: function(additionalFields)
    {
        var fields = [
            {
                name: 'metadata-ref', 
                type: 'string',
                sortType: Ext.bind(function(metadataRef) {
                    var metadataLabel = this._getMetadataLabel(metadataRef);
                    return Ext.data.SortTypes.asNonAccentedUCString(metadataLabel);
                }, this)
            }, 
            {name: 'synchro', type: 'boolean'}
        ].concat(additionalFields);
        
        var modelName = this._getModel();
        if (Ext.data.schema.Schema.get('default').hasEntity(modelName))
        {
            Ext.data.schema.Schema.get('default').getEntity(modelName).replaceFields(fields, true);
        }
        else
        {
            Ext.define(modelName, {
                extend: 'Ext.data.Model',
                idProperty: 'metadata-ref',
                
                fields: fields
            });
        }
    },
    
    _getModel: function()
    {
        return 'Ametys.plugins.userdirectory.UserPopulationMapping.Mapping$' + this.getId();
    },
    
    _getColumnsCfg: function()
    {
        return [{
            header: "{{i18n plugin.contentio:PLUGINS_CONTENTIO_WIDGET_MAPPING_FIELDS}}",
            dataIndex: 'metadata-ref', 
            renderer: Ext.bind(this._renderMetadata, this),
            flex: 1
        }, {
            header: "{{i18n plugin.contentio:PLUGINS_CONTENTIO_WIDGET_MAPPING_SYNCHRO}}", 
            dataIndex: 'synchro', 
            align: 'center', 
            width: 110,
            xtype: 'widgetcolumn', 
            widget: {
                xtype: 'checkbox',
                listeners: {
                    'change': Ext.bind(this._onWidgetChange, this, ['synchro'], 0)
                }
            }
        }];
    },
    
    updateRecord: function(record, value)
    {
    	record.set('synchro', value.synchro);
    	
    	Ext.Object.each (value, function (key, v) {
    		if (Ext.String.startsWith(key, "attribute-"))
    		{
    			record.set(key, v);
    		}
    	});
    	record.commit();
    },
    
    getValueFromRecord: function (record)
    {
    	var data = record.getData(),
    		attrs = {};
    	
    	// Find non-empty attributes
    	Ext.Object.each (data, function (key, v) {
    		if (Ext.String.startsWith(key, "attribute-") && v)
    		{
    			attrs[key] = v;
    		}
    	});
    	
    	if (!Ext.Object.isEmpty(attrs))
    	{
    		return Ext.apply({
    			'metadata-ref': record.get('metadata-ref'),
    			'synchro': record.get('synchro')
    		}, attrs)
    	}
    	
    	// Filter empty value
    	return null;
    },
    
    getErrors: function(value)
    {
        value = value || [];
        if (Ext.isString(value))
        {
            value = Ext.decode(value);
        }
        
        var errors = this.callParent(arguments);
        return errors;
    }
});