/*
 *  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.
 */
Ext.define('Ametys.plugins.contentio.widget.AbstractMapping', {
    extend: 'Ametys.form.AbstractField',
    
    /**
     * @cfg {String} [contentTypesField] The relative path of the content type field
     */
    
    /**
     * @private
     * @property {String} _contentTypesFieldName The property related to {@link #cfg-contentTypesField}
     */
    
    /**
     * @private
     * @property {String} _contentTypeId The id of content type
     */
    
    /**
     * @private
     * @property {Ext.grid.Panel} _grid The wrapped grid in this field
     */
    
    /**
     * @private
     * @property {String} _metadataSetName The metadataSetName for the mapping
     */
    
    /**
     * @private
     * @property {String[]} _excludedRecords The ids of the records to exclude (i.e. to not set) when setting the data into the store (in {@link #_fillGridWithMetadata})
     */
    
    initComponent: function()
    {
        this._contentTypesFieldName = this.contentTypesField || null;
        this._metadataSetName = this.metadataSetName || "main";
        this._excludedRecords = [];
        if (this.excludedMetadata)
        {
        	this._excludedRecords = this.excludedMetadata.split(",");
        }
        
        this.form.onRelativeFieldsChange([this._contentTypesFieldName], this, this._onContentTypeChanged);
        this.callParent(arguments);
        
        this._fillGridWithMetadata = Ext.Function.createInterceptor(this._fillGridWithMetadata, this._isNotDestroyed, this);
    },
    
    /**
     * @private
     * Listener called when the value of the content type 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
     */
    _onContentTypeChanged: function(field, newValue, oldValue)
    {
        if (this._contentTypeId != newValue)
        {
            this._contentTypeId = newValue;
            this._updateContentTypeModel();
        }
    },
    
    /**
     * Returns true if the grid is initialized and can received values
     * @return true if initialized
     */
    _isInitialized : function ()
    {
    	return this._contentTypeId != null;
    },
    
    /**
     * Initialize or reinitialize the grid with the model of selected content type
     * @param {Function} [callback] the callback function
     * @private
     */
    _initializeGrid: function (callback)
    {
    	this._updateContentTypeModel(callback);
    },
    
    /**
     * Update the grid store with the model of selected content type
     * @param {Function} [callback] the callback function
     * @private
     */
    _updateContentTypeModel: function (callback)
    {
    	this._contentTypeId = this._contentTypeId == null ? this.form.getRelativeField(this._contentTypesFieldName, this).getValue() : this._contentTypeId;
    	if (this._contentTypeId != null)
    	{
    		Ametys.data.ServerComm.send({
                plugin: 'cms',
                url: 'common-attributes.json',
                parameters: {
                    withFullLabel: true,
                    ids: [this._contentTypeId],
                    viewName: this._metadataSetName,
                    includeSubRepeaters: false
                },
                priority: Ametys.data.ServerComm.PRIORITY_MAJOR,
                callback: {
                    handler: this._fillGridWithMetadata,
                    scope: this,
                    arguments: [callback]
                },
                waitMessage: false,
                errorMessage: true,
                responseType: 'text'
            });
    	}
    	else if (Ext.isFunction(callback))
    	{
    		callback();
    	}
    },
    
    /**
     * @private
     * Is not destroyed?
     * @return {Boolean} true if the abstract mapping is destroyed, false otherwise
     */
    _isNotDestroyed: function()
    {
        return !this.destroyed;
    },
    
    /**
     * @private
     * Fills grid with metadata of the selected content type
     * @param {Object} response The server response
     * @param {Object[]} args The transmitted arguments
     * @param {Function} args.0 A callback function
     */
    _fillGridWithMetadata: function(response, args)
    {
    	this._metadata = {};
    	
        var metadata = Ext.decode(response.firstChild.textContent).attributes;
        
        var me = this,
        	data = [];
        
        Ext.Array.each(metadata, function(item) {
            var name = item.name;
        	me._metadata[name] = item.fullLabel;
        	
            if (!Ext.Array.contains(me._excludedRecords, name))
            {
            	data.push({
            		"metadata-ref": name
            	});
            }
        });
//        this._excludedRecords = [];
        
        this._grid.getStore().suspendEvent("update", "datachanged");
        this._grid.getStore().setData(data);
        this._grid.getStore().resumeEvent("update", "datachanged");
        
        if (Ext.isFunction(args[0]))
        {
        	args[0]();
        }
    },
    
    /**
     * @private
     * Renderer for metadata
     * @param {String} value the metadata path
     * @return {String} the rendered metadata
     */
    _renderMetadata: function(value)
    {
    	return this._getMetadataLabel(value) || value;
    },
    
    /**
     * @protected
     * Gets the label of the metadata
     * @param {String} metadataPath the metadata path
     * @return {String} the metadata label, or null if not found
     */
    _getMetadataLabel: function(metadataPath)
    {
        return this._metadata && this._metadata[metadataPath];
    },
    
    /**
     * @protected
     * Create the grid of mapping
     */
    _createGrid: function()
    {
        this._grid = Ext.create('Ext.grid.Panel', {
            store: {
                model: this._getModel(),
                sortOnLoad: true,
                listeners: {
                    'update': Ext.bind(this._onGridStoreChange, this),
                    'datachanged': Ext.bind(this._onGridStoreChange, this)
                }
            },
            
            columns: this._getColumnsCfg(),
            
            viewConfig: {
                markDirty: false
            },
            
            disableSelection: true
        });
        
        // FIXME Workaround for centering the checkboxes in the cells : https://www.sencha.com/forum/showthread.php?307438
        var gridId = this._grid.getId();
        var stylesheetId = gridId + '$custom-stylesheet';
        Ext.util.CSS.createStyleSheet('#' + gridId + ' .x-grid-cell .x-form-cb {left: inherit !important;}', stylesheetId);
        this._grid.on({
            'destroy': function(grid)
            {
                Ext.util.CSS.removeStyleSheet(stylesheetId);
            }
        });
    },
    
    /**
     * @protected
     * @template
     * Gets the model for the store of the grid
     * @return {String} The name of the model for the grid store
     */
    _getModel: function()
    {
        throw new Error("This method is not implemented in " + this.self.getName());
    },
    
    /**
     * @protected
     * @template
     * Gets the columns of the grid
     * @return {Object[]} The the columns of the grid
     */
    _getColumnsCfg: function()
    {
        throw new Error("This method is not implemented in " + this.self.getName());
    },
    
    
    /**
     * @private
     * Unckeck all the other checkboxes of the column. Must be a column of boolean type
     * @param {String} fieldName The name of the field to update in the record
     * @param {Ext.form.field.Field} widget The widget
     * @param {Object} newValue The new value
     */
    _uncheckOthers: function(fieldName, widget, newValue)
    {
        if (newValue && widget.getWidgetRecord)
        {
            var changingRecord = widget.getWidgetRecord();
            this._grid.getStore().getData().each(function(currentRecord) {
                if (currentRecord != changingRecord && currentRecord.get(fieldName))
                {
                    currentRecord.set(fieldName, false);
                }
            });
        }
    },
    
    /**
     * @private
     * Listener when a widget value has changed, in order to update the grid store.
     * @param {String} fieldName The name of the field to update in the record
     * @param {Ext.form.field.Field} widget The widget
     * @param {Object} newValue The new value
     */
    _onWidgetChange: function(fieldName, widget, newValue)
    {
        if (widget.getWidgetRecord)
        {
            var rec = widget.getWidgetRecord();
            rec.set(fieldName, newValue);
        }
    },
    
    getValue: function()
    {
        var me = this,
        	value = [];
        
        this._grid.getStore().getData().each(function(record) {
            var v = me.getValueFromRecord(record);
            if (v != null)
            {
            	value.push(v);
            }
        }, this);
        
        return Ext.encode(value);
    },
    
    /**
     * Get the values associated to a record
     * @param {Ext.data.Model} record The record to analyser
     * @return {Object} An object of the data of the record
     */
    getValueFromRecord: function (record)
    {
    	return Ext.clone(record.getData());
    },
    
    setValue: function(value)
    {
        if (Ext.isString(value))
        {
            value = Ext.decode(value);
        }
        
        var me = this,
            grid = this._grid,
            store = this._grid.getStore();
        
        function _internalSetValue()
        {
            store.suspendEvent("update");
            store.suspendEvent("datachanged");
        	Ext.Array.each (value, function(v) {
        		var index = store.findExact('metadata-ref', v['metadata-ref']);
        		if (index != -1)
        		{
        			me.updateRecord(store.getAt(index), v);
        		}
        	})
            store.resumeEvent("datachanged");
            store.resumeEvent("update");
            grid.getView().refresh();
        }
        
        if (!this._isInitialized())
        {
        	this._initializeGrid(_internalSetValue);
        }
        else
        {
        	_internalSetValue();
        }
        
        this.callParent(arguments);
    },
    
    /**
     * @protected
     * @template
     * Update a record from the received value
     * @param {Ext.data.Model} record The record
     * @param {Object} value the value for this record
     */
    updateRecord: function (record, value)
    {
    	// Nothing by default
    },
    
    /**
     * @private
     * Listener when records are added or removed in the store, or when a record has been updated
     * @param {Ext.data.Store} store the store
     */
    _onGridStoreChange: function(store)
    {
        this.checkChange();
    }
});