/*----------------------------------------------------------------

Class: adobe.ToolTip

Author: 
btapley
   
----------------------------------------------------------------*/

adobe.use("adobe.u");
adobe.use("adobe.u.link");
adobe.use("adobe.u.element");
adobe.ToolTip = Class.create({
/*----------------------------------------------------------------

	Method: initialize
	
>	new adobe.ToolTip(triggers, dataObject, options)
	
	Parameters:
	triggers - node array
	dataObject - <adobe.ToolTipData> instance
	options - hash
	
	Options:
	align - "left" or "right", default is "right"
	caching - true
	chrome - string, default is '<span class="tooltip-content">#{value}<\/span>'
	delay - number in milliseconds, defualt is 500
	key - string, default is "href"
	height - integer, defualt is 0
	offset - integer, defualt is 10
	width - integer, defualt is 0
	
	Returned Value:
	Class instance

----------------------------------------------------------------*/
	initialize: function(triggers, dataObject, options) {
		this.currentkey;
		this.currentstate;
		this.saved_title = "";
		this._timer = null;
		this.triggers = {};
		this.cache;
		
		this.options = Object.extend({
			align: "right",
			caching: true,
			chrome: '<span class="tooltip-content">#{value}<\/span>',
			delay: 500,
			key: "href",
			height: 0,
			offset:	10,
			width: 0
		}, options);
		
		this.dataObject = dataObject;
		
		/* Attach Events */

		var _boundAttachTrigger = this.attachTrigger.bind(this);
		triggers.each(_boundAttachTrigger);
		
		if(this.options.caching) {
			this.readTipData();
		}
		
		this.pane = new adobe.TooltipPane({
			offset: this.options.offset,
			align: this.options.align,
			height: this.options.height,
			width: this.options.width
		});
		
		if(this.options.chrome) {
			this.chrome = new Template(this.options.chrome);
		}
			
	},
/*----------------------------------------------------------------

	Method: show
	
	Parameters:
	key - string
	trigger - element
	
	Returned Value:
	Nothing

----------------------------------------------------------------*/
	show: function(key, trigger) {
		var key = key,
		trigger = trigger;
		
		if(key == this.currentkey && this.currentstate == "show") { 
			return;
		}
		
		this.readTipData(key, responder.bind(this));
		
		function responder(response) {
			if(!response) { 
				return this.hide(this.currentkey);
			}
			
			this.saved_title = trigger.readAttribute("title");
			
			if(this.saved_title) {
				trigger.writeAttribute("title", "");
			}
			
			this.currentkey = key;
			this.currentstate = "show";
			
			var sOffset = Position.realOffset(document.body),
			html = (document.compatMode == "CSS1Compat") ? document.body.parentNode : document.body,
			contentH = window.clientHeight || html.clientHeight,
			contentW = window.clientWidth || html.clientWidth,
			viewport = {
				top: sOffset[1],
				right: contentW + sOffset[0],
				bottom: contentH + sOffset[1],
				left: sOffset[0]
			};
			
			this.pane.setContent(this.chrome.evaluate({value:response}));
			this.pane.moveToElement(trigger, viewport);

			this._timer = this.pane.show.bind(this.pane).delay(this.options.delay/1000);
			
			return response;
		}
	},
/*----------------------------------------------------------------

	Method: hide
	
	Parameters:
	key - string
	trigger - element
	
	Returned Value:
	Nothing

----------------------------------------------------------------*/
	hide: function(key, trigger) {
		if(key != this.currentkey) { return; }
		
		if(this.currentstate == "show") {
			
			if(this.saved_title) {
				trigger.writeAttribute("title", this.saved_title);
			}
			
			window.clearTimeout(this._timer);
			this.pane.hide();
			this.currentstate = "hide";
		}
	},
/*----------------------------------------------------------------

	Method: readTipData
	
	Parameters:
	key - string
	callback - function
	
	Returned Value:
	Nothing

----------------------------------------------------------------*/
	readTipData: function(key, callback) {
		var key = key;
		var callback = callback;
		
		if(!this.cache) {
			return this.dataObject.fetchData(read.bind(this));
		}
		
		return read.call(this, this.cache);
		
		function read(data) {
			
			if(!data) return;
			
			if(this.options.caching) { this.cache = data; }

			var result = (key) ? data[key] : data;
			
			return (callback) ? callback(result) : result;
		}
	},
/*----------------------------------------------------------------

	Method: emptyCache
	
	Returned Value:
	Nothing

----------------------------------------------------------------*/
	emptyCache: function() {
		delete this.cache;	
	},
/*----------------------------------------------------------------

	Method: getAttributeKey
	
	Parameters:
	element - Element reference
	
	Returned Value:
	Element attribute value

----------------------------------------------------------------*/
	getAttributeKey: function(element) {
		return element.readAttribute(this.options.key);
	},
/*----------------------------------------------------------------

	Method: attachTrigger
	Attach Tooltip Events to an element
	
	Parameters:
	element - Element reference
	
	Returned Value:
	Element reference

----------------------------------------------------------------*/
	attachTrigger: function(element) {
		var _key = this.getAttributeKey(element),
		_boundShowHandler = this.show.bind(this, _key, element),
		_boundHideHandler = this.hide.bind(this, _key, element);
		
		this.triggers[_key] = [_boundHideHandler, _boundShowHandler];
		
		element.observe("mouseover", _boundShowHandler);
		element.observe("mouseout", _boundHideHandler);
		return element;
	},
/*----------------------------------------------------------------

	Method: detachTrigger
	Remove Tooltip Events attached to an element
	
	Parameters:
	element - Element reference
	
	Returned Value:
	Nothing

----------------------------------------------------------------*/
	detachTrigger: function(element) {
		var _key = this.getAttributeKey(element),
		_saved_trigger = this.triggers[_key];
		
		if(!_key || 
		   !_saved_trigger || 
		   !_saved_trigger.length ||
		   _saved_trigger.length != 2) {
			return;
		}
		element.stopObserving("mouseover", _saved_trigger[1]);
		element.stopObserving("mouseout", _saved_trigger[0]);
		delete this.triggers[_key];
		return element;
	}
});

