/**
 * A wrapper class which can be applied to any element. Fires a "tap" event while
 * touching the device. The interval between firings may be specified in the config but
 * defaults to 20 milliseconds.
 */
Ext.define('Ext.util.TapRepeater', {
    requires: ['Ext.DateExtras'],

    mixins: {
        observable: 'Ext.mixin.Observable'
    },

    /**
     * @event touchstart
     * Fires when the touch is started.
     * @param {Ext.util.TapRepeater} this
     * @param {Ext.event.Event} e
     */

    /**
     * @event tap
     * Fires on a specified interval during the time the element is pressed.
     * @param {Ext.util.TapRepeater} this
     * @param {Ext.event.Event} e
     */

    /**
     * @event touchend
     * Fires when the touch is ended.
     * @param {Ext.util.TapRepeater} this
     * @param {Ext.event.Event} e
     */

    config: {
        el: null,
        accelerate: true,
        interval: 10,
        delay: 250,
        preventDefault: true,
        stopDefault: false,
        timer: 0,
        pressCls: null
    },

    /**
     * Creates new TapRepeater.
     * @param {Object} config
     */
    constructor: function(config) {
        var me = this;
        //<debug warn>
        for (var configName in config) {
            if (me.self.prototype.config && !(configName in me.self.prototype.config)) {
                me[configName] = config[configName];
                Ext.Logger.warn('Applied config as instance property: "' + configName + '"', me);
            }
        }
        //</debug>
        me.initConfig(config);
    },

    updateEl: function(newEl, oldEl) {
        var eventCfg = {
                touchstart: 'onTouchStart',
                touchend: 'onTouchEnd',
                tap: 'eventOptions',
                scope: this
            };
        if (oldEl) {
            oldEl.un(eventCfg)
        }
        newEl.on(eventCfg);
    },

    // @private
    eventOptions: function(e) {
        if (this.getPreventDefault()) {
            e.preventDefault();
        }
        if (this.getStopDefault()) {
            e.stopEvent();
        }
    },

    // @private
    destroy: function() {
        this.clearListeners();
        Ext.destroy(this.el);
    },

    // @private
    onTouchStart: function(e) {
        var me = this,
            pressCls = me.getPressCls();
        clearTimeout(me.getTimer());
        if (pressCls) {
            me.getEl().addCls(pressCls);
        }
        me.tapStartTime = new Date();

        me.fireEvent('touchstart', me, e);
        me.fireEvent('tap', me, e);

        // Do not honor delay or interval if acceleration wanted.
        if (me.getAccelerate()) {
            me.delay = 400;
        }
        me.setTimer(Ext.defer(me.tap, me.getDelay() || me.getInterval(), me, [e]));
    },

    // @private
    tap: function(e) {
        var me = this;
        me.fireEvent('tap', me, e);
        me.setTimer(Ext.defer(me.tap, me.getAccelerate() ? me.easeOutExpo(Ext.Date.getElapsed(me.tapStartTime),
            400,
            -390,
            12000) : me.getInterval(), me, [e]));
    },

    // Easing calculation
    // @private
    easeOutExpo: function(t, b, c, d) {
        return (t == d) ? b + c : c * ( - Math.pow(2, -10 * t / d) + 1) + b;
    },

    // @private
    onTouchEnd: function(e) {
        var me = this;
        clearTimeout(me.getTimer());
        me.getEl().removeCls(me.getPressCls());
        me.fireEvent('touchend', me, e);
    }
});