var Interpolation = new Object();
Interpolation.linear = function(start, end, currentTime, endTime)
{
  if (currentTime == 0)
    return start;
  if (currentTime == endTime)
    return end;
    
  var diff = end - start;
  return start + (diff * (currentTime / endTime));
}

Interpolation.sin = function(start, end, currentTime, endTime, frequency)
{  
  if (frequency == undefined)
    frequency = 1;

  var diff = end - start;
  return start + diff * Math.sin(frequency * (currentTime / endTime));
}

Interpolation.decelerate = function(start, end, currentTime, endTime)
{
  if (currentTime == 0)
    return start;
  if (currentTime == endTime)
    return end;
    
  return Interpolation.sin(start, end, currentTime, endTime, Math.PI / 2);
}

Interpolation.accelerate = function(start, end, currentTime, endTime)
{
  if (currentTime == 0)
    return start;
  if (currentTime == endTime)
    return end;
    
  var diff = end - start;
  return start + diff * (1 - Math.cos((Math.PI / 2) * (currentTime / endTime)));
}

function AnimationController(target, animation)
{
  var self = this;      
  
  this.target = target;
  this.animation = animation;
  
  this.ticks = 0;
  this.startTime = 0;
  this.paused = false;
  this.pauseTime = 0;
  this.timeout = null;
  this.interval = null;
  
  this.reset = function()
  {
    self.ticks = 0;
    self.startTime = 0;
    self.paused = false;
    self.animation.initialize(self);
  }
  
  this.start = function(delay)
  {
    if (self.timeout != null)
      clearTimeout(self.timeout);
      
    if (self.target == undefined || self.target == null)
      return;
      
    if (delay != undefined && delay > 0)
    {
      self.timeout = setTimeout(function() { self.start(); }, delay * self.animation.tickSize);
      return;
    }
    
    if (self.paused)
    {
      var duration = self.getCurrentMillis() - self.pauseTime;
      self.startTime += duration;
      self.paused = false;
    }
    
    if (self.interval == null && self.ticks < self.animation.length)
    {
      self.animate();
      self.setInterval();
    }
  }
  
  this.pause = function()
  {
    this.paused = true;
    this.pauseTime = this.getCurrentMillis();
    this.clearInterval();
  }
  
  this.stop = function()
  {
    if (self.timeout != null)
    {
      clearTimeout(self.timeout)
      self.timeout = null;
    }
    
    self.clearInterval();
    
    self.reset();
  }
  
  this.restart = function(delay)
  {
    self.stop();
    self.start(delay);
  }
  
  this.setInterval = function()
  {
    if (self.interval == null)
      self.interval = setInterval(function() { self.animate() }, self.animation.tickSize);
  }
  
  this.clearInterval = function()
  {
    if (self.interval != null)
    {
      clearInterval(self.interval);
      self.interval = null;
    }
  }
    
  this.animate = function()
  {
    if (self.animation.realtime)
      self.ticks = self.getCurrentTicks();
    else
      self.ticks++;
      
    self.animation.update(self);
    
    if (self.ticks >= self.animation.length)
    {
      if (self.animation.loop)
        self.restart(self.animation.loopPause);
      else
        self.stop();
    }
  }
  
  this.getCurrentTicks = function()
  {
    return self.getCurrentMillis() / self.animation.tickSize;
  }
  
  this.getCurrentMillis = function()
  {
    var time = new Date();
    time = time.getTime();
    
    if (self.startTime <= 0)
    {
      self.startTime = time;
      return 0;
    }
    
    return time - self.startTime;
  }
}

