/*
 *  Copyright 2015 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 a widget for text field<br>
 * This widget is the default widget registered for fields of type Ametys.form.WidgetManager#TYPE_STRING.<br>
 * It does handle multiple values (see {@link #cfg-multiple}) using separated by commas text. 
 */
Ext.define('Ametys.form.widget.Text', {
    extend: "Ext.form.field.Text",
    
    /**
     * @cfg {Boolean} [multiple=false] True to handle multiple values 
     */
    /**
     * @cfg {Boolean/String} [singleValueForMultiple=false] When true and multiple, the returned value will not be multiple
     */

    constructor: function (config)
    {
        var validationConfig = config.validationConfig || {},
            maxLength = config.maxLength || validationConfig.maxlength,
            maxHeight = Ametys.form.WidgetManager.getWidgetMaxHeight(config),
        
        config = Ext.apply(config, {
            ametysShowMultipleHint: config.ametysShowMultipleHint != null
                  ? Ext.isBoolean(config.ametysShowMultipleHint)
                      ? config.ametysShowMultipleHint
                      : config.ametysShowMultipleHint == "true"
                  : config.multiple,
            maxLength: maxLength ? Number(maxLength) : undefined,
            maxHeight: maxHeight
        });
        
        this.callParent(arguments);
    },
    
    getValue: function ()
    {
        var me = this,
        val = me.callParent(arguments);
        
        if (this.multiple && this.getInitialConfig('singleValueForMultiple') !== "true" && this.getInitialConfig('singleValueForMultiple') !== true)
        {
            var values = val ? val.split(',') : [];
            for (var i = 0; i < values.length; i++)
            {
                values[i] = values[i].trim();
            }
            
            return values;
        }
        
        return val;
    },
    
    getSubmitValue: function()
    {
        return this.multiple && this.getInitialConfig('singleValueForMultiple') !== "true" && this.getInitialConfig('singleValueForMultiple') !== true ? Ext.encode(this.getValue()) : this.getValue();
    }
});

/**
 * Provides a widget for string enumeration, ordered by alphabetical order.<br>
 * This widget is the default widget registered for enumerated field of type Ametys.form.WidgetManager#TYPE_STRING.<br>
 * It does NOT handle multiple values.
 */
