/*
	Script: Hints.js
	
	Class: Hints
		Creates a "tool-tip"-style to accompany any elements.
	
	Author:
		Eric Clemmons
	
	License:
		MIT-style license
	
	Copyright:
		copyright (c) 2007 Eric Clemmons & WhiteFence
	
	Usage:
		You have a form with the usual information, but you don't want to
		muck it up with extra verbage or instructions.  This is the default
		behavior for Hints.
		
			(start example)
				<img src="http://www.skuare.net/test/img/logo.png" class="hint" title="Check Out Our Logo!" />
			
				<label>First Name</label>
					<input type="text" name="first_name" />
				
				<label>Last Name</label>
					<input type="text" name="last_name" />
					
				<label>Phone Number</label>
					<input type="text" name="phone" class="hint"
						title="No Dashes or Spaces!" />
				
				<label>E-Mail Address</label>
					<input type="text" name="email" class="hint"
						title="<a href='why.html'>Why Do We Want This?</a>" />
						
				<script>
					var hints = new Hints();
				</script>
			(end example)
			
		Or maybe you want to add a bit of info to some pictures.  Why not just
		utilize their "alt" tag.  After all, your site *is* accessible, right?
		Note: You should never mix HTML into your "alt" tags.  But it does
		display properly.
		
			(start example)
				<h3>Photo Gallery</h3>
				
				<img src="1.jpg" class="info"
					alt='iMac 17".  Sexy.' />
				<img src="2.jpg" class="info"
					alt="Apple iPhone. <br /><em>I\'m broke :(</em>" />
				<img src="3.jpg" class="info"
					alt="Picture of me. (<a href='mailto:'>e-mail me</a>)" />
					
				<script>
					var hints = new Hints($$('img.info'), {
						'hintLocation': 'alt',
						'onEvent': 'mouseenter',
						'offEvent': 'mouseleave'
					});
				</script>
			(end example)
	
	Parameters:
		theme			-	*Defaults to 'white'*.  Several themes are built-in
							(distributed) with <Hints>, which can be found in the
							next section.
		themes			-	Adding your own theme is pretty simple.  See next section.
		hintLocation	-	*Defaults to 'title'*.  Which attribute contains your
							hint body.  If this attribute does not exist, *no hint is
							created!*.
		width			-	*Defaults to '250px'*.  Size of the total hint in pixels.
		offset			-	*Defaults to '[-5, -8]'.  Array of pixel offsets to move 
							the hint in relation to the element, *after* it has
							been positioned.  
		position		-	*Defaults to 'right'*.  Where the hint is aligned in relation
							to the element.  Possible values are *top*, *bottom*, *right*,
							and *left*.
		slideDistance	-	*Defaults to '10'*.  When a hint is displayed, it "slides"
							into position.  If you want it to slide the other direction,
							simply give it a negative value.  To disable sliding, give it
							a value of '0'.
		opacity			-	*Defaults to '1'*.  In addition to 'sliding', the hint fades in.
							This parameter lets you make your hints semi-transparent.
		onEvent			-	*Defaults to 'focus'*.  This is how the hint is triggered by the
							element.  Since most hints accompany an "input" field, we default
							to "focus".  By default, *all non-form elements are defaulted to
							"mouseenter"*.
		offEvent		-	*Defaults to 'blur'*.  This is how the hint is hidden by the
							element.  Similar to "onEvent", *all non-form elements are
							defaulted to "mouseleave"*.
	
	Themes:
		Five themes are included by default that work in all major browsers.  However,
		since IE6 and IE7 both suck in terms of PNG support, they use transparent GIFs
		which are designed for light/white backgrounds.  All other browsers render
		beautifully.
		
		The included themes are...

			*	White (default)
			*	Silver
			*	Black
			*	Blue
			*	Green
				
		The 'themes' object contains five child-objects with 4 parameters each.

			*	background	-	Background image for the hint.
			*	pointer		-	Pointer image for the hint.
			*	color		-	Default text color.
			*	link		-	Default link color.

		To create your own hint, use the following template.  Please note that there *must*
		be a GIF version of each image for IE6 & IE7 that is the exact same file name, except
		with the proper extension.
			
			(start code)
				var brown = {
					background: 'http://www.skuare.net/test/img/brown_back.png',
					pointer: 'http://www.skuare.net/test/img/brown_pointer.png',
					color: '#fff',
					link: '#acf'
				};
				var hints = new Hints({
					themes['brown']: brown
				});
				
			(end code)
		
*/
var Hints = new Class({
	elements: [],
	options: {
		'theme': 'white',
		'themes': {
					'white': {	
						'background': 'img/huki.png',
						'color': '#333',
						'link': '#369'
					},
					'silver': {	
						'background': 'img/huki.png',
						'color': '#000',
						'link': '#369'
					},
					'black': {
						'background': 'img/huki.png',
						'color': '#fff',
						'link': '#ffa'						
					},
					'green': {	
						'background': 'img/huki.png',
						'color': '#000',
						'link': '#fff'
					},
					'blue': {	
						'background': 'img/huki.png',
						'color': '#fff',
						'link': '#147'
					}
				  },
		'styles': null,
		'hintLocation': 'title',
		'width': '250px',
		'offset': [-5, -8],
		'slideDistance': '10',
		'opacity': '1',
		'position': 'right',
		'onEvent': 'focus',
		'offEvent': 'blur',
		'template':	[ "div",	{ 'class': 'hint_bubble_round' },
		 				[	"span",	{ 'class': 'pointer' } ],
						[	"div",	{ 'class': 'top' },
								[ "span",	{ 'class': 'right' } ]
						],
						[	"div",	{ 'class': 'middle' },
								[ "div",
									{ 'class': 'content' },
									[ "span",
										{ 'class': 'close' },
										'(<a href="javascript:void(0);">Disable Hints</a>)'
									]
								],
								[ "span",	{ 'class': 'right' } ]
						],
						[	"div",
								{ 'class': 'bottom' },
								[ "span",	{ 'class': 'right' } ]
						]
					],
		'templateClose': 'span.close',
		'templateContent': 'div.content',
		'templatePointer': 'span.pointer'
	},
	
	/*
		Function: initialize
			Called upon instantiation that assigns the options and gathers the elements.
			Afterwards, it adds styles to the page (via <addStyles>) and adds the hints
			(via <addHints>).
	
		Parameters:
			elements	-	Elements to apply hints to.  If you pass in the options here,
							then we automatically search for elements with the class "hint".
			options		-	Options to override.
	*/
	initialize: function(elements, options) {
		if($type(elements) == "object" && !$defined(options)) {
			options = elements;
			elements = null;
		}

		($type(elements) == "string") ? elements = $$(elements) : elements = elements || $$('.hint');

		this.setOptions(this.options, options);
				
		if(!!!elements) return false;

		this.addStyles(this.options.styles);
		this.addHints(elements);
	},
	
	/*
		Function: addStyles
			Adds styles to the page by creating a new "style" tag and injecting into the document.head.
			If the browser is IE, then any style with ".png" is converted to ".gif".
		
		Parameters:
			styles	-	CSS styles to add.  These can be overridden, but there's really no point.
	*/
	addStyles: function(styles) {
		if(Client.engine.ie) this.convertPNGtoGIF(this.options.themes[this.options.theme]);
		this.options.styles = ""+
			"div.hint_bubble_round { position: relative; width: 250px; color: "+this.options.themes[this.options.theme].color+"; font-size: 1.0em; padding: 25px 50px 25px 25px; }"+
			"div.hint_bubble_round span.pointer { display: block; position: absolute; z-index: 100; }"+
			"div.hint_bubble_round span.pointer.right { width: 34px; height: 34px; left: 0px; top: 50%; margin-top: -20px; background: transparent url("+this.options.themes[this.options.theme].pointer+") 100% -33px; }"+
			"div.hint_bubble_round span.pointer.left { width: 34px; height: 34px; right: 0px; top: 50%; margin-top: -20px; background: transparent url("+this.options.themes[this.options.theme].pointer+") 0% -33px; }"+
			"div.hint_bubble_round span.pointer.bottom { width: 33px; height: 31px; left: 50%; top: 0px; margin-left: -15px; background: transparent url("+this.options.themes[this.options.theme].pointer+") -33px 100%; }"+
			"div.hint_bubble_round span.pointer.top { width: 33px; height: 37px; left: 50%; bottom: 0px; margin-left: -15px; background: transparent url("+this.options.themes[this.options.theme].pointer+") -33px 0%; }"+
			"div.hint_bubble_round div.top, "+
			"div.hint_bubble_round div.top span.right,"+
			"div.hint_bubble_round div.middle,"+
			"div.hint_bubble_round div.middle span.right,"+
			"div.hint_bubble_round div.bottom,"+
			"div.hint_bubble_round div.bottom span.right { position: relative; background-image: url("+this.options.themes[this.options.theme].background+"); background-position: top left; height: 25px; margin-top: 0px; }"+
			"div.hint_bubble_round div.top span.right { position: absolute; display: block; left: 100%; top: 0px; height: 25px; width: 25px; background-position: top right; }"+
			"div.hint_bubble_round div.middle { position: relative; left: 0px; width: 100%; background-position: 0px -25px; height: auto; }"+
			"div.hint_bubble_round div.middle div.content { display: block; position: relative; clear: both; padding: 0px 0px 17px 25px; text-align: center; }"+
			"div.hint_bubble_round div.middle div.content a { color: "+this.options.themes[this.options.theme].link+"; }"+
			"div.hint_bubble_round div.middle div.content span.close { display: block; text-align: right; height: 1.2em; overflow: hidden; position: absolute; bottom: 0px; right: 0px; font-size: 0.8em; color: "+this.options.themes[this.options.theme].link+"; }"+
			"div.hint_bubble_round div.middle span.right { position: absolute;	display: block; top: 0px; left: 100%; width: 25px; height: 100%; float: right; background-position: 100% -25px;	}"+
			"div.hint_bubble_round div.bottom { position: relative; clear: both; left: 0px; height: 16px; background-position: bottom left; }"+
			"div.hint_bubble_round div.bottom span.right { display: block; width: 25px; height: 16px; position: absolute; top: 0px; left: 100%; background-position: bottom right; }";
		
		if(styles != this.options.styles)
			styles = this.options.styles;
		
		if($('hint_bubble_round_styles'))
			$('hint_bubble_round_styles').remove();
		
		var stylesheet = new Element('style', {
			'type': 'text/css',
			'media': 'screen',
			'id': 'hint_bubble_round_styles'
		}).injectInside($$('head')[0]);
		
		if(Client.engine.ie) {
			document.styleSheets[document.styleSheets.length-1].cssText = styles;
		} else {
			stylesheet.appendText(styles);
		}
	},
	
	/*
		Function: convertPNGtoGIF
			Takes in a 'theme' object and converts any occurrence of ".png" to ".gif", for IE.
		
		Parameters:
			object	-	Theme object, as illustrated above.
	*/
	convertPNGtoGIF: function(object) {
		for(key in object)
			object[key] = object[key].replace(/png/, "gif");
	},
	
	/*
		Function: addHints
			The engine for <Hints>, as it creates hint template via <DOM>, injects it
			into the top of the page, activates the "Disable Hints" button (via <enableClose>),
			positions the hint (via <positionHint>), and enables all animations via <activateHint>.
	*/
	addHints: function(elements) {
		options = this.options;
		var self = this;
		var template = this.constructTemplate();
		
		elements.each(function(element) {
			var tmp = template.clone();
			if(!!!element.getProperty(options.hintLocation)) return false;
			
			var content = tmp.getElement(this.options.templateContent);
			content.innerHTML = element.getProperty(options.hintLocation)
								+ content.innerHTML;						
			tmp.injectTop(document.body);
			tmp = self.enableClose(tmp);	
			tmp = self.positionHint(tmp, element);
			tmp = self.activateHint(tmp, element);
		});
	},
	
	/*
		Function: constructTemplate
			Called by <addHints> to construct the array-based template to a DOM node-tree.
	*/
	constructTemplate: function() {
		var template = new DOM(this.options.template);	
		var pointer =	template.getElement(this.options.templatePointer)
						.addClass(this.options.position);	
		return template;
	},
	
	/*
		Function: enableClose
			Enables the "Disable Hints" button.
	*/
	enableClose: function(tmp) {
		var content = tmp.getElement(this.options.templateContent);
		var close = tmp.getElement(this.options.templateClose);
		close.addEvent('click', this.removeHints.bind(this));
		
		var height = close.getStyle('height');
		var unit = 'px';
		if(height.indexOf('em') != -1) unit = 'em';
			
		var instruct = new Fx.Style(close, 'height', { wait: false, unit: unit, duration: 300 }).set(0);
		content.addEvent('mouseenter', function() { instruct.start(height.toInt()); });
		content.addEvent('mouseleave', function() { instruct.start(0); });
		return tmp;
	},
	
	/*
		Function: positionHint
			Called each time the hint is displayed to ensure it lines up with the element,
			even after images/elements have been loaded/removed, or the page has been resized.
			
			Minor alignment bugs with IE6 are fixed here.
	*/
	positionHint: function(tmp, element) {
		var input = element.getCoordinates();
		var hint = tmp.getCoordinates();

		switch(this.options.position) {
			case 'top':
				var top = input.top.toInt() - hint.height.toInt() - options.offset[1].toInt();
				var left = input.left.toInt() + input.width.toInt() / 2
				 		 - hint.width.toInt() / 2;
				break;
			case 'bottom':
				var top = input.top.toInt() + input.height.toInt() + options.offset[1].toInt();
				var left = input.left.toInt() + input.width.toInt() / 2
				 		 - hint.width.toInt() / 2;
				break;
			case 'right':
				var top = input.top.toInt() - hint.height.toInt() / 2
						+ input.height.toInt() / 2;
				var left = input.left.toInt() + input.width.toInt() + options.offset[0].toInt();	
				break;
			case 'left':
				var top = input.top.toInt() - hint.height.toInt() / 2
						+ input.height.toInt() / 2;
				var left = input.left.toInt() - hint.width.toInt() - options.offset[0].toInt();	
				break;
			default:
		}

		tmp.setStyles({
			'position': 'absolute',
			'top': top,
			'left': left
		});
		
		/* Fix IE6 height for right span */
		if(Client.engine.ie6) {
			var middle = tmp.getElement('div.middle');
			middle.setStyle('height', middle.getStyle('height'));
			var bottom = tmp.getElement('div.bottom').getElement('span.right');
			bottom.setStyle('top', '-3px');
		}

		return tmp;
	},
	
	/*
		Function: activateHint
			Final step in the hint-building process, as it enables the animations
			for the event-handlers on each element.
	*/
	activateHint: function(template, element) {
		var marginX = template.getStyle('margin-left');
		var marginY = template.getStyle('margin-top');

		if(marginX.indexOf('em') == -1)
			var unit = 'px'; else var unit = 'em';
			
		marginX = marginX.toInt(); marginY = marginY.toInt();
		var distanceX = distanceY = 0;

		switch(this.options.position) {
			case 'left': distanceX = this.options.slideDistance; break;
			case 'right': distanceX = -1*this.options.slideDistance; break;
			case 'top': distanceY = this.options.slideDistance; break;
			case 'bottom': distanceY = -1*this.options.slideDistance; break;			
		}

		var templateFade = new Fx.Styles(template, { 'wait': false, 'unit': unit }).set({
			'opacity': 0,
			'margin-left': marginX - distanceX,
			'margin-top': marginY - distanceY
		});
		
		var tag = element.getTag();
		if(tag == "input" || tag == "textarea" || tag == "button") {
			var on = this.options.onEvent; 
			var off = this.options.offEvent;
		} else {
			var on = 'mouseenter'; 
			var off = 'mouseleave';
		}
		
		if(tag == "img") element.addEvent('load', this.positionHint.pass([template, element], this));
		
		var options = this.options;
		var self = this;
		element.showHint = function() {
			self.positionHint(template, element);
			templateFade.start({
				'opacity': options.opacity,
				'margin-left': [marginX - distanceX, marginX],
				'margin-top': [marginY - distanceY, marginY]
			});
		}
		element.hideHint = function() {
			templateFade.start({
				'opacity': 0,
				'margin-left': marginX + distanceX,
				'margin-top': marginY + distanceY
			});
		}
		element.addEvent(on, element.showHint);
		element.addEvent(off, element.hideHint);
		this.elements.push(element);
	},
	
	/*
		function: showHints
			Iterates through each element and shows its hint.
			
			*You can perform this same process on a per-element bases by calling
			"element.showHint()"*.
	*/
	showHints: function() {
		this.elements.each(function(element) {
			element.showHint();
		});
	},
	
	/*
		function: hideHints
			Similar to <showHints>, except that it calls *element.hideHint()* for
			each element.
	*/
	hideHints: function() {
		this.elements.each(function(element) {
			element.hideHint();
		});
	},
	
	/*
		Function: removeHints
			Iterates through each element and removes it's associated hint object,
			as well as the "showHint" and "hideHint" attributes, which contained
			the animation functions for the hint.
	*/
	removeHints: function() {
		this.elements.each(function(element) {
			element.removeEvents();
			element.removeAttribute('showHint');
			element.removeAttribute('hideHint');
			$$('.hint_bubble_round').each(function(hint) {
				hint.remove();
			});
		});
	}
});
Hints.implement(new Options);

var DOM = new Class({
	element: null,
	parent: null,
	
	initialize: function(object) {
		return this.evaluate(object);
	},

	evaluate: function(object) {
		var parent = new Element(object[0]);
		var current = parent;

		!$defined(this.element) ?
			this.element = this.parent = parent
			:
			this.parent.adopt(parent);

		for(var i = 1; i < object.length; i++) {
			this.parent = current;		
			if($type(object[i]) === "string")	parent.setHTML(object[i]);
			if($type(object[i]) === "object")	parent.set(object[i]);
			if($type(object[i]) === "array")	this.evaluate(object[i]);
		}

		return this.element;
	}
});