/*
 *  Copyright 2021 Anyware Services
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

/**
 * This class provides a widget to create a grid for manual input of choice list
 */
Ext.define('Ametys.plugins.forms.widget.InputChoices', {
    extend : 'Ametys.form.AbstractField',
    
    xtype: 'manual-source-grid',
    
    height: 250,
    
    /**
     * @cfg {Boolean} [displayCost=false] If display cost is true, display cost column in the grid
     */

    /**
     * @private
     * @property {Ext.grid.Panel} _grid The grid containing the input choices.
     */
    constructor: function(config)
    {
        config.displayCost = Ext.isBoolean(config.displayCost) ? config.displayCost : config.displayCost == 'true';
        config.displayEmail = Ext.isBoolean(config.displayEmail) ? config.displayEmail : config.displayEmail == 'true';
        
        this.callParent(arguments);
    },
    
    initComponent: function()
    {
        this._grid = this._createGrid();
        
        this.items = [this._grid];

        this.layout = 'fit';
        this.cls = this.emptyCls;
        
        this.callParent(arguments);
    },
    
    getValue: function ()
    {
        var options = {};
        
        var displayCost = this.displayCost;
        var displayEmail = this.displayEmail;
        this._grid.getStore().getData().each (function (record) {
            if (record.get('value') != "" || record.get('label') != "")
            {
                var recordInfo = {
                    "value" : record.get('value')
                };
                if (displayCost) 
                {
                    recordInfo.cost = record.get('cost');
                }
                if (displayEmail) 
                {
                    recordInfo.emails = record.get('emails');
                }
                options[record.get('label')] = recordInfo;
            }
        });
        
        return Ext.JSON.encode(options); 
    },
    
    setValue: function(value) 
    {   
        this.callParent(arguments);
        this._grid.getStore().removeAll();
        if (value != null)
        {
            var options = Ext.JSON.decode(value);
            this._loadGridStore(options);
        }
        else
        {
            this._loadGridStore({});
        }
    },
    
    /**
     * @private
     * Fill the grid with some values.
     * @param {Object} options The options to add in the grid.
     */
    _loadGridStore: function(options)
    {
        var store = this._grid.getStore();
        for (var key in options)
        {
            var storeContent = {
                label: key, 
                value: options[key].value,
                canEditAuto: false
            };
            if (this.displayCost)
            {
                storeContent.cost = options[key].cost ? options[key].cost : 0;
            }
            if (this.displayEmail)
            {
                storeContent.emails = options[key].emails ? options[key].emails : "";
            }
            store.add(storeContent);
        }
        
        var storeContent = {
            label: "", 
            value: "",
            isNew: true,
            canEditAuto: true
        };
        if (this.displayCost)
        {
            storeContent.cost = 0;
        }
        if (this.displayEmail)
        {
            storeContent.emails = "";
        }
        store.add(storeContent);
    },
    
    /**
     * @private
     * Creates the bottom grid of this dialog box.
     * @return {Ext.grid.Panel} The grid
     */
    _createGrid: function()
    {
        var fields = this._getFields();
        
        var grid = Ext.create('Ext.grid.Panel', {
            border: false,
            scrollable: true,
            cls:'inputchoices',
            store: {
                fields: fields,
                trackRemoved: false
            },
            
            columns: this._getGridColumns(),
            columnLines: true,
            forceFit: true,
            selModel: 'cellmodel',
        });
        
        return grid;
    },
    
    /**
     * @private
     * Get fields names for grid
     * @returns {String[]} the fields
     */
    _getFields: function()
    {
        var fields = [];
        if (this.displayCost)
        {
            fields = ['label', 'value', 'cost'];
        }
        else if (this.displayEmail)
        {
            fields = ['label', 'value', 'emails'];
        }
        else
        {
            fields = ['label', 'value'];
        }
        return fields;
    },
    
    /**
     * @private
     * Get the grid columns
     */
    _getGridColumns: function()
    {
        var columns = [
            {
                 xtype: 'widgetcolumn',
                 stateId: 'grid-column-label', 
                 header: "{{i18n PLUGINS_FORMS_QUESTIONS_DIALOG_CHOICE_BOX_GRID_COL_LABEL}}",
                 menuDisabled: true,
                 flex: 0.4,
                 sortable: false, 
                 dataIndex: 'label',
                 widget: {
                     xtype: 'textfield',
                     enableKeyEvents:true,
                     listeners: {
                        'change': Ext.bind(this._onWidgetChange, this, ['label'], 0),
                        'keydown': Ext.bind(this._onKeyDown, this),
                        'focus': Ext.bind(this._onFocus, this)
                     }
                }
            },
            {
                 xtype: 'widgetcolumn',
                 stateId: 'grid-column-value', 
                 header: "{{i18n PLUGINS_FORMS_QUESTIONS_DIALOG_CHOICE_BOX_GRID_COL_VALUE}}",
                 menuDisabled: true,
                 sortable: false, 
                 flex: 0.4,
                 dataIndex: 'value', 
                 widget: {
                     xtype: 'textfield',
                     enableKeyEvents:true,
                     listeners: {
                        'change': Ext.bind(this._onWidgetChange, this, ['value'], 0),
                        'keydown': Ext.bind(this._onKeyDown, this),
                        'focus': Ext.bind(this._onFocus, this)
                     }
                }
            }
        ];
        
        if (this.displayCost)
        {
            var costColumn = {
                xtype: 'widgetcolumn',
                header: "{{i18n PLUGINS_FORMS_QUESTIONS_DIALOG_CHOICE_BOX_GRID_COL_COST}}",
                flex:0.2, 
                menuDisabled: true,
                sortable: false, 
                dataIndex: 'cost', 
                widget: {
                     xtype: 'numberfield',
                     listeners: {
                        'change': Ext.bind(this._onWidgetChange, this, ['cost'], 0),
                        'keydown': Ext.bind(this._onKeyDown, this),
                        'focus': Ext.bind(this._onFocus, this)
                    }
                }
            }
            columns.push(costColumn);
        }
        
        if (this.displayEmail)
        {
            var emailColumn = {
                xtype: 'widgetcolumn',
                header: "{{i18n PLUGINS_FORMS_QUESTIONS_DIALOG_CHOICE_BOX_GRID_COL_EMAIL}}",
                flex:0.5, 
                menuDisabled: true,
                sortable: false, 
                dataIndex: 'emails', 
                widget: {
                     xtype: 'textfield',
                     listeners: {
                        'change': Ext.bind(this._onWidgetChange, this, ['emails'], 0),
                        'keydown': Ext.bind(this._onKeyDown, this),
                        'focus': Ext.bind(this._onFocus, this)
                    }
                }
            }
            columns.push(emailColumn);
        }
        
        columns.push({
             xtype: 'widgetcolumn',
             menuDisabled: true,
             sortable: false, 
             width:35,
             widget: {
                 xtype: 'panel',
                 cls:'inputchoicesPanel',
                 tools: [
                    {
                        type: 'moveup',
                        qtip: "{{i18n PLUGINS_FORMS_QUESTIONS_DIALOG_CHOICE_BOX_GRID_COL_BUTTON_UP_TOOLTIP}}", 
                        handler: this._up,
                        scope: this
                    }
                 ],
                 listeners: {
                     'afterlayout': Ext.bind(this._moveUpButtonRender, this)
                 }
             }
        });
        
        columns.push({
             xtype: 'widgetcolumn',
             menuDisabled: true,
             sortable: false, 
             width:35,
             widget: {
                 xtype: 'panel',
                 cls:'inputchoicesPanel',
                 tools: [
                    {
                        type: 'movedown',
                        qtip: "{{i18n PLUGINS_FORMS_QUESTIONS_DIALOG_CHOICE_BOX_GRID_COL_BUTTON_DOWN_TOOLTIP}}", 
                        handler: this._down,
                        scope: this
                    }
                ],
                listeners: {
                    'afterlayout': Ext.bind(this._moveDownButtonRender, this)
                }
            }
        });
        
        columns.push({
             xtype: 'widgetcolumn',
             menuDisabled: true,
             sortable: false, 
             width:35,
             widget: {
                 xtype: 'panel',
                 cls:'inputchoicesPanel',
                 tools: [
                    {
                        type: 'plus',
                        qtip: "{{i18n PLUGINS_FORMS_QUESTIONS_DIALOG_CHOICE_BOX_GRID_COL_BUTTON_ADD_TOOLTIP}}", 
                        handler: this._add,
                        scope: this
                    }
                ],
                listeners: {
                    'afterlayout': Ext.bind(this._lastRowRender, this)
                }
            }
        });
        
        columns.push({
             xtype: 'widgetcolumn',
             menuDisabled: true,
             sortable: false, 
             width:35,
             widget: {
                 xtype: 'panel',
                 cls:'inputchoicesPanel',
                 tools: [
                    {
                        type: 'delete',
                        qtip: "{{i18n PLUGINS_FORMS_QUESTIONS_DIALOG_CHOICE_BOX_GRID_COL_BUTTON_DELETE_TOOLTIP}}",
                        handler: this._remove,
                        scope: this
                    }
                ],
                listeners: {
                    'afterlayout': Ext.bind(this._lastRowRender, this)
                }
            }
        });
        
        return columns;
    },
    
    /**
     * @private
     * Listener to disable or enable move up button
     * @param {Object} panel the panel
     */
    _moveUpButtonRender: function(panel)
    {
        var record = panel.getWidgetRecord();
        var store = this._grid.getStore();
        var recordRow = store.indexOf(record);
        if (recordRow == 0)
        {
            panel.disable();
        }
        else if (recordRow == (store.getCount() - 1))
        {
            if (record.get('label') != '')
            {
                panel.enable();
            }
            else
            {
                panel.disable();
            }
        }
        else
        {
            panel.enable();
        }
    },
    
    /**
     * @private
     * Listener to disable or enable move down button
     * @param {Object} panel the panel
     */
    _moveDownButtonRender: function(panel)
    {
        var record = panel.getWidgetRecord();
        var store = this._grid.getStore();
        var recordRow = store.indexOf(record);
        if (recordRow == (store.getCount() - 2))
        {
            var lastRecord = store.getAt(store.getCount() - 1);
            if (lastRecord.get('label') != '')
            {
                panel.enable();
            }
            else
            {
                panel.disable();
            }
        }
        else if (recordRow == (store.getCount() - 1))
        {
            panel.disable();
        }
        else
        {
            panel.enable();
        }
    },
    
    /**
     * @private
     * Listener to disable last row buttons
     * @param {Object} panel the panel
     */
    _lastRowRender: function(panel)
    {
        var record = panel.getWidgetRecord();
        var store = this._grid.getStore();
        var recordRow = store.indexOf(record);
        if (recordRow == (store.getCount() - 1))
        {
            if (recordRow == 0 && panel.tools[0].initialConfig.type == "delete")
            {
                panel.disable();
            }
            else if (record.get("label") != '')
            {
                panel.enable();
            }
            else
            {
                panel.disable();
            }
        }
        else
        {
            panel.enable();
        }
    },
    
   /**
     * @private
     * Listener when a widget value has changed, in order to update the grid store.
     * @param {String} fieldName The name of the field to update in the record
     * @param {Ext.form.field.Field} widget The widget
     * @param {Object} newValue The new value
     */
    _onWidgetChange: function(fieldName, widget, newValue)
    {
        if (widget.getWidgetRecord)
        {
            var rec = widget.getWidgetRecord();
            if (rec != null)
            {
                rec.set(fieldName, newValue);

                var currentPos = this._grid.getStore().indexOf(rec);
                if (currentPos != -1)
                {
                    this.clearWarning();
                    this.clearInvalid();
                    
                    if (this._hasDuplicateLabels())
                    {
                        this.markInvalid("{{i18n PLUGINS_FORMS_QUESTIONS_DIALOG_CHOICE_BOX_GRID_LABEL_DUPLICATE}}");
                    }
                    else if (this._hasDuplicateValues())
                    {
                        this.markWarning("{{i18n PLUGINS_FORMS_QUESTIONS_DIALOG_CHOICE_BOX_GRID_VALUE_DUPLICATE}}");
                    }
                    else if (this.displayEmail && this._hasInvalidEmails(rec))
                    {
                        this.markInvalid("{{i18n PLUGINS_FORMS_QUESTIONS_DIALOG_CHOICE_BOX_GRID_LABEL_INVALID_EMAILS}}");
                    }
                }
                
                if (fieldName == 'label' && rec.get("canEditAuto"))
                {
                    var computedValue = this._getUniqueValue(newValue.replaceAll(/[^\w]/g, "_").toLowerCase());
                    rec.set("computedValue", computedValue)
                    rec.set("value", computedValue);
                }
                if (fieldName == 'value' && rec.get("computedValue") != rec.get("value"))
                {
                    rec.set("canEditAuto", false);
                }
                rec.commit();
            }
        }
    },
    
    /**
     * @private
     * On the key down, create new line if the last empty value is filled
     * @param {Object} widget the text widget
     * @param {Object} e the event
     */
    _onKeyDown: function(widget, e)
    {
        if (e.keyCode == e.ENTER)
        {
            if (widget.getWidgetRecord)
            {
                var rec = widget.getWidgetRecord();
                if (rec != null)
                {
                    var store = this._grid.getStore();
                    var currentPos = store.indexOf(rec);
                    var lastPos = store.getCount() - 1; 
                                                   
                    if (currentPos == lastPos)
                    {
                        rec.set("isNew", false);
                        store.insert(currentPos + 1, {
                            label: "", 
                            value: "",
                            isNew: true,
                            canEditAuto: true,
                            cost: this.displayCost ? 0 : null,
                            emails: this.displayEmail ? "" : null
                        });
                    }
                    
                    var newRecord = store.getAt(currentPos + 1);
                    Ext.get(this._grid.getView().getRow(newRecord)).down('input').focus()
                }
            }
            
            e.stopEvent();
            e.preventDefault();
            return false;
        }
    },
    
    /**
     * @private
     * On focus, select the whole row
     * @param {Object} widget the text widget
     */
    _onFocus: function(widget)
    {
        var record = widget.getWidgetRecord();
        var recordRow = this._grid.getStore().indexOf(record);
        this._grid.getSelectionModel().select(recordRow);
    },
    
    /**
     * @private
     * Down the selected record of the grid.
     * @param {Object} event the event
     * @param {Object} toolEl this tool element
     * @param {Object} header this header panel
     */
    _down: function(event, toolEl, header)
    {
        // Get the widget item panel.
        var itemPanel = header.ownerCt;
        var record = itemPanel.getWidgetRecord();
        var store = this._grid.getStore();
        var recordRow = store.indexOf(record);
        
        store.remove(record);
        store.insert(recordRow + 1, record);
    },
    
    /**
     * @private
     * Up the selected record of the grid.
     * @param {Object} event the event
     * @param {Object} toolEl this tool element
     * @param {Object} header this header panel
     */
    _up: function(event, toolEl, header)
    {
        // Get the widget item panel.
        var itemPanel = header.ownerCt;
        var record = itemPanel.getWidgetRecord();
        var store = this._grid.getStore();
        var recordRow = store.indexOf(record);
        
        store.remove(record);
        store.insert(recordRow - 1, record);
    },
    
    /**
     * @private
     * Adds a new choice to the grid.
     * @param {Object} event the event
     * @param {Object} toolEl this tool element
     * @param {Object} header this header panel
     */
    _add: function(event, toolEl, header)
    {
        // Get the widget item panel.
        var itemPanel = header.ownerCt;
        var record = itemPanel.getWidgetRecord();
        var store = this._grid.getStore();
        var recordRow = store.indexOf(record);
        
        var label = this._getUniqueLabel("{{i18n PLUGINS_FORMS_QUESTIONS_DIALOG_CHOICE_BOX_GRID_ACTION_ADD_DEFAULTLABEL}}");
//        var emails = 
        store.insert(recordRow + 1, {
            label: label, 
            value: this._getUniqueValue(label.replaceAll(/[^\w]/g, "_").toLowerCase()),
            cost: this.displayCost ? 0 : null,
            emails: this.displayEmail ? "" : null,
            canEditAuto: true
        });
    },
    
    /**
     * @private
     * Removes the selected record of the grid.
     * @param {Object} event the event
     * @param {Object} toolEl this tool element
     * @param {Object} header this header panel
     */
    _remove: function(event, toolEl, header)
    {
        // Get the widget item panel.
        var itemPanel = header.ownerCt;
        var record = itemPanel.getWidgetRecord();
        var store = this._grid.getStore();
        
        store.remove(record);
        
        if (this._hasDuplicateLabels())
        {
            this.clearWarning();
            this.markInvalid("{{i18n PLUGINS_FORMS_QUESTIONS_DIALOG_CHOICE_BOX_GRID_LABEL_DUPLICATE}}");
        }
        else if (this._hasDuplicateValues())
        {
            this.clearInvalid();
            this.markWarning("{{i18n PLUGINS_FORMS_QUESTIONS_DIALOG_CHOICE_BOX_GRID_VALUE_DUPLICATE}}");
        }
        else
        {
            this.clearInvalid();
            this.clearWarning();
        }
    },
    
    /**
     * @private
     * Get a unique value for this option
     * @param {String} newValue the new value
     * @return {String} a unique value
     */
    _getUniqueValue: function(newValue)
    {
        if (newValue == "")
        {
            return newValue;
        }
        
        var uniqueValue = newValue;
        var i = 1;
        var values = this._getGridValues(null);
        while (values.indexOf(uniqueValue) != -1)
        {
            uniqueValue = newValue + "_" + i;
            i++;
        }
        return uniqueValue;
    },
    
    /**
     * @private
     * Get a unique label for this option
     * @param {String} newLabel the new label
     * @return {String} a unique label
     */
    _getUniqueLabel: function(newLabel)
    {
        var uniqueLabel = newLabel;
        var i = 1;
        var values = this._getGridLabels(null);
        while (values.indexOf(uniqueLabel) != -1)
        {
            uniqueLabel = newLabel + " " + i;
            i++;
        }
        return uniqueLabel;
    },
    
    /**
     * @private
     * Get all the values in the grid minus the row to ignore
     * @param {Number} rowToIgnore row of current value. Null to get all grid values.
     * @return {String[]} an array of all values
     */
    _getGridValues : function(rowToIgnore)
    {
        var gridValues = [];
        var values = this._grid.getStore().getData().items;
        for (var index in values)
        {
            if(index != rowToIgnore)
            {
                gridValues.push(values[index].get('value'));
            }
        }
        return gridValues;
    },
    
    /**
     * @private
     * Get all the labels in the grid minus the row to ignore
     * @param {Number} rowToIgnore row of current value. Null to get all grid labels.
     * @return {String[]} an array of all labels  
     */
    _getGridLabels : function(rowToIgnore)
    {
        var gridLabels = [];
        var values = this._grid.getStore().getData().items;
        for (var index in values)
        {
            if(index != rowToIgnore)
            {
                gridLabels.push(values[index].get('label'));
            }
        }
        return gridLabels;
    },
    
    /**
     * @private
     * Check if the value is unique in the grid
     * @param {String} value the value to check
     * @param {Number} row position of the current cell
     * @return {Boolean} true if value is unique, false if not
     */
    _checkUniqueValue: function(value, row)
    {
        var values = this._getGridValues(row);
        return values.indexOf(value) == -1;
    },
    
    /**
     * @private
     * Check if the label is unique in the grid
     * @param {String} label the label to check
     * @param {Number} row position of the current cell
     * @return {Boolean} true if label is unique, false if not
     */
    _checkUniqueLabel: function(label, row)
    {
        var labels = this._getGridLabels(row);
        return labels.indexOf(label) == -1;
    },
    
    /**
     * @private
     * Check if the store has duplicate labels
     * @return {Boolean} true if the store has duplicate labels
     */
    _hasDuplicateLabels()
    {
        var labels = [];
        var hasDuplicates = false;
        this._grid.getStore().getData().each (function (record) {
            var label = record.get("label");
            if (labels.indexOf(label) != -1)
            {
                hasDuplicates = true;
            }
            
            labels.push(label);
        });
        
        return hasDuplicates;
    },
    
    /**
     * @private
     * Check if the store has duplicate values
     * @return {Boolean} true if the store has duplicate values
     */
    _hasDuplicateValues()
    {
        var values = [];
        var hasDuplicates = false;
        this._grid.getStore().getData().each (function (record) {
            var value = record.get("value");
            if (values.indexOf(value) != -1)
            {
                hasDuplicates = true;
            }
            
            values.push(value);
        });
        
        return hasDuplicates;
    },
    
    /**
     * @private
     * Check if the store has invalid emails
     * @param {Object} record the grid row being changed
     * @return {Boolean} true if the store has invalid emails
     */
    _hasInvalidEmails(record)
    {
        var emailsValue = record.getData().emails;
        var error = false;
        var mailsTab = emailsValue.split(/[ ,;\r]/);
        for (var i in mailsTab)
        {
            var mail = mailsTab[i].trim();
            error = error || mail != '' && !(/^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,})$/.test(mail));
        }
        
        return error;
    },
    
    getErrors: function(value)
    {
        var errors = this.callParent(arguments);
        if (this._hasDuplicateLabels())
        {
            errors.push("{{i18n PLUGINS_FORMS_QUESTIONS_DIALOG_CHOICE_BOX_GRID_LABEL_DUPLICATE}}");
        }
        
        return errors;
    }
});