/******************************************\
  GSSI Effects framework
  Requires: Base, Environment, DOM, Styles
\******************************************/

if (! GSSI ) { alert("gssi.effects.js: gssi.base.js not loaded") };
if (! GSSI.Environment ) { alert("gssi.effects.js: gssi.environment.js not loaded") };
if (! GSSI.DOM ) { alert("gssi.effects.js: gssi.dom.js not loaded") };
if (! GSSI.Styles ) { alert("gssi.effects.js: gssi.styles.js not loaded") };

GSSI.Effects = {};

GSSI.Effects.Interpolators = {};

GSSI.Effects._parsecolor = function ( c ) {
	var o = {};
	o.r = parseInt('0x' + c.substring(1,3));
	o.g = parseInt('0x' + c.substring(3,5));
	o.b = parseInt('0x' + c.substring(5,7));
	return o;
};

GSSI.Effects.Interpolators.Color = GSSI.Class({
	initialize: function( from, to ) {
		this.from = GSSI.Styles.normalizeColor(from); // force '#rrggbb'
		this.to = GSSI.Styles.normalizeColor(to);
		this._fromobj = GSSI.Effects._parsecolor(this.from);
		this._toobj = GSSI.Effects._parsecolor(this.to);
	},
	render: function( p ) {
		p=0+p;if(p>1){p=1.0;};if(p<0){p=0.0;};
		if (p==0.0) { return this.from; }
		if (p==1.0) { return this.to; }
		var c;
		c = Math.round( (p * this._toobj.b) + ((1.0-p) * this._fromobj.b) ).toString(16);
		if (c.length<2) { c='0'+c; }
		c = Math.round( (p * this._toobj.g) + ((1.0-p) * this._fromobj.g) ).toString(16) + c;
		if (c.length<4) { c='0'+c; }
		c = Math.round( (p * this._toobj.r) + ((1.0-p) * this._fromobj.r) ).toString(16) + c;
		if (c.length<6) { c='0'+c; }
		return '#' + c;
	}
});

GSSI.Effects._lengthunits = { 'in':true, 'px':true, 'em':true, 'en':true, 'pt':true, 'ex':true };

GSSI.Effects.Interpolators.Length = GSSI.Class({
	initialize: function( from, to, unit ) {
		this.from = from.toString();
		this.to = to.toString();
		this._fromval = parseFloat(this.from);
		this._toval = parseFloat(this.to);
		this._unit = unit || this.from.substring(this.from.length-2);
		if (! GSSI.Effects._lengthunits[this._unit] ) { this._unit = 'px'; };
	},
	render: function( p ) {
		p=0+p;if(p>1){p=1.0;};if(p<0){p=0.0;};
		if (p==0.0) { return this.from; }
		if (p==1.0) { return this.to; }
		var c;
		c = (p * this._toval) + ((1.0-p) * this._fromval);
		return '' + c + this._unit;
	}
});

GSSI.Effects.Interpolators.Number = GSSI.Class({
	initialize: function( from, to ) {
		this.from = parseFloat(from.toString());
		this.to = parseFloat(to.toString());
	},
	render: function( p ) {
		p=0+p;if(p>1){p=1.0;};if(p<0){p=0.0;};
		if (p==0.0) { return this.from; }
		if (p==1.0) { return this.to; }
		return (p * this.to) + ((1.0-p) * this.from);
	}
});

GSSI.Effects._styleinterpolatortable = { c:GSSI.Effects.Interpolators.Color, l:GSSI.Effects.Interpolators.Length, n:GSSI.Effects.Interpolators.Number };

GSSI.Effects.Transitions = {
  linear: function(p) { return p; },
  easeout: function(p) { return Math.sin(p*Math.PI/2); },
  easein: function(p) { return 1.0 - Math.sin((p+1)*Math.PI/2); },
  sinusoidal: function(p) { return (-Math.cos(p*Math.PI)/2) + 0.5; },
  reverse: function(p) { return 1.0-p; },
  noisy: function(p) { return (p*1.2)-0.15 + (Math.random()/5); },
  pulse: function(p, pulses) { 
    pulses = pulses || 3; 
	var pp = (p % (1/pulses)) * pulses;
	return (pp>0.5)?( (1.0-pp)*2 ):( pp*2 );
  },
  none: function(p) { return 0; },
  full: function(p) { return 1; }
};