adobe.TooltipPane = Class.create({
	initialize: function(options) {
		this.options = Object.extend({
			align: "right",
			offset: 0,
			chromestyle: "tooltip",
			height: 0,
			width: 0,
			parent: null
		}, options);
		
		/* Sprite Properties */
		this.x = 0;
		this.y = 0;
		this.height = 0;
		this.width = 0;
		this.parent = this.options.parent || this.setParentDisplay();
		this.visible = false;
		
		this.displayProperties = {
			left: {
				name: "tooltip-left",
				opposite: "right",
				registration: "left"
			},
			right: {
				name: "tooltip-right",
				opposite: "left",
				registration: "right"
			}
		};
		
		this.display = new Element("div").addClassName(this.options.chromestyle);
		this.container = this.display.appendChild(new Element("div").addClassName("tooltipBody"));
		this.pointer = this.display.appendChild(new Element("div").addClassName("pointer"));
		
		if(this.options.height) { this.setHeight(this.options.height); }
		if(this.options.width) { this.setWidth(this.options.width); }
		
		
	},
	moveTo:function(x, y) {
		this.x = parseInt(x);
		this.y = parseInt(y);
		this.display.style.left = this.x.pixelate();
		this.display.style.top = this.y.pixelate();
	},
	moveToElement: function(trigger, viewport) {					
		var trigger = $(trigger);
		
		if(!this.parent) {
			this.hide();
			this.setParentDisplay(trigger.getDocument().getElementsByTagName("BODY")[0]);
			this.render();
		}
		
		var triggerPoint = trigger.cumulativeOffset(),
		displayOrientation = this.options.align,
		triggerLeftBound =  triggerPoint[0],
		triggerTopBound = triggerPoint[1],
		paneLeftBound = triggerLeftBound,
		paneTopBound = triggerTopBound;
		
		if(displayOrientation) {
			var triggerWidth = parseInt(trigger.offsetWidth),
			triggerHeight = parseInt(trigger.offsetHeight),
			display = this.display,
			displayWidth = parseInt(display.offsetWidth),
			displayHeight = parseInt(display.offsetHeight),
			pointer = this.pointer,
			//pointerWidth = parseInt(pointer.offsetWidth),
			pointerHeight = parseInt(pointer.offsetHeight),
			viewport =  viewport,
			displayAnchorPoint = {
				top: triggerTopBound + (triggerHeight/2) - (displayHeight/2),
				right: triggerLeftBound + triggerWidth + this.options.offset,
				left: triggerLeftBound - displayWidth - this.options.offset
			},
			displayTopBound = displayAnchorPoint.top;
			
			if(viewport) {
				
				var displayBounds = {
					top: displayAnchorPoint.top,
					left: displayAnchorPoint.left,
					right: displayAnchorPoint.right + displayWidth,
					bottom: displayAnchorPoint.top + displayHeight
				};
				
				var clipping = {
					top: _getOverflow(displayBounds.top, viewport.top, "less"),
					right: _getOverflow(displayBounds.right, viewport.right, "more"),
					bottom: _getOverflow(displayBounds.bottom, viewport.bottom, "more"),
					left: _getOverflow(displayBounds.left, viewport.left, "less")
				};
				
				// left and right detection
				if(clipping.right && !clipping.left) {
					displayOrientation = "left";
				} else if(clipping.left && !clipping.right) {
					displayOrientation = "right";
				}
				
				//top and bottom detection
				if(clipping.top && !clipping.bottom) {
					
					var shiftedBottomPosition = displayBounds.bottom + clipping.top;
					
					/* _getOverflow will return zero (false) for no overflow */
					var enoughSpaceBelowForShift = _getOverflow(shiftedBottomPosition,
										    viewport.bottom, 
										    "less");
					
					if(enoughSpaceBelowForShift) {
						displayTopBound = displayTopBound + clipping.top;
					}

				} else if(clipping.bottom && !clipping.top) {
					
					var shiftedTopPosition = displayBounds.top + clipping.bottom;
					
					/* _getOverflow will returns zero (false) for no overflow */					
					var enoughSpaceAboveForShift = _getOverflow(shiftedTopPosition,
										    viewport.top, 
										    "more");
					
					if(enoughSpaceAboveForShift) {
						displayTopBound = displayTopBound + clipping.bottom;
					}
				}
					
			}
			
			var displayProperties = this.displayProperties[displayOrientation],
			displayOppositeProperties = this.displayProperties[displayProperties.opposite];
			
			displayLeftBound = displayAnchorPoint[displayProperties.registration];
				
			pointer.style.top = _getPointerY();
			
			if(display.hasClassName(displayOppositeProperties.name)) {
				display.removeClassName(displayOppositeProperties.name);
			}
			display.addClassName(displayProperties.name);
		}
		
		return this.moveTo(displayLeftBound, displayTopBound);
		
		function _getPointerHeight() {
			return pointerHeight || (pointerHeight = parseInt(pointer.offsetHeight));
		}
		
		function _getPointerX() {
			return (triggerLeftBound - displayLeftBound).pixelate();
		}
		
		function _getPointerY() {
			var triggerCenterPoint = (triggerTopBound + triggerHeight/2),
			pointerCenter = (_getPointerHeight()/2);
			return (triggerCenterPoint - displayTopBound -  pointerCenter).pixelate();
		}
		
		function _getOverflow(insideBound, outsideBound, test) {
			var result;
			
			switch(test) {
				case "more":
					result = (insideBound > outsideBound) ? 
						Math.round((outsideBound - insideBound)) : 
						0;
					break;
				case "less":
					result = (insideBound < outsideBound) ? 
						Math.round(Math.abs((outsideBound - insideBound))) : 
						0;
					break;
			}
			
			return result;
		}
	},
	setContent: function(html) {
		this.container.update(html);
	},
	setVisible: function(bool) {
		this.visible = !!bool;
		this.display.style.visibility=(this.visible) ? "visible" : "hidden";
	},
	setParentDisplay: function(element) {
		this.parent = element || window.document.getElementsByTagName("BODY")[0];
	},
	render: function(element) {
		this.parent.appendChild(this.display);
	},
	setHeight: function(px) {
		this.height = parseInt(px);
		this.display.style.height = this.height.pixelate();
	},
	setWidth: function(px) {
		this.width = parseInt(px);
		this.display.style.width = this.width.pixelate();
	},
	unsetHeight: function() {
		this.height = "";
		this.display.style.height = this.height;
	},
	unsetWidth: function(px) {
		this.width = "";
		this.display.style.width = this.width;
	},
	show: function() {
		this.setHeight(this.display.offsetHeight);
		this.setVisible(true);
	},
	hide: function() {
		this.setVisible(false);
		this.unsetHeight();
	}
});


