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


/**
 * Provides an advanced widget for dates, which are adaptable to the instant they are resolved (they can be relative to this instant)
 * <br>Be careful, the current version of this widget returns a String-type value !
 */
Ext.define('Ametys.form.widget.AdaptableDate', {
    extend: 'Ametys.form.AbstractFieldsWrapper',
    
    statics: {
        /**
         * @property {Array[]} __UNITS_STORE_CONFIG The configuration for combobox of temporal units (the values must comply to the Java 'java.time.temporal.ChronoUnit' units)
         * @private
         * @readonly
         */
        __UNITS_STORE_CONFIG: [
            ['DAYS', "{{i18n PLUGINS_CORE_UI_WIDGET_ADAPTABLE_DATE_UNIT_DAYS}}"], 
            ['WEEKS', "{{i18n PLUGINS_CORE_UI_WIDGET_ADAPTABLE_DATE_UNIT_WEEKS}}"], 
            ['MONTHS', "{{i18n PLUGINS_CORE_UI_WIDGET_ADAPTABLE_DATE_UNIT_MONTHS}}"],
            ['YEARS', "{{i18n PLUGINS_CORE_UI_WIDGET_ADAPTABLE_DATE_UNIT_YEARS}}"]
        ],
        
        /**
         * @property {Number} __NUMBER_FIELD_WIDTH The width for number fields 
         * @private
         * @readonly
         */
        __NUMBER_FIELD_WIDTH: 60
    },
    
    /**
     * @cfg {String} nowOptionText The text for the 'NOW' option 
     */
    nowOptionText: "{{i18n PLUGINS_CORE_UI_WIDGET_ADAPTABLE_DATE_NOW_OPTION_DEFAULT_TEXT}}",
    
    /**
     * @cfg {String} emptyText The text for the empty field
     */
    emptyText: "{{i18n PLUGINS_CORE_UI_WIDGET_ADAPTABLE_DATE_EMPTY_TEXT}}",
    
    /**
     * @private {Number} _suspendUiUpdating Internal counter to know if radios and subfields must be updated (when == 0) when calling {@link #setValue}
     */
    _suspendUiUpdating: 0, 
    
    initComponent: function()
    {
        var menu = Ext.create('Ext.menu.Menu', {
            items: [{
                xtype: 'container',
                margin: '0 10 10 10', // top right bottom left
                layout: {
                    type: 'vbox',
                    align: 'stretch'
                },
                itemId: 'radiocontainer',
                items: this._getRadioContainerItems([
                    this._nowOption(),
                    this._pastOption(),
                    this._futureOption(),
                    this._staticDateOption()
                ])
            }]
        });
        
        this.items = [{
            xtype: 'component',
            itemId: 'readableValue',
            cls: Ametys.form.AbstractField.READABLE_TEXT_CLS,
            html: null
        }, {
            xtype: 'button',
            iconCls: 'ametysicon-datetime-calendar-day',
            menu: menu
        }];
        
        if (this.allowBlank !== false)
        {
            this.items.push({
                xtype: 'button',
                iconCls: 'ametysicon-sign-raw-cross',
                handler: function(btn)
                {
                    this.down('#radiocontainer').items.each(function(lineContainer) {
                        var radio = this._retrieveRadio(lineContainer);
                        radio.setValue(false);
                    }, this);
                    this._setInternalValue(null);
                },
                scope: this
            });
        }
        
        this.defaults = {
            style: {
                marginRight: '4px'
            }
        };
        
        this.callParent(arguments);
    },
    
    afterRender: function()
    {
        this.callParent(arguments);
        this._updateUI();
    },

    /**
     * @private
     * Gets the configuration of items for the 'radio container'
     * @param {Object[]} configArrays Arrays of component configurations which must be placed on the same line
     * @return {Object[]} the configuration of items for the 'radio container'
     */
    _getRadioContainerItems: function(configArrays)
    {
        return Ext.Array.map(configArrays, function(configArray) {
            var items = Ext.Array.filter(configArray, function(cfg) { return cfg != null; });
            return {
                xtype: 'container',
                layout: {
                    type: 'hbox',
                    align: 'middle'
                },
                defaults: {
                    style: {
                        marginRight: '6px',
                        marginBottom: '10px'
                    }
                },
                items: items
            };
        });
    },
    
    /**
     * @private
     * Gets the configuration of items for the line 'NOW/TODAY/CURRENT...'
     * @return {Object[]} the configuration of items for the line 'NOW/TODAY/CURRENT...'
     */
    _nowOption: function()
    {
        return [{
            xtype: 'radio',
            name: 'type',
            itemId: 'now-radio',
            inputValue: 'now',
            boxLabel: this.nowOptionText,
            listeners: {
                'change': this._onRadioChange,
                scope: this
            }
        }];
    },
    
    /**
     * @private
     * Gets the configuration of items for the line '4 days/weeks/months ago' (past)
     * @return {Object[]} the configuration of items for the line '4 days/weeks/months ago' (past)
     */
    _pastOption: function()
    {
        var text1 = this._textCmpCfg("{{i18n PLUGINS_CORE_UI_WIDGET_ADAPTABLE_DATE_PAST_OPTION_TEXT_1}}"),
            number = this._numberFieldCfg('past-value'),
            text2 = this._textCmpCfg("{{i18n PLUGINS_CORE_UI_WIDGET_ADAPTABLE_DATE_PAST_OPTION_TEXT_2}}"),
            unit = this._unitComboboxCfg('past-unit'),
            text3 = this._textCmpCfg("{{i18n PLUGINS_CORE_UI_WIDGET_ADAPTABLE_DATE_PAST_OPTION_TEXT_3}}");
        
        return [{
            xtype: 'radio',
            name: 'type',
            itemId: 'past-radio',
            inputValue: 'past',
            listeners: {
                'change': this._onRadioChange,
                scope: this
            }
        }, 
        text1,
        number, 
        text2, 
        unit,
        text3
        ];
    },
    
    /**
     * @private
     * Gets the configuration of items for the line 'in 4 days/weeks/months' (future)
     * @return {Object[]} the configuration of items for the line 'in 4 days/weeks/months' (future)
     */
    _futureOption: function()
    {
        var text1 = this._textCmpCfg("{{i18n PLUGINS_CORE_UI_WIDGET_ADAPTABLE_DATE_FUTURE_OPTION_TEXT_1}}"),
            number = this._numberFieldCfg('future-value'),
            text2 = this._textCmpCfg("{{i18n PLUGINS_CORE_UI_WIDGET_ADAPTABLE_DATE_FUTURE_OPTION_TEXT_2}}"),
            unit = this._unitComboboxCfg('future-unit'),
            text3 = this._textCmpCfg("{{i18n PLUGINS_CORE_UI_WIDGET_ADAPTABLE_DATE_FUTURE_OPTION_TEXT_3}}");
        
        return [{
            xtype: 'radio',
            name: 'type',
            itemId: 'future-radio',
            inputValue: 'future',
            listeners: {
                'change': this._onRadioChange,
                scope: this
            }
        }, 
        text1,
        number, 
        text2,
        unit,
        text3
        ];
    },
    
    /**
     * @private
     * Gets the configuration of items for the line 'The 01/01/2019' (for selecting a 'static' date)
     * @return {Object[]} the configuration of items for the line 'The 01/01/2019' (for selecting a 'static' date)
     */
    _staticDateOption: function()
    {
        var text = this._textCmpCfg("{{i18n PLUGINS_CORE_UI_WIDGET_ADAPTABLE_DATE_STATIC_OPTION_TEXT}}");
        
        return [{
            xtype: 'radio',
            name: 'type',
            itemId: 'staticDate-radio',
            inputValue: 'staticDate',
            listeners: {
                'change': this._onRadioChange,
                scope: this
            }
        }, 
        text, 
        {
            xtype: 'edition.date',
            itemId: 'staticDate-value',
            allowBlank: false,
            value: new Date(),
            listeners: {
                'focus': this._onFocus,
                'change': this._onSubfieldValueChange,
                scope: this
            }
        }];
    },
    
    /**
     * @private
     * Gets the configuration of a text component
     * @param {Object/String} baseConfig The base configuration, or just its text as a string
     * @return {Object} The configuration object
     */
    _textCmpCfg: function(baseConfig)
    {
        if (Ext.isString(baseConfig))
        {
            baseConfig = {
                html: baseConfig
            };
        }
        
        if (Ext.isEmpty(baseConfig.html))
        {
            // no need to draw a useless component
            return null;
        }
        
        return Ext.apply({
            xtype: 'component',
            listeners: {
                'afterrender': function(cmp) {
                    cmp.getEl().on('mousedown', Ext.bind(this._onFocus, this, [cmp], 0));
                },
                scope: this
            }
        }, baseConfig);
    },
    
    /**
     * @private
     * Gets the configuration of a number field
     * @param {String} itemId The item id
     * @return {Object} The configuration object
     */
    _numberFieldCfg: function(itemId)
    {
        var minValue = 1;
        return {
            xtype: 'numberfield',
            itemId: itemId,
            minValue: minValue,
            value: minValue,
            allowBlank: false,
            allowDecimals: false,
            listeners: {
                'focus': this._onFocus,
                'change': this._onSubfieldValueChange,
                scope: this
            },
            width: this.statics().__NUMBER_FIELD_WIDTH
        };
    },
    
    /**
     * @private
     * Gets the configuration of a unit combobox
     * @param {String} itemId The item id
     * @return {Object} The configuration object
     */
    _unitComboboxCfg: function(itemId)
    {
        return {
            xtype: 'combobox',
            itemId: itemId,
            store: this.statics().__UNITS_STORE_CONFIG,
            allowBlank: false,
            forceSelection: true,
            listeners: {
                'focus': this._onFocus,
                'change': this._onSubfieldValueChange,
                'afterrender': function(combo) {
                    if (combo.getValue() == null)
                    {
                        combo.setValue(combo.getStore().getAt(0));
                    }
                },
                scope: this
            }
        };
    },
    
    /**
     * @private
     * Retrieves the radio button of the given line
     * @param {Ext.container.Container} lineContainer The line container
     * @return {Ext.form.field.Radio} the radio button of the given line
     */
    _retrieveRadio: function(lineContainer)
    {
        if (lineContainer == null)
        {
            return null;
        }
        
        return lineContainer.items.findBy(function(child) {
            return child.getXType() == 'radiofield';
        });
    },
    
    /**
     * @private
     * Listener called when a subfield takes the focus, in order to select the associated radio
     * @param {Ext.Component} cmp The focused component
     */
    _onFocus: function(cmp)
    {
        var lineContainer = cmp.up(),
            radio = this._retrieveRadio(lineContainer);
        if (radio && !radio.getValue())
        {
            radio.setValue(true);
        }
    },
    
    /**
     * @private
     * Listener called when a radio is changed, in order to update the internal value
     * @param {Ext.form.field.Radio} radio The radio
     * @param {Object} newValue The new value
     * @param {Object} oldValue The original value
     */
    _onRadioChange: function(radio, newValue, oldValue)
    {
        var lineContainer = radio.up();
        if (newValue === true)
        {
            lineContainer.items.each(function(item) {
                if (item.isFormField)
                {
                    item.validate();
                }
            });
            this._setInternalValue(lineContainer);
        }
        else
        {
            lineContainer.items.each(function(item) {
                if (item.isFormField)
                {
                    item.clearInvalid();
                }
            });
        }
    },
    
    /**
     * @private
     * Listener called when a subfield is changed, in order to update the readable value
     * @param {Ext.form.field.Field} subfield The subfield
     * @param {Object} newValue The new value
     * @param {Object} oldValue The original value
     */
    _onSubfieldValueChange: function(subfield, newValue, oldValue)
    {
        var lineContainer = subfield.up(),
            radio = this._retrieveRadio(lineContainer);
        if (radio && radio.getValue())
        {
            this._setInternalValue(lineContainer);
        }
    },
    
    /**
     * @private
     * Updates the internal representation of the current value
     * @param {Ext.container.Container} selectedLineContainer The selected line container. Can be null for 'empty field'
     */
    _setInternalValue: function(selectedLineContainer)
    {
        this._suspendUiUpdating++;
        this.setValue(this._computeStringValue(selectedLineContainer));
        this._suspendUiUpdating--;
        this._updateReadableValue(selectedLineContainer);
    },
    
    setValue: function(value)
    {
        this.callParent(arguments);
        if (this._suspendUiUpdating == 0)
        {
            // Parse value
            var radiocontainer = this.down('#radiocontainer');
            if (value == '$today')
            {
                var radio = radiocontainer.down('#now-radio');
                radio.setValue(true);
            }
            else if (value != null && Ext.String.startsWith(value, '$ago_fullday$'))
            {
                var amountAndUnit = value.substring('$ago_fullday$'.length),
                    separatorIndex = amountAndUnit.indexOf('$'),
                    amount = amountAndUnit.substring(0, separatorIndex),
                    unit = amountAndUnit.substring(separatorIndex + 1),
                    radio = radiocontainer.down('#past-radio'),
                    numberField = radiocontainer.down('#past-value'),
                    unitField= radiocontainer.down('#past-unit');
                radio.setValue(true);
                numberField.setValue(amount);
                unitField.setValue(unit);
            }
            else if (value != null && Ext.String.startsWith(value, '$in_fullday$'))
            {
                var amountAndUnit = value.substring('$in_fullday$'.length),
                    separatorIndex = amountAndUnit.indexOf('$'),
                    amount = amountAndUnit.substring(0, separatorIndex),
                    unit = amountAndUnit.substring(separatorIndex + 1),
                    radio = radiocontainer.down('#future-radio'),
                    numberField = radiocontainer.down('#future-value'),
                    unitField= radiocontainer.down('#future-unit');
                radio.setValue(true);
                numberField.setValue(amount);
                unitField.setValue(unit);
            }
            else if (value != null)
            {
                var date = value,
                    radio = radiocontainer.down('#staticDate-radio'),
                    dateField = radiocontainer.down('#staticDate-value');
                radio.setValue(true);
                dateField.setValue(date);
            }
        }
    },
    
    /**
     * @private
     * Computes the current string value
     * @param {Ext.container.Container} selectedLineContainer The selected line container. Can be null for 'empty field'
     * @return {String} The value
     */
    _computeStringValue: function(selectedLineContainer)
    {
        var checkedRadio = this._retrieveRadio(selectedLineContainer);
        if (checkedRadio)
        {
            switch (checkedRadio.getSubmitValue()) {
                case 'now':
                    return '$today';
                case 'past':
                    var valueField = selectedLineContainer.getComponent('past-value'),
                        unitField = selectedLineContainer.getComponent('past-unit'),
                        value = valueField.isValid() ? valueField.getValue() : null,
                        unit = unitField.isValid() ? unitField.getValue() : null;
                    return (value == null || unit == null) ? null : '$ago_fullday$' + value + '$' + unit;
                case 'future':
                    var valueField = selectedLineContainer.getComponent('future-value'),
                        unitField = selectedLineContainer.getComponent('future-unit'),
                        value = valueField.isValid() ? valueField.getValue() : null,
                        unit = unitField.isValid() ? unitField.getValue() : null;
                    return (value == null || unit == null) ? null : '$in_fullday$' + value + '$' + unit;
                case 'staticDate':
                    var dateField = selectedLineContainer.getComponent('staticDate-value'),
                        dateStrValue = dateField.isValid() ? dateField.getSubmitValue() : null;
                    return Ext.isEmpty(dateStrValue) ? null : dateStrValue;
                default:
                    return null;
            }
        }
        else
        {
            return null;
        }
    },
    
    /**
     * @private
     * Updates UI
     */
    _updateUI: function()
    {
        var checkedLineContainer = null;
        this.down('#radiocontainer').items.each(function(lineContainer) {
            var radio = this._retrieveRadio(lineContainer);
            if (radio && radio.getValue())
            {
                // found, stop iteration
                checkedLineContainer = lineContainer;
                return false;
            }
        }, this);
        
        this._updateReadableValue(checkedLineContainer);
    },
    
    /**
     * @private
     * Updates the readable value of this field
     * @param {Ext.container.Container} lineContainer The line container. Can be null for 'empty field'
     */
    _updateReadableValue: function(lineContainer)
    {
        var readableValueCmp = this.items.getByKey('readableValue');
        if (lineContainer)
        {
            var val = lineContainer.items.getRange()
                .map(function(item) {
                    var xtype = item.getXType();
                    return xtype === 'radiofield' && item.boxLabel
                        || xtype === 'combobox' && (item.getReadableValue() || '?')
                        || xtype === 'numberfield' && (Ext.isNumber(item.getValue()) && item.getValue().toString() || '?')
                        || xtype === 'component' && item.getInitialConfig().html
                        || xtype === 'edition.date' && (item.getRawValue() || '?')
                        || null;
                })
                .filter(function(txt) { return txt != null && txt.length > 0; })
                .map(function(txt) { return txt.trim(); })
                .join(' ');
            readableValueCmp.setHtml(val);
        }
        else
        {
            readableValueCmp.setHtml(this.emptyText);
        }
    }
});