GSSI.Effects.Morpher = GSSI.Class({
	initialize: function( element, fromStyle, toStyle, options ) {
		options = GSSI.Base.Extend( {
			duration:2.0,
			fps:20,
			transition: GSSI.Effects.Transitions.linear,
			loop: false,
			callback: null
		}, options );
		
		this.element = GSSI.$(element);
		this._devices = {};
		if (options.fps > 30) { options.fps = 30; };
		this.delay = 1000 / options.fps;
		this.changeperframe = 1.0 / (options.duration * options.fps);
		this.transition = options.transition;
		this.position = 0;
		this.reverse = false;
		this.callback = options.callback;
		
		if ((!fromStyle)||(!toStyle)) { return; };
		var fromval, toval, iterclass;
		for(var s in fromStyle) {
			iterclass = GSSI.Effects._styleinterpolatortable[ GSSI.Styles.typeChar( s ) ];
			if (! iterclass) { continue; }
			fromval = fromStyle[s];
			toval = toStyle[s];
			if ((fromval===null)||(fromval===undefined)||(toval===null)||(toval===undefined)) { continue; };
			
			this._devices[ GSSI.Styles.normalizeName(s) ] = new iterclass( fromval, toval );
		}
		
		var effect = this;	
		this._timerfunction = function() {
			var cb = false;
			if (effect._reverse) {
				effect.position -= effect.changeperframe;
				if (effect.position > 0.0) {
					effect._timer = window.setTimeout( effect._timerfunction, effect.delay );
				} else {
					effect.position = 0.0; cb = true;
				}
			} else {
				effect.position += effect.changeperframe;
				if (effect.position < 1.0) {
					effect._timer = window.setTimeout( effect._timerfunction, effect.delay );
				} else {
					effect.position = 1.0; cb = true;
				}
			}
			effect.render();
			if (cb && effect.callback) { effect.callback(); };
		}
		
	 },
	 
	 render: function( ) {
		 var tp = this.transition(this.position);
		 var v;
		 for(var s in this._devices) {
			 v = this._devices[s].render(tp);
			 this.element.setStyle(s, v);
		 }
	 },
	 
	 stop: function() {
		 if (this._timer) { window.clearTimeout(this._timer); }
		 this._timer = null;
	 },
	 
	 stopAt: function(p) {
		 if (this._timer) { window.clearTimeout(this._timer); }
		 this._timer = null;
		 p=0+p;if(p>1){p=1.0;};if(p<0){p=0.0;};
		 var fudge = (p<0.5)?(0.0001):(-0.0001); // works around position rendering problem by forcing a second render.
		 this.position = p + fudge;
		 this.render();
		 this.position = p;
		 this.render();
	 },
	 
	 run: function() {
		 this.runFrom(0.0);
	 },
	 
	 runFrom: function(p) {
		 if (this._timer) { window.clearTimeout(this._timer); }
		 this._timer = null;
		 this._reverse = false;
		 p=0+p;if(p>1){p=1.0;};if(p<0){p=0.0;};
		 this.position = p;
		 this._timerfunction();
	 },
	 
	 runReverse: function() {
		 this.runReverseFrom(1.0);
	 },

	 runReverseFrom: function(p) {
		 if (this._timer) { window.clearTimeout(this._timer); }
		 this._timer = null;
		 this._reverse = true;
		 p=0+p;if(p>1){p=1.0;};if(p<0){p=0.0;};
		 this.position = p;
		 this._timerfunction();
	 },

	 resume: function () { this._timerfunction(); },
	 resumeBackwards: function () { this._reverse = !this._reverse; this._timerfunction(); }
});