/*
 *  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 tool allows to search contents.
 */
Ext.define('Ametys.plugins.cms.search.ContentSearchTool', {
    extend: 'Ametys.plugins.cms.search.AbstractFacetedSearchTool',
    
    /**
     * @cfg {Boolean/String} [allowEdition=true] Set to false to disable grid edition
     */
    
    /**
     * @cfg {Boolean} [allowAdditionalExtensions=true] Set to false to disable additional extensions brought by {@link Ametys.plugins.cms.search.ContentSearchToolExtensions}
     */
    
    /**
     * @cfg {Boolean} [allowExportResults=true] Set to false to disallow the search results export
     */
    
    /**
     * @cfg {Boolean} [autoOpenSingleContent=false] Set to true to open the content when the search returns only one result.
     */
    
    /**
     * @cfg {Number/String} [workflowEditActionId=2] The workflow action to use when editing the grid
     */
    
    /**
     * @property {Ametys.plugins.cms.search.advanced.AdvancedSearchFormPanel} advancedSearchForm The advanced search form
     * @private
     */
    
    /**
     * @property {String} _advancedLanguageCriterionId the id of the criterion language for advanced search
     * @private
     */
    
    /**
     * @property {String} _modelId The id of search model
     * @private
     */
    
    /**
     * @property {String} _facetModelName The unique name of the model used by the facet store
     * @private
     */
    
    /**
     * @property {String} _exportCSVUrl The url for CSV export
     * @private
     */
    /**
     * @property {String} _exportCSVUrlPlugin The plugin name for CSV export
     * @private
     */

    /**
     * @property {String} _exportDOCUrl The url for DOC export
     * @private
     */
    /**
     * @property {String} _exportDOCUrlPlugin The plugin name for DOC export
     * @private
     */
    
    /**
     * @property {String} _exportXMLUrl The url for XML export
     * @private
     */
    /**
     * @property {String} _exportXMLUrlPlugin The plugin name for XML export
     * @private
     */
    
    /**
     * @property {String} _exportPDFUrl The url for PDF export
     * @private
     */
    /**
     * @property {String} _exportPDFUrlPlugin The plugin name for PDF export
     * @private
     */
    
    /**
     * @property {String} _printUrl The url for print
     * @private
     */
    /**
     * @property {String} _printUrlPlugin The plugin name for print
     * @private
     */
    
    /**
     * @property {String} _summaryView The name of the summary view. Not null to display a summary of the record content on a click on a '+' icon on the left of the row, null otherwise.
     * @private
     */
    /**
     * @property {Object} _initialFormatting The initial formatting of the tool
     * @private
     */
    
    /**
     * @cfg {Object} groupHeaderTpl The group header template search grid
     */
	groupHeaderTpl: [
	                '{columnName}: {name:this.formatName}',
	                {
	                    formatName: function(name) {
	                        return Ext.String.trim(name.toString());
	                    }
	                }
	            ],
    
    /**
     * @cfg {Boolean} enableGroupingMenu True to enable the grouping control in search grid columns
     */
    enableGroupingMenu: true,
    
    statics: {
        /**
         * Add the default content field mappings to a field array (content type icon, workflow step icon, ...)
         * @param {Array} fields the field array to fill.
         */
        addDefaultContentFields: function(fields)
        {
            if (fields != null && Ext.isArray(fields))
            {
                Array.prototype.push.apply(fields, this._getDefaultContentFields());
            }
        },
        
        /**
         * @protected
         * Gets the default field mappings
         * @return {Object[]} the default field mappings
         */
        _getDefaultContentFields: function()
        {
            return [
                {name: 'id', mapping: 'id'},
                {name: 'name', mapping: 'name'},
                {name: 'iconSmall', mapping: 'smallIcon'},
                {name: 'iconGlyph', mapping: 'iconGlyph'},
                {name: 'iconDecorator', mapping: 'iconDecorator'},
                {name: 'languageIcon', mapping: 'properties.contentLanguage.icon'},
                {name: 'workflowStepId', mapping: 'properties.workflowStep.stepId'},
                {name: 'workflowStepIcon', mapping: 'properties.workflowStep.smallIcon'}
            ];
        },
        
        /**
         * @property {Number}
         * @readonly
         * @static
         * The default limit for the 'show column' menu of the result grid header, to display a filter on the columns
         */
        HEADER_SHOW_COLUMN_MENU_DEFAULT_FILTERING_LIMIT: 10
    },
    
    /**
     * @readonly
     * @property {Boolean} contentSearchTabCompatible Specify this tool is compatible with the 'content search tab'.
     */
    contentSearchTabCompatible: true,
    
    /**
     * @cfg {Number} [headerShowColumnMenuFilteringLimit=Ametys.plugins.cms.search.ContentSearchTool.HEADER_SHOW_COLUMN_MENU_DEFAULT_FILTERING_LIMIT] The limit for the 'show column' menu of the result grid header, to display a filter on the columns
     */
    
    _getDefaultContentFields: function()
    {
        var defaultContentFields = this.statics()._getDefaultContentFields();
        if (this._summaryView)
        {
            defaultContentFields.push(
                {name:'rowBodyContent', defaultValue: ''}, 
                {name:'rowBodyLoaded', defaultValue: false});
        }
        return defaultContentFields;
    },
    
    constructor: function(config)
    {
        this.callParent(arguments);
        
        this.workflowEditActionId = parseInt(config.workflowEditActionId || 2);
        
        this.allowAdditionalExtensions = config.allowAdditionalExtensions != false && config.allowAdditionalExtensions != "false"; // true by default
        this.allowEdition = config.allowEdition != false && config.allowEdition != "false"; // true by default
        this.allowExportResults = config.allowExportResults != false && config.allowExportResults != "false"; // true by default
        this.autoOpenSingleContent = config.autoOpenSingleContent == true || config.autoOpenSingleContent == "true"; // false by default
        
        if (!this.headerShowColumnMenuFilteringLimit)
        {
            this.headerShowColumnMenuFilteringLimit = this.statics().HEADER_SHOW_COLUMN_MENU_DEFAULT_FILTERING_LIMIT;
        }
        
        Ametys.message.MessageBus.on(Ametys.message.Message.ARCHIVED, this._onContentArchived, this);
        Ametys.message.MessageBus.on(Ametys.message.Message.UNARCHIVED, this._onContentUnarchived, this);
    },
    
    /**
     * @protected
     * @template
     * Get the role of the message target to use to listen and write to the bus.
     * The default impl returns Ametys.message.MessageTarget#CONTENT
     * @return {String} The role such as 'content'.
     */
    getMessageTargetRole: function()
    {
        return Ametys.message.MessageTarget.CONTENT;
    },
    
    /**
     * @protected
     * Gets the configuration object for the the RowRender plugin
     * @return {Object} the configuration object for the the RowRender plugin
     */
    _getRowRenderPluginCfg: function()
    {
        return {
            ptype: 'rowexpander',
            id: 'rowexpander',
            expandOnDblClick: false,
            rowBodyTpl: new Ext.XTemplate('{rowBodyContent}')
        };
    },
    
    /**
     * @private
     * Listener on the view of the result grid fired when the RowBody is expanded.
     * Loads from server the content of the RowBody to set.
     * @param {HTMLElement} rowNode The <tr> element which owns the expanded row.
     * @param {Ext.data.Model} record The record providing the data.
     * @param {HTMLElement} expandRow The <tr> element containing the expanded data.
     */
    _onExpandBody: function(rowNode, record, expandRow)
    {
        if (record.get('rowBodyLoaded'))
        {
            return;
        }
        
        function updateRowBodyContent(data, arguments)
        {
            if (data == null)
            {
                // An error occured (such as the metadata set does not exist for the given content)
                record.set('rowBodyContent', '', {dirty: false});
            }
            else
            {
                var result = Ext.dom.Query.selectValue(">", data);
                record.set('rowBodyContent', result, {dirty: false});
            }
            record.set('rowBodyLoaded', true, {dirty: false});
        };
        
        Ametys.data.ServerComm.send({
            workspace: 'cms',
            url: "_content.html?contentId=" + record.get('id') + "&viewName=" + this._summaryView,
            parameters: {},
            priority: Ametys.data.ServerComm.PRIORITY_MAJOR,
            callback: {
                handler: updateRowBodyContent,
                scope: this
            },
            waitMessage: {
                msg: "{{i18n UITOOL_SEARCH_ROWBODY_WAITING_MESSAGE}}",
                target: this.grid
            },
            errorMessage: false,
            responseType: 'text'
        });
    },
    
    /**
     * @protected
     * Get the config to be used to create the result grid.
     * @param {Ext.data.Store} store The store to use for the grid
     * @return {Object} The config object
     */
    _getResultGridCfg: function(store)
    {
        return { 
            store: store,
            
            region: 'center',
            split: true,
            border: false,
            
            allowEdition: this.allowEdition,
            workflowEditActionId: this.workflowEditActionId,
            
            stateful: true,
            stateId: this.getStateId() + "$grid",
            
            columns: [],
            showColumnMenuFilteringLimit: this.headerShowColumnMenuFilteringLimit,
            showColumnMenuSortOption: true,
            
            messageTarget: this.getMessageTargetRole(),
            
            selModel : {
                mode: 'MULTI'
            },
            
            plugins: [this._getRowRenderPluginCfg(), {
                ptype: 'multisort',
                maxNumberOfSortFields: 3
            }],
            
            features: [{
                ftype: 'grouping',
                enableGroupingMenu: this.enableGroupingMenu,
                groupHeaderTpl: this.groupHeaderTpl
            }],
            
            viewConfig: {
                loadingText: "{{i18n plugin.cms:UITOOL_SEARCH_WAITING_MESSAGE}}"
            },
            
            listeners: {
                'itemdblclick': {fn: function (view, record, item, index, e) { this.openContent(record); }, scope: this},
                
                'outofdate': Ext.bind(this.showOutOfDate, this, [false], false),
                'dirtychange': Ext.bind(this.onDirtyChange, this)
            }
        };
    },
    
    _createResultGrid: function(store)
    {
        return Ext.create("Ametys.cms.content.EditContentsGrid", this._getResultGridCfg(store));
    },
    
    close: function (manual)
    {
        if (manual && this.grid.isModeEdition())
        {
            var me = this;
            this.grid.discardChanges(true, function() {
                Ametys.plugins.cms.search.ContentSearchTool.superclass.close.call(me, [manual])
            });
        }
        else
        {
            this.callParent(arguments);
        }
    },
    
    /**
     * Open content (on double-click for example)
     * @param {Ext.data.Model} record The content to open
     */
    openContent: function (record)
    {
        Ametys.cms.content.EditContentsGrid.openContent (record);
    },
    
    /**
     * @private
     * Listener when the grid is dirty or not
     * @param {Boolean} dirty True if the grid is dirty
     */
    onDirtyChange: function(dirty)
    {
        this.setDirty(dirty);
        
        // Hide the search form and facet
        this._getFormPanel().setVisible(!dirty);
        this.facetPanel.setVisible(this.facetPanel.shouldBeVisible && !dirty);
    },
    
    getSearchFormPanelSubItems: function()
    {
        var subitems = this.callParent(arguments); // array with one item: the simple search form. We will add the advanced search form
        this.advancedSearchForm = this._createAdvancedSearchFormPanel();
        subitems.push(this.advancedSearchForm);
        return subitems;
    },
    
    /**
     * Create the advanced form panel.
     * @private
     */
    _createAdvancedSearchFormPanel: function()
    {
        return Ext.create('Ametys.plugins.cms.search.advanced.AdvancedSearchFormPanel', {
            itemId: 'advanced-search-form-panel',
            border: false,
            bodyPadding: 10,
            cls: 'search-tool-advanced-form-panel',
            scrollable: true
        });
    },
    
    getMBSelectionInteraction: function() 
    {
        return Ametys.tool.Tool.MB_TYPE_ACTIVE;
    },
    
    _getStoreCfg: function()
    {
        // The proxy of this store is automatically configured in the _configureProxy method
        return {
            remoteSort: true,
            sortOnLoad: true,
             
            listeners: {
                'beforeload': {fn: this._onBeforeLoad, scope: this},
                'load': {fn: this._onLoad, scope: this}
            }
        };
    },
    
    _getReaderCfg: function()
    {
        return {
            type: 'json',
            rootProperty: 'contents',
            transform: {
                fn: this._transformSearchData,
                scope: this
            }
//          messageProperty: 'message'
        };
    },
    
    _createModel: function(fields)
    {
        fields = fields || [
                    {name: 'id', mapping: 'id'},
                    {name: 'icon-small', mapping: 'smallIcon'},
                    {name: 'icon-glyph', mapping: 'glyphIcon'},
                    {name: 'icon-decorator', mapping: 'iconDecorator'},
                    {name: 'name', mapping: 'name'},
                    {name: 'title', mapping: 'title', type: 'string'},
                    {name: 'lastModified', type: 'date', mapping: 'properties.lastModified'},
                    {name: 'contributor', mapping: 'properties.contributor.login', type: 'string'},
                    {name: 'contributorDisplay', mapping: 'properties.contributor.fullname', type: 'string'},
                    {name: 'workflow-step', mapping: 'properties.workflowStep.name'},
                    {name: 'workflow-step-id', mapping: 'properties.workflowStep.stepId'},
                    {name: 'workflow-icon-small', mapping: 'properties.workflowStep.smallIcon'}
        ];
                
        this.callParent(arguments);
    },
    
    /**
     * Transform search data before loading it. Used to extract error and facet results.
     * @param data {Object} The original data object.
     * @return {Object} The transformed data object.
     * @private
     */
    _transformSearchData: function(data)
    {
        this._error = data.error;
        this._facets = data.facets;
        
        // Return the data untouched.
        return data;
    },
    
    _getSearchFormPanelBBar: function()
    {
        var items = this.callParent(arguments);
        if (this.allowAdditionalExtensions)
        {
            var me = this;
            var leftAddOns = this._getAdditionalExtensions('l'); 
            if (leftAddOns)
            {
                Ext.Array.each (leftAddOns, function (buttonConfig) {
                    items.push (Ext.apply(buttonConfig, {toolId: me.getId()}));
                });
            }
        }
        
        // Right items.
        items.push ('->');
        
        this._getSwitchModeButtons(items);
        
        if (this.allowAdditionalExtensions)
        {
            var rightAddOns = this._getAdditionalExtensions('r'); 
            if (rightAddOns && rightAddOns.length > 0)
            {
                Ext.Array.each (rightAddOns, function (buttonConfig) {
                    items.push (Ext.apply(buttonConfig, {toolId: me.getId()}));
                });
            }
        }
        
        return items;
    },
    
    /**
     * @protected
     * Get the additional buttons to switch to simple or advanced mode
     * @param {Ext.button.Button} items the toolbar items
     */
    _getSwitchModeButtons: function (items)
    {
        items.push(
                {
                    // Switch to simple mode
                    itemId: 'toggle-simple-search-mode',
                    text: "{{i18n plugin.cms:PLUGINS_CMS_UITOOL_SEARCH_BUTTON_SIMPLE_SEARCH}}",
                    iconCls: 'ametysicon-magnifier12',
                    enableToggle: true,
                    toggleGroup: this.getId() + '-search-mode',
                    allowDepress: false,
                    toggleHandler: this._toggleSimpleSearchMode,
                    scope: this,
                    hidden: true,
                    tooltip: {
                        title: "{{i18n plugin.cms:PLUGINS_CMS_UITOOL_SEARCH_BUTTON_SIMPLE_SEARCH}}",
                        text: "{{i18n plugin.cms:PLUGINS_CMS_UITOOL_SEARCH_BUTTON_SIMPLE_SEARCH_TOOLTIP}}",
                        glyphIcon: 'ametysicon-magnifier12',
                        inribbon: false
                    }
                },{
                    // Switch to advanced mode
                    itemId: 'toggle-advanced-search-mode',
                    text: "{{i18n plugin.cms:PLUGINS_CMS_UITOOL_SEARCH_BUTTON_ADVANCED_SEARCH}}",
                    iconCls: 'ametysicon-magnifier12', // FIXME decorator-ametysicon-three115
                    enableToggle: true,
                    allowDepress: false,
                    toggleGroup: this.getId() + '-search-mode',
                    toggleHandler: this._toggleAdvancedSearchMode,
                    hidden: true,
                    scope: this,
                    tooltip: {
                        title: "{{i18n plugin.cms:PLUGINS_CMS_UITOOL_SEARCH_BUTTON_ADVANCED_SEARCH}}",
                        text: "{{i18n plugin.cms:PLUGINS_CMS_UITOOL_SEARCH_BUTTON_ADVANCED_SEARCH_TOOLTIP}}",
                        iconDecorator: 'decorator-ametysicon-three115',
                        glyphIcon: 'ametysicon-magnifier12',
                        inribbon: false
                }
        });
    },
    
    /**
     * Show or hide the "switch mode" buttons.
     * @param {Boolean} visible `true` to display the buttons, `false` to hide them.
     * @private
     */
    _showHideSwitchModeButtons: function (visible)
    {
        var btn = this.searchPanel.getDockedItems('toolbar[dock="bottom"]')[0].getComponent('toggle-simple-search-mode');
        if (btn)
        {
            btn.setVisible(visible);
        }
        
        btn = this.searchPanel.getDockedItems('toolbar[dock="bottom"]')[0].getComponent('toggle-advanced-search-mode');
        if (btn)
        {
            btn.setVisible(visible);
        }
    },
    
    /**
     * @protected
     * Get the additional extensions
     * @param {String} location The button location ('l' for left or 'r' for right)
     * @return {Array} The additional buttons
     */
    _getAdditionalExtensions: function(location) 
    {
        return Ametys.plugins.cms.search.ContentSearchToolExtensions.getAdditionalButtons(location);
    },
    
    /**
     * Get the current search parameters
     * @return {Object} The current search parameters
     */
    getCurrentSearchParameters: function ()
    {
        var params = {
                id: this._modelId,
                values: this.getSearchValues(),
                sort: this.getCurrentSorters()
        };
        
        if (this.isAdvancedMode())
        {
            params.searchMode = 'advanced';
            params.language = this.advancedSearchForm.getLanguage();
        }
        
        return params;
    },
    
     /**
     * Get the current search formatting (active columns, sorts, facets)
     * @return {Object} The current formatting
     */
    getCurrentFormatting: function()
    {
        var columns = [];
        
        var columnsState = this.grid.getState().columns;
        var gridColumns = this.grid.getColumns();
        for (let i in gridColumns)
        {
            let column = gridColumns[i];
            // Column without dataIndex are autogenerated by ExtJS
            if (column.dataIndex)
            {
                let initialConfig = column.initialConfig;
                let state = columnsState[i];
                if (state.id == initialConfig.stateId)
                {
                    // Copy the state without the id to update info like hidden, width etc…
                    delete state.id;
                    initialConfig = Ext.apply(initialConfig, state);
                }
                
                // formatting need to be serializable so remove the function.
                delete initialConfig.renderer;
                columns.push(initialConfig);
            }
        }
        
        var params = {
            id: this._modelId,
            sort: this.getCurrentSorters(),
            facets: this.getFacetValues(),
            columns: columns,
            state: this.grid.getState()
        };
        return params;
    },
    
    /**
     * Apply the initial formatting for this search tool
     */
    resetFormatting: function()
    {
        if (this.applyFormatting)
        {
            // Reset user prefs for the grid
            var gridStateId = this.grid.getStateId();
            Ext.state.Manager.getProvider("workspace").set(gridStateId, {});
            
            // Aplly initial formatting
            this.applyFormatting(this._getFormattingForReset());
            
            // Grouping
            this.grid.getStore().clearGrouping();
        }
    },
    
    /**
     * @protected
     * Get the formatting to apply when resetting
     * @return {Object} the formatting to apply when resetting
     */
    _getFormattingForReset: function()
    {
        return this._initialFormatting;
    },
    
    /**
     * Apply the given formatting
     * @param {Object} formatting The formatting to apply
     * @param {Object} [formatting.state] The state to apply to the grid
     * @param {Object} [formatting.facets] The facets to select
     * @param {Object} [formatting.sort] The sorters to apply
     */
    applyFormatting: function(formatting)
    {
        this._updatingModel = true; // block search during formatting
        
        if (formatting.sort)
        {
            this.store.sorters = null; // There is no other way to clean old sorters
            this.store.setSorters(formatting.sort);
        }
        
        if (formatting.columns)
        {
            formatting.columns.forEach(column => {
                column.renderer = Ametys.plugins.cms.search.SearchGridHelper.getRenderer(column);
                // In previous version, the column formatting was the initial formatting
                // without the state applied to it.
                // Previous version stored the state id in the id field. So we use it to detect the case and apply the state for retrocompatibility
                if (column.id != null && formatting.state)
                {
                    delete column.id;
                    let columnsState = formatting.state.columns;
                    for (let i in columnsState)
                    {
                        if (columnsState[i].id == column.stateId)
                        {
                            let state = columnsState[i];
                            delete state.id;
                            column = Ext.apply(column, state);
                        }
                    }
                }
            });
            
            // N.B: this.grid.applyState(formatting.state); is not sufficient (columns order is lost and does not work well)
            this.grid.reconfigure(this.store, formatting.columns, null, false /* applyState=false: do not use current state, use columns/sort provided by formatting */);
        }
        
        this._applyFacets(formatting.facets, function(withFacets) {
            this._updatingModel = false; // unblock search
            this._launchSearch({faceting: withFacets, sort: formatting.sort}); // launch search with this formatting
        }, this);
        
    },
    
    /**
     * @private
     * Apply the facets (select facets in the tree)
     * @param {Object} facets the facets to apply
     * @param {Function} callback the callback function to invoke after applying facets. Parameters are :
     * @param {Boolean} callback.withFacets if facets are active (eg. at least one facet was selected)
     * @param {Object} scope The scope for callback function
     */
    _applyFacets: function(facets, callback, scope)
    {
        if (facets && this.facetPanel)
        {
            if (this.facetPanel.getRootNode().childNodes.length == 0)
            {
		        // The facets are not visible because the search was never launch
                // we need to launch the search first before applying the facets
                
                this._updatingModel = false; // unblock search
                
                // launch a search without facets
                this._launchSearch({faceting: false}, function () {
                    this._updatingModel = true; // block search 
                    this._applyFacets(facets, callback);
                });
            }
            else
            {
                var facetActive = false;
                
                Ext.Array.forEach(this.facetPanel.getRootNode().childNodes, function(node) {
                    var facetGroup = node.get('name');
                    
                    if (facets[facetGroup])
                    {
                        // make sure the facets group is expanded
                        node.expand();
                    }
                    
                    Ext.Array.forEach(node.childNodes, function (facet) {
                        if (facets[facetGroup] && Ext.Array.contains(facets[facetGroup], facet.get('value')))
                        {
                            facet.set('checked', true);
                            facetActive = true;
                        }
                        else
                        {
                            facet.set('checked', false);
                        }
                    });
                });
                
                // Invoked callback function
                callback.call(scope || this, facetActive);
            }
        }
        else
        {
            callback.call(scope || this, false); // no facets
        }
    },
    
    _possiblyCallInternalSetParams: function()
    {
        // Overriden method, without calling parent because we do not want to always call #_internalSetParams (in case there is no model id for example)
        
        // Was not already opened ?
        if (!this._modelId)
        {
            // First render will be done by onActivate...
            this.setTitle(this.getParams().title || this.getInitialConfig('title'));
            this.setDescription(this.getParams().description || this.getInitialConfig('description') || this.getInitialConfig('default-description'));
            this.setToolHelp(this.getParams()['help'] || this.getInitialConfig('help'));
            this.setGlyphIcon(this.getParams()['icon-glyph'] || this.getInitialConfig('icon-glyph'));
            this.setIconDecorator(this.getParams()['icon-decorator'] || this.getInitialConfig('icon-decorator'));
            this.setIconDecoratorType(this.getParams()['icon-decorator-type'] || this.getInitialConfig('icon-decorator-type'));
            this.setSmallIcon(this.getParams()['icon-small'] || this.getInitialConfig('icon-small'));
            this.setMediumIcon(this.getParams()['icon-medium'] || this.getInitialConfig('icon-medium'));
            this.setLargeIcon(this.getParams()['icon-large'] || this.getInitialConfig('icon-large'));
        }
        // Was not already opened ?
        else
        {
            this._internalSetParams(false);
        }
    },
    
    onActivate: function()
    {
        this.callParent(arguments);
        
        this._internalSetParams(false);
    },
    
    _retrieveCriteriaAndColumns: function(force)
    {
        var params = this.getParams();
        var modelId = params.modelId || params.id;
        if (this._modelId != modelId || force)
        {
            // First render will be done by onActivate
            this._modelId = modelId;
            
            this._updatingModel = true;
            
            Ametys.data.ServerComm.callMethod({
                role: "org.ametys.cms.search.model.SearchModelHelper",
                methodName: "getSearchModelConfiguration",
                parameters: [this._modelId, this.getContextualParameters()],
                callback: {
                    handler: this._getSearchModelCb,
                    scope: this,
                    arguments: {
                        toolParams: params
                    }
                },
                errorMessage: {
                    msg: "{{i18n plugin.cms:UITOOL_SEARCH_ERROR}}",
                    category: Ext.getClassName(this)
                }
            });
        }
        else
        {
            this._initSearchForm(params);
            this._updatingModel = false;
            if (this._startSearchAtOpening)
            {
                this.refresh();
            }
        }
    },
    
    /**
     * @template
     * Get the contextual parameters used to get search model and search results.
     * @return {Object} the contextual parameters
     */
    getContextualParameters: function()
    {
        var params = Ametys.getAppParameters();

        // default language
        params.language = Ametys.cms.language.LanguageDAO.getCurrentLanguage();

        return params;
    },
    
    /**
     * Get the contextual parameters for search.
     * @return {Object} the contextual parameters.
     */
    getSearchContextualParameters: function()
    {
        var params = this.getContextualParameters();
        
        if (this.isAdvancedMode())
    	{
        	params.language = this.advancedSearchForm.getLanguage();
    	}
        
        return params;
    },
    
    /**
     * @private
     * Callback function after getting model.
     * Updates the model and grid columns
     * @param {Object} result The server response
     * @param {Object} params The callback arguments
     */
    _getSearchModelCb: function (result, params)
    {
        this._exportCSVUrl = result.exportCSVUrl;
        this._exportCSVUrlPlugin = result.exportCSVUrlPlugin;
        this._exportDOCUrl = result.exportDOCUrl;
        this._exportDOCUrlPlugin = result.exportDOCUrlPlugin;
        this._exportXMLUrl = result.exportXMLUrl;
        this._exportXMLUrlPlugin = result.exportXMLUrlPlugin;
        this._exportPDFUrl = result.exportPDFUrl;
        this._exportPDFUrlPlugin = result.exportPDFUrlPlugin;
        this._printUrl = result.printUrl;
        this._printUrlPlugin = result.printUrlPlugin;
        this._summaryView = result.summaryView;
        if (this._summaryView)
        {
            this.grid.getView().on('expandbody', this._onExpandBody, this);
        }
        else
        {
            // workaround for "disabling" the rowexpander plugin when no summary view is defined
            this.grid.on('afterlayout', function(grid) {
                Ext.suspendLayouts();
                var rowExpanderPlugin = grid.getPlugin('rowexpander'),
                    rowRenderColumn = rowExpanderPlugin && rowExpanderPlugin.expanderColumn;
                if (rowRenderColumn && !rowRenderColumn.isHidden())
                {
                    rowRenderColumn.hide();
                }
                Ext.resumeLayouts(true);
            });
        }
        
        var advancedCriteria = result['advanced-criteria'];
        if (Ext.isObject(advancedCriteria) && !Ext.Object.isEmpty(advancedCriteria))
        {
            this._advancedSearchEnabled = true;
            this._advancedLanguageCriterionId = advancedCriteria.language ? advancedCriteria.language.name : null;
            this._showHideSwitchModeButtons (true); 
            // Initialize the advanced search form (from the advanced criteria).
            this.advancedSearchForm.initialize(result);
        }
        else
        {
            // Disable the advanced form and the search mode buttons.
            if (this.advancedSearchForm) { this.advancedSearchForm.hide() };
            this._showHideSwitchModeButtons (false);
        }
        
        if (this.facetPanel)
        {
        	// Hide or show the facet panel depending on if the model has facets.
            this.facetPanel.setVisible(result.hasFacets);
            this.facetPanel.shouldBeVisible = result.hasFacets;
        }
        
        var toolParams = params.toolParams;
        this._configureSearchTool(result['simple-criteria'], result, toolParams);
        
        // Set the 'initial' formatting
        this._initialFormatting = this._getInitialFormatting(result.columns);
    },
    
    /**
     * @private
     * Gets the 'initial' formatting
     * @param {Object[]} initialColumns The initial column configurations
     * @return {Object} The 'initial' formatting
     */
    _getInitialFormatting: function(initialColumns)
    {
        var columns = Ametys.plugins.cms.search.SearchGridHelper.getColumnsFromJson(initialColumns, true, null/* Do NOT pass this.grid because we do NOT want grid state information */);
        Ext.Array.forEach(columns, function(column) {
            delete column.renderer; // avoid storing renderer as function
        });
        
        var sort = Ametys.plugins.cms.search.SearchGridHelper.getSortersFromJson(columns, null/* Do NOT pass this.grid because we do NOT want grid state information */);
        
        var initialFormatting = {
            id: this._modelId,
            sort: sort,
            facets: {},
            columns: columns
        };
        return initialFormatting;
    },
    
    _initSearchForm: function(params)
    {
        // TODO Compute the values from params.values AND the criterion default values
        // and initialize the form with this.form.setValues (which fires the 'formready' event).
        if (params.values)
        {
            var advancedMode = params.searchMode == 'advanced';
            
            var btn = this.searchPanel.getDockedItems('toolbar[dock="bottom"]')[0].getComponent('toggle-advanced-search-mode');
            if (btn)
            {
                btn.toggle(advancedMode);
            }
            
            if (advancedMode)
            {
                this.advancedSearchForm.initForm(params.values, params.language);
                var btn = this.searchPanel.getDockedItems('toolbar[dock="bottom"]')[0].getComponent('toggle-advanced-search-mode');
                if (btn)
                {
                    btn.toggle(true);
                }
            }
            else
            {
                this.callParent(arguments);
                
                var btn = this.searchPanel.getDockedItems('toolbar[dock="bottom"]')[0].getComponent('toggle-simple-search-mode');
                if (btn)
                {
                    btn.toggle(true);
                }
            }
        }   
        else
        {
            var btn = this.searchPanel.getDockedItems('toolbar[dock="bottom"]')[0].getComponent('toggle-simple-search-mode');
            if (btn)
            {
                btn.toggle(true);
            }
        }
        
        this._onFormInitialized();
    },
    
    refresh: function ()
    {
        if (this.grid.isModeEdition())
        {
            var me = this;
            this.grid.discardChanges(true, function() {
                me.refresh();
            });
        }
        else
        {
            this.callParent(arguments);
        }
    },
    
    /**
     * Function called before loading the store
     * @param {Ext.data.Store} store The store
     * @param {Ext.data.operation.Operation} operation The object that will be passed to the Proxy to load the store
     * @protected
     */
    _onBeforeLoad: function(store, operation)
    {
        if (this.grid && !this._updatingModel && (!(this.form instanceof Ametys.form.ConfigurableFormPanel) ||  this.form.isFormReady()))
        {
            this.grid.getView().unmask();
            
            if (!this.isAdvancedMode() && !this.form.isValid())
            {
                this._stopSearch();
                return false;
            }
            
            operation.setParams( Ext.apply(operation.getParams() || {}, {
                model: this._modelId,
                values: this.getSearchValues()
            }));
            
            if (this.isAdvancedMode())
            {
                operation.setParams(Ext.apply(operation.getParams(), {
                    searchMode: 'advanced'
                }));
            }
            
            // Facet handling
            this.facetValues = this._faceting ? this.getFacetValues() : {};
            operation.setParams(Ext.apply(operation.getParams(), {
                facetValues: this.facetValues,
                contextualParameters: this.getSearchContextualParameters()
            }));
            
            this._error = null;
            this._facets = null;
        }
        else
        {
            // avoid use less requests at startup (applyState...)
            return false;
        }
    },
    
    /**
     * Determines if the activated mode is the advanced mode
     * @return true if the activated mode is the advanced mode
     */
    isAdvancedMode: function()
    {
        if (this.searchPanel.getLayout().type == 'card')
        {
            var activeSearchFormId = this.searchPanel.getLayout().getActiveItem().getItemId();
            return activeSearchFormId == 'advanced-search-form-panel';
        }
        else
        {
            return false;
        }
    },
    
    /**
     * Get the search values
     * @return {Object} The search values
     */
    getSearchValues: function()
    {
        if (this.isAdvancedMode())
        {
            return Ext.clone(this.advancedSearchForm.getValueTree());
        }
        else
        {
            return Ext.clone(this.form.getJsonValues());
        }
    },
    
    /**
     * Initialize the advanced search form criteria from the simple values.
     */
    initAdvancedFromSimple: function()
    {
        var switchMask = Ext.create('Ext.LoadMask', {
            target: this.mainPanel,
            msg: "{{i18n plugin.cms:PLUGINS_CMS_UITOOL_SEARCH_ADVANCED_SEARCH_LOADING}}",
            msgCls: 'ametys-mask-unloading'
        });
        switchMask.show();
        
        // Send the language value and those of other criteria separately
        var simpleValues = Ext.clone(this.form.getJsonValues());
        
        if (this._advancedLanguageCriterionId)
        {
            var language = simpleValues[this._advancedLanguageCriterionId];
            delete simpleValues[this._advancedLanguageCriterionId];
        }
        
        this.advancedSearchForm.initForm({
            simpleValues: simpleValues
        }, language);
        
        if (switchMask)
        {
            switchMask.hide();
        }
    },
    
    /**
     * Function called after loading results
     * @param {Ext.data.Store} store The store
     * @param {Ext.data.Model[]} records An array of records
     * @param {Boolean} successful True if the operation was successful.
     * @param {Ext.data.operation.Operation} operation Operation performed by the proxy
     * @protected
     */
    _onLoad: function (store, records, successful, operation)
    {
        if (operation.aborted)
        {
            // Load has been canceled. Do nothing.
            return;
        }
        
    	// Hack to process groups locally even if remoteSort is enabled.
        store.getData().setAutoGroup(true);
        
        this._setGridDisabled(false);
        
        if (!successful)
        {
            Ametys.log.ErrorDialog.display({
                title: "{{i18n plugin.cms:UITOOL_SEARCH_ERROR_TITLE}}",
                text: "{{i18n plugin.cms:UITOOL_SEARCH_ERROR}}",
                details: "",
                category: "Ametys.plugins.cms.search.ContentSearchTool"
            });
            return;
        }
        
//      var rawData = store.getProxy().getReader().rawData;
        
        if (this._error)
        {
            Ametys.log.ErrorDialog.display({
                title: "{{i18n plugin.cms:UITOOL_SEARCH_ERROR_QUERY_TITLE}}",
                text: this._error,
                details: "",
                category: "Ametys.plugins.cms.search.ContentSearchTool"
            });
        }
        
        // Load facet store
        if (this.facetPanel.isVisible() && this._facets)
        {
            
            if (!this._faceting)
            {
                // TODO Create a brand new memory proxy with the data and set it, instead of just changing the 'data' property?
                this.facetPanel.getStore().getProxy().setData(this._facets);
                this.facetPanel.getStore().load();
                this.facetPanel.getRootNode().expand();
            }
            else
            {
                this._updateFacetPanel(this._facets);
            }
        }
        
        if (records.length == 0)
        {
            this.grid.getView().mask("{{i18n plugin.cms:UITOOL_CONTENTEDITIONGRID_NO_RESULT}}", 'ametys-mask-unloading');
        }
        
        if (this.autoOpenSingleContent && records.length == 1)
        {
            // Direct open the content
            this.openContent(records[0]);
        }
        
        if (this._expanded || records.length == 0) 
        {
            this.searchPanel.expand();
        }
        else
        {
            this.searchPanel.collapse();
        }
        
        /* var columns = this.grid.columnManager.columns;
        Ext.Array.each (columns, function (column) {
            column.autoSize();
        });*/
        
        var toolParams = this.getCurrentSearchParameters();
        toolParams.title = this.getTitle();
        toolParams['icon-small'] = this.getSmallIcon();
        toolParams['icon-medium'] = this.getMediumIcon();
        toolParams['icon-large'] = this.getLargeIcon();
        toolParams.startSearchAtOpening = true;
        
        var description = this.getDescription();
        if (this._advancedSearchEnabled == true)
        {
            description += '<br/><u>' + "{{i18n PLUGINS_CMS_UITOOL_SEARCH_NAVHISTORY_DESCRIPTION_MODE_LABEL}}" + '</u> : ';
            if (this.isAdvancedMode())
            {
                description += "{{i18n PLUGINS_CMS_UITOOL_SEARCH_NAVHISTORY_DESCRIPTION_MODE_ADVANCED}}";
            }
            else
            {
                description += "{{i18n PLUGINS_CMS_UITOOL_SEARCH_NAVHISTORY_DESCRIPTION_MODE_SIMPLE}}";
            }
        }
        
        description += "<br/><a>{{i18n PLUGINS_CMS_UITOOL_SEARCH_NAVHISTORY_SEARCH_ACTION}}</a>";
        
        var toolId = this.getFactory().getId();
        var id = toolId + '$' + this._modelId;
        // Save to history dao
        Ametys.navhistory.HistoryDAO.addEntry({
            id: id + "$" + Ext.JSON.encode(toolParams.values).replace(/"/g, "").replace(/\\/g, "").replace(/'/g, "").replace(/ /g, "_"), // Unique ID for this search
            label: this.getTitle(),
            description: description,
            iconGlyph: this.getGlyphIcon(),
            iconSmall: this.getSmallIcon(),
            iconMedium: this.getMediumIcon(),
            iconLarge: this.getLargeIcon(),
            type: Ametys.navhistory.HistoryDAO.SEARCH_TYPE,
            action: function () {Ametys.tool.ToolsManager.openTool(toolId, toolParams)}
        });
    },
    
    sendCurrentSelection: function()
    {
        this.grid.sendCurrentSelection();
    },
    
    /**
     * @inheritdoc
     * @param {Object/Ext.button.Button} [params] Additional parameters, or the button which was clicked if this function was bound to a button
     * @param {Boolean} [params.faceting] true if the search was launch from facet tree.
     * @param {Object} [params.sort] the sorters to apply
     * @param {Function} [callback] Callback function to execute after search
     * @param {Object} [scope] The scope for callback function
     */
    _launchSearch: function(params, callback, scope)
    {
        params = params || {};
        // If search launched by the search button, and sorters have to be changed (i.e. one of the textual search criteria is not empty), change the sorters
        var searchButton = params.isButton && params.itemId == 'search';
        if (searchButton)
        {
            
            var sorters = Ametys.plugins.cms.search.SearchGridHelper.getDefaultSorters(this.form.getValues());
            if (sorters !== undefined)
            {
                this.store.sorters = null; // clean old sorters
                this.store.setSorters(sorters);
            }
        }
        
        this.callParent([params, callback, scope]);
    },
    
    /**
     * Change the search mode.
     * @param {Ext.button.Button} button The pressed button
     * @param {Boolean} state The state
     * @private
     */
    _toggleAdvancedSearchMode: function(button, state)
    {
        if (this.searchPanel.getLayout().type == 'card')
        {
            // Load the advanced search form from the simple search form values.
            this.searchPanel.getLayout().setActiveItem('advanced-search-form-panel');
            this.searchPanel.setTitle("{{i18n PLUGINS_CMS_UITOOL_ADVANCED_SEARCH_PANEL_TITLE}}");
            
            this.initAdvancedFromSimple();
        }
    },
    
    /**
     * Change the search mode.
     * @param {Ext.button.Button} button The pressed button
     * @param {Boolean} state The state
     * @private
     */
    _toggleSimpleSearchMode: function(button, state)
    {
        if (this.searchPanel.getLayout().type == 'card')
        {
            // Active the simple search form
            this.searchPanel.getLayout().setActiveItem('simple-search-form-panel');
            this.searchPanel.setTitle("{{i18n plugin.cms:UITOOL_SEARCH_CRITERIA}}");
        }
    },
        
    /**
     * Get the plugin for XML export
     * @return {String} The plugin name
     */
    getExportXMLUrlPlugin: function ()
    {
        return this._exportXMLUrlPlugin;
    },
    
    /**
     * Get the plugin for CSV export
     * @return {String} The plugin name
     */
    getExportCSVUrlPlugin: function ()
    {
        return this._exportCSVUrlPlugin;
    },
    
    /**
     * Get the plugin for doc export
     * @return {String} The plugin name
     */
    getExportDOCUrlPlugin: function ()
    {
        return this._exportDOCUrlPlugin;
    },
    
    /**
     * Get the plugin for PDF export
     * @return {String} The plugin name
     */
    getExportPDFUrlPlugin: function ()
    {
        return this._exportPDFUrlPlugin;
    },
    
    /**
     * Get the plugin for print
     * @return {String} The plugin name
     */
    getPrintUrlPlugin: function ()
    {
        return this._printUrlPlugin;
    },
    
    /**
     * Get the url for XML export
     * @return {String} The url
     */
    getExportXMLUrl: function ()
    {
        return this._exportXMLUrl;
    },
    
    /**
     * Get the url for CSV export
     * @return {String} The url
     */
    getExportCSVUrl: function ()
    {
        return this._exportCSVUrl;
    },
    
    /**
     * Get the url for doc export
     * @return {String} The url
     */
    getExportDOCUrl: function ()
    {
        return this._exportDOCUrl;
    },
    
    /**
     * Get the url for PDF export
     * @return {String} The url
     */
    getExportPDFUrl: function ()
    {
        return this._exportPDFUrl;
    },
    
    /**
     * Get the url for print
     * @return {String} The url
     */
    getPrintUrl: function ()
    {
        return this._printUrl;
    },
    
    /**
     * Get the JS parameters for export
     * @return {Object} the parameters
     */
    getSearchParametersForExport: function ()
    {
        var params = {}
        params.values = this.getSearchValues();
        // If columns are not already set from searchValues, we set them to have only the displayed columns, as in the grid
        if (params.values.columns == null)
        {
            params.values.columns = this.getVisibleColumns();
        }
        params.sort = this.getEncodedSortersForExport() || undefined;
        params.model = this._modelId;
        
        if (this.isAdvancedMode())
        {
            params.searchMode = 'advanced';
        }
        
        params.facetValues = this.getFacetValues();
        params.contextualParameters = this.getExportContextualParameters();
        params.limit = -1; // no limit
        
        return params;
    },
    
    /**
     * Get search contextual parameters for export.
     * @return {Object} the contextual parameters.
     */
    getExportContextualParameters: function()
    {
        return this.getContextualParameters();
    },
    
    /**
     * Get the array of current active sorters.
     * @return {String} The list of sorters. Entry of the list are object with keys 'property' and 'direction' (optionnal)
     */
    getCurrentSorters: function()
    {
        var sorters = [];
        this.store.getSorters().each (function (sorter) {
            sorters.push({property: sorter.getProperty(), direction: sorter.getDirection()})
        });
        return sorters;
    },
    
    /**
     * Get the JSON encoded array of sorters.
     * @return {String} A JSON String representing the list of sorters. Entry of the list are object with keys 'property' and 'direction' (optionnal)
     */
    getEncodedSortersForExport: function()
    {
        return Ext.encode(this.getCurrentSorters() || [{property: 'creationDate', direction: 'ASC'}]);
    },
    
    /**
     * Get the available columns for grouping
     * @return {Object[]} The grouping fields
     */
    getAvailableGroupingFields: function()
    {
        var groupingFields = [];
        Ext.Array.forEach(this.grid.getColumns(), function (column) {
            if (column.dataIndex)
            {
                groupingFields.push([column.dataIndex, column.text]);
            }
        });
        return groupingFields;
    },
    
    /**
     * Get the list of columns which are visible in the tool
     * @return {String[]} The id of columns in a Array
     */
    getVisibleColumns: function()
    {
        var columns = [];
        Ext.Array.forEach(this.grid.getVisibleColumns(), function (column) {
            if (column.dataIndex)
            {
                columns.push(column.dataIndex);
            }
        });
        return columns;
    }
});