/*
 * @class Ext.util.translatable.Abstract
 * @private
 *
 * The abstract class. Sub-classes are expected, at the very least, to implement translation logics inside
 * the 'translate' method
 */
Ext.define('Ext.util.translatable.Abstract', {
    mixins: ['Ext.mixin.Observable'],

    requires: ['Ext.fx.Easing'],

    config: {
        element: null,

        easing: {},

        easingX: {},

        easingY: {},

        fps: 60
    },

    constructor: function(config) {
        var element;

        this.doAnimationFrame = Ext.Function.bind(this.doAnimationFrame, this);

        this.translation = {
            x: 0,
            y: 0
        };

        this.activeEasing = {
            x: null,
            y: null
        };

        this.initialConfig = config;

        if (config && config.element) {
            element = config.element;
            delete config.element;

            this.setElement(element);
        }
    },

    applyElement: function(element) {
        if (!element) {
            return;
        }

        return Ext.get(element);
    },

    updateElement: function(element) {
        this.initConfig(this.initialConfig);
        this.refresh();
    },

    factoryEasing: function(easing) {
        return new Ext.fx.Easing(easing);
    },

    applyEasing: function(easing) {
        easing = this.factoryEasing(easing);

        if (!this.getEasingX()) {
            this.setEasingX(easing);
        }

        if (!this.getEasingY()) {
            this.setEasingY(easing);
        }

        return easing;
    },

    applyEasingX: function(easing) {
        return this.factoryEasing(easing);
    },

    applyEasingY: function(easing) {
        return this.factoryEasing(easing);
    },

    updateFps: function(fps) {
        this.animationInterval = 1000 / fps;
    },

    doTranslate: function(translation) {
        var current = this.translation;

        if ('x' in translation) {
            current.x = translation.x;
        }

        if ('y' in translation) {
            current.y = translation.y;
        }

        return this;
    },

    translate: function(translation, animation) {
        this.stopAnimation();

        if (animation) {
            return this.translateAnimated(translation, animation);
        }

        return this.doTranslate(translation);
    },

    translateAnimated: function(translation, animation) {
        if (!Ext.isObject(animation)) {
            animation = {};
        }

        var easing = animation.easing,
            activeEasing = this.activeEasing,
            current = this.translation,
            now = Ext.Date.now(),
            easingX = ('x' in translation) ? (easing || animation.easingX || this.getEasingX()) : null,
            easingY = ('y' in translation) ? (easing || animation.easingY || this.getEasingY()) : null;

        if (easingX) {
            easingX = this.factoryEasing(easingX);
            easingX.setStartTime(now);
            easingX.setStartValue(current.x);
            easingX.setEndValue(translation.x);

            if ('duration' in animation) {
                easingX.setDuration(animation.duration);
            }
        }

        if (easingY) {
            easingY = this.factoryEasing(easingY);
            easingY.setStartTime(now);
            easingY.setStartValue(current.y);
            easingY.setEndValue(translation.y);

            if ('duration' in animation) {
                easingY.setDuration(animation.duration);
            }
        }

        activeEasing.x = easingX;
        activeEasing.y = easingY;

        this.isAnimating = true;
        this.animationTimer = setInterval(this.doAnimationFrame, this.animationInterval, this);

        this.fireEvent('animationstart', this);
    },

    doAnimationFrame: function() {
        if (!this.isAnimating) {
            return;
        }

        var easing = this.activeEasing,
            easingX = easing.x,
            easingY = easing.y,
            isEasingXEnded = easingX === null || easingX.isEnded,
            isEasingYEnded = easingY === null || easingY.isEnded,
            translation = {};

        if (isEasingXEnded && isEasingYEnded) {
            this.stopAnimation();
            return;
        }

        if (!isEasingXEnded) {
            translation.x = Math.round(easingX.getValue());
        }

        if (!isEasingYEnded) {
            translation.y = Math.round(easingY.getValue());
        }

        this.doTranslate(translation);
        this.fireEvent('animationframe', this, translation);
    },

    stopAnimation: function() {
        if (!this.isAnimating) {
            return;
        }

        var activeEasing = this.activeEasing;

        activeEasing.x = null;
        activeEasing.y = null;

        this.isAnimating = false;

        clearInterval(this.animationTimer);
        this.fireEvent('animationend', this);
    },

    refresh: function() {
        this.translate(this.translation);
    }
});