/*	ADD LOAD HANDLER -----------------------------------------------
	allows adding more than one function to the onload event
	based on Simon Willison's discussion of closures at:
	http://simon.incutio.com/archive/2004/05/26/addLoadEvent
*/
function addLoadHandler(func) {
  var oldonload = window.onload;
  if (typeof window.onload != 'function') {
    window.onload = func;
  } else {
    window.onload = function() {
      oldonload();
      func();
    }
  }
}

/*	ADD ONUNLOAD HANDLER -------------------------------------------
	allows adding more than one function to the window's onlunload
	event based on Simon Willison's closure demo
*/
function addUnLoadHandler(func) {
	var oldonunload = window.onunload;
	if (typeof window.onunload != 'function') {
		window.onunload = function() {
			func();
			window.onload = null;
			window.onunload = null;
		}
	} else {
		window.onunload = function() {
			func();
			oldonunload();
		}
	}
}


/*	ANIMATOR -------------------------------------------------------
	an object that sets up a central timer to use for multiple
	animations; simply loops through an array of functions and calls
	each in turn each
	time through the interval

	REQUIRED SCRIPTS:
		- ADD LOAD HANDLER
		- ADD UNLOAD HANDLER

*/
function Animator(name,interval) {
//	cache our parameters
	this.name = name;
	this.interval = interval;

//	set a timer that calls our animation method every -interval- milliseconds
	this.timer = setInterval(this.name + '.animate();',this.interval);
//	nullify ourself on unload
	addUnLoadHandler(new Function('return ' + this.name + '.unload();'));
	return this;
}
//	an array to hold the specific animation funcitons triggered by this timer
Animator.prototype.animations = [];
//	a method that loops through the animations array calling each function
Animator.prototype.animate = function() {
	var i,numAnimations;
	numAnimations = this.animations.length;
	for (i = 0; i < numAnimations; i++) {
		if (typeof this.animations[i] == 'function') this.animations[i]();
	}
}
//	a method for adding a function to the animations object; takes a function
//	to call each time through the interval and returns an index by which
//	the function can be referenced
Animator.prototype.addAnimation = function(func) {
	var numAnimations = this.animations.length
	this.animations[numAnimations] = func;
	return numAnimations;
}
//	method to delete a function from the animations object; takes a string
//	corresponding to the name of the method to be deleted
Animator.prototype.removeAnimation = function(index) {
	this.animations[index] = null;
	return true;
}
//	unload function to clean up this object to avoid memory leaks
//	in certain archaic browsers
Animator.prototype.unload = function() {
	var i,numAnimations;
	numAnimations = this.animations.length;
	for (i = 0; i < numAnimations; i++) {
		this.animations[i] = null;
	}
	clearInterval(this.timer);
	window[this.name] = null;
	return true;
}
addLoadHandler(new Function("animator = new Animator('animator',40);"));


/*	ADD STYLESHEET -------------------------------------------------
	adds a stylesheet prior to document load
	must be placed after any stylesheets it is to override
	
	PARAMETERS
	- path = string; path to CSS file, including filename
*/
function addStyleSheet(path) {
	var headElement,linkElement;
//		test that we have the necessary support by getting a
//		reference to the HEAD element, creating a new LINK element
//		and checking appendChild & setAttribute methods
	if (	(headElement = document.getElementsByTagName('HEAD')[0]) &&
			(linkElement = document.createElement('LINK')) &&
			linkElement.setAttribute &&
			headElement.appendChild ) {

//		set REL & TYPE attributes for a CSS stylesheet
			linkElement.setAttribute('rel','stylesheet');
			linkElement.setAttribute('type','text/css');
//		set the HREF to the supplied cssPath + the stylesheet filename 'ticker.css'
		linkElement.setAttribute('href',path);
//		append the link to the head element, thereby adding the stylesheet to the document
		return headElement.appendChild(linkElement);
	}
//	note that the method above, while kosher in XHTML with an application/xml+xhtml MIME type,
//	does NOT work in IE5/Mac so the script gracefully degrades in that browser; if compatibility
//	with IE5/Mac is needed comment out all lines from getting the reference to the head down to
//	this commment and uncomment the line immediately following this one; note, though, that
//	document.write is verboten in XHTML sent as application/xml+xhtml
//	document.write('<link rel="stylesheet" type="text/css" href="' + path + '">');
}