/*----------------------------------------------------------------

Class: adobe.ToolTipData
Wrap a data source with a common interface

Returned Value:
Class instance

----------------------------------------------------------------*/
adobe.ToolTipData = Class.create({
/*----------------------------------------------------------------

	Method: initialize
	
	Parameters:
	request - Object instance
	queryKey - string
	queryValue - string
	
	Returned Value:
	Class instance

----------------------------------------------------------------*/
	initialize: function(request, queryKey, queryValue) {
		this.request = request;
		this.queryKey = queryKey;
		this.queryValue = queryValue;
	},
/*----------------------------------------------------------------

	Method: fetchData
	
	Parameters:
	callback(optional) - function
	
	Returned Value:
	Object instance

----------------------------------------------------------------*/
	fetchData: function(callback) {
		var result = {};
		
		for(var i=0;i<this.request.length;i++) {
			var _item = this.request[i],
			key = _item[this.queryKey],
			value = _item[this.queryValue];
			result[key] = value;
		}
		
		return (callback) ? callback(result) : result;
	}
});

/*----------------------------------------------------------------

Class: adobe.ToolTipXHRData
Extends <adobe.ToolTipData>

Returned Value:
Class instance

----------------------------------------------------------------*/

adobe.ToolTipXHRData = Class.create(adobe.ToolTipData, {
	fetchData: function(callback) {
		var callback = callback,
		request = new Ajax.Request(this.request, {
			onComplete: (function(response) {
				var keys = response.responseXML.getElementsByTagName(this.queryKey);
				var values = response.responseXML.getElementsByTagName(this.queryValue);
				var result = {};
				
				for(var i=0;i<keys.length;i++) {
					var key = Element.getTextContent(keys[i]),
					value = Element.getTextContent(values[i]);
					result[key] = value;
				}
				
				return (callback) ? callback(result) : result;
			}).bind(this)
		});
	}
});

