/*
 *  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 does allow to do Solr search on contents
 * @private
 */
Ext.define('Ametys.plugins.cms.search.solr.SolrContentSearchTool', {
	extend: "Ametys.plugins.cms.search.ContentSearchTool",
	
    statics: {
        /**
         * @private
         * @readonly
         * @property {Number} LABEL_WIDTH The label width in the form
         */
        LABEL_WIDTH: 160
    },
    
	/**
     * @cfg {Number} [formMinHeight=200] The form's minimum height
     */
	formMinHeight: 200,
	
	/**
     * @cfg {Number} [formMaxHeight=600] The form's maximum height
     */
	formMaxHeight: 600,
	
	/**
	 * @property {String} _mode The CodeMirror mode
	 * @private
	 */
	_mode: 'text/x-solr-ametys',
	
	/**
	 * @private
	 * @property {Boolean} [_readOnly=false] set to 'true' to open tool in read-only mode
	 */
	_readOnly: false,
    
    /**
     * @private
     * @property {Boolean/Object} _modelError If false, there is currently no error with the Solr model. Otherwise, it is an object containing information about the error with the Solr model.
     */
    
    getStateId: function()
    {
        return "uitool-solrsearch"
    },
    
    getParams: function()
    {
        let params = this.callParent(arguments);
        
        if (this.isNotDestroyed())
        {
            params = Ext.apply({}, {
                flex: this.form.collapsed ? this.form._lastKnownFlex : this.form.flex,
                contentTypes: this.form.getComponent('contentTypes').getValue(),
                query: this.form.getComponent('query').getValue(),
                queryFlex: this.form.getComponent('query').flex,
                columns: this.form.getComponent('columns').getValue(),
                columnsFlex: this.form.getComponent('columns').flex,
                facets: this.form.getComponent('facets').getValue(),
                facetsFlex: this.form.getComponent('facets').flex
            }, params);
        }
        
        return params; 
    },
	
	setParams: function(params)
	{
        var initialTitle = this.getInitialConfig('title') || '';
        if (params.title && !Ext.String.startsWith(params.title, initialTitle + ' - '))
        {
            params.title = initialTitle + ' - ' + params.title;
            this.setTitle(params.title);
        }
        
        this._readOnly = params.readOnly === true;
        
        this.callParent(arguments);
        
        if (params.flex)
        {
            this.form.flex = params.flex;
        }
        if (params.contentTypes)
        {
            this.form.getComponent('contentTypes').setValue(params.contentTypes);
        }
        if (params.query)
        {
            this.form.getComponent('query').setValue(params.query);
        }
        if (params.queryFlex)
        {
            this.form.getComponent('query').flex = params.queryFlex;
        }                    
        if (params.columns)
        {
            this.form.getComponent('columns').setValue(params.columns);
        }
        if (params.columnsFlex)
        {
            this.form.getComponent('columns').flex = params.columnsFlex;
        }
        if (params.facets)
        {
            this.form.getComponent('facets').setValue(params.facets);
        }
        if (params.facetsFlex)
        {
            this.form.getComponent('facets').flex = params.facetsFlex;
        } 
        
        if (this.isActivated())
        {
            // As the SolrContentSearchTool enables to customize the columns, we need to manually force the reconfiguring of the columns here
            this.store.removeAll();
            this._internalSetParams(true);
        }
		
		if (this._readOnly)
		{
			this.searchPanel.getForm().getFields().each(function (field) {
				field.setReadOnly(true);
			})
		}
	},
    
	_createSearchFormPanel: function ()
	{
		var me = this;
	    
	    var cfg = this._getSearchFormPanelConfig();
	    
		var formCfg = Ext.apply(cfg, {
			split: true,
            
            border: false,
            layout: {
				type: 'vbox',
				align: 'stretch'
			},
            
            ui: 'light',
        	header: {
        		titlePosition: 1
        	},
            
            title: "{{i18n PLUGINS_CMS_UITOOL_SOLR_SEARCH_CRITERIA}}",
            collapsible: true,
            titleCollapse: true,
            animCollapse: false,
            
            minHeight: me.formMinHeight,
            maxHeight: me.formMaxHeight,
        	flex: 0.5,
            
            referenceHolder: true,
            defaultButton: 'launch',
            defaultButtonTarget: 'el',
            
			scrollable: true,
			
			defaults: {
				cls: 'ametys',
				labelAlign: 'right',
				labelSeparator: '',
				labelWidth: Ametys.plugins.cms.search.solr.SolrContentSearchTool.LABEL_WIDTH,
				xtype: 'textfield'
			},
			
			items: this._getSearchFormPanelItem(),
			
			listeners: {
                'resize': function(cmp, width, height)
                {
                    if (!this.collapsed)
                    {
                        this._lastKnownFlex = this.flex
                    }
                    Ametys.tool.Tool.prototype.setParams.call(me, me.getParams());
                }
            },            
            
            getJsonValues: function() 
            {
                var values  = {},
                    fields  = this.form.getFields().items,
                    fLen    = fields.length,
                    field;

                for (f = 0; f < fLen; f++) 
                {
                    field = fields[f];
                    values[field.getName()] = field.getJsonValue();
                }
                
                return values;
            }
		});
		
		this.form = Ext.create ('Ext.form.FormPanel', formCfg);
		
        // Initialize SolrHint to load all needed data
        SolrHint.init();
        
		return this.form;
	},
	
	/**
     * @private
     */
	_saveParams: function() 
	{
        Ametys.tool.Tool.prototype.setParams.call(this, this.getParams());
    },
	
	/**
	 * @private
	 * Get the items of form panel
	 * @return {Object} The form items configuration
	 */
	_getSearchFormPanelItem: function()
	{
		return [
		        {
		            xtype: 'edition.select-content-types',
		            fieldLabel: "{{i18n PLUGINS_CMS_UITOOL_SOLR_SEARCH_CTYPES}}",
		            ametysDescription: "{{i18n PLUGINS_CMS_UITOOL_SOLR_SEARCH_CTYPES_DESC}}",
		            name: 'contentTypes',
		            itemId: 'contentTypes',
		            allowBlank: false,
		            
		            readOnly: this._readOnly,
		            hideTrigger: this._readOnly,
		            multiple: true,
		            
		            excludePrivate: false,
		            excludeMixin: false,
		            excludeAbstract: false,
		            excludeReferenceTable: false,
		            listeners: {
                        'change': Ext.Function.createSequence(this._onChangeCTypes, this._saveParams),
                        scope: this
                    }
		        },
		        {
		        	xtype: 'solr-code',
                    margin: '0 0 0 0', // The 5px classic bottom margin will be replaced by the underlying splitter
					mode: this._mode,
					readOnly: this._readOnly,
					fieldLabel: "{{i18n PLUGINS_CMS_UITOOL_SOLR_SEARCH_QUERY}}",
					ametysDescription: "{{i18n PLUGINS_CMS_UITOOL_SOLR_SEARCH_QUERY_DESC}}",
					name: "query",
                    itemId: "query",
                    
					minHeight: 40,
					flex: 4,
                    listeners: {
                        'change': this._saveParams,
                        'resize': this._saveParams,
                        scope: this
                    },
                                
                    allowBlank: false,
                    value: '*:*',
					cmParams: {
					    lineWrapping: true,
					    extraKeys: {
				          'Ctrl-Space': 'autocomplete'
				        }
					}
				},
                {
                    xtype: 'splitter',
                    cls: 'ametys-solr-splitter',
                    margin: '0 ' + 26 + ' 0 ' + (Ametys.plugins.cms.search.solr.SolrContentSearchTool.LABEL_WIDTH + 5)
                },
				{
				    xtype: 'solr-code',
                    margin: '0 0 0 0', // The 5px classic bottom margin will be replaced by the underlying splitter
				    readOnly: this._readOnly,
                    mode: 'solr-ametys-columns',
                    fieldLabel: "{{i18n PLUGINS_CMS_UITOOL_SOLR_SEARCH_COLUMNS}}",
                    ametysDescription: "{{i18n PLUGINS_CMS_UITOOL_SOLR_SEARCH_COLUMNS_DESC}}",
                    name: 'columns',
                    itemId: 'columns',

                    minHeight: 30,
                    flex: 1,
                    listeners: {
                        'change': this._saveParams,
                        'resize': this._saveParams,
                        scope: this
                    },
                    
                    singleLine: true,
                    cmParams: {
                        lineNumbers: false,
                        lineWrapping: true,
                        styleActiveLine: false,
                        extraKeys: {
                          'Ctrl-Space': 'autocomplete'
                        },
                        hintOptions: {
                            allowAllOption: true
                        }
                    }
				},
                {
                    xtype: 'splitter',
                    cls: 'ametys-solr-splitter',
                    margin: '0 ' + 26 + ' 0 ' + (Ametys.plugins.cms.search.solr.SolrContentSearchTool.LABEL_WIDTH + 5)
                },
				{
				    xtype: 'solr-code',
				    readOnly: this._readOnly,
                    mode: 'solr-ametys-columns',
					fieldLabel: "{{i18n PLUGINS_CMS_UITOOL_SOLR_SEARCH_FACETS}}",
					ametysDescription: "{{i18n PLUGINS_CMS_UITOOL_SOLR_SEARCH_FACETS_DESC}}",
					name: 'facets',
				    itemId: 'facets',
                    
                    minHeight: 30,
                    flex: 1,
                    listeners: {
                        'change': this._saveParams,
                        'resize': this._saveParams,
                        scope: this
                    },
				    
                    singleLine: true,
                    cmParams: {
                        lineNumbers: false,
                        lineWrapping: true,
                        styleActiveLine: false,
                        extraKeys: {
                          'Ctrl-Space': 'autocomplete'
                        },
                        hintOptions: {
                            fieldName: 'facets'
                        }
                    }
				}
		]
	},
	
	_getSwitchModeButtons: function (items)
	{
		// Nothing
	},
	
	_createResultGrid: function(store)
	{
	    var grid = this.callParent(arguments);
	    
	    grid.on('columnmove', this._onColumnMove, this);
	    
	    return grid;
	},
	
	/**
	 * Get the selected content types
	 * @return {String} the coma-separated list of selected content types, if any
	 */
    getSelectedCTypes: function()
    {
        return this.form.down('#contentTypes').getValue();
    },
    
	isAdvancedMode: function ()
	{
		return false;
	},
    
    getSearchValues: function()
    {
        let values = this.callParent(arguments);
        
        values.parameters = this._lastExecutionParameters || {};
        
        return values;
    },
	
	_launchSearch: function(params)
	{   
        let me = this;
        
        if (params && params.faceting && me._lastExecutionParameters)
        {
            exe(me._lastExecutionParameters);
        }
        else
        {
            Ametys.plugins.coreui.script.ScriptParameters.askScriptParameters(this.getSearchValues().query,
                exe,
                undefined, // title
                undefined, // description
                { values: me.getParams().parameterValues }
            );
        }
        
        function exe(parameters) 
        {
            me._lastExecutionParameters = parameters;
            
            if (parameters)
            {
                let values = {};
                for (let paramName of Object.keys(parameters))
                {
                    values[paramName] = parameters[paramName].value;
                }

                let p = Ametys.tool.Tool.prototype.getParams.call(me)
                p.parameterValues = values;
                // Calling set params to save params correctly, but not this tool instance to avoid re-execution of the search
                Ametys.tool.Tool.prototype.setParams.call(me, p)
            }

            Ametys.data.ServerComm.send({
                plugin: 'cms',
                url: 'solr-search-model.json',
                parameters: {
                    model: me._modelId,
                    values: me.getSearchValues(), // reexecute getSearchValues() so the modified _lastExecutionParameters is taken in account
                    contextualParameters: {}
                },
                priority: Ametys.data.ServerComm.PRIORITY_MAJOR,
                waitMessage: true,
                errorMessage: {
                    msg: "{{i18n UITOOL_SEARCH_ERROR}}",
                    category: me.self.getName()
                },
                responseType: 'text',
                callback: {
                    handler: me._getSolrModelCb,
                    scope: me,
                    arguments: params
                }
            });
        }        
	},
	
	/**
	 * @private
	 * Callback for the solr model retrieving process
	 * @param {Object} response the server's response
	 * @param {Object} params the callback arguments
	 */
	_getSolrModelCb: function(response, params)
    {
	    var result = Ext.JSON.decode(Ext.dom.Query.selectValue('', response));
	    
	    if (result.success === true && Ext.isArray(result.columns))
        {
            this._modelError = false;
            
	        // Reconfigure
            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, true, this.grid, true);
            // Force columns to be visible as they were explicitly set by the user in the form
            Ext.Array.forEach(columns, function(column) {
                column.hidden = false;
            });
            
            Ext.data.schema.Schema.get('default').getEntity(this._modelName).replaceFields(fields, true);
            
            this.grid.reconfigure(this.store, columns);
            
            // Re-sort columns according to the original solr request (and not current state as it was done by reconfigure)
            this._ignoreColumnSort = true;
            for (let i = 0; i < columns.length; i++)
            {
                let realCols = this.grid.getColumns();
                let realCol = realCols.find(x => x.dataIndex == columns[i].dataIndex);
                let currentPosition = realCols.indexOf(realCol);
                if (currentPosition != i + 1)
                {
                    this.grid.headerCt.insert(i + 1, realCol);
                }
            }
            this._ignoreColumnSort = false;
            
            var params = params || {};
            var sorters = params.sort || Ametys.plugins.cms.search.SearchGridHelper.getSortersFromJson(columns, this.grid);
            this.store.sorters = null; // There is no other way to clean old sorters
            this.store.setSorters(sorters);
	        
	        Ametys.plugins.cms.search.solr.SolrContentSearchTool.superclass._launchSearch.call(this, params);
	        
	        // Store the "initial" formatting
            this._initialFormatting = this._getInitialFormatting(result.columns);
        }
	    else
        {
	        var message = "{{i18n UITOOL_SEARCH_ERROR}}";
	        var details = '';
	        if (result.error == 'column-error')
	        {
	            message = "{{i18n PLUGINS_CMS_UITOOL_SOLR_SEARCH_ERROR_COLUMNS}}";
	            details = result.message;
	        }
	        
            this._modelError = {
                message: message,
                details: details
            };
	        this._showModelErrorDialog(this._modelError);
        }
    },
    
    /**
     * @protected
     * Shows a dialog box with the current error on the Solr model (typically call this method after the user tries to launch a search)
     * @param {Object} dialogOptions The options for the dialog box to display
     * @param {String} dialogOptions.message The main text of the box. Should be localized
     * @param {String/Error} [dialogOptions.details] The detailed text of the box. Hidden by default. Can be technical.
     */
    _showModelErrorDialog: function(dialogOptions)
    {
        Ametys.log.ErrorDialog.display({
            title: "{{i18n UITOOL_SEARCH_ERROR_TITLE}}",
            text: dialogOptions.message,
            details: dialogOptions.details,
            category: 'Ametys.plugins.cms.search.solr.SolrContentSearchTool'
        });
    },
    
    _onBeforeLoad: function(store, operation)
    {
        if (this._modelError)
        {
            this._showModelErrorDialog(this._modelError);
            return false;
        }
        return this.callParent(arguments);
    },
	
    /**
     * @private
     * Get the additional extensions to use for this search tool.
     * @param {String} location the location where to put the extensions, can be 'l' for 'left', 'r' for 'right' (defaults to 'l')
     * @return {Object[]} The elements to add to the search bar
     */
	_getAdditionalExtensions: function (location) 
	{
		return Ametys.plugins.cms.search.solr.SolrContentSearchToolExtensions.getAdditionalButtons(location);
	},
	
	/**
	 * @private
	 * Callback function invoked whenever the content types' combo box value changed.
	 * If multiple records are selected, it also computes the common ancestor for all the selected records
	 * @param {Ext.form.field.Tag} combo The combobox
	 * @param {Object} newValue The new value
	 * @param {Object} oldValue The original value
	 */
    _onChangeCTypes: function(combo, newValue, oldValue)
    {
        var me = this;
        
        // Delete the facet combo's last query to force reloading the store.
        delete me.form.down('#facets').lastQuery;
        
        // Update the contentType for autocompletion
        me.form.getComponent('query').setContentTypes(newValue);
        me.form.getComponent('columns').setContentTypes(newValue);
        me.form.getComponent('facets').setContentTypes(newValue);
        
        // Reinit sorters
        me.store.sorters = null; // There is no other way to clean old sorters
        me.store.setSorters([]);
    },

    /**
     * @private
     * Called when a column is moved to reflect (if possible) the new order on the column field of the search form
     * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
     * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
     * @param {Number} fromIdx the starting index
     * @param {Number} toIdx the ending index
     */
    _onColumnMove: function(ct, column, fromIdx, toIdx)
    {
        if (this._ignoreColumnSort)
        {
            return;
        }
        
        var columnsField = this.form.down('#columns');
        
        var columnValues = columnsField.getValue();
        columnValues = columnValues.trim() ? columnValues.split(",").map(function(s) { return s.trim(); }) : [] /* As a split of "" lead to [""]... here we lead to [] */;

        // If there are as many columns in reality as the field is referencing: it means that each column is referenced (no empty field nor '*' operator)
        if (columnValues.length == ct.getGridColumns().length - 1) // -1 stand for the special extjs internal column
        {
            var valueToMove = columnValues[fromIdx];
            Ext.Array.removeAt(columnValues, fromIdx);
            
            if (fromIdx < toIdx)
            {
                // toIdx has changed since we removed a value before
                toIdx -= 1;
            }
            
            Ext.Array.insert(columnValues, toIdx, [valueToMove])
            
            columnsField.setValue(columnValues.join(', '));
        }
    },
    
    applyFormatting: function(formatting)
    {
        var actualColumnFormatting = [];
        // We need to retrieve the current columns list before applying the formatting
        var current = this.getCurrentFormatting().columns;
        
        // First add column from formatting if they exist
        for (let i in formatting.columns)
        {
            for (let j in current)
            {
                if (current[j].dataIndex == formatting.columns[i].dataIndex)
                {
                    actualColumnFormatting.push(formatting.columns[i]);
                    current.splice(j, 1);
                    break;
                }
            }
        }
        
        formatting.columns = actualColumnFormatting.concat(current);
        
        // update the column field to reflect the formatting
        var columnsField = this.form.down('#columns');
        columnsField.setValue(formatting.columns.map(c => c.dataIndex).join(", "))
        
        // reinsert the new formatting
        arguments[0] = formatting;
        this.callParent(arguments);
    }
});