function Animator(interval) 
{
  this.interval = interval;
  this.animations = {};
  this.animationCounter = 0;
  this.ie = (navigator.appName.toLowerCase() == "microsoft internet explorer");

  this.FILTERPROGIDS = 
  {
    opacity:"DXImageTransform.Microsoft.Alpha",
    transformation:"DXImageTransform.Microsoft.Matrix"
  };
}

Animator.prototype = {
  // isCloned is used when an animation is added internally, and the data is given with 'the' in front of attributes...
  start:function(data, isCloned) {
    var elements = isCloned? 1 : (this.isArray(data.attribute)? data.attribute.length : 1);
    for (var i=0; i<elements; i++) {
      if (isCloned) var newData = data;
      else {
        var newData = {};
        newData.theElement = data.element;
        newData.theAttribute = this.isArray(data.attribute)? data.attribute[i] : data.attribute;
        newData.theTarget = this.isArray(data.target)? data.target[i] : data.target;
        newData.theUnit = data.unit? data.unit : "px";
        newData.theDelay = data.delay? (this.isArray(data.delay)? data.delay[i] : data.delay) : 0;
        newData.theDuration = (this.isArray(data.duration)? data.duration[i] : data.duration) + newData.theDelay;
        newData.theStartCode = data.startCode? (this.isArray(data.startCode)? data.startCode[i] : (i==0? data.startCode : "")) : "";
        newData.theEndCode = data.endCode? (this.isArray(data.endCode)? data.endCode[i] : "") : "";
        newData.thePositioning = (data.positioning? (this.isArray(data.positioning)? data.positioning[i] : data.positioning) : 'absolute').toLowerCase();
        newData.theDirection = (data.direction? (this.isArray(data.direction)? data.direction[i] : data.direction) : '').toLowerCase();
        newData.theKeepVolume = data.keepVolume? (this.isArray(data.keepVolume)? data.keepVolume[i] : data.keepVolume) : false;
        newData.theProfile = data.profile? (this.isArray(data.profile)? data.profile[i] : data.profile) : "DEFAULT";
        newData.theLoops = data.loops? (this.isArray(data.loops)? data.loops[i] : data.loops) : 0;
        newData.theAmplitude = 10;
        newData.isAnimating = false;
      }
      newData.oriDuration = newData.theDuration;
      newData.theSource = this.getValue(newData.theElement, newData.theAttribute);
      newData.theStartTime = (new Date).getTime();

      // don't run xtop and xsomething animations, for testing
      if (newData.theAttribute.substr(0,1)=='x') continue;

      // if positioning is relative, change target to the absolute value
      if (newData.thePositioning == 'relative') {
        newData.thePositioning = 'absolute';
        newData.theTarget += newData.theSource;
      }

      //for (var a in newData) alert(a + ':' + newData[a]);

      // fire new animations for special handling
      switch (newData.theAttribute) {
        case "width":
        case "height": {
          if (newData.theKeepVolume) {
            var newAnimation = this.clone(newData);

            // if this is not the first run of a loop, calculate the original volume, otherwise it's still there
            newData.theVolume = data.theVolume? data.theVolume : Math.round(this.getValue(newData.theElement, "width") * this.getValue(newData.theElement, "height"));

            var newAnimation = this.clone(newData);
            newAnimation.theDirection = 'center';
            newAnimation.theKeepVolume = false;
            newAnimation.theAttribute = (newData.theAttribute == "width")? "height" : "width";
            newAnimation.theProfile = 'LINEAR';

            // set the new target to keep the volume
            newAnimation.theTarget = Math.round(newAnimation.theVolume / newData.theTarget);
            this.start(newAnimation, true);
          }
          if (newData.theDirection == 'center') {
            var newAnimation = this.clone(newData);

            newData.theDirection = '';
            var newAnimation = this.clone(newData);
            newAnimation.theAttribute = (newData.theAttribute == "width")? "left" : "top";
            newAnimation.theTarget = Math.round((this.getValue(newData.theElement, newAnimation.theAttribute) - ((newData.theTarget - newData.theSource) / 2)));
            this.start(newAnimation, true);
          }
        }
      }

      // if we have 1 endCode, set it to the element with the longest duration
      if ((!this.isObject(maxDuration)) || (newData.theDuration >= maxDuration.theDuration)) var maxDuration = newData;
      if (i == elements-1) maxDuration.theEndCode = data.endCode? (this.isArray(data.endCode)? "" : data.endCode) : "";

      // kill all running similar animations
      this.kill(newData);

      if (newData.theSource != newData.theTarget) {
        newData.theElement.animations = newData.theElement.animations? newData.theElement.animations++ : 1;
        this.animations[++this.animationCounter] = newData;
      }
    }

    if (!this.animating) this.animate();
  },

  clone:function(data) {
    var newdata = {};
    for (var attribute in data) {
      newdata[attribute] = data[attribute];
    }
    return newdata;
  },

  animate:function() {
    this.animating = false;

    for (var i in this.animations) {
      var animation = this.animations[i];

      var el = animation.theElement;
      animation.theNowTime = (new Date).getTime();

      if (!animation.isAnimating) {
        if (animation.theDelay > 0) {
          if (animation.theNowTime > (animation.theStartTime + animation.theDelay)) {
            animation.theDuration -= animation.theDelay;
            animation.theDelay = 0;
            animation.theStartTime = animation.theNowTime;
          }
        } else {
          animation.isAnimating = true;
          if (animation.theStartCode) eval(animation.theStartCode);
        }
      } else {
        this.setValue(animation.theElement, animation.theAttribute, this.calculateNewValue(animation), animation.theUnit);
      }

      if (this.getValue(animation.theElement, animation.theAttribute) != animation.theTarget) this.animating = true;
      else {
        if (animation.theEndCode) eval(animation.theEndCode);

        // loop
        if (animation.theLoops-- > 0) {
          animation.theStartTime = (new Date).getTime();
          var t = animation.theTarget;
          animation.theTarget = animation.theSource;
          animation.theSource = t;
          animation.theDuration = animation.oriDuration;
          if (animation.theLoops>=997) animation.theLoops==999;
          this.animating = true;
        } else {
          animation.theElement.animations--;
          if (animation.theElement.animations<0) animation.theElement.animations = 0;
          delete this.animations[i];
        }
      }
    }
    if (this.animating) setTimeout("animator.animate();", this.interval);
  },



  setValue:function(theElement, theAttribute, theValue, theUnit) 
  {
    try {
      if (!theUnit) var theUnit = "px";

      theValue = Math.round(theValue);
      switch (theAttribute) {
        case "opacity" :
          theElement.style.filter = "alpha(opacity=" + theValue + ")";
          break;
        case "zoom" :
          theValue *= 100;
          theUnit = "%";
          theElement.style[theAttribute] = theValue + "%";
          break;
        default:
          theElement.style[theAttribute] = theValue + theUnit;
      }
    } 
    catch(e) 
    {
    }
  },

  getBoundingClientRectValue:function(theElement, theAttribute) 
  {
    var theRect = theElement.getBoundingClientRect();
    return theRect[theAttribute];
  },

  getValue:function(theElement, theAttribute) 
  {
    try 
    {
      theValue = (this.ie? theElement.currentStyle[theAttribute] : document.defaultView.getComputedStyle(theElement, "").getPropertyValue(theAttribute)).replace(/[^\d\-]/gi, '') * 1;
    } 
    catch(e) 
    {
    }
    switch (theAttribute) 
    {
      case "opacity" :
        theValue = theElement.filters("alpha").opacity * 100;
      case "zoom" :
        theValue /= 100;
    }
    return theValue;
  },

  calculateNewValue:function(data) 
  {
    var startVal = data.theSource;
    var targetVal = data.theTarget;

    var factor = (data.theNowTime - data.theStartTime) / data.theDuration;
    var numBounces = 5;
    var waitForAmplitude = false;

    if (Math.abs(factor) < 0.01) factor = 0.01;
    if (factor > 1) factor = 1;

    switch (data.theProfile.toUpperCase()) {
      case "LINEAR":
        break;
      case "SNAP":
        waitForAmplitude = true;
        var demping = 1 - factor * factor * factor;
        factor = numBounces * Math.PI * factor * factor;
        factor = 1 - demping * Math.sin(factor) / factor;
        break;
      case "ACCELERATE":
        factor *= factor;
        break;
      case "DECELERATE":
        factor = (2 - factor)*factor;
        break;
      case "DEFAULT":
        if (factor == 1) break;
        factor = (1 - Math.cos(factor * Math.PI)) / 2;
        break;
    }
    var newValue = 1 * startVal + factor * (targetVal - startVal);
    if (!waitForAmplitude &&
      (
      ((data.theSource < data.theTarget) && (newValue > data.theTarget))
      ||
      ((data.theSource > data.theTarget) && (newValue < data.theTarget))
      )
      || (demping <0)
      ) {
      newValue = data.theTarget;
      }

    return newValue;
  },

  kill:function(data) {
    for (var i in this.animations) {
      var animation = this.animations[i];
      if ((animation.theElement == data.theElement) && (animation.theAttribute == data.theAttribute)) {
        this.animations[i].theElement.animations--;
        if (this.animations[i].theElement.animations<0) this.animations[i].theElement.animations = 0;
        delete this.animations[i];
      }
    }
  },

  setInterval:function(interval) {
    this.interval = interval;
  },

  isArray:function(a) {
    return ((a != null) && (typeof(a) == "object") && a.constructor && (a.constructor == Array));
  },

  isObject:function(o) {
    return ((o != null) && (typeof(o) == "object") && o.constructor && (o.constructor == Object));
  }
};


// create animator
var animator = new Animator(25);