/**
 * 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}.
 * ```javascript
 *  @example({ framework: 'extjs' })
 *     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'
 *                     }
 *                 ]
 *             }
 *         ]
 *     });
 * ```
 * ```javascript
 * @example({framework: 'ext-react', packages:['ext-react']})
 * import React, { Component } from 'react';
 * import { ExtContainer, ExtNumberField, ExtFormPanel } from '@sencha/ext-react';
 *
 * export default class MyExample extends Component {
 *     render() {
 *        return (
 *             <ExtContainer layout="center">
 *                 <ExtFormPanel shadow>
 *                     <ExtNumberField
 *                         label="Number"
 *                         width="150"
 *                     />
 *                 </ExtFormPanel>
 *             </ExtContainer>
 *         )
 *     }
 * }
 * ```
 * ```javascript
 * @example({framework: 'ext-angular', packages:['ext-angular']})
 * import { Component } from '@angular/core'
 * declare var Ext: any;
 * @Component({
 *  selector: 'app-root-1',
 *  styles: [``],
 *  template: `
 *      <ExtContainer layout="center">
 *          <ExtFormPanel shadow="true">
 *              <ExtNumberField
 *                  label="Number"
 *                  width="150"
 *              >
 *              </ExtNumberField>
 *          </ExtFormPanel>
 *      </ExtContainer>
 *     `
 * })
 * export class AppComponent {} 
 * ```
 * ```html
 * @example({framework: 'ext-web-components', packages:['ext-web-components'], tab: 1 })
 * <ext-container layout="center">
 *    <ext-formpanel shadow="true">
 *        <ext-numberfield
 *             label="Number"
 *             width="150"
 *         >
 *        </ext-numberfield>
 *    </ext-formpanel>
 * </ext-container>
 * ```
 * ```javascript
 * @example({framework: 'ext-web-components', packages:['ext-web-components'], tab: 2 })
 * import '@sencha/ext-web-components/dist/ext-container.component';
 * import '@sencha/ext-web-components/dist/ext-formpanel.component';
 * import '@sencha/ext-web-components/dist/ext-numberfield.component';
 *
 * export default class NumberFieldComponent {} 
 * ```
 *
 * 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} decimalSeparator
         * Character(s) to allow as the decimal separator.
         * Defaults to {@link Ext.util.Format#decimalSeparator decimalSeparator}.
 * @since 7.0
         */
        decimalSeparator: null
    },
 
    /**
     * @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() {
        var me = this;
 
        // Force numberFormat creation
        me.getDecimals();
 
        if (me.getDecimalSeparator() === null) {
            me.setDecimalSeparator(Ext.util.Format.decimalSeparator);
        }
 
        me.callParent();
 
        // This isn't supported in browsers yet, but is part of the spec.
        me.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 (me.getInputMask()) {
            Ext.raise('NumberFields cannot use input masks');
        }
        //</debug>
    },
 
    updateDecimals: function(decimals) {
        var me = this,
            format = '0',
            zeroChar = me.getTrim() ? '#' : '0',
            value;
 
        // logic to allow decimals or no
        me.syncDecimalValidator();
 
        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) {
        var me = this,
            decimalSeparator = me.getDecimalSeparator();
 
        // Force numberFormat creation
        this.getDecimals();
 
        if (typeof value === 'number') {
            value = Ext.util.Format.number(value, this.numberFormat);
        }
 
        if (value && Ext.isString(value) && decimalSeparator) {
            value = value.replace(Ext.util.Format.decimalSeparator, decimalSeparator);
        }
 
        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;
    },
 
    rawToValue: Ext.emptyFn,
 
    updateDecimalSeparator: function() {
        this.syncDecimalValidator();
    },
 
    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;
        },
 
        /**
         * Validate field against decimals and decimalSeparator
         * If decimals is 0, don't allow decimals else use decimalSeparator
         */
        syncDecimalValidator: function() {
            var me = this,
                separator = (me.getDecimals() === 0) ? null : me.getDecimalSeparator();
 
            me.setParseValidator(
                Ext.create('Ext.data.validator.Number', {
                    decimalSeparator: separator
                })
            );
 
            me.validate();
        }
    }
}, 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
    ]);
});