function alpha(id,from,to,time,queue) {
  var obj = document.getElementById(id);
  animator.add(obj,"alpha",[from],[to],time,false,queue);
}

function move(id,from,to,time,queue) {
  var obj = document.getElementById(id);
  animator.add(obj,"move",from,to,time,false,queue);
}

function resize(id,from,to,time,queue) {
  var obj = document.getElementById(id);
  animator.add(obj,"resize",from,to,time,false,queue);
}

function color(id,from,to,time,queue) {
  var obj = document.getElementById(id);
  from = (typeof from=="object"&&from.length)?from:hex2rgb(from);
  to = (typeof to=="object"&&to.length)?to:hex2rgb(to);
  animator.add(obj,"color",from,to,time,false,queue);
}

function background(id,from,to,time,queue) {
  var obj = document.getElementById(id);
  from = (typeof from=="object"&&from.length)?from:hex2rgb(from);
  to = (typeof to=="object"&&to.length)?to:hex2rgb(to);
  animator.add(obj,"background",from,to,time,false,queue);
}

function bordercolor(id,from,to,time,queue) {
  var obj = document.getElementById(id);
  from = (typeof from=="object"&&from.length)?from:hex2rgb(from);
  to = (typeof to=="object"&&to.length)?to:hex2rgb(to);
  animator.add(obj,"bordercolor",from,to,time,false,queue);
}

function animate(obj,type,from,to,time,warp,queue) {
  animator.add(obj,type,from,to,time,warp,queue);
}