Ext.define('Ametys.form.widget.ComboBox', {
    extend: "Ext.form.field.ComboBox",
    
    /**
     * @cfg {Boolean} [multiple=false] True to handle multiple values 
     */
    
    /**
     * @cfg {Boolean} [naturalOrder=false] True to sort drop down list by natural order. By default alphabetical order is applied to the store.
     * @deprecated 4.3 Use {@link order}
     */
    
    /**
     * @cfg {String} [order="ASC"] The order can be 'ASC' for ascendant alphabetical order or 'DESC' descendant alphabetical order or 'natural' to keep the natural order of the store.
     * @since Ametys-Runtime-4.3
     */
    
    /**
     * @cfg {Boolean} [anyMatch=true] True to allow matching of the typed characters at any position in the valueField's value.
     */
    
    /**
     * @cfg {Boolean/String} [singleValueForMultiple=false] When true and multiple, the returned value will not be multiple
     */

    constructor: function (config)
    {
        config.valueField = config.valueField || 'value';
        config.displayField = config.displayField || 'text';
        config.maxHeight = Ametys.form.WidgetManager.getWidgetMaxHeight(config);
            
        var storeCfg = this.getStoreCfg(config),
            typeAhead, editable, anyMatch;
        
        if (Ext.isBoolean(config.typeAhead))
        {
            typeAhead = config.typeAhead; 
        }
        else if (config.typeAhead == "true" || config.typeAhead == "false")
        {
            typeAhead = config.typeAhead == "true";
        }
        
        if (Ext.isBoolean(config.editable))
        {
            editable = config.editable; 
        }
        else if (config.editable == "true" || config.editable == "false")
        {
            editable = config.editable == "true";
        }
        
        config.anyMatch = Ext.isBoolean(config.anyMatch) ? config.anyMatch : config.anyMatch != "false"; // default to true
        config.forceSelection = Ext.isBoolean(config.forceSelection) ? config.forceSelection : config.forceSelection != "false"; // default to true
        
        config = Ext.apply(config, {
            typeAhead: typeAhead !== undefined ? typeAhead : (config.multiple ? false : true),
            editable: editable !== undefined ? editable : (config.multiple ? false : true),
            forceSelection: config.forceSelection,
            triggerAction: 'all',
            
            store: storeCfg,
            multiSelect: config.multiple
        });
        
        this.callParent(arguments);
        
        // Trigger drop down when clicking the field even when editable is false
        if (!config.multiple)
        {
            this.on('click', Ext.bind(this._onClick, this), this, {element: 'inputEl'});
        }
    },
    
    /**
     * Get the store configuration
     * @param {Object} config The current configuration object
     * @param {Object} [config.proxyStore] The proxy to apply to the store. Use is enumeration is empty or null.
     * @param {Object} [config.storeListeners] The listeners to apply to the store
     * @return {Object} The store configuration
     */
    getStoreCfg: function(config)
    {
        var storeCfg = {
            id: 0,
            fields: [ {name: config.valueField, convert: Ext.bind(this.convertEnumerationValue, this)}, {name: config.displayField, type: 'string'}]
        };
        
        if (config.enumeration)
        {
            storeCfg = Ext.apply(storeCfg, {
                type: 'array', // Ext.data.ArrayStore
                data: config.enumeration
            });

            config.queryMode = config.queryMode || 'local';
        }
        else if (config.proxyStore)
        {
            storeCfg = Ext.apply(storeCfg, {
                type: 'store', // Ext.data.Store
                proxy: config.proxyStore,
                autoLoad: config.autoLoad || false
            });
            
            config.queryMode = config.queryMode || 'remote';
        }
        else
        {
            this.getLogger().error("The combobox widget is not well configured. Should have enumeration or proxy store.");
        }
        
        if (config.storeListeners)
        {
            storeCfg.listeners = config.storeListeners;
        }
        
        if (config.order)
        {
            config.order = config.order.toLowerCase();
            if (config.order != 'natural')
            {
                var order = config.order == 'desc' ? 'DESC' : 'ASC';
                storeCfg.sorters = [{property: config.displayField, direction: order}]; // default order
            }
        }
        else
        {
            // naturalOrder is deprecated but we keep it for legacy purpose
            config.naturalOrder = Ext.isBoolean(config.naturalOrder) ? config.naturalOrder : config.naturalOrder == 'true';
            if (!config.naturalOrder)
            {
                storeCfg.sorters = [{property: config.displayField, direction: 'ASC'}]; // default order
            }
        }
        
        return storeCfg;
    },
    
    getValue: function()
    {
        let value = this.callParent(arguments);
        
        if (this.getInitialConfig('multiple') && (this.getInitialConfig('singleValueForMultiple') === "true" || this.getInitialConfig('singleValueForMultiple') === true))
        {
            return value.join(',');
        }
        else
        {
            return value;
        }
    },
    
    setValue: function(value)
    {
        if (this.getInitialConfig('multiple') && typeof value == 'string')
        {
            value = value.split(",").map(function(e) { return e.trim(); });
        }
        
        const lastValue = this.lastValue;
        
        this.callParent([value]);
        
        if (this.form && !this.getStore().isLoaded())
        {
            let formWasNotReady = !this.form.isFormReady();
            
            this.getStore().on('load', function() {
                let formerReadyState = this.form._formReady; 
                if (formWasNotReady)
                {
                    this.form._formReady = false; // The check change will fire the form fieldchange event otherwise (but the value was set before, the widget is just slow)
                }
                
                this.lastValue = lastValue;
                this.checkChange()
                
                this.form._formReady = formerReadyState; 
            }, this, {single: true});
        }
    },
    
    /**
     * Convert the enumeration value to store it
     * @param {Object} value the enumeration value
     * @return {Object} the converted value
     */
    convertEnumerationValue: function(value)
    {
        return value;
    },
    
    /**
     * @private
     * Function invoked when the combo box is clicked
     */
    _onClick: function()
    {
        // Do not collapse when the combo box is already expanded
        if (!this.readOnly && !this.disabled && !this.isExpanded) 
        {
            this.onTriggerClick();
        }
    }
});

/**
 * Provides a widget hidden field.<br>
 * Handle multiple and not multiple values.
 */
