/**
 * The Number field creates an HTML5 text input that allows the editing of number values, and is usually created inside
 * a form. Most browsers will show a specialized virtual keyboard for entering numbers. The Number field
 * only accepts numerical input.  If you want a Number field with up/down spinners, see {@link Ext.field.Spinner}.
 *
 *     @example
 *     Ext.create('Ext.form.Panel', {
 *         fullscreen: true,
 *         items: [
 *             {
 *                 xtype: 'fieldset',
 *                 title: 'How old are you?',
 *                 items: [
 *                     {
 *                         xtype: 'numberfield',
 *                         label: 'Age',
 *                         minValue: 18,
 *                         maxValue: 150,
 *                         name: 'age'
 *                     }
 *                 ]
 *             }
 *         ]
 *     });
 *
 * Or on its own, outside of a form:
 *
 *     Ext.create('Ext.field.Number', {
 *         label: 'Age',
 *         value: '26'
 *     });
 *
 * ## minValue, maxValue
 *
 * The {@link #minValue} and {@link #maxValue} configurations are self-explanatory and simply constrain the value
 * For example, to create a salary field that limits entry to between 25,000 and 50,000 we can do this:
 *
 *     @example
 *     Ext.create('Ext.form.Panel', {
 *         fullscreen: true,
 *         items: [
 *             {
 *                 xtype: 'fieldset',
 *                 title: 'Are you rich yet?',
 *                 items: [
 *                     {
 *                         xtype: 'numberfield',
 *                         label: 'Salary',
 *                         value: 30000,
 *                         minValue: 25000,
 *                         maxValue: 50000
 *                     }
 *                 ]
 *             }
 *         ]
 *     });
 *
 * This creates a field that starts with a value of $30,000 and will not go beneath $25,000 or above $50,000.
 *
 * Because number field inherits from {@link Ext.field.Text textfield} it gains all of the functionality that text
 * fields provide, including getting and setting the value at runtime, validations and various events that are fired as
 * the user interacts with the component. Check out the {@link Ext.field.Text} docs to see the additional functionality
 * available.
 */
