YAHOO.util.Anim = function(el, attributes, duration, method) {
    if (el) {
        this.init(el, attributes, duration, method); 
    }
};
YAHOO.util.Anim.prototype = {
    toString: function() {
        var el = this.getEl();
        var id = el.id || el.tagName;
        return ("Anim " + id);
    },
    patterns: { // cached for performance
        noNegatives:        /width|height|opacity|padding/i, // keep at zero or above
        offsetAttribute:  /^((width|height)|(top|left))$/, // use offsetValue as default
        defaultUnit:        /width|height|top$|bottom$|left$|right$/i, // use 'px' by default
        offsetUnit:         /\d+(em|%|en|ex|pt|in|cm|mm|pc)$/i // IE may return these, so convert these to offset
    },
    doMethod: function(attr, start, end) {
        return this.method(this.currentFrame, start, end - start, this.totalFrames);
    },
    setAttribute: function(attr, val, unit) {
        if ( this.patterns.noNegatives.test(attr) ) {
            val = (val > 0) ? val : 0;
        }

        YAHOO.util.Dom.setStyle(this.getEl(), attr, val + unit);
    },                        
    getAttribute: function(attr) {
        var el = this.getEl();
        var val = YAHOO.util.Dom.getStyle(el, attr);

        if (val !== 'auto' && !this.patterns.offsetUnit.test(val)) {
            return parseFloat(val);
        }
        
        var a = this.patterns.offsetAttribute.exec(attr) || [];
        var pos = !!( a[3] ); // top or left
        var box = !!( a[2] ); // width or height
        
        // use offsets for width/height and abs pos top/left
        if ( box || (YAHOO.util.Dom.getStyle(el, 'position') == 'absolute' && pos) ) {
            val = el['offset' + a[0].charAt(0).toUpperCase() + a[0].substr(1)];
        } else { // default to zero for other 'auto'
            val = 0;
        }

        return val;
    },
    getDefaultUnit: function(attr) {
         if ( this.patterns.defaultUnit.test(attr) ) {
            return 'px';
         }
         
         return '';
    },
    setRuntimeAttribute: function(attr) {
        var start;
        var end;
        var attributes = this.attributes;
        this.runtimeAttributes[attr] = {};
        var isset = function(prop) {
            return (typeof prop !== 'undefined');
        };
        if ( !isset(attributes[attr]['to']) && !isset(attributes[attr]['by']) ) {
            return false; // note return; nothing to animate to
        }        
        start = ( isset(attributes[attr]['from']) ) ? attributes[attr]['from'] : this.getAttribute(attr);
        // To beats by, per SMIL 2.1 spec
        if ( isset(attributes[attr]['to']) ) {
            end = attributes[attr]['to'];
        } else if ( isset(attributes[attr]['by']) ) {
            if (start.constructor == Array) {
                end = [];
                for (var i = 0, len = start.length; i < len; ++i) {
                    end[i] = start[i] + attributes[attr]['by'][i];
                }
            } else {
                end = start + attributes[attr]['by'];
            }
        }
        this.runtimeAttributes[attr].start = start;
        this.runtimeAttributes[attr].end = end;

        // set units if needed
        this.runtimeAttributes[attr].unit = ( isset(attributes[attr].unit) ) ? attributes[attr]['unit'] : this.getDefaultUnit(attr);
    },
    init: function(el, attributes, duration, method) {
        var isAnimated = false;
        var startTime = null;
        var actualFrames = 0; 
        el = YAHOO.util.Dom.get(el);
        this.attributes = attributes || {};
        this.duration = duration || 1;
        this.method = method || YAHOO.util.Easing.easeNone;
        this.useSeconds = true; // default to seconds
        this.currentFrame = 0;
        this.totalFrames = YAHOO.util.AnimMgr.fps;
        this.getEl = function() { return el; };
        this.isAnimated = function() {
            return isAnimated;
        };
        this.getStartTime = function() {
            return startTime;
        };        
        this.runtimeAttributes = {};
        this.animate = function() {
            if ( this.isAnimated() ) {
                return false;
            }
            this.currentFrame = 0;
            this.totalFrames = ( this.useSeconds ) ? Math.ceil(YAHOO.util.AnimMgr.fps * this.duration) : this.duration;
            YAHOO.util.AnimMgr.registerElement(this);
        };
        this.stop = function(finish) {
            if (finish) {
                 this.currentFrame = this.totalFrames;
                 this._onTween.fire();
            }
            YAHOO.util.AnimMgr.stop(this);
        };
        var onStart = function() {            
            this.onStart.fire();
            this.runtimeAttributes = {};
            for (var attr in this.attributes) {
                this.setRuntimeAttribute(attr);
            }
            isAnimated = true;
            actualFrames = 0;
            startTime = new Date(); 
        };
        var onTween = function() {
            var data = {
                duration: new Date() - this.getStartTime(),
                currentFrame: this.currentFrame
            };
            data.toString = function() {
                return (
                    'duration: ' + data.duration +
                    ', currentFrame: ' + data.currentFrame
                );
            };
            this.onTween.fire(data);
            var runtimeAttributes = this.runtimeAttributes;
            for (var attr in runtimeAttributes) {
                this.setAttribute(attr, this.doMethod(attr, runtimeAttributes[attr].start, runtimeAttributes[attr].end), runtimeAttributes[attr].unit); 
            }
            actualFrames += 1;
        };
        var onComplete = function() {
            var actual_duration = (new Date() - startTime) / 1000 ;
            
            var data = {
                duration: actual_duration,
                frames: actualFrames,
                fps: actualFrames / actual_duration
            };
            data.toString = function() {
                return (
                    'duration: ' + data.duration +
                    ', frames: ' + data.frames +
                    ', fps: ' + data.fps
                );
            };
            isAnimated = false;
            actualFrames = 0;
            this.onComplete.fire(data);
        };
        this._onStart = new YAHOO.util.CustomEvent('_start', this, true); 
        this.onStart = new YAHOO.util.CustomEvent('start', this);
        this.onTween = new YAHOO.util.CustomEvent('tween', this);
        this._onTween = new YAHOO.util.CustomEvent('_tween', this, true);
        this.onComplete = new YAHOO.util.CustomEvent('complete', this);
        this._onComplete = new YAHOO.util.CustomEvent('_complete', this, true);
        this._onStart.subscribe(onStart);
        this._onTween.subscribe(onTween);
        this._onComplete.subscribe(onComplete);
    }
};
YAHOO.util.AnimMgr = new function() {
    var thread = null; 
    var queue = [];      
    var tweenCount = 0;
    this.fps = 1000;
    this.delay = 1;
    this.registerElement = function(tween) {
        queue[queue.length] = tween;
        tweenCount += 1;
        tween._onStart.fire();
        this.start();
    };
    this.unRegister = function(tween, index) {
        tween._onComplete.fire();
        index = index || getIndex(tween);
        if (index != -1) {
            queue.splice(index, 1);
        }
        tweenCount -= 1;
        if (tweenCount <= 0) {
            this.stop();
        }
    }; 
    this.start = function() {
        if (thread === null) {
            thread = setInterval(this.run, this.delay);
        }
    }; 
    this.stop = function(tween) {
        if (!tween) {
            clearInterval(thread);
            
            for (var i = 0, len = queue.length; i < len; ++i) {
                if ( queue[0].isAnimated() ) {
                    this.unRegister(queue[0], 0);  
                }
            }

            queue = [];
            thread = null;
            tweenCount = 0;
        }
        else {
            this.unRegister(tween);
        }
    };   
    this.run = function() {
        for (var i = 0, len = queue.length; i < len; ++i) {
            var tween = queue[i];
            if ( !tween || !tween.isAnimated() ) { continue; }

            if (tween.currentFrame < tween.totalFrames || tween.totalFrames === null)
            {
                tween.currentFrame += 1;
                
                if (tween.useSeconds) {
                    correctFrame(tween);
                }
                tween._onTween.fire();          
            }
            else { YAHOO.util.AnimMgr.stop(tween, i); }
        }
    };
    var getIndex = function(anim) {
        for (var i = 0, len = queue.length; i < len; ++i) {
            if (queue[i] == anim) {
                return i; // note return;
            }
        }
        return -1;
    };
    var correctFrame = function(tween) {
        var frames = tween.totalFrames;
        var frame = tween.currentFrame;
        var expected = (tween.currentFrame * tween.duration * 1000 / tween.totalFrames);
        var elapsed = (new Date() - tween.getStartTime());
        var tweak = 0;
        
        if (elapsed < tween.duration * 1000) { // check if falling behind
            tweak = Math.round((elapsed / expected - 1) * tween.currentFrame);
        } else { // went over duration, so jump to end
            tweak = frames - (frame + 1); 
        }
        if (tweak > 0 && isFinite(tweak)) { // adjust if needed
            if (tween.currentFrame + tweak >= frames) {// dont go past last frame
                tweak = frames - (frame + 1);
            }
            
            tween.currentFrame += tweak;      
        }
    };
};

YAHOO.util.Easing = {
    easeOut: function (t, b, c, d) {
    	return -c *(t/=d)*(t-2) + b;
    },
    easeOutStrong: function (t, b, c, d) {
    	return -c * ((t=t/d-1)*t*t*t - 1) + b;
    }
};

YAHOO.register("animation", YAHOO.util.Anim, {version: "2.2.0", build: "127"});
