/*
 *  Copyright 2013 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 helper provides a dialog box to search then select contents
 */
Ext.define('Ametys.cms.uihelper.SelectContentBySearch', {
	singleton: true,
	
	/**
	 * @property {Number} PAGE_SIZE The number of records to display by 'page'
	 * @readonly
	 */
	PAGE_SIZE: 50,
	
	/**
	 * @property {Number} MIN_HEIGHT The default minimum height of the dialog box
	 * @readonly
	 */
	MIN_HEIGHT: 300,
	
	/**
	 * @property {Number} MAX_HEIGHT The default maximum height of the dialog box
	 * @readonly
	 */
	MAX_HEIGHT: 600,
	
    /**
     * @property {Boolean} [_validated=false] True when the user validate the dialog box
     * @private
     */
	 
	/**
	 * Configure and open the search contents dialog box.
     * @param {Object} config The configuration options.
     * @param {Object} config.modelId The id of search model to base search on.
     * @param {Object} [config.searchModelParameters] A map of parameters passed to the search model.
     * @param {String} [config.contentType] The id of content type to restrict the search on. Can be null.
     * @param {String} [config.icon] The full path to icon (16x16 pixels) for the dialog box. Can be null if iconCls is used
     * @param {String} [config.iconCls] One or more space separated CSS classes to be applied to the icon dialog box
     * @param {String} config.title The title of the dialog box.
     * @param {Function} config.callback The callback function invoked when contents are selected. The callback function will received the following parameters:
     * @param {String[]/Ametys.cms.content.Content[]} config.callback.contents The selected contents as a Array of ids or an Array of Ametys.cms.content.Content[]. See #config.fullContents
     * @param {String} config.helpmessage1 The message displayed at the top of the first card
     * @param {String} config.helpmessage2 The message displayed at the top of the second card
     * @param {Boolean} [config.fullContents=false] If `true`, the callback function receives Content objects as argument. If `false`, only content ids are provided.
     * @param {Boolean} [config.multiple=false] `true` to allow selecting multiple contents, `false` otherwise.
     * @param {Boolean} [config.excludeSubContents=false] `true` to exclude sub-contents from search
     * @param {Object} [config.forceValues] A map with the values to force in search form for the field with the key as name. These values are only used if the field exists in the form.
     * @param {String} [config.forceMode=hide] If forceValues is used, a String which specifies how the forced field should appear : 'hide' to hide the fields, 'readOnly' to display the fields preveting the user from changing the fields or 'disabled' to disable the fields
     * @param {String} [config.boxSize] The dialog box size: 'large' for the window width with a 0.8 ratio or 'default' for a width of 600 pixels
	 */
	open: function (config)
	{
		config = config || {};
	    
	    var modelId = config.modelId;
		var contentType = config.contentType;
		var contextualParameters = Ext.applyIf(config.searchModelParameters || {}, this.getContextualParameters());

        this._validated = false;
		
		var form = this._createConfigurableFormPanel();
		
		var store = this._getStore(
            form,
            config.excludeSubContents ? config.excludeSubContents : false,
            contentType,
            modelId,
            contextualParameters
        );
        
		var box = this._createDialogBox(
            config.title, 
            config.icon, 
            config.iconCls, 
            config.minHeight || this.MIN_HEIGHT, 
            config.maxHeight || this.MAX_HEIGHT, 
            config.helpmessage1 || '', 
            config.helpmessage2 || '', 
            form,
            store,
            config.boxSize || 'default', 
            config.multiple || false,
            config.fullContents || false,
            config.callback
        );
		
		
		Ametys.data.ServerComm.callMethod({
			role: "org.ametys.cms.search.model.SearchModelHelper",
			methodName: "getRestrictedSearchModelConfiguration",
			parameters: [modelId, contextualParameters],
			callback: {
				handler: Ext.bind(this._getSearchModelCb, this, [box, store], 2),
			 	scope: this,
			 	arguments: config
			},
			errorMessage: {
				msg: "{{i18n UITOOL_SEARCH_ERROR}}",
				category: Ext.getClassName(this)
			},
			waitMessage: "{{i18n plugin.core-ui:PLUGINS_CORE_UI_LOADMASK_DEFAULT_MESSAGE}}"
		});
	},
	
    /**
     * @template
     * Get the contextual parameters used to get search model and search results.
     * @return {Object} the contextual parameters
     */
    getContextualParameters: function ()
    {
        return {
            language: this._getCurrentLanguage()
        };
    },
    
    /**
     * Provides the current language to be used.
     * @return {String} The current language
     */
    _getCurrentLanguage: function()
    {
        return Ametys.cms.language.LanguageDAO.getCurrentLanguage();
    },
    
	/**
	 * @private
	 * Callback function after getting model.
	 * Updates the model and grid columns
	 * @param {Object} result The server response as JSON object
     * @param {Object} config The configuration options of the dialog box.
	 * @param {Ametys.window.DialogBox} box The dialog box
     * @param {Ext.data.Store} store The search store
	 */
	_getSearchModelCb: function (result, config, box, store)
	{
        var form = box.down("#select-content-form");
        if (form instanceof Ametys.form.ConfigurableFormPanel)
        {
            form.configure(result['simple-criteria']);
        }
        
        var fields = Ametys.plugins.cms.search.SearchGridHelper.getFieldsFromJson(result.columns);
        Ametys.plugins.cms.search.ContentSearchTool.addDefaultContentFields(fields);
        var columns = Ametys.plugins.cms.search.SearchGridHelper.getColumnsFromJson(result.columns, false);
        
		store.setProxy({
			type: 'ametys',
			plugin: result.searchUrlPlugin,
			url: result.searchUrl,
			reader: {
                type: 'json',
                rootProperty: 'contents'
			}
		});
		
        if (result.pageSize != null && result.pageSize > 0)
        {
            store.setPageSize(result.pageSize);
        }
		
		// Update model
		store.model.replaceFields(fields, true);
		var grid = box.down("#select-content-grid");
		grid.reconfigure (store, columns);
        store.setSorters([]); // sort by score by default
		
		form.getForm().reset();
		this._navHandler(0, grid, form, store);
		
        if (form instanceof Ametys.form.ConfigurableFormPanel)
        {
            form.setValues(); // setValues must always be called for configurable form panel in order to complete its initialization
        }
        
        if (config.forceValues)
        {
            form.executeFormReady(Ext.bind(this._initValues, this, [form, config.forceValues, config.forceMode || 'hide']));
        }
		
		box.show();
	},
    

    /**
     * @private
     * Initialize the form creation with the forced values
     * @param {ConfigurableFormPanel} form The form to initialize
     * @param {Object} values The values to force
     * @param {String} forceMode The force mode : 'hide', 'readOnly' or 'disabled'
     */
    _initValues: function (form, values, forceMode)
    {
        var prefix = form.getFieldNamePrefix();
        for (key in values) 
        {
            var fd = form.getField(prefix + key);
            if (fd)
            {
                fd.setValue(values[key]);
                
                if (forceMode == 'readOnly')
                {
                    fd.setReadOnly(true)
                }
                else if (forceMode == 'disabled')
                {
                    fd.setDisabled(true)
                }
                else
                {
                    fd.setVisible(false);
                }
            }
        }
    },
	
	/**
	 * Creates the dialog box
	 * @param {String} title The title of the dialog box.
	 * @param {String} icon The full path to icon (16x16 pixels) for the dialog box. Can be null if iconCls is used
     * @param {String} iconCls CSS classes to be applied to the icon dialog box. Can be null ig icon is used.
     * @param {number} minHeight The minimum size of the bow
     * @param {number} maxHeight The maximum size of the bow
	 * @param {String} helpMsg1 The message displayed at the top of the first card
	 * @param {String} helpMsg2 The message displayed at the top of the second card
	 * @param {Ametys.form.ConfigurableFormPanel} form The configurable form panel
     * @param {Ext.data.Store} store The search store
     * @param {String} boxSize The dialog box size: 'large' for the window width with a 0.8 ratio or 'default' for a width of 600 pixels
	 * @param {Boolean} multiple True to allow multiple selections
     * @param {Boolean} fullContents True to returns {@link Ametys.cms.content.Content}. If false only the content identifiers will be returned
     * @param {Function} cbFn The callback function to call after choosing contents or canceling the contents search
     * @private
	 */
	_createDialogBox: function (title, icon, iconCls, minHeight, maxHeight, helpMsg1, helpMsg2, form, store, boxSize, multiple, fullContents, cbFn)
	{
		var grid = Ext.create ('Ext.grid.Panel', {
			flex: 1,
      	  	store: store,
      	  	itemId: 'select-content-grid',
				
      	  	scrollable: true,
			    
      	  	columns: [],
			    
      	  	selModel : {
			    mode: multiple ? 'MULTI' : 'SINGLE'
			},
			
			viewConfig: {
				loadingText: "{{i18n plugin.cms:UITOOL_SEARCH_WAITING_MESSAGE}}"
			},
			
			dockedItems: [{
					xtype: 'pagingtoolbar',
					store: store,
					dock: 'bottom',
					displayInfo: true,
					displayMsg: "{{i18n plugin.cms:UITOOL_CONTENTEDITIONGRID_RESULT_1}}{0}{{i18n plugin.cms:UITOOL_CONTENTEDITIONGRID_RESULT_2}}{1}{{i18n plugin.cms:UITOOL_CONTENTEDITIONGRID_RESULT_3}}{2}",
					emptyMsg: "{{i18n plugin.cms:UITOOL_CONTENTEDITIONGRID_NO_RESULT}}"
			}],
				
			listeners: { 'selectionchange': Ext.bind(this._onSelectionChange, this, [form], 2)}
		});
		
		var box = Ext.create('Ametys.window.DialogBox', {
			title: title,
			icon: icon,
            iconCls: icon ? null : iconCls,
            itemId: 'select-content-box',
                        
			width: boxSize === "large" ? window.innerWidth * 0.8 : 600,
			scrollable: false,
			layout: 'card',
			activeItem: 0,
			minHeight: minHeight,
			maxHeight: maxHeight,
			freezeHeight: true,
			
			items: [{
			        	 border: false,
                         layout: {
                             type: 'vbox',
                             align: 'stretch' 
                         },
			        	 items : [
			        	          {
			        	        	  xtype: 'component',
			        	        	  cls: 'a-text',
			        	        	  html: helpMsg1
			        	          },
			        	          form
			        	 ]
			         },
			         {
			        	 border: false,
			        	 layout: {
                             type: 'vbox',
                             align: 'stretch' 
                         },
			        	 items : [
			        	          {
			        	        	  xtype: 'component',
			        	        	  cls: 'a-text',
			        	        	  html: helpMsg2
			        	          },
			        	          grid
			        	 ]
			         }
			],
			
			defaultFocus: form,
			closeAction: 'destroy',
			
			referenceHolder: true,
			defaultButton: 'validate',
			
			buttons : [{
	            text: "{{i18n PLUGINS_CMS_HELPER_SELECTCONTENTBYSEARCH_BUTTON_PRECEDE}}", 
	            handler: Ext.bind(this._navHandler, this, [0, grid, form, store]),
	            disabled: true
			}, {
				reference: 'next',
				text: "{{i18n PLUGINS_CMS_HELPER_SELECTCONTENTBYSEARCH_BUTTON_NEXT}}", 
	            handler: Ext.bind(this._navHandler, this, [1, grid, form, store]),
	            disabled: false
	        }, {
	        	reference: 'validate',
				text: "{{i18n PLUGINS_CMS_HELPER_SELECTCONTENTBYSEARCH_BUTTON_OK}}",
				disabled: true,
				handler: Ext.bind(this._validate, this, [grid, fullContents, multiple, cbFn])
		    }, {
		    	text: "{{i18n PLUGINS_CMS_HELPER_SELECTCONTENTBYSEARCH_BUTTON_CANCEL}}",
		    	handler: Ext.bind(function() {box.close();}, this) 
		    }],
            
            listeners: {
                close: Ext.bind(this._onClose, this, [cbFn])
            }
		});
		
		return box;
	},
	
	/**
     * Creates the configurable form panel
     * @private
     */
	_createConfigurableFormPanel: function()
	{
        return Ext.create ('Ametys.form.ConfigurableFormPanel', {
            border: false,
            bodyPadding: 10,
            itemId: 'select-content-form',
            
            flex: 1,
            scrollable: true,
            defaultFieldConfig: {
                labelWidth: 130,
                style: 'margin-right: 30px'
            },
            
            additionalWidgetsConfFromParams: {
                contentType: 'contentType' // some widgets requires the contentType configuration  
            },
        });
    },
	
	/**
	 * This function is called when navigating into the card layout by 'Next' or 'Precede' buttons
	 * @param {Number} index the index of card layout to show
	 * @param {Ext.grid.Panel} grid The search grid
     * @param {Ametys.form.ConfigurableFormPanel} form The configurable form panel
     * @param {Ext.data.Store} store The search store
	 * @private
	 */
	_navHandler: function (index, grid, form, store)
	{
        var box = grid.findParentByType("dialog");
		if (index == 1)
		{
			if (!form.isValid())
			{
				return;
			}
			
			grid.getSelectionModel().clearSelections();
			store.loadPage(1, {
				scope: this,
				callback: function(records, operation, success) {
					if (success && records.length > 1)
					{
						grid.getSelectionModel().select(records[0]);
						grid.getView().focusRow(0);
					}
				}
			});
			
			box.defaultButton = 'validate';
		}
		
		box.getLayout().setActiveItem(index);
		
		if (index == 0)
		{
			form.focus();
			box.defaultButton = 'next'; 
		}
		
		box.getDockedItems('toolbar[dock="bottom"]')[0].items.get(0).setDisabled(index==0);
		box.getDockedItems('toolbar[dock="bottom"]')[0].items.get(1).setDisabled(index==1);
        box.getDockedItems('toolbar[dock="bottom"]')[0].items.get(2).setDisabled(index==0);
	},
	
	/**
	 * Function called before loading the store
	 * @param {Ext.data.Store} store The search store
	 * @param {Ext.data.operation.Operation} operation The object that will be passed to the Proxy to load the store
     * @param {Ametys.form.ConfigurableFormPanel} form The configurable form panel
     * @param {String} contentType The id of content type to restrict the search on. Can be null.
     * @param {Boolean} excludeSubContents True to exclude sub-contents from search
     * @param {String} modelId The id of search model to base search on.
     * @param {Object} contextualParameters The contextual parameters used to get search model and search results.
	 * @private
	 */
	_onBeforeLoad: function (store, operation, form, excludeSubContents, contentType, modelId, contextualParameters)
	{
		operation.setParams(Ext.apply(operation.getParams() || {}, {
			values: form.getJsonValues(),
			excludeSubContents: excludeSubContents,
			restrictedContentTypes: contentType != null ? [contentType] : null,
			model: modelId,
			contextualParameters: contextualParameters
		}));
	},
	
	/**
	 * Listener when the selection has changed in the grid
	 * @param {Ext.selection.Model} sm The selection model
	 * @param { Ext.data.Model[]} selected The selected records
     * @param {Ametys.form.ConfigurableFormPanel} form The configurable form panel
	 * @private
	 */
	_onSelectionChange: function (sm, selected, form)
	{
		form.findParentByType("dialog").getDockedItems('toolbar[dock="bottom"]')[0].items.get(2).setDisabled(selected.length == 0);
	},
	
	/**
	 * This function is called when validating the dialog box
	 * @param {Ext.grid.Panel} grid The search grid
	 * @param {Boolean} fullContents True to returns {@link Ametys.cms.content.Content}. If false only the content identifiers will be returned
     * @param {Boolean} multiple True to allow multiple selections
     * @param {Function} cbFn The callback function to call after choosing contents or canceling the contents search
     * @private
	 */
	_validate: function (grid, fullContents, multiple, cbFn)
	{
		var contentIds = [];
		
		var selection = grid.getSelectionModel().getSelection();
		if (selection.length == 0)
		{
			return;
		}
		
		for (var i=0; i < selection.length; i++)
		{
			contentIds.push(selection[i].get('id'));
		}
		
		if (cbFn)
		{
		    if (fullContents)
	        {
		        Ametys.cms.content.ContentDAO.getContents(contentIds, Ext.bind(this._callCbFullContents, this, [multiple, cbFn], 1));
	        }
		    else
	        {
		        multiple ? Ext.Function.defer (cbFn, 0, null, [contentIds]) : Ext.Function.defer (cbFn, 0, null, [[contentIds[0]]]);
	        }
		}
		
        this._validated = true;
        grid.findParentByType("dialog").close();
	},
	
	/**
	 * @private
	 * Invoke the callback function, providing content objects.
     * @param {Array} contents The content objects.
     * @param {Boolean} multiple True to allow multiple selections
     * @param {Function} cbFn The callback function to call after choosing contents or canceling the contents search
	 */
	_callCbFullContents: function(contents, multiple, cbFn)
	{
	    multiple ? Ext.Function.defer(cbFn, 0, null, [contents]) : Ext.Function.defer(cbFn, 0, null, [[contents[0]]]);
	},
    
    /**
     * @private
     * Listener when closing the dialog box
     * @param {Function} cbFn The callback function to call after choosing contents or canceling the contents search
     
     */
    _onClose: function(cbFn)
    {
        if (!this._validated && cbFn)
        {
            Ext.Function.defer (cbFn, 0, null, [null]);
        }
        this._validated = false;
    },
	
	/**
	 * Get the store the result grid should use as its data source
     * @param {Ametys.form.ConfigurableFormPanel} form The configurable form panel
     * @param {Boolean} excludeSubContents True to exclude sub-contents from search
     * @param {String} contentType The id of content type to restrict the search on. Can be null.
     * @param {String} modelId The id of search model to base search on.
     * @param {Object} contextualParameters The contextual parameters used to get search model and search results.
	 * @private
	 */
	_getStore: function (form, excludeSubContents, contentType, modelId, contextualParameters)
	{
		return Ext.create('Ext.data.Store', {
            remoteSort: true,
            proxy: {
                type: 'ametys',
                plugin: 'cms',
                url: 'search/list.json',
                reader: {
                    type: 'json',
                    rootProperty: 'contents'
                }
            },
            
            autoDestroy: true,
            pageSize: this.PAGE_SIZE,
            sortOnLoad: true,
            
			listeners: {
				'beforeload': {
                    fn: Ext.bind(this._onBeforeLoad, this, [form, excludeSubContents, contentType, modelId, contextualParameters], 2), 
                    scope: this
                }
			}
        });
	}
});