/**
 * copyright (c) 2005-2007 - lx barjon <lx@iassa.com>
 * class toolTips
 * version : 1.1.1 (28-12-2007)
 * licence : GNU GPL
 *
 **************************************************************************
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 **************************************************************************
 * 28/12/07
 **********
 * FIX : Problème lors du lacement de plusieurs objets toolTips : l'option afterRemoteLoading n'était pas lancée si un objet toolTip était lancé après un objet avec remoteContent et afterRemoteLoading
 */

var remoteCpt;
var toolTips = {};
toolTips = Class.create();
toolTips.prototype = {

	initialize: function( options ) {
		this.version = '1.1.1';
		
		if ( !document.getElementById || !document.getElementsByTagName ) return;
		
		this.debug = false;
		_debug = this.debug;
		this.actionDebug = false;
		if( this.debug || this.actionDebug ) {
			if (!$('jsLog')) {
				var jsLog = Builder.node( 'div', {id: 'jsLog', style: 'position: absolute; bottom: 0; background-color: #fee; border: 1px solid #f00; height: 300px; overflow: auto; font-size: 9pt;'} );
				document.getElementsByTagName( 'body' )[0].appendChild(jsLog);
			}
			if( !$('jsLogMP') ) {
				var MP = Builder.node( 'div', {id: 'jsLogMP', style: 'position: absolute; bottom: 0; background-color: #f00; width;100px; padding: 2px; font-size: 8pt;'} );
				document.getElementsByTagName( 'body' )[0].appendChild(MP);
				if ( !$('jsLogMP') ) alert( 'probssss!!' );
			}
			$('jsLog').innerHTML += '<strong>toolTips (v ' + this.version + ') - DEBUG Info</strong><br><br>';
			$('jsLogMP').innerHTML = 'mousePos';
		}
		
		remoteCpt = 0;
		
		Object.extend( this.options = {
			pathToImgs: '',
			// mode = title
			//    -> recupère tous les tag <a> est stocke le contenu de l'attribut title pour un affichage perso
			//    -> ajoute les event (mouseover et mouseout) automatiquement
			// mode = nom_de_class (exemple 'tip')
			//    -> recup tous les tag ayant pour class nom_de_class (exemple <div class="tip"></div>)
			//    -> bouge le tag dans le container approprié (this.options.tipsContainer) pour un affichage perso
			// Il est possible d'utiliser les deux modes en même temps, dans ce cas il faut que les container ait un id différent
			mode: 'title',
			changeOpacity: false, // false | HTML tag ID
			showOn: 'mouseover',
			hideOn: 'mouseout',
			closeImg: 'close.png',
			useCloseImg: false,
			// ajouter option de positionement du même type que l'option fixTPMode
			// correction position image (actuellement en haut à droite)
			ciXCorrect: 2,
			ciYCorrect: 2,
			// si hideDelay > showDelay, hideDelay prend la valeur showDelay-0.1)
			showDelay: 1,       // delais avant affichage, en seconde
			hideDelay: 0.1,     // delais avant masquage, en seconde
			mouseOverTips: false,   // true -> permet de garder le tip affiché s'il on passe la souris dessus
			// - options possibles
			// fixX : pour fixer les tips sur l'axe des x, peut être utilisé seul (le tips ne bougera que sur les Y)
			//				ou avec fixY (le tips sera immobile aux coordonnées x,y fournies)
			// fixY : pour fixer les tips sur l'axe des y
			// il faut fournir l'unité pour ces 2 options : ex. fixX: "30%", fixY: "150px"
			// rend obsolete les options ecartX et ecart Y
			ecartX:           5,       // ecart sur X par rapport à la souris
			ecartY:           10,      // ecart sur Y par rapport à la souris
			fixToParent:      false,   // Cette option permet de fixer le tip par rapport au tag correspondant (le parent)
			                           // 'false' par défaut -> le positionnement du tip se fait par rapport à la position de la souris (le tip suit la souris). Les options fixX et fixY seront prise en compte
                                 // true -> le tip est positionné par rapport au parent (bas gauche par défaut, s'adapte en fonction de la zone d'affichage).
                                 // du coup les options ecartX et ecartY sont utilisées pour décaler le tip fonction de son positionnement par rapport au parent
			fixTPMode:          'BL',  // pour le positionnement du tip par rapport au parent
			                           // utile uniquement si fixToParent est à true
			                           //  - TL -> top left
			                           //  - TR -> top right
			                           //  - BL -> bottom left
			                           //  - BR -> bottom right
			hookToID:         false,   // accrocher les tips définis en fonction de leur id. L'id des tips doit être du type help_idElementToHook ou idElementToHook est l'id du tag auquel il faut attacher le tip correspondant
			useHelpButton:    false,   // ajoute des images sur lesquelles il faut passer la souris pour obtenir les tips
			hbEcartX:         0,       // pour le positionnement sur X des images
			hbEcartY:         -5,      // pour le positionnement sur Y des images
			getRemoteContent: false,   // si true -> récupère le contenu du type. Le contenu doit être une adresse http valide pour un dl de données via AJAX
			afterRemoteLoading: function() {},
			riseReload: false,          // recharge l'url recup a chaque affichage (si getRemoteContent)
			// option anim
			useAnim: false,
			showAnim: Effect.SlideDown,
			hideAnim: Effect.DropOut,
			duration: 0.5,
			// draggable
			draggable: false
		}, options || {} );
		
		// vérif options showDelay et hideDelay
		if ( this.options.hideDelay > this.options.showDelay && this.options.showOn != 'click' ) this.options.hideDelay = this.options.showDelay - 0.1;
		// création du div qui contiendra les tips
		this.container = $( 'tc_' + this.options.mode );
		if ( !this.container ) {
			var tipsContainer = Builder.node( 'div', {id: 'tc_' + this.options.mode, style: 'position: absolute; display: none;'} );
			document.getElementsByTagName( 'body' )[0].appendChild(tipsContainer);
			this.container = $('tc_' + this.options.mode);
		} else this.container.innerHTML = '';
		if ( !this.container ) return;
		// draggable
		if( this.options.draggable ) new Draggable(this.container);
		// ajoute image close si besoin
		if( this.options.useCloseImg && this.options.hideOn == 'click' ) {
			if (this.debug) $('jsLog').innerHTML += '- use close image<br>';
			this.closeImg = Builder.node( 'img', {src: this.options.pathToImgs + 'close.png', style: 'position: absolute; z-index: 5500010; cursor: pointer', className: 'tipsCloseImg'} );
			this.container.appendChild(this.closeImg);
			Event.observe( this.closeImg, 'click', this.closeImg_hide.bindAsEventListener(this) );
			this.closeImg.hide();
			
		}
		if (this.debug) $('jsLog').innerHTML += 'tips container created :: ' + this.container.id + '<br>';

		// pour stocker le parent du tip actuellement affiché, pour le cas ou l'option mouseOverTips soit à true
		if ( this.options.mouseOverTips ) {
			this.currentTipsParent = false;
		}
		
		this.currentTip = false;

		// pour stocker les infos concernant les tips
		this.elements = new Array();
		this._set_links_and_tooltips();
		this.mp = this._mouse_positioning.bindAsEventListener(this);
	},
	
	closeImg_hide: function() {
		this.hide_tips(this.currentTip);
	},

	mOver: function(event) {
		var element = Event.element(event);
		if (this.actionDebug) $('jsLog').innerHTML += '<strong>TRY to rise tip :: ' + element.id + '</strong><br>';
		if ( !this.options.fixToParent ) {
			Event.observe( document, "mousemove", this.mp );
		}
		var mouseX = Event.pointerX(event);
		var mouseY = Event.pointerY(event);
		if ( !element ) return;
		if ( element.tipState == 'off' ) {
			if (this.actionDebug) $('jsLog').innerHTML += '<em>__ tip is off, rise it<br>currentTip->'+this.currentTip.id+'</em><br>';
			if ( this.hideTimeout || this.currentTip ) {
				if (this.actionDebug) $('jsLog').innerHTML += '<em>__________cancel hide timeout</em><br>';
				this._cancel_hidding();
				if( this.currentTip ) this.hide_tips(this.currentTip);
			}
			this.currentTip = element;
			if (this.actionDebug) $('jsLog').innerHTML += '<em>__ new current tip :: ' + this.currentTip.id + '</em><br>';
			this._cancel_rising();
			var _riseTips = this.rise_tips.bind( this );
			if( this.hideEffect ) riseTime = (this.options.duration + this.options.showDelay)  * 1000;
			else riseTime = this.options.showDelay * 1000;
			this.riseTimeout = setTimeout( function() { _riseTips(element, mouseX, mouseY); }, riseTime );
		} else if(this.options.hideOn != 'click') {
			this._cancel_hidding();
		}
	},

	mOut: function(event) {
		var element = Event.element(event);
		if (this.actionDebug) $('jsLog').innerHTML += '<strong>TRY to hide tip :: ' + element.id + '</strong><br>';
		if ( !this.options.fixToParent ) {
			Event.stopObserving( document, "mousemove", this.mp );
		}
		if ( !element ) return;
		this._cancel_rising();
		if ( element.tipState == 'on' ) {
			if (this.actionDebug) $('jsLog').innerHTML += '<em>__ tip is on, hide it</em><br>';
			var _hideTips = this.hide_tips.bind( this );
			this.hideTimeout = setTimeout( function() { _hideTips(element); }, (this.options.hideDelay * 1000) );
		}
	},

	rise_tips: function(element, mouseX, mouseY) {
		if ( this.options.mode == 'title' ) {
			this.container.innerHTML = element.tipContent;
			if(this.options.useAnim) new this.options.showAnim(this.container, {duration: this.options.duration});
			else Element.show(this.container);
		} else {
			if ( this.options.getRemoteContent && this.options.riseReload ) {
				var myAjax = new Ajax.Updater( {
					success: element.tipContent.id
				}, element.url, {
					method: 'get',
					parameters: '',
					onFailure: function() {
						element.tipContent.innerHTML = '<p style="text-align: center;"><br />Erreur lors du transfert des informations distantes.<br /></p>';
					},
					evalScripts: true
				} );
			}
			Element.show(element.tipContent);
			if ( this.options.fixToParent ) this._parent_positioning( element );
			else this._position_container( mouseX, mouseY );
			if( this.options.changeOpacity ) $(this.options.changeOpacity).style.opacity = 0.5;
			Element.hide(element.tipContent);
			if(this.options.useAnim) {
				Element.show(this.container);
				new this.options.showAnim(element.tipContent, {duration: this.options.duration});
				if( this.options.useCloseImg && this.options.hideOn == 'click' ) {
					var _ci = this.closeImg;
					setTimeout( function() { _ci.show(); }, (this.options.duration + 0.2) * 1000);
				}
			} else {
				if( this.options.useCloseImg && this.options.hideOn == 'click' ) this.closeImg.show();
				Element.show(this.container);
				Element.show(element.tipContent);
			}
		}
		element.tipState = 'on';
		if ( this.options.mouseOverTips ) {
			this.currentTipsParent = element;
		}
	},

	hide_tips: function(element) {
		if ( this.options.mode == 'title' ) {
			this.container.innerHTML = '';
			if(this.options.useAnim) new this.options.hideAnim(this.container, {duration: this.options.duration});
			else Element.hide(this.container);
		} else {
			if(this.options.useAnim) {
				this.hideEffect = new this.options.hideAnim(element.tipContent, {duration: this.options.duration});
				var _container = this.container;
				this.hideTimeoutB = setTimeout( function() { Element.hide(_container); }, (this.options.duration*1000) );
				if( this.options.useCloseImg && this.options.hideOn == 'click' ) {
					var _ci = this.closeImg;
					setTimeout( function() { _ci.hide(); }, 200);
				}
			} else {
				if( this.options.useCloseImg && this.options.hideOn == 'click' ) this.closeImg.hide();
				this.container.hide();
				Element.hide(element.tipContent);
			}
		}
		element.tipState = 'off';
		if ( this.options.mouseOverTips ) {
			this.currentTipsParent = false;
		}
		if (this.actionDebug) $('jsLog').innerHTML += '____Tip :: ' + this.currentTip.id + ' :: hidden<br>';
		this.currentTip = false;
		if( this.options.changeOpacity ) $(this.options.changeOpacity).style.opacity = 1;
	},

	_tipsMOut: function() {
		var _hideTips = this.hide_tips.bind( this );
		var _ctp = this.currentTipsParent;
		this.hideTimeout = setTimeout( function() { _hideTips(_ctp); }, (this.options.hideDelay * 1000) );
	},

	_mouse_positioning: function(event) {
		var mouseX = Event.pointerX(event);
		var mouseY = Event.pointerY(event);
		this._position_container( mouseX, mouseY );
		if (this.debug) $('jsLogMP').innerHTML = 'x '+mouseX+ ' / y '+mouseY;
	},

	_parent_positioning: function(element) {
		var coord = Position.cumulativeOffset(element);
		var posX = coord[0];
		var posY = coord[1];
		if ( this.options.fixTPMode != 'TL') {
			if ( this.options.fixTPMode == 'TR' || this.options.fixTPMode == 'BR') posX += element.offsetWidth;
			if ( this.options.fixTPMode == 'BL' || this.options.fixTPMode == 'BR' ) posY += element.offsetHeight;
		}
		this._position_container( posX, posY );
	},

	_position_container: function( X, Y) {
		if( this.container.style.display == 'none' ) {
			var oldDisplay = 'none';
			this.container.show();
		}
		this.container.style.visibility = 'hidden';
		if ( this.options.fixX ) {
			this.container.style.left = this.options.fixX;
		} else {
			var farX = X + this.container.offsetWidth + this.options.ecartX;
			var limitX = (document.documentElement.scrollLeft || document.body.scrollLeft || 0) + (window.innerWidth || screen.availWidth) - 25;
			var xOverlap = limitX - this.container.offsetWidth;
			if (farX > limitX ) this.container.style.left = xOverlap + 'px';
			else this.container.style.left = (X + this.options.ecartX) + 'px';
		}
		if ( this.options.fixY ) {
			this.container.style.top = this.options.fixY;
		} else {
			var farY = Y + this.container.offsetHeight + this.options.ecartY;
			var limitY = (document.documentElement.scrollTop || document.body.scrollTop || 0) + (window.innerHeight || (screen.availHeight-window.screenTop)) -20;
			var yOverlap = limitY - this.container.offsetHeight;
			if (farY > limitY ) this.container.style.top = yOverlap + 'px';
			else this.container.style.top = (Y + this.options.ecartY) + 'px';
		}
		this.container.style.visibility = '';
		if( oldDisplay == 'none' ) this.container.hide();
	},

	_set_links_and_tooltips: function() {
		if (this.debug) $('jsLog').innerHTML += 'Mode = '+this.options.mode+'<br>';
		if ( this.options.mode == 'title' ) {
			var allLinks = document.getElementsByTagName( 'a' );
			var linkCpt = 0;
			if (this.debug) $('jsLog').innerHTML += 'num Tips ='+allLinks.length+'<br>';
			for ( var cpt = 0; cpt < allLinks.length; cpt++ ) {
				if ( allLinks[cpt].title != '' ) {
					this.elements[linkCpt] = allLinks[cpt];
					this.elements[linkCpt].tipContent = allLinks[cpt].title;
					allLinks[cpt].title = '';
					this.elements[linkCpt].tipState = 'off';
					Event.observe( allLinks[cpt], this.options.hideOn, this.mOut.bindAsEventListener(this) );
					Event.observe( allLinks[cpt], this.options.showOn, this.mOver.bindAsEventListener(this) );
					linkCpt++;
				}
			}
		} else {
			var allLinks = new Array();
			var allTips = document.getElementsByClassName(this.options.mode);
			if (this.debug) $('jsLog').innerHTML += 'num Tips = '+allTips.length+'<br>';
			if ( this.options.getRemoteContent && _debug ) $('jsLog').innerHTML += 'Load remote content tips : ';
			for ( var cpt = 0; cpt < allTips.length; cpt++ ) {
				this.container.appendChild(allTips[cpt]);
				// détermine le parent du tip
				if ( this.options.hookToID ) {
					// l'id du tip doit être de la forme : help_idElementToHook
					var parent = $(allTips[cpt].id.substring(5, allTips[cpt].id.length));
				} else {
					parent = allTips[cpt].parentNode;
				}
				// ajouter une image pour afficher le tip ?
				if ( this.options.useHelpButton ) {
					// ajoute et positionne bouton image
					var helpImg = Builder.node( 'img', {className: 'tipHelpImg', src: this.options.pathToImgs + 'help.png', style: 'position: relative;'} );
					if ( parent.tagName.toLowerCase() == 'input' || parent.tagName.toLowerCase() == 'textarea' || parent.tagName.toLowerCase() == 'select' ) {
						helpImg.id = parent.id+'_helpImg';
						parent.parentNode.appendChild(helpImg);
					} else parent.appendChild(helpImg);
					if( parent.style.display == 'none' ) helpImg.hide();
					var posX = this.options.hbEcartX;
					var posY = this.options.hbEcartY;
					helpImg.style.left = posX + 'px';
					helpImg.style.top = posY + 'px';
					// parent devient bouton image
					parent = helpImg;
				}
				if ( this.options.mouseOverTips ) {
					Event.observe( allTips[cpt], "mouseout", this._tipsMOut.bind(this), false );
					Event.observe( allTips[cpt], "mouseover", this._cancel_hidding.bind(this), false );
				}
				this.elements[cpt] = parent;
				this.elements[cpt].tipContent = allTips[cpt];
				Element.hide(allTips[cpt]);
				if ( this.options.getRemoteContent ) {
					_afterRemoteLoading = this.options.afterRemoteLoading.bind(this);
					this.elements[cpt].url = this.elements[cpt].tipContent.innerHTML;
					this.elements[cpt].tipContent.innerHTML = 'Chargement&nbsp;...';
					var tipContent = this.elements[cpt].tipContent;
					this.myAjax = new Ajax.Updater( {
						success: this.elements[cpt].tipContent.id
					}, this.elements[cpt].url, {
						method: 'get',
						parameters: '',
						onComplete: function() { 
							remoteCpt++; 
							if (_debug) $('jsLog').innerHTML += remoteCpt+', ';
							if (remoteCpt == allTips.length) {
								if (_debug) $('jsLog').innerHTML += 'COMPLETED | '+remoteCpt+' tip(s) chargé(s)<br>';
								_afterRemoteLoading();
							}
						},
						onFailure: function(request) {
							tipContent.innerHTML = '<p style="text-align: center;"><br />Erreur lors du transfert des informations distantes.<br /></p>';
						},
						evalScripts: true
					} );
				}
				this.elements[cpt].tipContent.style.position = 'relative';
				this.elements[cpt].tipState = 'off';
				Event.observe( parent, this.options.hideOn, this.mOut.bindAsEventListener(this) );
				Event.observe( parent, this.options.showOn, this.mOver.bindAsEventListener(this) );
			}
		}
	},

	_cancel_hidding: function() {
		if( this.hideTimeout ) clearTimeout( this.hideTimeout );
	},

	_cancel_rising: function() {
		if( this.riseTimeout ) clearTimeout( this.riseTimeout );
	}

}