Ext.define('Ametys.form.widget.Hidden', {
    extend: "Ext.form.field.Hidden",
    
    setValue: function (value)
    {
        if (this.multiple && typeof value == 'string')
        {
            value = value.split(",").map(function(e) { return e.trim(); });
        }
        
        return this.multiple ? this.callParent([Ext.Array.from(value)]) : this.callParent([value]);
    },
    
    getValue: function ()
    {
        var val = this.callParent(arguments) || '';
        
        if (this.multiple && typeof val == 'string')
        {
            var values = val ? val.split(',') : [];
	        for (var i = 0; i < values.length; i++)
	        {
	            values[i] = values[i].trim();
	        }
	        
	        return values;
        }
        
        return this.multiple ? Ext.Array.from(val) : val;
    },
    
    getSubmitValue: function()
    {
        var val = this.getValue();
        return this.multiple || (typeof val != 'string' && typeof val != 'number') ? Ext.encode(val) : val;
    }
});

/**
 * Provides a widget for number input field.<br>
 * This widget is not registered in fields, it is mostly used for Ametys.form.widget.Long and Ametys.form.widget.Double.<br>
 * It does NOT handle multiple values.
 */
Ext.define('Ametys.form.widget.Number', {
    extend: "Ext.form.field.Number",
    
    constructor: function (config)
    {
        config.keyNavEnabled = Ext.isBoolean(config.keyNavEnabled) ? config.keyNavEnabled : config.keyNavEnabled != "false"; // default to true
        config.mouseWheelEnabled = Ext.isBoolean(config.mouseWheelEnabled) ? config.mouseWheelEnabled : config.mouseWheelEnabled == "true"; // default to false
        config.spinDownEnabled = Ext.isBoolean(config.spinDownEnabled) ? config.spinDownEnabled : config.spinDownEnabled != "false"; // default to true
        config.spinUpEnabled = Ext.isBoolean(config.spinUpEnabled) ? config.spinUpEnabled : config.spinUpEnabled != "false"; // default to true
        config.hideTrigger = Ext.isBoolean(config.hideTrigger) ? config.hideTrigger : config.hideTrigger == "true"; // default to false
        
        config.maxHeight = Ametys.form.WidgetManager.getWidgetMaxHeight(config);
        
        this.callParent(arguments);
    }
});

/**
 * Provides a widget for long input field.<br>
 * This widget is the default widget registered for fields of type Ametys.form.WidgetManager#TYPE_LONG.<br> 
 * It does NOT handle multiple values.
 */
Ext.define('Ametys.form.widget.Long', {
    extend: "Ametys.form.widget.Number",
    
    constructor: function (config)
    {
        /**
         * @cfg {Boolean} [onlyPositive=true] False to handle negative values, is overriden by minValue & maxValue configs
         */
        
        config = Ext.apply(config, {
            allowDecimals: false
        });
        
        config.onlyPositive = Ext.isBoolean(config.onlyPositive) ? config.onlyPositive : config.onlyPositive != "false"; // default to true
        config.minValue = parseInt(config.minValue);
        config.maxValue = parseInt(config.maxValue);
        
        if (config.onlyPositive && isNaN(config.minValue) && (isNaN(config.maxValue) || config.maxValue > 0))
        {
             config.minValue = 0; // default to 0 to allow only positive numbers
        }
        
        this.callParent(arguments);
    }
});

/**
 * Provides a widget for multiple long input field.<br>
 * This widget is the default widget registered for fields of type Ametys.form.WidgetManager#TYPE_LONG.<br>
 * It only handle multiple values.
 */
