/**
 * Wraps a Ext.form.Number field to provide a number input field with up/down spinner button and
 * optional step value for each spin up/down increment/decrement.
 *
 * Example usage:
 *
 *     @example
 *     var spinner = Ext.create('Ext.field.Spinner', {
 *         label: 'Spinner Field',
 *         minValue: 0,
 *         maxValue: 100,
 *         stepValue: 2,
 *         cycle: true
 *     });
 *     Ext.Viewport.add({ xtype: 'container', items: [spinner] });
 *
 */
Ext.define('Ext.field.Spinner', {
    extend: 'Ext.field.Number',
    xtype: 'spinnerfield',
    alternateClassName: 'Ext.form.Spinner',
 
    requires: [
        'Ext.field.trigger.SpinDown',
        'Ext.field.trigger.SpinUp'
    ],
 
    /**
     * @event spin
     * Fires when the value is changed via either spinner buttons.
     * @param {Ext.field.Spinner} this 
     * @param {Number} value 
     * @param {String} direction 'up' or 'down'.
     */
 
    /**
     * @event spindown
     * Fires when the value is changed via the spinner down button.
     * @param {Ext.field.Spinner} this 
     * @param {Number} value 
     */
 
    /**
     * @event spinup
     * Fires when the value is changed via the spinner up button.
     * @param {Ext.field.Spinner} this 
     * @param {Number} value 
     */
 
    /**
     * @event updatedata
     * @hide
     */
 
    /**
     * @event action
     * @hide
     */
 
    config: {
        /**
         * @cfg {Number} stepValue
         * Value that is added or subtracted from the current value when a spinner
         * is tapped.
         */
        stepValue: 1,
 
        /**
         * @cfg {Boolean} accelerateOnTapHold
         * `true` if autorepeating should start slowly and accelerate.
         */
        accelerateOnTapHold: true,
 
        /**
         * @cfg {Boolean} cycle
         * When set to `true`, it will loop the values of a minimum or maximum is
         * reached. If the maximum value is reached, the value will be set to the
         * minimum.
         */
        cycle: false,
 
        /**
         * @cfg clearable
         * @inheritdoc
         */
        clearable: false,
 
        /**
         * @cfg {Boolean} groupButtons
         * `true` if you want to group the buttons to the right of the fields. `false` if
         * you want the buttons to be at either side of the field.
         * @deprecated 6.2.0 This concern should be handled by the theme.
         */
        groupButtons: true
    },
 
    triggers: {
        spindown: {
            type: 'spindown',
            group: 'spin',
            repeat: true
        },
        spinup: {
            type: 'spinup',
            group: 'spin',
            repeat: true
        }
    },
 
    /**
     * @cfg value
     * @inheritdoc
     */
    value: 0,
 
    /**
     * @cfg decimals
     * @inheritdoc
     */
    decimals: 0,
 
    /**
     * @property classCls
     * @inheritdoc
     */
    classCls: Ext.baseCSSPrefix + 'spinnerfield',
    groupedButtonsCls: Ext.baseCSSPrefix + 'grouped-buttons',
 
    initElement: function() {
        this.callParent();
 
        this.inputElement.dom.readOnly = true;
    },
 
    updateGroupButtons: function(groupButtons) {
        var downTrigger = this.getTriggers().spindown;
 
        downTrigger.setGroup(groupButtons ? 'spin' : null);
        downTrigger.setSide(groupButtons ? null : 'left');
    },
 
    applyTriggers: function(triggers, oldTriggers) {
        var accelerate = this.getAccelerateOnTapHold(),
            upTrigger, downTrigger, upRepeat, downRepeat;
 
        if (triggers && accelerate) {
            upTrigger = triggers.spinup;
            downTrigger = triggers.spindown;
            upRepeat = upTrigger.repeat;
 
            if (upRepeat) {
                upTrigger.repeat = Ext.apply({
                    accelerate: accelerate
                }, upRepeat);
            }
 
            downRepeat = downTrigger.repeat;
 
            if (downRepeat) {
                downTrigger.repeat = Ext.apply({
                    accelerate: accelerate
                }, downRepeat);
            }
        }
 
        return this.callParent([triggers, oldTriggers]);
    },
 
    onKeyDown: function(e) {
        var limit;
 
        if (this.getInputType() !== 'number') {
            switch (e.getKey()) {
                case e.UP:
                    e.stopEvent();
                    this.spin(false);
                    break;
 
                case e.DOWN:
                    e.stopEvent();
                    this.spin(true);
                    break;
 
                // Home and End keys: https://www.w3.org/TR/wai-aria-practices-1.1/#spinbutton
                case e.HOME:
                    limit = this.getMinValue();
 
                    if (limit != null) {
                        e.stopEvent();
                        this.setValue(limit);
                    }
 
                    break;
 
                case e.END:
                    limit = this.getMaxValue();
 
                    if (limit != null) {
                        e.stopEvent();
                        this.setValue(limit);
                    }
 
                    break;
            }
        }
 
        this.callParent([e]);
    },
 
    /**
     * @private
     */
    onSpinDown: function() {
        if (!this.getDisabled() && !this.getReadOnly()) {
            this.spin(true);
        }
    },
 
    /**
     * @private
     */
    onSpinUp: function() {
        if (!this.getDisabled() && !this.getReadOnly()) {
            this.spin(false);
        }
    },
 
    /**
     * @private
     */
    spin: function(down) {
        var me = this,
            originalValue = me.getValue(),
            stepValue = me.getStepValue(),
            direction = down ? 'down' : 'up',
            minValue = me.getMinValue(),
            maxValue = me.getMaxValue(),
            value;
 
        if (down) {
            value = originalValue - stepValue;
        }
        else {
            value = originalValue + stepValue;
        }
 
        // if cycle is true, then we need to check fi the value hasn't 
        // changed and we cycle the value
        if (me.getCycle()) {
            if (originalValue === minValue && value < minValue) {
                value = maxValue;
            }
 
            if (originalValue === maxValue && value > maxValue) {
                value = minValue;
            }
        }
        else if (minValue != null && value < minValue) {
            value = minValue;
        }
        else if (maxValue != null && value > maxValue) {
            value = maxValue;
        }
 
        me.spinning = true;
        me.setValue(value);
        me.spinning = false;
        value = me.getValue();
 
        me.fireEvent('spin', me, value, direction);
        me.fireEvent('spin' + direction, me, value);
    },
 
    rawToValue: Ext.emptyFn,
 
    privates: {
        spinning: false,
 
        canSetInputValue: function() {
            return this.spinning || this.callParent();
        }
    }
});