function Animation(startValues, endValues, length, fps, realtime, loop, loopPause, interpolationFunc, interpolationMod)
{
  if (fps == undefined)
    fps = 24;
  if (realtime == undefined)
    realtime = false;
  if (loop == undefined)
    loop = false;
  if (interpolationFunc == undefined)
    interpolationFunc = Interpolation.linear;
  
  if (realtime)
    length = Math.round(length * fps);
  
  this.realtime = realtime;
  this.startValues = startValues;
  this.endValues = endValues;
  this.length = length;
  this.loop = loop;
  this.loopPause = loopPause;
  this.interpolationFunc = interpolationFunc;
  this.interpolationMod = interpolationMod;
  this.tickSize = Math.round(1000 / fps);
  
  this.update = function(controller)
  {
    for (attribute in this.startValues)
    {
      this.setTargetAttribute(controller, attribute, this.interpolate(attribute, controller.ticks));
    }
  }
  
  this.interpolate = function(attribute, ticks)
  {
    var start = this.startValues[attribute];
    var end = this.endValues[attribute];
    
    if (start == undefined)
      return 0;
    if (end == undefined)
      return start;
    
    return this.interpolationFunc(start, end, ticks, this.length, this.interpolationMod);
  }
  
  this.setTargetAttribute = function(controller, attribute, value)
  {
    controller.target[attribute] = this.formatValue(attribute, value);
  }
  
  this.formatValue = function(attribute, value)
  {
    return value + '';
  }
  
  this.initialize = function(controller)
  {
  }
  
  this.constructController = function(target)
  {
    var controller = new AnimationController(target, this);
    this.initialize(controller);
    
    return controller;
  }
  
  this.apply = function(target)
  {
    return this.constructController(target);
  }
}

function ConstructStyleAnimation(startValues, endValues, length, fps, realtime, loop, loopPause, interpolationFunc, interpolationMod)
{
  var animation = new Animation(startValues, endValues, length, fps, realtime, loop, loopPause, interpolationFunc, interpolationMod);
  
  animation.setTargetAttribute = function(controller, attribute, value)
  {
    if (attribute == 'scale')
    {
      controller.target['width'] = this.formatValue('width', value * controller.initialWidth);
      controller.target['height'] = this.formatValue('height', value * controller.initialHeight);
    }
    else if (attribute == 'scaleX')
    {
      controller.target['width'] = this.formatValue('width', value * controller.initialWidth);
    }
    else if (attribute == 'scaleY')
    {
      controller.target['height'] = this.formatValue('height', value * controller.initialWidth);
    }
    else if (attribute == 'shiftX')
    {
      controller.target['left'] = this.formatValue('left', value + controller.initialX);
    }
    else if (attribute == 'shiftY')
    {
      controller.target['top'] = this.formatValue('top', value + controller.initialY);
    }
    else
    {
      controller.target[attribute] = this.formatValue(attribute, value);
      
      if (attribute == 'opacity')
        controller.target['filter'] = this.formatValue('alpha', value);
    }
  }
  
  animation.formatValue = function(attribute, value)
  {
    if (attribute == 'width' || attribute == 'height')
      return Math.abs(value) + 'px';
    if (attribute == 'top' || attribute == 'bottom' || attribute == 'left' || attribute == 'right')
      return value + 'px';
    if (attribute == 'alpha')
    {
      if (value == 1)
        return 'none';
      else
        return 'alpha(opacity=' + (value * 100) + ')';
    }
    if (attribute == 'display')
    {
      if (value == 0)
        return 'none';
      else
        return 'block';
    }
    
    return value + '';
  }
  
  animation.getNumericStyleValue = function(controller, attribute)
  {
    var value = controller.target[attribute];
    
    if (value == undefined)
      return undefined;
    
    value = value.toLowerCase();
    
    var index = value.indexOf('px');
    if (index > 0)
      value = value.substr(0, index);
    
    return parseInt(value);
  }
  
  animation.initialize = function(controller)
  {
    controller.initialX = this.getNumericStyleValue(controller, 'left');
    controller.initialY = this.getNumericStyleValue(controller, 'top');
    controller.initialWidth = this.getNumericStyleValue(controller, 'width');
    controller.initialHeight = this.getNumericStyleValue(controller, 'height');
  }
  
  animation.apply = function(target)
  {
    return this.constructController(target.style);
  }
  
  return animation;
}