Ext.define('Ametys.form.widget.LongMulti', {
    extend: "Ext.form.field.TextArea",

    statics:
    {
        /**
         * @property {Number} FIELD_HEIGHT The default height for textarea field
         * @private
         * @readonly
         */
        FIELD_HEIGHT: 80
    },

    /**
     * @cfg {String} errorText
     * The error text to display if the validation fails
     */
    errorText : "{{i18n PLUGINS_CORE_UI_WIDGET_LONG_MULTI_FIELD_ERROR}}",
    
    /**
     * @cfg {Boolean} [onlyPositive=true] False to handle negative values, is overriden by minValue
     */
    
    /**
     * Constructor of the widget to handle multiple long values
     * @param {Object} config widget config
     */
    constructor: function (config)
    {
        var height = config.height;

        config = Ext.apply(config, {
            height: height ? Number(height) : Ametys.form.widget.TextArea.FIELD_HEIGHT
        });
        
        config.onlyPositive = Ext.isBoolean(config.onlyPositive) ? config.onlyPositive : config.onlyPositive != "false"; // default to true
        this._minValue = parseInt(config.minValue);
        if (config.onlyPositive && isNaN(this._minValue))
        {
             this._minValue = 0; // default to 0 to allow only positive numbers
        }

        this.callParent(arguments);
    },

    /**
     * Sets a long value into the field
     * @param {Number[]} value The value to set as an array of number
     */
    setValue: function(value)
    {
        if (!value)
        {
            this.callParent(arguments);
        }
        else
        {
            if (Ext.isArray(value))
            {
                value = value.join("\n");
            }

            this.callParent([value]);
        }
    },

    getValue: function()
    {
        var value = this.callParent(arguments) || '';

        if (value)
        {
            var strValues = value.split(/\r?\n/);
            
            var me = this,
                parseValues = [];
            Ext.Array.each(strValues, function (strValue) {
                strValue = strValue.trim();
                if (!Ext.isEmpty(strValue))
                {
                    var parseValue = me.parseValue(strValue);
                    if (parseValue != null)
                    {
                        parseValues.push(parseValue);
                    }
                }
            });
            
            return parseValues;
        }

        return [];
    },

    getErrors: function (value)
    {
        value = value || this.getRawValue();// Just in case
        var errors = this.callParent(arguments);

        if (value)
        {
            var strValues = value.split(/\r?\n/);
            
            var me = this,
                invalidValues = [];
            Ext.Array.each(strValues, function (strValue) {
                strValue = strValue.trim();
                if (!Ext.isEmpty(strValue))
                {
                    var intValue = me.parseValue(strValue);
                    if (intValue == null)
                    {
                        invalidValues.push(strValue);
                    }
                }
            });
            
            if (invalidValues.length > 0)
            {
                errors.push(this.errorText + invalidValues.join(", "));
            }
        }

        return errors;
    },

    /**
     * Parse a single String value to a Number
     * @param {String} value
     * @return {Number}
     * @protected
     */
    parseValue : function(value)
    {
        var intValue = parseInt(value);
        if (isNaN(intValue) || "" + intValue != value.trim())
        {
            this.getLogger().warn(value + " is not a number");
            return null;
        }
        else if(!isNaN(this._minValue) && this._minValue > intValue)
        {
            this.getLogger().warn(value + " cannot be under " + this._minValue);
            return null;
        }
        else
        {
            return intValue;
        }
    },

    getSubmitValue: function ()
    {
        return !this.value ? null : Ext.JSON.encode(this.getValue());
    }
});

/**
 * Provides a widget for decimal input field.<br>
 * This widget is the default widget registered for fields of type Ametys.form.WidgetManager#TYPE_DOUBLE.<br>
 * It does NOT handle multiple values.
 */
Ext.define('Ametys.form.widget.Double', {
    extend: "Ametys.form.widget.Number",

    /**
     * @cfg {Boolean} [onlyPositive=true] False to handle negative values, is overriden by minValue & maxValue configs
     */
    
    constructor: function (config)
    {
        config = Ext.apply(config, {
            allowDecimals: true
        });
        
        config.onlyPositive = Ext.isBoolean(config.onlyPositive) ? config.onlyPositive : config.onlyPositive != "false"; // default to true
        config.minValue = parseFloat(config.minValue);
        config.maxValue = parseFloat(config.maxValue);
        if (config.onlyPositive && isNaN(config.minValue) && (isNaN(config.maxValue) || config.maxValue > 0))
        {
             config.minValue = 0; // default to 0 to allow only positive numbers
        }
        
        this.callParent(arguments);
    }
});

/**
 * Provides a widget for multiple decimal input field.<br>
 * This widget is the default widget registered for fields of type Ametys.form.WidgetManager#TYPE_DOUBLE.<br>
 * It only handle multiple values.
 */
