/* 
DetailTip
---------
Description: 
A detail tip class that uses positiontip and iFrameShimFix for extension/composites.
created: 		6/2008
last modified: 	7/28/2008 by timw
version:		0.4

 To Improve: 
	- add detailTip markup as option arg
	- find better way than innerHTML
	- re-think close link behavior in cases where user goes over new link.
*/
var DetailTip = Class.create({
	initialize: function(elTip, elTriggers, options){
		//note: DetailTip.SetPosition exists as static method. e.g. DetailTip.SetPosition(trigger, this.elTip, this.positionPrefs);
	
		this.options = Object.extend({
			delay: 0.5,					// Delay before tip is shown or hidden
			useEffects: false,			// use Fade/Appear effects?
			useClose: false,				// add a close link?
			triggerEvent: 'mouseover',	// which event to trigger the showing of a tip? ('mouseover' or 'click')
			useMouseMove: false			// adjust the position on mouse move?
		}, options || {});
		
		// extend nested position preferences.
		this.positionPrefs = Object.extend({
			orientation: 'east',
			xOffset: 10,	
			yOffset: 10,
			minMargin: 0,
			useCentering: true,
			useArrow: true,
			arrowClassName: 'dt_arrow',
			arrowDimensions: {width: 50, height: 80}
		}, this.options.positionPrefs || {});
		
		this.elTip = $(elTip) || false;
		
		this.elTriggers = elTriggers;
		// element check
		if(this.elTriggers.length == 0){return;}
		
		
		if(!this.elTip){
			// tip doesn't exist, create it as first child of body
			this.elTip = new Element('div', {id: 'detailtip'});
			var tipMarkup = 
				((this.positionPrefs.useArrow) ? '<div class="dt_arrow"></div>': '')
				+ '<div class="dt_head">'
				+ ((this.options.useClose) ? '<div class="dt_close"><a href="#close#">close</a></div>' : '')
				+ '</div>'
				+ '<div class="detailtip_content"><!-- content will go here --></div>'
				+ '<div class="dt_foot"></div>'
			;
			this.elTip.update(tipMarkup);
			// generic markup only includes div.detailtip_content
			//this.elTip.insert({bottom: new Element('div', {className: 'detailtip_content'})});
			$(document.body).insert({top: this.elTip });
		}
		else if(this.elTip.parentNode.nodeName != 'BODY'){
			// make sure tooltip is first child of body to avoid z-index issues
			$(document.body).insert({top: this.elTip });
		}
		if(!this.elTip.down('div.detailtip_content')){
			// make sure we have a content element
			this.elTip.insert({bottom: new Element('div', {className: 'detailtip_content'})});
		}
		
		this.elTipContent = this.elTip.down('div.detailtip_content');
		this.elTip.hide();
		this.isVisible = false;
		
		// attach event handlers
		this.elTip.observe('mouseover', this.__tipOver.bindAsEventListener(this));
		this.elTip.observe('mouseout', this.__tipOut.bindAsEventListener(this));
		
		var handle = this.__handleTriggerEvents.bindAsEventListener(this);
		this.elTriggers.invoke('observe', this.options.triggerEvent, handle);
		
		if(this.options.useClose){
			var lnkClose = this.elTip.down('div.dt_close a');
			lnkClose.observe('click', this.__closeClick.bindAsEventListener(this));
		}
		else{
			this.elTriggers.invoke('observe', 'mouseout', handle);
		}
		
		if(this.options.useMouseMove){
			this.elTriggers.invoke('observe', 'mousemove', handle);
		}

		
		// private members
		this._tpm = new TipPositionManager(this.positionPrefs);
		this._isIE6 = false /*@cc_on || @_jscript_version < 5.7 @*/;		
		this._inTip = false;
		this._inTrigger = false;
		this._timeoutId = null;
		this._currentDetailKey = -1;
		this._effect = null;
		this._contentCache = $H();
		this._lastTriggerIndex = null;
		
	},
	
	__tipOver: function(e){
		this._inTip = true;
	},

	__tipOut: function(e){
		if(e.relatedTarget && !e.relatedTarget.descendantOf(this.elTip)){
			this._inTip = false
		}
		if(!this.options.useClose){
			// auto-close
			this._clearTimer();
			this._timeoutId = setTimeout(this.hideTip.bind(this), this.options.delay*1000);			
		}
	},
	
	__closeClick: function(e){
		e.stop();
		this.hideTip(true);
	},
	
	__handleTriggerEvents: function(e){
		var trigger = e.element();
		
		// make sure we have the trigger, not a child node.
		while(this.elTriggers.indexOf(trigger) == -1 && trigger.nodeName != 'BODY'){
			trigger = trigger.up();
		}
		var triggerIndex = this.elTriggers.indexOf(trigger);
		
		if(e.type == this.options.triggerEvent){
			this._inTrigger = true;
			var isNewTrigger = this._lastTriggerIndex !== triggerIndex;
			var key = this.getTipKey(trigger);
			if(!key || this._inTip){ alert('aborting'); return; }// invalid key or user is in the tip
			
			
			var isNewKey = (this._currentDetailKey != key);

			if(isNewKey){
				var content = this.getContent(key);
				this.updateContent(content);
			}
			
			if(isNewTrigger){
				
				this._lastTriggerIndex = triggerIndex;

				this._currentDetailKey = key;
			
				this._tpm.setPosition(trigger, this.elTip, {left: e.clientX, top: e.clientY});
				//this._tpm.setPosition(trigger, this.elTip, {left: e.pointerX, top: e.pointerY});
				
				if(this._isIE6){
					this._tpm.iFrameShimFix('activate', this.elTip);
				}
			}
			
			if(isNewKey && this.isVisible){
				//console.log('forcing hide of previous detail');
				if(this.options.useEffects && this._effect){
					this._effect.cancel();
				}
				this.hideTip(true);
			}
			
			this._clearTimer();
			this._timeoutId = setTimeout(this.showTip.bind(this, key), this.options.delay*1000);			
		}
		else if(e.type == 'mousemove'){
			// reposition the tip according to mouse movement
			this._tpm.setPosition(trigger, this.elTip, {left: e.clientX, top: e.clientY});
			if(this._isIE6){
				this._tpm.iFrameShimFix('activate', this.elTip);
			}
		}
		else if(e.type == 'mouseout'){
			this._inTrigger = false;
			
			this._clearTimer();

			this._timeoutId = setTimeout(this.hideTip.bind(this), this.options.delay*1000);
			
		}
	},
	
	getTipKey: function(trigger){
		return trigger.readAttribute('rel') || null;
	},
	
	getContent: function(tipID){
		if(!this._contentCache.get(tipID)){
			//add to cache
			this._contentCache.set(tipID, $(tipID).innerHTML);// find a better way than innerHTML
		}
		return this._contentCache.get(tipID);
	},
	
	updateContent: function(content){
		this.elTipContent.update(content);
	},
	
	showTip: function(tipID){
		if(this._inTrigger && (tipID == this._currentDetailKey)){
			//console.log('show passed for ' + tipID);
			if(this.options.useEffects){
				this._effect = new Effect.Appear(this.elTip, {duration: 0.15});
			}
			else{
				this.elTip.show();
			}
			
			this.isVisible = true;
		}
	},
	
	hideTip: function(force){
		//console.log('hide fired');
		if(!this._inTip || (force === true)){
			if(this.options.useEffects && force != true){
				this._effect = new Effect.Fade(this.elTip, {duration: 0.15});
			}
			else{
				this.elTip.hide();
			}
			
			this.isVisible = false;
			
			if(this._isIE6){
				this._tpm.iFrameShimFix('deactivate', this.elTip);
			}			
		}
	},
	
	_clearTimer: function(){
		if(typeof(this._timeoutId) == 'number'){
			clearTimeout(this._timeoutId);
		}
		this._timeoutId == null;	
	}
});