/*	GET EASED STEP -------------------------------------------------
	gets an 'eased' step for easing animation; will do ease in 
	(positive ease value) or ease out (negative ease value)
	
	PARAMETERS
	- currStep = integer; which step (or 'frame') in the animation
	  we are currently on
	- numSteps = integer; total number of steps in the animation
	- delta = float; the total change in the value being animated
	  (e.g. if we're moving from position 0 to position 10, the
	   delta is 10)
	- easeValue = integer; factor for easing; higher ease factor
	  means a more gradual ease (slower acceleration)
*/
function getEasedStep(currStep,numSteps,delta,easeValue) {
	var base,easeFactor,destination;
//	if the ease value is positive...
	if (0 < easeValue) {
//		calculate the base; this is equal to the percentage complete, calculated by dividing
//		currentStep by numSteps
		base = currStep/numSteps;
//		calculate ease factor by raising the base to the easeValue-th power
		easeFactor = Math.pow(base,easeValue);
//		calculate the destination by multiplying the total change (delta) by the easeFactor
		destination = easeFactor * delta;
//		rerturn the rounded destination value
		return Math.round(destination)
//	otherwise, the ease value must be negative...
	} else {
//		calculate the base, then subtract from one (this is because we need to invert the
//		direction and subtracting 1 from it will render a negative number but with an
//		absolute value equal to the amount left rather than complete as with a positive
//		ease value
		base = currStep/numSteps-1;
//		calculate the ease factor by raising the base to the power equal to the absolute 
//		value of the ease value; we take the absolute value of this to eliminate the sign
//		difference between even and odd powers (base is negative due to subtracting above)
		easeFactor = Math.abs(Math.pow(base,Math.abs(easeValue)));
//		now subtract 1 to reverse the value back from percentage left to percentage complete
		easeFactor -= 1;
//		calculate the destination, reversing the sign to positive in the process
		destination = easeFactor * delta * -1;
//		rerturn the rounded destination value
		return Math.round(destination);
	}
} 

/*	SET OPACITY ----------------------------------------------------
	sets the opacity of a supplied element
	
	PARAMETERS
	- opacity = float; percentage opacity to which the element will
	be set
	- el = HTML element; element whose opacity is to be set
	- cssClass = class name to set on the element to avoid IE ClearType
	bug (see below)
*/
function setOpacity(opacity,el,cssClass) {
//	TBH, I'm not sure why this is here, but it was in the original credited above
//	and I'm loathe to remove it without extensive testing
	opacity = (opacity >= 100)?99.999:opacity;
  
//	IE/Win
//	n.b.: in IE6 on WinXP with ClearType enabled, the headlines get all muddy looking
//	if you muck with the alpha UNLESS you add a background colour or image to the
//	container whose opacity is being adjusted, hence adjusting the className attribute
//	but if a background image/colour isn't an option comment this line out to remove
//	the alpha fade from IE win if you're concerned with headline text rendering in that
//	specific case; note that non-smoothed or 'standard smoothed' text doesn't display
//	the problem -- only ClearType
	el.style.filter = "alpha(opacity:"+opacity+")";
//	headline.className = cssClass;
  
//	Safari<1.2, Konqueror
	el.style.KHTMLOpacity = opacity/100;

//	Older Mozilla and Firefox
	el.style.MozOpacity = opacity/100;

//	Safari 1.2, newer Firefox and Mozilla, CSS3
	el.style.opacity = opacity/100;
}



/*	TOOLTIPS -------------------------------------------------------
	creates tooltips in each of an array of elements that pop up
	onmouseover (after a reasonable delay) then fade out onmouseout;
	
	PARAMETERS
	- getActuators = function; anonymous function for finding the
	elements which will activate the tooltips
	- getTip = function; anonymous function for finding the tooltip
	element once we've identified it's actuator; this function will
	be passed a reference to the actuator element as a parameter
	- cssPath = string; path to the CSS file containing the
	tooltip styles, including the positioning and initial hiding
	of the tips

	REQUIRED SCRIPTS
		- ADD LOAD HANDLER
		- ADD UNLOAD HANDLER
		- ADD STYLESHEET
		- ANIMATOR
		- GET EASED STEP
		- SET OPACITY
*/

//	instantiate the object
var toolTips = {};