Ext.define('Ametys.form.widget.DoubleMulti', {
    extend: "Ametys.form.widget.LongMulti",

    /**
     * @cfg {String} decimalSeparator
     * Character(s) to allow as the decimal separator. Defaults to {@link Ext.util.Format#decimalSeparator decimalSeparator}.
     * @locale
     */
    decimalSeparator : null,
    
    /**
     * @cfg {String} errorText
     * The error text to display if the validation fails
     */
    errorText : "{{i18n PLUGINS_CORE_UI_WIDGET_DOUBLE_MULTI_FIELD_ERROR}}",
    
    /**
     * @cfg {Boolean} [onlyPositive=true] False to handle negative values, is overriden by minValue config
     */
    
     /**
     * Constructor of the widget to handle multiple double values
     * @param {Object} config widget config
     */
    constructor: function (config)
    {
        this.callParent(arguments);
        
        config.onlyPositive = Ext.isBoolean(config.onlyPositive) ? config.onlyPositive : config.onlyPositive != "false"; // default to true
        this._minValue = parseFloat(config.minValue);
        if (config.onlyPositive && isNaN(this._minValue))
        {
             this._minValue = 0; // default to 0 to allow only positive numbers
        }

    },
    
    initComponent: function() {
        var me = this;
        if (me.decimalSeparator === null) {
            me.decimalSeparator = Ext.util.Format.decimalSeparator;
        }
        me.callParent();
    },

    /**
     * Sets a decimal value into the field
     * @param {Number[]} value The value to set as an array of number
     */
    setValue: function(value)
    {
        if (!value)
        {
            this.callParent(arguments);
        }
        else
        {
            if (Ext.isArray(value))
            {
                value = value.join("\n");
                value = value.replace(/\./g, this.decimalSeparator);
            }

            this.callParent([value]);
        }
    },

    /**
     * Parse a single String value to a Number
     * @param {String} value
     * @return {Number}
     * @protected
     */
    parseValue : function(value)
    {
        var translatedValue = String(value).replace(this.decimalSeparator, '.').trim();
        var floatValue = translatedValue == '' ? NaN : Number(translatedValue);

        if (isNaN(floatValue))
        {
            this.getLogger().warn(value + " is not a number");
            return null;
        }
        else if(!isNaN(this._minValue) && this._minValue > floatValue)
        {
            this.getLogger().warn(value + " cannot be under " + this._minValue);
            return null;
        }
        else
        {
            return floatValue;
        }
    }
});

/**
 * Provides a widget for checkbox input field.<br>
 * This widget is the default widget registered for fields of type Ametys.form.WidgetManager#TYPE_BOOLEAN.<br> 
 * It does NOT handle multiple values.
 */
Ext.define('Ametys.form.widget.Checkbox', {
    extend: "Ext.form.field.Checkbox",
    
    constructor: function (config)
    {
        config = Ext.apply(config, {
            checked: Ext.isBoolean(config.value) ? config.value : config.value == "true",
            hideEmptyLabel: Ext.isBoolean(config.hideEmptyLabel) ? config.hideEmptyLabel : config.hideEmptyLabel !== "false",   
            hideLabel: Ext.isBoolean(config.hideLabel) ? config.hideLabel : config.hideLabel == "true",     
            inputValue: 'true', 
            uncheckedValue: 'false'
        });
        
        this.callParent(arguments);
    }
});

/**
 * Provides a widget for rich text field.<br>
 * This widget is the default widget registered for fields of type Ametys.form.WidgetManager#TYPE_RICH_TEXT.<br>
 * It does NOT handle multiple values.<br>
 * It can be "remotely" configured by using {@link Ametys.form.widget.RichText.RichTextConfiguration}
 */
