/**
 * @private
 */
Ext.define('Ext.fx.runner.CssAnimation', {
    extend: 'Ext.fx.runner.Css',
 
    constructor: function() {
        this.runningAnimationsMap = {};
 
        this.elementEndStates = {};
 
        this.animationElementMap = {};
 
        this.keyframesRulesCache = {};
 
        this.uniqueId = 0;
 
        return this.callParent(arguments);
    },
 
    attachListeners: function() {
        this.listenersAttached = true;
 
        Ext.getWin().on({
            animationstart: 'onAnimationStart',
            animationend: 'onAnimationEnd',
            scope: this
        });
    },
 
    onAnimationStart: function(e) {
        var name = e.browserEvent.animationName,
            elementId = this.animationElementMap[name],
            animation = this.runningAnimationsMap[elementId][name],
            elementEndStates = this.elementEndStates,
            elementEndState = elementEndStates[elementId],
            data = {};
 
        //console.log("START============= " + name);
 
        if (elementEndState) {
            delete elementEndStates[elementId];
 
            data[elementId] = elementEndState;
 
            this.applyStyles(data);
        }
 
        if (animation.before) {
            data[elementId] = animation.before;
 
            this.applyStyles(data);
        }
    },
 
    onAnimationEnd: function(e) {
        var element = e.target,
            name = e.browserEvent.animationName,
            animationElementMap = this.animationElementMap,
            elementId = animationElementMap[name],
            runningAnimationsMap = this.runningAnimationsMap,
            runningAnimations = runningAnimationsMap[elementId],
            animation = runningAnimations[name];
 
        //console.log("END============= " + name);
 
        if (animation.onBeforeEnd) {
            animation.onBeforeEnd.call(animation.scope || this, element);
        }
 
        if (animation.onEnd) {
            animation.onEnd.call(animation.scope || this, element);
        }
 
        delete animationElementMap[name];
        delete runningAnimations[name];
 
        this.removeKeyframesRule(name);
    },
 
    generateAnimationId: function() {
        return 'animation-' + (++this.uniqueId);
    },
 
    run: function(animations) {
        var data = {},
            elementEndStates = this.elementEndStates,
            animationElementMap = this.animationElementMap,
            runningAnimationsMap = this.runningAnimationsMap,
            runningAnimations, states,
            elementId, animationId, i, ln, animation,
            name, runningAnimation,
            names, durations, easings, delays, directions, iterations;
 
        if (!this.listenersAttached) {
            this.attachListeners();
        }
 
        animations = Ext.Array.from(animations);
 
        for (= 0,ln = animations.length; i < ln; i++) {
            animation = animations[i];
 
            animation = Ext.factory(animation, Ext.fx.Animation);
            elementId = animation.getElement().getId();
            animationId = animation.getName() || this.generateAnimationId();
 
            animationElementMap[animationId] = elementId;
 
            animation = animation.getData();
            states = animation.states;
 
            this.addKeyframesRule(animationId, states);
 
            runningAnimations = runningAnimationsMap[elementId];
 
            if (!runningAnimations) {
                runningAnimations = runningAnimationsMap[elementId] = {};
            }
 
            runningAnimations[animationId] = animation;
 
            names = [];
            durations = [];
            easings = [];
            delays = [];
            directions = [];
            iterations = [];
 
            for (name in runningAnimations) {
                if (runningAnimations.hasOwnProperty(name)) {
                    runningAnimation = runningAnimations[name];
 
                    names.push(name);
                    durations.push(runningAnimation.duration);
                    easings.push(runningAnimation.easing);
                    delays.push(runningAnimation.delay);
                    directions.push(runningAnimation.direction);
                    iterations.push(runningAnimation.iteration);
                }
            }
 
            data[elementId] = {
                'animation-name'            : names,
                'animation-duration'        : durations,
                'animation-timing-function' : easings,
                'animation-delay'           : delays,
                'animation-direction'       : directions,
                'animation-iteration-count' : iterations
            };
 
//            Ext.apply(data[elementId], animation.origin);
 
            if (animation.preserveEndState) {
                elementEndStates[elementId] = states['100%'];
            }
        }
 
        this.applyStyles(data);
    },
 
    addKeyframesRule: function(name, keyframes) {
        var percentage, properties,
            keyframesRule,
            styleSheet, rules, styles, rulesLength, key, value;
 
        styleSheet = this.getStyleSheet();
        rules = styleSheet.cssRules;
        rulesLength = rules.length;
        styleSheet.insertRule('@' + this.vendorPrefix + 'keyframes ' + name + '{}', rulesLength);
 
        keyframesRule = rules[rulesLength];
 
        for (percentage in keyframes) {
            properties = keyframes[percentage];
 
            rules = keyframesRule.cssRules;
            rulesLength = rules.length;
 
            styles = [];
 
            for (key in properties) {
                value = this.formatValue(properties[key], key);
                key = this.formatName(key);
 
                styles.push(key + ':' + value);
            }
 
            keyframesRule.insertRule(percentage + '{' + styles.join(';') + '}', rulesLength);
        }
 
        return this;
    },
 
    removeKeyframesRule: function(name) {
        var styleSheet = this.getStyleSheet(),
            rules = styleSheet.cssRules,
            i, ln, rule;
 
        for (= 0,ln = rules.length; i < ln; i++) {
            rule = rules[i];
 
            if (rule.name === name) {
                styleSheet.removeRule(i);
                break;
            }
        }
 
        return this;
    }
});