/**
 * 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', {
 
    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>
        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.mixins.observable.constructor.call(me, 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();
        }
    },
 
    destroy: function() {
        this.el = Ext.destroy(this.el);
        this.callParent();
    },
 
    /**
     * @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]));
    },
 
    /**
     * @private
     * Easing calculation
     */
    easeOutExpo: function(t, b, c, d) {
        return (== 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);
    }
});