AI.ANIMATION = function(opt)
{
 var
//  me = this,
  subjects = [],
  target = 0,
  state = 0,
  intervalId = null,
  step =       opt && opt.step ? opt.step : 10,
  duration =   opt && opt.duration ? opt.duration : 400,
  transition = opt && opt.transition ? opt.transition : AI.ANIMATION.tx.easeInOut,
  onComplete = opt && opt.onComplete ? opt.onComplete : function(){},
  onStart =    opt && opt.onStart ? opt.onStart : function(){},
  onStep =     opt && opt.onStep ? opt.onStep : function(){},
  scope =      opt && opt.scope ? opt.scope : this,
  linked =     opt && opt.linked ? opt.linked : null,
  starting = false,
  interval = duration / step;

 // forward the current state to the animation subjects
 function propagate()
 {
  var i, value = transition(state);
  for ( i = 0; i < subjects.length; i++ )
  {
   if ( subjects[i].setState ) { subjects[i].setState(value); }
   else { subjects[i](value); }
  }
  return this;
 }

 // called once per frame to update the current state
 function onTimerEvent()
 {
//    alerter('ontimerevent');
  var movement = (interval / duration) * (state < target ? 1 : -1);
  if ( starting === true ) { onStart.call(this, state, linked); starting = false; }
  if ( Math.abs(movement) >= Math.abs(state - target) ) { state = target; }
  else { state += movement; }
  propagate();
  onStep.call(scope, state, linked);
  if ( target == state )
  {
//      alerter('target==state' + '/' + target + '/' + state);
   clearInterval(intervalId);
   intervalId = null;
   onComplete.call(scope, target, linked);
  }
 }

 // animate from the current state to provided value
 this.seekTo = function(dest) { return this.seekFromTo(state, dest); };

 // animate from the current state to provided value
 this.seekFromTo = function(from, dest)
 {
  starting = true;
  target = Math.max(0, Math.min(1, dest));
  state = Math.max(0, Math.min(1, from));
  if ( !intervalId ) { intervalId = setInterval(onTimerEvent, interval); }
  return this;
 };

 // animate from the current state to provided value
 this.jumpTo = function(dest)
 {
  target = state = Math.max(0, Math.min(1, dest));
  starting = true;
  onTimerEvent();
  return this;
 };

 // seek to the opposite of the current target
 this.toggle = function() { this.seekTo(1 - target); return this; };

 // add a function or an object with a method setState(state) that will be called with a number
 // between 0 and 1 on each frame of the animation
 this.addSubject = function(subject) { subjects.push(subject); return this; };

 // remove an object that was added with addSubject
 this.removeSubject = function(subject) { subjects.remove(subject); return this; };

 // remove all subjects
 this.clearSubjects = function() { subjects = []; return this; };

 // shortcuts pour les fonctions privées
 this.play = function() { return this.seekFromTo(0, 1); };
 this.reverse = function() { return this.seekFromTo(1, 0); };

 // setter
 this.setInterval = function(I) { interval = I; return this; };
 this.setDuration = function(D) { duration = D; return this; };
 this.setTransition = function(T) { transition = T; return this; };
 this.setOnComplete = function(O) { onComplete = O; return this; };
 this.setScope = function(S) { scope = S; return this; };

 return this;
};

// make a transition function that gradually accelerates. pass a=1 for smooth
// gravitational acceleration, higher values for an exaggerated effect
AI.ANIMATION.makeEaseIn = function(a) { return function(state) { return Math.pow(state, a*2); }; };
// as makeEaseIn but for deceleration
AI.ANIMATION.makeEaseOut = function(a) { return function(state) { return 1 - Math.pow(1 - state, a*2); }; };
AI.ANIMATION.tx =
{
 easeInOut: function(pos) { return ((-Math.cos(pos*Math.PI)/2) + 0.5); },
 linear:    function(x) { return x; },
 easeIn:    AI.ANIMATION.makeEaseIn(1.5),
 easeOut:   AI.ANIMATION.makeEaseOut(1.5)
};