Ext.define('Ext.field.Number', {
    extend: 'Ext.field.Text',
    xtype: 'numberfield',
    alternateClassName: 'Ext.form.Number',
 
    requires: [
        'Ext.data.validator.Number'
    ],
 
    config: {
        /**
         * @cfg {Number} minValue
         * The minimum value that this Number field can accept. Defaults to no minimum.
         */
        minValue: null,
 
        /**
         * @cfg {Number} maxValue
         * The maximum value that this Number field can accept. Defaults to no maximum.
         */
        maxValue: null,
 
        /**
         * @cfg {Number} decimals
         * The maximum precision to display after the decimal separator.
         */
        decimals: 2,
 
        /**
         * @cfg {Boolean} trim
         * `false` to always show zeros when formatting the number
         */
        trim: true
    },
 
    /**
     * @cfg {String} inputType
     * For desktop environments, an input of `type=text` is used and a rich user experience
     * is provided for numeric entry. For mobile environments, an input of `type=number` is
     * used and basic validation is performed on keystroke and `minValue` and `maxValue`
     * clamping is only done on blur or `setValue` if the field is not focused.
     *
     * If you specify `inputType` of `'text'`, the text input will be used on all devices
     * at the expense of numeric input keyboard on non-iOS devices. Alternatively, you may
     * specify an `inputType` of `'tel'` which will bring up the phone number input
     * keyboard, which isn't as ideal as the numeric keyboard.
     */
    inputType: Ext.os.is.Desktop ? 'text' : 'number',
 
    /**
     * @cfg {String} minValueText
     * The error message that will be displayed if the field's value is less than minValue
     * @Locale
     * @since 6.5.1
     */
    minValueText: 'The minimum value for this field is {0}',
 
    /**
     * @cfg {String} maxValueText
     * The error message that will be displayed if the field's value is greater than maxValue.
     * @Locale
     * @since 6.5.1
     */
    maxValueText: 'The maximum value for this field is {0}',
 
    /**
     * @cfg {String} decimalsText
     * The error message that will be displayed if the field's value has incorrect number of
     * decimal places.
     * @Locale
     * @since 6.5.1
     */
    decimalsText: 'The maximum decimal places is {0}',
 
    badFormatMessage: 'Value is not a valid number',
 
    classCls: Ext.baseCSSPrefix + 'numberfield',
 
    parseValidator: 'number',
 
    initialize: function() {
        // Force numberFormat creation
        this.getDecimals();
 
        this.callParent();
 
        // This isn't supported in browsers yet, but is part of the spec.
        this.inputElement.dom.setAttribute('inputmode', 'numeric');
 
        //<debug>
        // Check after configuration to catch subclasses with config in prototype
        // We cannot uses masks. The parsing would not be able to deal with
        // fixed characters such as commas.
        if (this.getInputMask()) {
            Ext.raise('NumberFields cannot use input masks');
        }
        //</debug>
    },
 
    updateDecimals: function(decimals) {
        var me = this,
            format = '0',
            zeroChar = me.getTrim() ? '#' : '0',
            value;
 
        if (decimals) {
            format += '.' + Ext.String.repeat(zeroChar, decimals);
        }
        me.numberFormat = format;
 
        if (!me.isConfiguring) {
            value = me.getValue();
            if (Ext.isDate(value)) {
                me.setInputValue(value);
            }
        }
    },
 
    applyInputValue: function(value) {
        // Force numberFormat creation
        this.getDecimals();
        if (typeof value === 'number') {
            value = Ext.util.Format.number(value, this.numberFormat);
        }
        return value;
    },
 
    doValidate: function (value, errors, skipLazy) {
        var me = this,
            String = Ext.String,
            minValue = me.getMinValue(),
            maxValue = me.getMaxValue();
 
        me.callParent([ value, errors, skipLazy ]);
 
        if (minValue != null && value < minValue) {
            errors.push(String.format(me.minValueText, minValue));
        }
        else if (maxValue != null && value > maxValue) {
            errors.push(String.format(me.maxValueText, maxValue));
        }
    },
 
    onKeyDown: function(e) {
        var me = this,
            raw;
 
        // Prevalidate the result of the keystroke unless we are using DOM inputType of number.
        if (me.getInputType() !== 'number') {
            if (!e.ctrlKey && !e.altKey) {
                raw = me.calculateNewValue(e.key());
 
                // If the resulting raw value would be invalid, veto the event
                if (!me.specialKeys[e.getCharCode()] && !me.isAllowableValue(raw)) {
                    e.preventDefault();
                    return false;
                }
            }
        }
 
        me.callParent([e]);
    },
 
    transformValue: function (value) {
        if (!(value || value === 0)) {
            value = null;
        }
 
        return value;
    },
 
    privates: {
        calculateNewValue: function(text) {
            var me = this,
                textSelection = me.getTextSelection(),
                raw = me.getInputValue();
 
            // Characters are selected, replace them.
            if (textSelection[1]) {
                raw = raw.substr(0, textSelection[0]) + text + raw.substr(textSelection[1]);
            }
            // No characters are selected, just insert at caret.
            else {
                raw = Ext.String.insert(raw, text, me.getCaretPos());
            }
            return raw;
        },
 
        handlePaste: function(e) {
            var me = this,
                text;
 
            if (me.getInputType() !== 'number') {
                text = e.getClipboardData('text/plain');
 
                if (text) {
                    text = me.calculateNewValue(text);
 
                    if (me.isAllowableValue(text)) {
                        me.setValue(text);
                    }
 
                    e.preventDefault();
 
                    return false;
                }
            }
 
            me.callParent([e]);
 
            me.validate();
        },
 
        isAllowableValue: function(value) {
            var minValue = this.getMinValue(),
                allowNegative = minValue == null || minValue < 0;
 
            if (!allowNegative && Ext.String.startsWith(value, '-')) {
                return false;
            }
 
            return this.isPartialValue(value) || this.parseValue(value) !== null;
        },
 
        isPartialValue: function(value) {
            var me = this,
                minValue = me.getMinValue(),
                allowNegative = minValue == null || minValue < 0;
 
            // '-' should not tickle the validation process if -ve is allowed.
            // '.' should not tickle the validation process if decimals are allowed.
            // '-.' likewise if both are allowed.
            // We do not want the validation to kick in and rewrite the calculated
            // value back into the field if the user is typing "-.5" which entails
            // going through invalid states.
            if (allowNegative && value === '-') {
                return true;
            }
            if (me.getDecimals() && (value === '.' || (allowNegative && value === '-.'))) {
                return true;
            }
            return false;
        }
    }
}, function (C) {
    var E = Ext.event.Event;
 
    C.prototype.specialKeys = Ext.Array.toMap([
        E.BACKSPACE,
        E.TAB,
        E.RETURN,
        E.CTRL,
        E.DELETE,
        E.LEFT,
        E.RIGHT,
        E.UP,
        E.DOWN,
        E.HOME,
        E.END,
        E.META
    ]);
});