Ext.define('Ametys.cms.form.widget.RichText', {
    extend: "Ext.form.field.RichText",
    
    statics:
    {
        /**
         * @property {Number} FIELD_HEIGHT The default height for textarea field
         * @private
         * @readonly 
         */
        FIELD_HEIGHT: 400
    },
    
    constructor: function (config)
    {
        var validationConfig = config.validationConfig || {},
            maxLength = config.maxLength || validationConfig.maxlength,
            height = config.height,
            resizable = config.resizable != undefined 
                ? Ext.isBoolean(config.resizable) 
                    ? config.resizable 
                    : config.resizable == 'true' 
                : true; 
            
        config.category = config.category || '';
        
        config = Ext.apply(config, {
            height: height ? Number(height) : Ametys.cms.form.widget.RichText.FIELD_HEIGHT,
            maxLength: maxLength ? Number(maxLength) : undefined,
            resizable: resizable,
            charCounter: true,
            checkTitleHierarchy: true,
            
            editorCSSFile: Ametys.form.widget.RichText.RichTextConfiguration.getCSSFiles(config.category),
            validElements: config.validElements || Ametys.form.widget.RichText.RichTextConfiguration.getTags(config.category),
            validStyles: config.validStyles ? Ext.JSON.decode(config.validStyles) : Ametys.form.widget.RichText.RichTextConfiguration.getStylesForTags(config.category),
            validClasses: config.validClasses ? Ext.JSON.decode(config.validClasses) : Ametys.form.widget.RichText.RichTextConfiguration.getClassesForTags(config.category),
            
            validator: function(value)
            {
                return Ametys.form.widget.RichText.RichTextConfiguration.validates(value, config.category);
            }
        });
        
        this.callParent(arguments);
        
        this.addListener('editorsetcontent', function(field, editor, object) { Ametys.form.widget.RichText.RichTextConfiguration.convertOnSetContent(field, editor, object, config.category); });
        this.addListener('editorgetcontent', function(field, editor, object) { Ametys.form.widget.RichText.RichTextConfiguration.convertOnGetContent(field, editor, object, config.category); });
        
        this.addListener('editorkeypress', function(field, editor, e) { Ametys.form.widget.RichText.RichTextConfiguration.fireEvent('keypress', field, editor, e); });
        this.addListener('editorkeydown', function(field, editor, e) { Ametys.form.widget.RichText.RichTextConfiguration.fireEvent('keydown', field, editor, e); });
        this.addListener('editorkeyup', function(field, editor, e) { Ametys.form.widget.RichText.RichTextConfiguration.fireEvent('keyup', field, editor, e); });
        this.addListener('editorvisualaid', function(field, editor, object) { Ametys.form.widget.RichText.RichTextConfiguration.fireEvent('visualaid', field, editor, object); });
        this.addListener('editorpreprocess', function(field, editor, object) { Ametys.form.widget.RichText.RichTextConfiguration.fireEvent('preprocess', field, editor, object); });
        this.addListener('editorhtmlnodeselected', function(field, editor, node) { Ametys.form.widget.RichText.RichTextConfiguration.fireEvent('htmlnodeselected', field, editor, node); });
    }
    
});

/**
 * Provides a widget for textarea field (multiline text).<br>
 * This widget is registered for fields of type Ametys.form.WidgetManager#TYPE_STRING.<br>
 * It does NOT handle multiple values. 
 */
Ext.define('Ametys.form.widget.TextArea', {
    extend: "Ametys.form.field.TextArea",
    
    statics:
    {
        /**
         * @property {Number} FIELD_HEIGHT The default height for textarea field
         * @private
         * @readonly 
         */
        FIELD_HEIGHT: 80
    },
        
    constructor: function (config)
    {
        var validationConfig = config.validationConfig || {},
            maxLength = config.maxLength || validationConfig.maxlength,
            charCounter = Ext.isBoolean(config.charCounter) ? config.charCounter : config.charCounter == "true",
            height = config.height;
        
        config = Ext.apply(config, {
            height: height ? Number(height) : Ametys.form.widget.TextArea.FIELD_HEIGHT,
            maxLength: maxLength ? Number(maxLength) : null,
            charCounter: charCounter || !!maxLength
        });
        
        this.callParent(arguments);
    }
    
});

/**
 * Provides a widget for reference of type external<br>
 * This widget is the default widget registered for fields of type Ametys.form.WidgetManager#TYPE_REFERENCE.<br>
 * It does NOT handle multiple values.
 */
