/* $Id: Fader.js 408 2009-07-19 20:35:13Z felix $ */

/**
 * Class: Fader
 * 
 * A Fader class for fading in and out elements
 */

/**
 * Constructor: Fader
 * 
 * Parameters:
 *   d - Either the ID of the element to be faded or the element itself
 */

function Fader (d) {
	// timer handles for the delay timer and the animation timer
	this.t = this.dt = null;
	// the element to be faded
	this.d =  (typeof d == 'string') ? document.getElementById(d) : d;
	// precalculating Pi/2
	this.Pi2 = Math.PI / 2;
}

/**
 * Method: fade
 * 
 * The main public method that initiates the fading
 * 
 * Parameters:
 *   duration - the duration of the fading procedure in millis <integer:2000> 
 *   params - a parameter object with the following properties and defaults:
 *   
 * >  {
 * >    delay: <integer:0>,
 * >    opacity: <float:1.0>,
 * >    inverse: <boolean:false>,
 * >    granularity: <integer:27>
 * >  }
 * 
 */
Fader.prototype.fade = function(duration, params) {
	var p = typeof params == 'object' ? params : {};
	this.t0 = this.time();
	var T = duration ? duration : 2000;
	this.f = 1.0 / T;
	this.g = (typeof p.granularity == 'number' && p.granularity) ? p.granularity : 27;
	this.o = p.inverse ? 0 : 1;
	this.i = p.inverse ? -1 : 1;
	// the initial opacity is calculated from @style. If this is impossible, we take the params' value, or its default
	p.opacity = (this.d.style.opacity && typeof this.d.style.opacity != 'undefined') ? parseFloat(this.d.style.opacity) : (typeof p.opacity != 'undefined' ? p.opacity : this.o);
	this.t0 = this.t0 - ((p.inverse == true ? p.opacity : (1 - p.opacity) ) * T / this.Pi2);
	// create the browser-specific internal dynamic function Fader::_s which sets Fader::d's opacity
	this._s = navigator.userAgent.indexOf('MSIE') < 0 ? function(o) { this.d.style.opacity = o.toString(); } : function (o) { this.d.style.filter = "alpha(opacity=" + 100 * o + ")"; };
	// stop ongoing transitions
	this.cancel();
	this.running = true;
	var t = this;
	if (p.delay) {
		this.t0 = this.t0 + parseInt(p.delay);
//		this.dt = setTimeout(function() {t._f(); }, p.delay);
		this.dt = setTimeout(function() {t.t = setInterval(function() {t._f();}, t.g); }, p.delay);
	} else {
//		this._f();
		this.t = setInterval(function() {t._f();}, this.g);
	}
};

/**
 * Method: appear
 * 
 * Public method that initiates a reverse fading
 * 
 * Parameters:
 *   duration - the duration of the procedure in millis <integer:2000> 
 *   params - a parameter object with the following properties and defaults:
 *   
 * >  {
 * >    delay: <integer:0>,
 * >    opacity: <float:0.0>,
 * >    inverse: <boolean:true>
 * >    granularity: <integer:27>
 * >  }
 * 
 */
Fader.prototype.appear = function(duration, params) {
	params = (typeof params == 'object') ? params : {};
	this.fade(duration, {
		delay: params.delay,
		opacity: (typeof params.opacity != 'undefined') ? params.opacity : 0,
		inverse: (typeof params.inverse != 'undefined') ? params.inverse : true,
		granularity: params.granularity
	});
};


/**
 * Method: cancel
 * Public method that cancels an ongoing (or scheduled) transition
 */
Fader.prototype.cancel = function() {
	this.running = false;
	if (this.dt) {
		clearTimeout(this.dt);
	}
	if (this.t) {
//		clearTimeout(this.t);
		clearInterval(this.t);
	}
};

/**
 * Method: time
 * Private method that returns the current timestamp in millis
 */
Fader.prototype.time = function() {
	return (new Date()).getTime();
};

/**
 * Method: _f
 * Internal private method that does the animation
 */
Fader.prototype._f = function() {
	if (this.running) {
		var dx = (this.time() - this.t0) * this.Pi2 * this.f * this.i;
		if (Math.abs(dx) > this.Pi2) {
			this._s(Math.min(Math.max(1 - this.o, 0), 1));
			this.cancel();
		} else {
			this._s(this.o - Math.sin(dx));
//			var t = this;
//			this.t = setTimeout(function () { t._f(); }, t.g);
		}
	}
};

/**
 * Method: _s
 * Private internal dynamic method that sets the lement's opacity
 */