AI.ANIMATION.numericalTransition = function(E, P, F, T, U)
{
 var
  els = AI.utils.forceArray(E),
//  property = P == 'opacity' && window.ActiveXObject ? 'filter' : P,
  property = P == 'opacity' && AI.BROWSER.IE ? 'filter' : P,
  from = parseFloat(F),
  dest = parseFloat(T),
  units = U || 'px';
//AI.console.info('from = ' + from + '/F = ' + F);
//AI.console.info('dest = ' + dest + '/T = ' + T);
 function getStyle(state)
 {
  state = from + ((dest - from) * state);
//  AI.console.warning(state + '/' + dest + '/' + from);
  if ( property == 'filter' ) { return "alpha(opacity=" + Math.round(state*100) + ")"; }
  if ( property == 'opacity' ) { return state; }
  return Math.round(state) + units;
 }

 this.setState = function(state)
 {
  var i, style = getStyle(state);
//  AI.console.error(property + '/' + style + '/' + state + '/' + (typeof els));
  for ( i = 0; i < els.length; i++ ) { els[i].style[property] = style; }
 };

 return this;
};

AI.ANIMATION.widgetNumericalTransition = function(W, prop, T)
{
 var
  els = AI.utils.forceArray(W),
  from = parseFloat(W['get' + prop]()),
  dest = parseFloat(T);

 function getStyle(state)
 {
  state = Math.round(from + ((dest - from) * state));
  return Math.round(state);
 }

 this.setState = function(state)
 {
  var i, style = getStyle(state);
//    AI_debug('Animation', 'P = ' + property + ' / style = ' + style, 'error');
//		var visibility = (this.property == 'opacity' && state == 0) ? 'hidden' : '';
  for ( i = 0; i < els.length; i++ )
  {
   els[i]['set' + prop](style);
  }
 };

 return this;
};

AI.ANIMATION.colorTransition = function(E, P, F, T)
{
 var
  els = AI.utils.forceArray(E),
//  property = P == 'opacity' && window.ActiveXObject ? 'filter' : P,
  property = P == 'opacity' && AI.BROWSER.IE ? 'filter' : P,
  from, dest;

 function expandColor(color)
 {
  var hexColor, R, G, B;
  hexColor = AI.ANIMATION.parseColor(color);
  if ( hexColor )
  {
   R = parseInt(hexColor.slice(1, 3), 16);
   G = parseInt(hexColor.slice(3, 5), 16);
   B = parseInt(hexColor.slice(5, 7), 16);
   return [R,G,B];
  }
  return [255, 255, 255];
 }

 from = expandColor(F);
 dest = expandColor(T);

 function getStyle(color, state)
 {
  return AI.ANIMATION.toColorPart(Math.round(from[color] + ((dest[color] - from[color]) * state)));
 }

 this.setState = function(state)
 {
  var i, color = '#' + getStyle(0, state) + getStyle(1, state) + getStyle(2, state);
  for ( i = 0; i < els.length; i++ )
  {
   els[i].style[property] = color;
  }
 };

 return this;
};

AI.ANIMATION.toColorPart = function(str)
{
 var digits = str.toString(16);
 if ( str < 16) { return '0' + digits; }
 return digits;
};

// return a properly formatted 6-digit hex colour spec, or false
AI.ANIMATION.parseColor = function(string)
{
 var color = '#', match = AI.ANIMATION.rgbRe.exec(string), i;
 if ( match )
 {
  for ( i = 1; i <= 3; i++ ) { color += AI.ANIMATION.toColorPart( Math.max(0, Math.min(255, intval(match[i]))) ); }
  return color;
 }
 match = AI.ANIMATION.hexRe.exec(string);
 if ( match )
 {
  if ( match[1].length == 3 )
  {
   for ( i = 0 ; i < 3; i++ ) { color += match[1].charAt(i) + match[1].charAt(i); }
   return color;
  }
  return '#' + match[1];
 }
 return false;
};
AI.ANIMATION.rgbRe = /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i;
AI.ANIMATION.hexRe = /^\#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;

/*
--------------------------------------------------------------------------------
  ALIAS
--------------------------------------------------------------------------------
*/

/*
--------------------------------------------------------------------------------
  DEPRECATED niv 1
--------------------------------------------------------------------------------
*/

/*
--------------------------------------------------------------------------------
  DEPRECATED niv 2
--------------------------------------------------------------------------------
*/
var Animation = AI.ANIMATION;

/*
--------------------------------------------------------------------------------
  DEPRECATED niv 3
--------------------------------------------------------------------------------
*/