Ext.define('Ametys.form.widget.UrlReference', {
    extend: 'Ametys.form.widget.Text',
    
    statics: {
        
        /**
         * @property {Object} WARNS The possible errors on http check and their associated warn message
         * @private
         * @readonly
         */
        WARNS: {
            'NOT_FOUND': "{{i18n PLUGINS_CORE_UI_WIDGET_URL_REFERENCE_NOT_FOUND_WARN}}",
            'UNAUTHORIZED': "{{i18n PLUGINS_CORE_UI_WIDGET_URL_REFERENCE_UNAUTHORIZED_WARN}}",
            'TIMEOUT': "{{i18n PLUGINS_CORE_UI_WIDGET_URL_REFERENCE_TIMEOUT_WARN}}",
            'REDIRECT': "{{i18n PLUGINS_CORE_UI_WIDGET_URL_REFERENCE_REDIRECT_WARN}}",
            'SECURITY_LEVEL_ERROR': "{{i18n PLUGINS_CORE_UI_WIDGET_URL_REFERENCE_SECURITY_LEVEL_ERROR_WARN}}",
            'SERVER_ERROR': "{{i18n PLUGINS_CORE_UI_WIDGET_URL_REFERENCE_SERVER_ERROR_WARN}}",
            'NOT_HTTP': "{{i18n PLUGINS_CORE_UI_WIDGET_URL_REFERENCE_NOT_HTTP_WARN}}"
        }
    },
    
    /**
     * @cfg {Boolean} [checkUrlValidity=false] Set to 'true' to activate the check of external url value. If not valid, the field will be marked with warning.
     */
    
    /**
     * @cfg {Boolean} [hideWarnForNoHttpUrl=false] When {link #cfg-checkUrlValidity} is true, set to 'false' to not display a warn when the url is not a http url (ie. does not start with http(s)://).
     */
    
    /**
     * @property {String} [referenceType="__external"] The type of the reference.
     * @readonly 
     */
    referenceType: '__external',
    
    constructor: function (config)
    {
        config.checkUrlValidity = Ext.isBoolean(config.checkUrlValidity) ? config.checkUrlValidity : config.checkUrlValidity == 'true';
        config.hideWarnForNoHttpUrl = Ext.isBoolean(config.hideWarnForNoHttpUrl) ? config.hideWarnForNoHttpUrl : config.hideWarnForNoHttpUrl == 'true';
        
        this.callParent(arguments);  
        
        if (config.checkUrlValidity)
        {
            this.on('change', Ext.Function.createBuffered(this._checkUrlValidity, 500, this));
        }
    },
    
    /**
     * Sets a data value into the field 
     * @param {Object|String} value The value to set as a Object or a JSON encoded object
     * @param {Number} value.referenceType The type of reference
     * @param {Number} value.value The url
     */
    setValue: function(value) 
    {
        if (!value)
        {
            this.callParent(arguments);
        }
        else 
        {
            if (!Ext.isObject(value))
            {
                try
                {
                    value = Ext.JSON.decode(value);
                }
                catch(e)
                {
                    value = {
                        type: this.referenceType,
                        value: value
                    }
                }
            }
            
            this.callParent([value.value]);
        }
    },
    
    /**
     * @inheritdoc
     */
    getValue: function()
    {
        var value = this.callParent(arguments) || '';
        
        if (value)
        {
            return {
                type: this.referenceType,
                value: value
            }
        }
        
        return value;
    },
    
    getSubmitValue: function ()
    {
        return !this.value ? null : Ext.JSON.encode(this.getValue());
    },
    
    isEqual: function (newVal, oldVal)
    {
        if (Ext.isObject(newVal) && Ext.isObject(oldVal))
        {
            return newVal.type == oldVal.type
                && newVal.value == oldVal.value
        }
        
        return this.callParent(arguments);
    },
    
    /**
     * @private
     * Check the validity of the current value
     */
    _checkUrlValidity: function()
    {
        var value = this.getValue();
        if (value && value.value)
        {
            Ametys.data.ServerComm.callMethod({
	            role: "org.ametys.core.util.HttpUtils",
	            methodName: "checkHttpUrl",
	            parameters: [value.value],
	            callback: {
	                handler: this._checkUrlValidityCb,
	                scope: this
	            },
	            errorMessage: false
	        });
            
        }
    },
    
    /**
     * @private
     * Callback function after checking the URL validity.
     * Display a warn message if the URL is not valid.
     * @param {Object} response the server result
     */
    _checkUrlValidityCb: function (response)
    {
        if (response.success || response.checkResult == 'NOT_HTTP' && this.hideWarnForNoHttpUrl)
        {
            this.clearWarning();
        }
        else
        {
            this.markWarning(Ametys.form.widget.UrlReference.WARNS[response.checkResult]);
        }
    }
});