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

/**
 * Abstract field displaying a combobox allowing to select one of the available data sources. Optionally, 
 * this field can allow the addition of other data sources. 
 * You can override #getStore and you must provide a function #creationHandler
 */
Ext.define('Ametys.form.widget.AbstractDataSource', {
    extend: 'Ametys.form.AbstractFieldsWrapper',
	
    statics: {
        /**
         * @property {String} DEFAULT_DATASOURCE_SUFFIX The suffix to add to default datasource
         * @readonly
         * @static
         */
    	DEFAULT_DATASOURCE_SUFFIX: '-default-datasource'
    },
    
    /**
     * @cfg {Boolean} [allowInternal=false] True to also show internal data sources
     */
    allowInternal: false,
    
    /**
     * @cfg {Boolean} [allowPrivate=false] True to also show private data sources
     */
	allowPrivate: false,
	
    /**
     * @cfg {Boolean} [allowCreation=false] True to allow the creation of new data sources
     */
	allowCreation: false,
	
	/**
     * @cfg {Boolean} [allowDefault=true] False to hide the default data sources
     */
	allowDefault: true,
	
	/**
	 * @cfg {String} dataSourceType the type of the data source
	 */
	/**
	 * @cfg {Function} creationFunction the function used to add a new data source
	 */
	
    /**
     * @cfg {String} [createButtonText=""] The text of the create button.
     */
    createButtonText: '',
    /**
     * @cfg {String} createButtonIcon The button icon path for the create button.
     */
    createButtonIcon: null,
    
    /**
     * @cfg {String} createButtonIconCls The button icon CSS for the create button.
     */
    createButtonIconCls: 'ametysicon-data110 decorator-ametysicon-add64',
    
    /**
     * @cfg {String} createButtonTooltip The button icon tooltip for the create button.
     */
    createButtonTooltip: "{{i18n PLUGINS_CORE_UI_WIDGET_DATASOURCE_BUTTON_DEFAULT_TOOLTIP}}",
    
	/**
	 * @private
	 * @property {Ext.data.Store} _store the combo box's store
	 */
	/**
	 * @private
	 * @property {String} _selectedRecordId the id of the record currently selected
	 */
	/**
	 * @private
	 * @property {String} _dataSourceToSelect The id of the data source to select 
	 */
    /**
     * @private
     * @property {String} _initialValue the initial value of the widget
     */
    /**
     * @private
     * @property {String} _defaultDataSourceId the id of the default data source
     */
	
	initComponent: function()
	{
		this.items = [];
		
    	this.items[0] = Ext.create('Ext.form.field.ComboBox', 
    			        {
	    		  	        flex: 1,
	    		  	        
  	    	    		    store: this.getStore(),
  	    	    		    
  	    	    		    // FIXME set value => config tool dirty
	  	    	            forceSelection: true,
	  	    	            
	  	    	            valueField: 'id',
  	    	    		    displayField: 'name',
  	    	    		    
  	    	    		    listeners: {
  	    	    		    	change: Ext.bind(this._onChange, this),
  	    	    		    	click: {fn: Ext.bind(this._onClick, this), element: 'inputEl'},
  	    	    		    	scope: this
  	    	    		    }
      			        });
    	
    	if (this.allowCreation && this.isCreateDataSourceSupported())
		{
    		this.items[1] = Ext.create('Ext.button.Button', 
    						{ 
				    			width: 20,
                                text: this.createButtonText,
			                    icon: this.createButtonIcon,
				    			iconCls: this.createButtonIconCls,
			                    tooltip: this.createButtonTooltip,
				    			handler: Ext.bind(this._createButtonHandler, this)
				    		}); 
		}
    	
    	this.callParent();
	},
	
	constructor: function(config)
	{
        config.allowPrivate = Ext.isBoolean(config.allowPrivate) ? config.allowPrivate : config.allowPrivate != 'false';
        config.allowCreation = Ext.isBoolean(config.allowCreation) ? config.allowCreation : config.allowCreation != 'false';
        config.allowInternal = Ext.isBoolean(config.allowInternal) ? config.allowInternal : config.allowInternal != 'false';
        config.allowDefault = Ext.isBoolean(config.allowDefault) ? config.allowDefault : config.allowDefault != 'false';
        
		this.callParent(arguments);
		
		this._selectedRecordId = null;
		this._defaultDataSourceId = this.dataSourceType + Ametys.form.widget.AbstractDataSource.DEFAULT_DATASOURCE_SUFFIX;
		
		// Bus messages listeners
		Ametys.message.MessageBus.on(Ametys.message.Message.CREATED, this._onCreated, this);
		Ametys.message.MessageBus.on(Ametys.message.Message.MODIFIED, this._onModified, this);
		Ametys.message.MessageBus.on(Ametys.message.Message.DELETED, this._onDeleted, this);
	},
	
	/**
	 * @private
	 * Get the underlying combo box
	 * @return {Ext.form.field.ComboBox} The combobox
	 */
	_getCombobox: function()
	{
		return this.items.get(0);
	},
	
    setValue: function(value) 
    {
    	this._getCombobox().setValue(value ? value : null);
    },

    getValue: function() 
    {
    	return this._getCombobox().getValue();
    },
    
	/**
	 * Get the store used by the combo box
	 * @return {Ext.data.Store} the store
	 */
	getStore: function()
	{
		if (this._store == null)
		{
			this._store = Ext.create('Ext.data.Store',
			{
				autoLoad: true,
				
				model: this.getDataModel(),
				
				proxy: {
					type: 'ametys',
					plugin: 'admin',
					url: 'datasources/get',
					reader: {
						type: 'json',
						rootProperty: 'datasources'
					},
					extraParams: this.getStoreExtraParameters()
				},
				
				sorters: 'name',
				
				listeners: {
					beforeload: {fn: this._onBeforeLoad, scope: this},
					load: {fn: this._onLoad, scope: this, single: true}
				}
			});
		}
		
		return this._store;
	},
	
	/**
	 * Get the name of model associated with the store
	 * @return {String} the name of the model
	 */
	getDataModel: function ()
	{
		return 'Ametys.form.widget.AbstractDataSource.Model';
	},
	
	/**
	 * @protected
	 * Get the extra parameters that will be included on every request of store
	 * @return {Object} the extra parameters 
	 */
	getStoreExtraParameters: function ()
	{
		return {};
	},
    
    /**
     * @private
     * Function invoked when clicking on create button
     */
    _createButtonHandler: function ()
    {
        this.createDataSource(Ext.bind(this._selectDataSource, this));
    },
    
    /**
     * @private
     * Select the created data source in the combo box
     * @param {Object} datasource the datasource
     * @param {String} datasource.id the id of the new data source
     */
    _selectDataSource: function (datasource)
    {
        if (datasource && datasource.id)
        {
            var me = this;
	        if (this._store.getById(datasource.id) != null)
	        {
	            this.setValue(datasource.id);
	        }
	        else
	        {
                // Too soon, store the id to select after reload
	            this._dataSourceToSelect = datasource.id;
	        }
        }
    },
    
    /**
     * @protected
     * @template
     * Test if #createDataSource will be possible
     */
    isCreateDataSourceSupported: function()
    {
        throw new Error("The method #isCreateDataSourceSupported is not implemented in " + this.self.getName());
    },
	
	/**
     * @protected
     * @template
	 * Handler when the 'Add data source' button is clicked
     * @param {Function} callback the function to call after creating data source
	 */
	createDataSource: function(callback)
	{
		throw new Error("The method #createDataSource is not implemented in " + this.self.getName());
	},
	
	/**
	 * @private
	 * Function invoked right before the store is loaded
	 * @param {Ext.data.Store} store the store
	 * @param {Ext.data.operation.Operation} operation The {@link Ext.data.operation.Operation} object that will be passed to the Proxy to load the Store
	 */
	_onBeforeLoad: function(store, operation)
	{
		var me = this;

		operation.setParams(Ext.apply(operation.getParams() || {}, {
			includePrivate: me.allowPrivate,
			includeInternal: me.allowInternal,
			includeDefault: me.allowDefault,
			type: me.dataSourceType
		}));
	},
	
	/**
	 * @private
	 * Function invoked once the store is loaded.
	 * @param {Ext.data.Store} store the store
	 */
	_onLoad: function(store)
	{
		var initialValue = this.getValue();
		
		// This listener is invoked once, in order to store the initial value of the combo box
		this._initialValue = initialValue;
		
		// Preselect default data source if there is no initial value
		// Or if the value has been removed (deleted data source when disabled)
		if (!initialValue || !store.getById(initialValue))
		{
			this.setValue(this._defaultDataSourceId);
		}
	},
	
	/**
	 * @private
	 * Function invoked whenever the value of the combo box changes
	 * @param {Ext.form.field.ComboBox} comboBox the combo box
	 * @param {String} newValue the new value of the combo box
	 * @param {String} oldValue the previous value of the combo box
	 */
	_onChange: function(comboBox, newValue, oldValue)
	{
		if (this._initialValue != null && oldValue != null && newValue != oldValue)
		{
			if (newValue != this._initialValue)
			{
				// The value has switched: warn the administrator that the data will have to be transferred
				this.markWarning("{{i18n PLUGINS_CORE_UI_WIDGET_DATASOURCE_DATABASE_TRANSFER_WARNING}}");
			}
			else if (this.hasActiveWarning())
			{
				// The user went back to the initial value: the warning does no longer need to be displayed
				this.clearWarning();
			}
		}
		
		this._selectedRecordId = newValue;
		this.setValue(newValue);
	},
	
	/**
	 * @private
	 * Function invoked when the combo box is clicked
	 */
	_onClick: function()
	{
		var combo = this._getCombobox();
		
		// Do not collapse when the combo box is already expanded
		if (!combo.readOnly && !combo.disabled && !combo.isExpanded) 
		{
			combo.onTriggerClick();
		}
	},
	
	/**
	 * @private
	 * Handler function invoked whenever a {@link Ametys.message.Message#CREATED}
	 * message is sent out on the message bus. Add the corresponding record to the grid panel's store.
	 * @param {Ametys.message.Message} message the message
	 */
	_onCreated: function(message)
	{
		var me = this;
		var target = message.getTarget(Ametys.message.MessageTarget.DATASOURCE);
		if (target)
		{
			// load callback in #_selectNewDataSource is not called if the store is reloaded
			this._store.reload({
				callback: function() {
					if (this._dataSourceToSelect != null)
					{
						this.setValue(this._dataSourceToSelect);
						this._dataSourceToSelect = null;
					}
				},
				scope: this
			});
		}
	},
	
	/**
	 * @private
	 * Handler function invoked whenever a {@link Ametys.message.Message#MODIFIED}
	 * message is sent out on the message bus. Add the corresponding record to the grid panel's store.
	 * @param {Ametys.message.Message} message the message
	 */
	_onModified: function(message)
	{
		var target = message.getTarget(Ametys.message.MessageTarget.DATASOURCE);
		if (target)
		{
			var id = target.getParameters().id;
			if (this._selectedRecordId == id)
			{
				this._store.load({
					callback: function() {
						// Reset the same value after the store is loaded because 
						// the name, which is the displayed text, might have changed
						this.setValue(id);
					},
					scope: this
				});
			}
			else if (this._selectedRecordId == this._defaultDataSourceId && target.getParameters().isDefault)
			{
				// The default data source has changed
				this._store.load({
					callback: function() {
						// Reset the same value after the store is loaded because 
						// the default data source has changed
						this.setValue(this._defaultDataSourceId);
					},
					scope: this
				});
			}
			else
			{
				this._store.reload();
			}
		}
	},
	
	/**
	 * @private
	 * Handler function invoked whenever a {@link Ametys.message.Message#CREATED}
	 * message is sent out on the message bus. Add the corresponding record to the grid panel's store.
	 * @param {Ametys.message.Message} message the message
	 */
	_onDeleted: function(message)
	{
		var target = message.getTarget(Ametys.message.MessageTarget.DATASOURCE);
		if (target)
		{
			var id = target.getParameters().id;
			if (this._selectedRecordId == id)
			{
				this._store.load({
					callback: function() {
						this._getCombobox().setValue(null);
					},
					scope: this
				});
			}
			else
			{
				this._store.reload();
			}
		}
	},
	
	destroy: function()
	{
        Ametys.message.MessageBus.unAll(this);
        this.callParent(arguments);
	}
});

Ext.define('Ametys.form.widget.AbstractDataSource.Model', { 
	extend: 'Ext.data.Model',
	
	fields: [
		{name: 'id'},
		{name: 'type'},
		{name: 'name', type: 'string'}
	]
});