//	add the setup method
toolTips.setup = function (getActuators,getTip,cssPath) {
	if (	document.getElementById &&
			document.getElementsByTagName &&
			typeof this.tips == 'undefined') {
//		create a cache to hold the tooltips so we can get at them later
		this.tips = [];
//		add the necessary styles to the document
		addStyleSheet(cssPath);
//		add the init method to the onload handler
		addLoadHandler (function() { return toolTips.init(getActuators,getTip);});
//		add the teardown method to the onunload handler
		addUnLoadHandler (function() { return toolTips.unload(); });
	}
}
//	add the init method
toolTips.init = function (getActuators,getTip) {
//	declare our variables
	var actuators, numActuators, i;

//	get the actuators for the toolTips
	actuators = getActuators();
//	get the number of actuators
	numActuators = actuators.length;
//	loop through them
	for (i =0; i < actuators.length; i++) {
//		for each actuator, instantiate a toolTip object and add it to this object's tips array
		this.tips[i] = new this.toolTip ('toolTips.tips[' + i +']',actuators[i],getTip(actuators[i]));
	}
	return true;
}
/*	add the tooltip constructor

	PARAMETERS
	- id = string; should resolve to a reference to this object when used in a function constructor
	- actuator = HTML element; reference to the element which will activate the tooltip
	- tip = HTML element; reference to the element which will be the tip itself
*/
toolTips.toolTip = function (id,actuator,tip) {
//	cache the id, actuator and tip
	this.name = id;
	this.actuator = actuator;
	this.tip = tip;
//	set the mouseover & mouseout handlers for the actuator
	this.actuator.onmouseover = new Function('return ' + this.name + '.over();');
	this.actuator.onmouseout = new Function('return ' + this.name + '.out();');
	this.tip.onmouseover = new Function('e','clearTimeout(' + this.name + '.timer); if (!e) var e = window.event; e.cancelBubble = true; if (e.stopPropagation) e.stopPropagation(); return true;');
//	set the opacity of the tip to zero to start
	setOpacity(0,this.tip,this.tipClass);
//	set the current step to 0 to start
	this.currStep = 0;
}
//	set the number of steps for animating the fade in/out
toolTips.toolTip.prototype.numSteps = 12;
//	set ease value for the fade in/out
toolTips.toolTip.prototype.ease = 4;
//	set the class for the actuator when the tooltip is on
toolTips.toolTip.prototype.actuatorClass = 'tipOn';
//	set the class for the tip element
toolTips.toolTip.prototype.tipClass = 'toolTip';
//	mouseover handler for tooltips
toolTips.toolTip.prototype.over = function() {
//	append the 'tipOn' class to the actuator, which should apply the 'on' tooltip styles
	this.timer = setTimeout(this.name + '.startFadeUp();',200);
}
//	start the fade up
toolTips.toolTip.prototype.startFadeUp = function() {
//	append the 'tipOn' class to the actuator, which should apply the 'on' tooltip styles
	this.actuator.className = this.actuator.className + ((this.actuator.className.length > 0)?' ':'') + this.actuatorClass;
	if (typeof this.animationIndex != 'null') animator.removeAnimation(this.animationIndex);
	this.actuator.style.zIndex = 100;
	this.tip.style.zIndex = 100;
	this.animationIndex = animator.addAnimation(new Function('return ' + this.name + '.fade(1);'));
	return true;
}
toolTips.toolTip.prototype.out = function() {
/*	create a regex that will match the 'tipOff' class + the leading space (if there is one)
	var hoverReplace = new RegExp(((this.actuator.className.indexOf(' ') != -1)?' ':'') + 'tipOn');
//	replace the 'tipOff' class (plus space, if any) with an empty string, which shoudl remove the 'on' tooltip styless
	this.actuator.className = this.actuator.className.replace(hoverReplace,'');
*/
	clearTimeout(this.timer);
	if (typeof this.animationIndex != 'null') animator.removeAnimation(this.animationIndex);
	this.actuator.style.zIndex = 0;
	this.tip.style.zIndex = 0;
	this.animationIndex = animator.addAnimation(new Function('return ' + this.name + '.fade(-1);'));
	return true;
}
/*	fading function

	PARAMETERS
	- dir = integer; 1 (fading in) or -1 (fading out) only
*/
toolTips.toolTip.prototype.fade = function(dir) {
//	if we're on the last step...
	if (this.currStep >= this.numSteps) {
		if (0 > dir) this.finishFadeOut();
		else this.finishFadeIn();
//		remove the animation from the timer
		animator.removeAnimation(this.animationIndex);
		this.animationIndex = null;
//		and reset the currStep property
		this.currStep = 0;
//	otherwise...
	} else {
//		set the opacity of the element, using the dir parameter as a switch to determine whether we started at 0 or 100
		setOpacity((0 > dir?100:0) + (getEasedStep(this.currStep,this.numSteps,100,this.ease*-1) * dir), this.tip, this.tipClass);
		this.currStep++;
	}
}
//	finishes the fade in
toolTips.toolTip.prototype.finishFadeIn = function() {
//	set the opacity to full
	setOpacity(100, this.tip, this.tipClass);
	return true;
}
//	finishes the fade out
toolTips.toolTip.prototype.finishFadeOut = function() {
//	set the opacity to zero
	setOpacity(0, this.tip, this.tipClass);
//	create a regex that will match the 'tipOff' class + the leading space (if there is one)
	var hoverReplace = new RegExp(((this.actuator.className.indexOf(' ') != -1)?' ':'') + 'tipOn');
//	replace the 'tipOff' class (plus space, if any) with an empty string, which shoudl remove the 'on' tooltip styless
	this.actuator.className = this.actuator.className.replace(hoverReplace,'');
	this.actuator.style.zIndex = '';
	return true;
}
//	add a cleanup method to nullify all handlers, references to elements and the objects themselves
//	prolly a bit belt-and-suspenders, but don't really have time to test everything just now
//	so better safe than sorry
toolTips.unload = function() {
	var i, numTips = this.tips.length;
	for (i = 0; i < numTips; i++) {
		this.tips[i].actuator.onmouseover = null;
		this.tips[i].actuator.onmouseout = null;
		this.tips[i].actuator = null;
		this.tips[i].tip = null;
		this.tips[i] = null;
	}
	window['toolTips'] = null;
	return true;
}

toolTips.setup(	function(){ var container, elements; if (container = document.getElementById('questionnaire')) { if (elements = container.getElementsByTagName('label')) return elements; } return false; },
				function(parent){return parent.getElementsByTagName('span')[0]},
				'css/tng_tooltips.css' );

