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;

	// 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);
		}
	}

	// 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;
	}

	// 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' && IS.IE ? 'filter' : P,
  from = parseFloat(F),
  dest = parseFloat(T),
  units = U || 'px';

	function getStyle(state)
 {
		state = from + ((dest - from) * state);
		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 style = getStyle(state);
		for ( var 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' && IS.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
--------------------------------------------------------------------------------
*/
var Animation = AI.ANIMATION;

/*
--------------------------------------------------------------------------------
  DEPRECATED niv 2
--------------------------------------------------------------------------------
*/

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