/*----------------------------------------------------------------

Class: adobe.ToolTipSelectorData
Extends <adobe.ToolTipData>

Returned Value:
Class instance

----------------------------------------------------------------*/

adobe.ToolTipSelectorData = Class.create(adobe.ToolTipData, {
	fetchData: function(callback) {
		var nodes = $$(this.request);
		var result = {};
		for(var i =0;i<nodes.length; i++) {
			var node = nodes[i],
			key = node.readAttribute(this.queryKey),
			value = node.readAttribute(this.queryValue);
			result[key] = value;
		}
		return (callback) ? callback(result) : result;
	}
});

/*----------------------------------------------------------------

Class: adobe.ToolTipXHRSubject
Extends <adobe.ToolTipData>. Deprecated in favor of <adobe.ToolTipXHRData>.

Returned Value:
Class instance

----------------------------------------------------------------*/
adobe.ToolTipXHRSubject = Class.create(adobe.ToolTipXHRData, {
/*----------------------------------------------------------------
	
	Method: initialize
	
	Parameters:
	request - path as string
	caching - Not implemented
	
	Returned Value:
	Class instance

----------------------------------------------------------------*/
	initialize: function(request, caching) {
		this.request = request;
		this.queryKey = "key";
		this.queryValue = "value";
	}
});