function Animator() {
  
  // [obj, [type, [from], [to], warp, queue, start, end]
  this.stack = new Array();
  this.timer = false;
  
  this.start = function() {
    if(!this.timer)
      this.timer = setInterval("animator.cycle()",10);
  }
  
  this.stop = function() {
    clearInterval(this.timer);
    delete this.timer;
    this.timer = false;
  }
  
  this.cycle = function() {
    var t = new Date().getTime();
    for(var i = 0; i < this.stack.length; i++) {
      if(this.stack[i]) {
        for(var j = 1; j < this.stack[i].length; j++) {
          if(this.stack[i][j]) {
            var progress = (t - this.stack[i][j][5])
                / (this.stack[i][j][6] - this.stack[i][j][5]);
            if(t>=this.stack[i][j][6]) {
              this.update(this.stack[i][0]
                ,this.stack[i][j][0]
                ,this.stack[i][j][1]
                ,this.stack[i][j][2]
                ,this.stack[i][j][3]
                ,1);
              var finish = this.stack[i][j][4];
              if(this.remove(this.stack[i][0],this.stack[i][j][0]))
                return eval(finish);
              eval(finish);
              continue;
            }
            this.update(this.stack[i][0]
                ,this.stack[i][j][0]
                ,this.stack[i][j][1]
                ,this.stack[i][j][2]
                ,this.stack[i][j][3]
                ,progress);
          }
        }
      }
    }
  }
  
  this.update = function(obj,type,from,to,warp,progress) {
    var p = new Array();
    for(var i = 0; i < from.length; i++)
      p[i] = (warp)?warp[i](progress):progress;
    var cur = new Array();
    for(var i = 0; i < from.length; i++)
      cur[i] = from[i] + (to[i] - from[i]) * p[i];
    switch(type) {
      case "alpha":
        if(document.all)
          obj.filters.alpha.opacity = parseInt(cur[0] * 100);
        else
          obj.style.opacity = cur[0];
        break;
      case "move":
        obj.style.left = cur[0] + "px";
        obj.style.top = cur[1] + "px";
        break;
      case "resize":
        obj.style.width = cur[0] + "px";
        obj.style.height = cur[1] + "px";
        break;
      case "color":
        var color = "rgb("
          + parseInt(cur[0]) + ","
          + parseInt(cur[1]) + ","
          + parseInt(cur[2]) + ")";
        obj.style.color = color;
        break;
      case "background":
        var color = "rgb("
          + parseInt(cur[0]) + ","
          + parseInt(cur[1]) + ","
          + parseInt(cur[2]) + ")";
        obj.style.backgroundColor = color;
        break;
      case "bordercolor":
        var color = "rgb("
          + parseInt(cur[0]) + ","
          + parseInt(cur[1]) + ","
          + parseInt(cur[2]) + ")";
        obj.style.borderColor = color;
        break;
    }
  }
  
  this.add = function(obj,type,from,to,time,warp,queue) {
    var t = new Date().getTime();
    var obj_slot = -1;
    for(var i = 0; i < this.stack.length; i++) {
      if(this.stack[i]==false)
        obj_slot = i;
      else if(this.stack[i][0]==obj) {
        var anim_slot = -1;
        for(var j = 1; j < this.stack[i].length; j++) {
          if(this.stack[i][j]==false)
            anim_slot = j;
          else if(this.stack[i][j][0]==type) {
            var progress = (t - this.stack[i][j][5])
                / (this.stack[i][j][6] - this.stack[i][j][5]);
            var old_warp = this.stack[i][j][3];
            progress = (typeof old_warp == "function")?old_warp(progress):progress;
            var cur = new Array();
            for(var k = 0; k < from.length; k++)
              cur[k] = from[k] + (to[k] - from[k]) * progress;
            this.stack[i][j][1] = cur;
            this.stack[i][j][2] = to;
            this.stack[i][j][3] = warp;
            this.stack[i][j][4] = queue;
            this.stack[i][j][5] = t;
            this.stack[i][j][6] = t + time;
            return this.start();
          }
        }
        anim_slot = (anim_slot==-1)?this.stack[i].length:anim_slot;
        this.stack[i][anim_slot] = new Array();
        this.stack[i][anim_slot][0] = type;
        this.stack[i][anim_slot][1] = from;
        this.stack[i][anim_slot][2] = to;
        this.stack[i][anim_slot][3] = warp;
        this.stack[i][anim_slot][4] = queue;
        this.stack[i][anim_slot][5] = t;
        this.stack[i][anim_slot][6] = t + time;
        return this.start();
      }
    }
    obj_slot = (obj_slot==-1)?this.stack.length:obj_slot;
    this.stack[obj_slot] = new Array();
    this.stack[obj_slot][0] = obj;
    this.stack[obj_slot][1] = new Array();
    this.stack[obj_slot][1][0] = type;
    this.stack[obj_slot][1][1] = from;
    this.stack[obj_slot][1][2] = to;
    this.stack[obj_slot][1][3] = warp;
    this.stack[obj_slot][1][4] = queue;
    this.stack[obj_slot][1][5] = t;
    this.stack[obj_slot][1][6] = time + t;
    this.start();
  }
  
  this.remove = function(obj,type) {
    var num_objs = 0;
    for(var i = 0; i < this.stack.length; i++) {
      if(this.stack[i]) {
        num_objs++;
        if(this.stack[i][0]==obj) {
          var num_anims = 0;
          for(var j = 1; j < this.stack[i].length; j++) {
            if(this.stack[i][j]) {
              num_anims++;
              if(this.stack[i][j][0]==type) {
                delete this.stack[i][j];
                this.stack[i][j] = false;
                num_anims--;
              }
            }
          }
          if(num_anims==0) {
            delete this.stack[i];
            this.stack[i] = false;
            num_objs--;
          }
        }
      }
    }
    if(num_objs==0) {
      this.stop();
      delete this.stack;
      this.stack = new Array();
      return true;
    }
    return false;
  }
  
}
var animator = new Animator();

// Turn a hexadecimal color value into rgb values
function hex2rgb(hex) {
  var hex = (hex.substr(0,1)=="#")?hex.substr(1,7):hex;
  var rgb = new Array();
  rgb[0] = parseInt(hex.substring(0,2),16);
  rgb[1] = parseInt(hex.substring(2,4),16);
  rgb[2] = parseInt(hex.substring(4,6),16);
  return rgb;
}
