/**
 *	Classe Javascript permettant de mettre en place un système d'autocompletion
 *	sur un input text pour les escales MONET
 *		
 *	Cette classe prend en arguments:
 *		* La reference d'un element de type Input contenant le libellé
 *		  Ex: $('inputLibelle')
 *		* La reference d'un element de type Input Hidden qui va contenir le 
 *		  code de la selection faite par l'utilisateur
 *		  Ex: $('inputCode')
 *		* Un tableau javascript contenant les codes IATA des escales
 *		  Ex: new Array('CDG','ORY','PAR')
 *		* Un tableau javascript contenant les libillés associés aux codes 
 *		  ci dessus. L'ordre doit etre respecté.
 *		  Ex: new Array('Charles de Gaulle','Orly','Paris, tous aéroports')
 *
 *	Cette classe peut etre utilisée en complement avec un taglib Java Monet:
 *	 	nom du taglib: escalesCompletion (<af:escalesCompletion>)
 *	 	tld struts: struts-af.tld
 * ============================================================================
 *	Auteur: 	Charbonnier Benoit
 *	Date: 		2006/05/17
 *	Version:	1.0   
 ******************************************************************************/

var completionObjectOnPage = new Array();
/* Variable servant à executer la fonction updateMultiStop ds le cas où on en a besoin */
var functionJsItic;
var functionJsIticDeparture;
/* Constantes servant a recuperer ces données dans le tableau ci dessus */
var SCRIPT_NAME = 0;
var INPUT_NAME = 1;
var CONTAINER_NAME = 2;

/* default size; may be overridded in the jsp page */
Completion.prototype.CONTAINER_WIDTH= 'auto';
Completion.prototype.CONTAINER_HEIGHT= '6';

/******************************************************************************
 *	CONSTRUCTEUR DE LA CLASSE DE COMPLETION
 * */ 
function Completion(inputElement, inputHiddenElement, codesArray, libellesArray, countriesArray, aeroportsArray, functionJsUpdateMultiStop) {
	this.input = inputElement;
	this.inputHidden = inputHiddenElement;
	this.container = null;
	this.codes = codesArray;
	this.libelles = libellesArray;
	this.countries=countriesArray;
	this.aeroports=aeroportsArray;
	this.resultsCode = new Array();
	this.resultsLibelle = new Array();
	
	this.timeoutId = null;
	this.TIMEOUT_DURATION = 3000;
	this.TIMEOUT_SHORT_DURATION = 500;
	
	this.scriptName = this.input.name + "Compl";
	this.containerName = this.input.name + "Container";
	
	this.container = this._generateContainer(this.containerName);
        this.containerHasFocus = false;
	
	/* On pousse l'objet dans un tableau qui sera utile au gestionnaire d'evenement */
	this.index = completionObjectOnPage.length;
	completionObjectOnPage.push(new Array(this.scriptName,this.input.name,this.containerName));

        if (functionJsUpdateMultiStop) {
        
        if(this.inputHidden.name=='departure'){
        	functionJsIticDeparture=functionJsUpdateMultiStop;
        } else {
        	functionJsItic = functionJsUpdateMultiStop;
        	}
        }
        
        
        
}

/*******************************************************************************
 * fonction qui cree de maniere dynamique le container final utilisé pour
 * fournir les propositions à l'utilisateur 
 * */  
Completion.prototype._generateContainer = function(name) {

	var newContainer = document.createElement("select");
	newContainer.setAttribute("id",name + "_id");
	newContainer.setAttribute("name",name);
	
	newContainer.setAttribute("size", isMac() ? "0" : this.CONTAINER_HEIGHT); 
	newContainer.setAttribute("tabIndex","2100");
	/* On set les styles de base que doit avoir le conteneur */
	newContainer.setAttribute("style","border: 1px #000 solid;");
	newContainer.style.position = "absolute";
	newContainer.style.visibility = "hidden";
	newContainer.style.zIndex = "1";
	/* On laisse la possibilite aux utilisateurs de surcharger ce style */
	newContainer.setAttribute("class","completion");
	
	/* Definition des evenements */
	newContainer.onclick = onSelectClickHandler;
	newContainer.onmouseout = onSelectMouseOutHandler;
	newContainer.onmouseover = onSelectMouseOverHandler;
	
	/* On rajoute dynamiquement le container dans la source */
	this.input.parentNode.insertBefore(newContainer,this.input);
	/* On registre l'objet dans la classe */
	return newContainer;
}
String.prototype.trim = function () {
    return this.replace(/^\s*/, "").replace(/\s*$/, "");
}

/*******************************************************************************
 * fonction permettant de remplir la liste des choix a proposer a l'utilisateur  
 */ 
Completion.prototype.findCompletion = function() {
	if(this.timeoutId != null)
		clearTimeout(this.timeoutId);
	switch(_lastKeyCode) {
		case KEY_DOWN_ARROW:
			if(!this._isVisible() && !this._isEmptyContainer())
				this._show();
			this._focusNextSibling();
			break;
		case KEY_UP_ARROW:
			if(!this._isVisible() && !this._isEmptyContainer())
				this._show();
			this._focusPreviousSibling();
			break;
		case KEY_ENTER: 
			this._setProposition();
			this._hide();
			break;
		case KEY_ESCAPE:
			this._hide();
			break;
		
		case KEY_LEFT_ARROW:
			break;
		case KEY_RIGHT_ARROW:
			break;
		default:
			this._clearResults();
			value = this.input.value.toLowerCase();
			value=value.trim();
			if(value == "") {
				this._hide();
			} else {
				this._getMatchingProposition(value);
		 		this._fillContainer(value);
				if(!this._isVisible() && !this._isEmptyContainer())
					this._show();
			} 
			break;
	}
	/* s'il n'y a qu'une seule proposition dans le container on ecourte de le timer */
	if(this.container.childNodes.length == 1) {
		this.timeoutId = setTimeout(completionObjectOnPage[this.index][SCRIPT_NAME]+"._setProposition();",this.TIMEOUT_SHORT_DURATION);
	} else {
		this.timeoutId = setTimeout(completionObjectOnPage[this.index][SCRIPT_NAME]+"._setProposition();",this.TIMEOUT_DURATION);
	}
}

/*******************************************************************************
 * fonction recupererant les propositions sur les libelles  
 * */  
Completion.prototype._getMatchingProposition = function(value) {
	value = this._formatForComparaison(value);

	for(var i=0;i<this.libelles.length;i++) {
		/* On cherche sur les libellés */
		if(this._formatForComparaison(this.libelles[i].substr(0,value.length)) == value) {
	  	this.resultsLibelle.push(i);
		}
		/* On cherche sur les codes IATA */
		if(this.codes[i].toLowerCase().substr(0,value.length) == value) {
			if(!this.resultsLibelle.contains(i)) {
				this.resultsCode.push(i);
			}
		}
		/* On cherche sur les pays */
		if(this.countries[i].toLowerCase().substr(0,value.length) == value) {
			if(!this.resultsLibelle.contains(i) && !this.resultsCode.contains(i)) {
				this.resultsCode.push(i);
			}
		}
		/* On cherche sur les aeroports */
		if(this.aeroports[i].toLowerCase().substr(0,value.length) == value) {
			if(!this.resultsLibelle.contains(i) && !this.resultsCode.contains(i)) {
				this.resultsCode.push(i);
			}
		}
		
		
	}
}

/*******************************************************************************
 * Fonction qui supprime les caractères accentués
 * */
Completion.prototype._formatForComparaison = function(value) {
	value = value.toLowerCase();

	var chars_accent = new Array("é", "è", "ê", "ë", "ç", "à", "â", "ä", "î", "ï", "ù", "ô", "ó", "ö");
	var chars_non_accent = new Array("e", "e", "e", "e", "c", "a", "a", "a", "i", "i", "u", "o", "o", "o");
	for (var i = 0; i < chars_accent.length; i++) {
		value = value.replace(chars_accent[i], chars_non_accent[i]);
	}

	return value
}


/*******************************************************************************
 * Fonction qui transforme le tableau des resultats afin de le preparer a
 * l'affichage   
 * */  
Completion.prototype._fillContainer = function(value) {
	this._sizePositionContainer();
	var libelleResultsCount = this.resultsLibelle.length;
	var codeResultsCount = this.resultsCode.length;
	/* On traite les libellés */
	for(var i=0;i<libelleResultsCount;i++) {
	  var newElmt = document.createElement("option");
	  newElmt.setAttribute("value",this.codes[this.resultsLibelle[i]]);
	  /* Préselection en cas de CodeIATA saisi */
		if(this.codes[this.resultsLibelle[i]].toLowerCase() == value) {
			newElmt.setAttribute("selected","selected");
		}
	  newElmt.innerHTML = this.libelles[this.resultsLibelle[i]];
	  this.container.appendChild(newElmt);
	}
	/* On traite les codes IATA */
	if(codeResultsCount > 0) {
		for(i=0;i<codeResultsCount;i++) {
			newElmt = document.createElement("option");
			newElmt.setAttribute("value",this.codes[this.resultsCode[i]]);
			newElmt.setAttribute("style","color: #8f8f8f;");
			/* Préselection en cas de CodeIATA saisi */
			if(this.codes[this.resultsCode[i]].toLowerCase() == value) {
				newElmt.setAttribute("selected","selected");
			}
		  	if(libelleResultsCount > 0)
				newElmt.setAttribute('class','iata');
			newElmt.innerHTML = this.libelles[this.resultsCode[i]];
		  	this.container.appendChild(newElmt);
	  	}
  	}
	/* S'il n'y a qu'une seule proposition */
	if(this.container.childNodes.length == 1) {
		this.container.selectedIndex = 0;
	}
	/* Met une taille minimale au conteneur si celui est vide */
	this.container.style.width = (this.container.childNodes.length == 0) ? '200px' : this.CONTAINER_WIDTH;
}

/*******************************************************************************
 * fonction determinant si le container contient des reponses    
 * */ 
Completion.prototype._isEmptyContainer = function() {
	return (!this.container.childNodes.length>0)
}

/*******************************************************************************
 * fonction mettant a jour les differents champs suite a la selection faite par
 * l'utilisateur    
 * */ 
Completion.prototype._setProposition = function() {
	if(this.container.selectedIndex != -1) {
		this.input.value = this.container.childNodes[this.container.selectedIndex].innerHTML;
		this.setInputHidden(this.container.childNodes[this.container.selectedIndex].value);
		this.input.focus();
		this._setCursorAtEnd();
		
                
                if(this.inputHidden.name=='departure'){
                	eval(functionJsIticDeparture);
                } else {
                	eval(functionJsItic);
                
				}
	}
	if(this.inputHidden.value != "") {
		clearTimeout(this.timeoutId);
		this._hide();
	} else {
		clearTimeout(this.timeoutId);
		this.timeoutId = setTimeout(completionObjectOnPage[this.index][SCRIPT_NAME]+"._setProposition();",this.TIMEOUT_DURATION);
	}
	
}

/*******************************************************************************
 * fonction qui force la mise à jour des differents champs en fonction d'un code iata
 * */ 
Completion.prototype._setForcedProposition = function() {
	var code = this.inputHidden.value;
	if(code != "") {
		for(var i=0;i<this.libelles.length;i++) {
			if(this.codes[i].toLowerCase().substr(0,code.length) == code.toLowerCase()) {
				this.input.value = this.libelles[i];
				this.setInputHidden(code);
			}
		}
	}
}

/*******************************************************************************
 * fonction qui valide l'item selectionne dans le container, et masque ce dernier
 * */  
Completion.prototype.chooseAndLeave = function() {
	if(this.container.selectedIndex != -1) {
		this.input.value = this.container.childNodes[this.container.selectedIndex].innerHTML;
		this.setInputHidden(this.container.childNodes[this.container.selectedIndex].value);
	}
	clearTimeout(this.timeoutId);
	setTimeout('new function() { if (!' + this.scriptName + '.isContainerHasFocus()) { ' + this.scriptName + '._hide() } }' , 100);
        eval(functionJsItic);
        eval(functionJsIticDeparture);
}

/*******************************************************************************
 * fonction qui vide le tableau des resultats ainsi que le conteneur d'affichage   
 * */ 
Completion.prototype._clearResults = function() {
	this.inputHidden.value = "";
	this.resultsCode = new Array();
	this.resultsLibelle = new Array();
	while(!this._isEmptyContainer()) {
		this.container.removeChild(this.container.childNodes[0]);
	}
}

Completion.prototype._hide = function() {
	this.container.style.visibility = "hidden";
	if (isMac()) {
		this.container.size = 0;   /* pour contourner bug Mac/FF */
	}
}
Completion.prototype._show = function() {
	this.container.size = this.CONTAINER_HEIGHT;  /* restore or custom height */
	this.container.style.visibility = "visible";
}
Completion.prototype._isVisible = function() {
	return this.container.style.visibility == "visible" ? true:false;
}

Completion.prototype._sizePositionContainer = function() {
	this.container.style.left = calculateOffsetLeft(this.input)+"px";
	this.container.style.top = calculateOffsetTop(this.input)+this.input.offsetHeight-1+"px";
	/* Instructions commentées car IE gere tres mal le width des selects provenant du style */
	/*this.container.style.width = calculateWidth(this.container,this.input)+"px";*/
}

Completion.prototype._focusNextSibling = function() {
	if(!this._isEmptyContainer() && this.container.selectedIndex < this.container.childNodes.length - 1 ) {
		this.container.selectedIndex++;
	}
	this._setCursorAtEnd();
}

Completion.prototype._focusPreviousSibling = function() {
	if(!this._isEmptyContainer() && this.container.selectedIndex > 0 ) {
		this.container.selectedIndex--;
	}
	this._setCursorAtEnd();
}

Completion.prototype.clearHideTimeout = function() {
	if(this.timeoutId != null)
		clearTimeout(this.timeoutId);
}

Completion.prototype.activateHideTimeout = function() {
	/* On est oblige d'appeler la methode en utlisant le tableau completionObjectOnPage
	car une fois le timeout terminé, le this n'a plus de contexte */
	this.timeoutId = setTimeout(completionObjectOnPage[this.index][SCRIPT_NAME]+"._hide()",this.TIMEOUT_DURATION);
}

Completion.prototype.setContainerHasFocus = function(getFocus) {
	this.containerHasFocus = getFocus;
}
Completion.prototype.isContainerHasFocus = function() {
	return this.containerHasFocus;
}

/*******************************************************************************
 *	Fonctions permettant de calculer les différentes valeurs necessaires
 *	au placement de la liste de propositions dans la page.
 *	Les emplacements sont calcules en absolu 
 *	*/
function calculateOffsetLeft(r){
	return _calculateOffset(r,"offsetLeft")
}
function calculateOffsetTop(r){
	return _calculateOffset(r,"offsetTop")
}
function _calculateOffset(r,attr){
	var kb=0;
	while(r){
		kb+=r[attr];
		r=r.offsetParent
	}
	return kb
}
/*******************************************************************************	
 *	Calcule la largeur d'un champ par rapport a un autre
 *	si largeur element > largeur element de reference => largeur element
 *	sinon => largeur element de reference
 * */
function calculateWidth(element,referenceElement) {
	var width = $(referenceElement).offsetWidth-1;
	return $(element).offsetWidth > width ? $(element).style.width : width;
}

Completion.prototype._setCursorAtEnd = function(){
	if(this.input.createTextRange) {
		var t = this.input.createTextRange();
		t.moveEnd("character",this.input.value.length);
		t.select();
	} else if(this.input.setSelectionRange) {
		this.input.setSelectionRange(this.input.value.length,this.input.value.length)
	}
}

/*******************************************************************************	
 *	GESTIONNAIRE STATIQUE
 *******************************************************************************
 * fonction statique qui force la mise à jour des differents champs de completions
 * disponibles sur la page
 * */ 
function CompletionManager() {}
var GestionnaireCompletion = new CompletionManager();
CompletionManager.prototype.setForcedProposition = function() {
	for(var i=0;i<completionObjectOnPage.length;i++) {
		eval(completionObjectOnPage[i][SCRIPT_NAME]+"._setForcedProposition();");
	}
}
/*******************************************************************************	
 * fonction statique qui permet de verifier que les champs de completion sont remplis avant de
 * avant de soumettre (utile notement sous safari qui auto-valide les formulaires sur la touche ENTREE)
 * */ 
CompletionManager.prototype.checkSafariSubmit = function() {
	var result = false;
	for(var i=0;i<completionObjectOnPage.length;i++) {
		result = result && eval(completionObjectOnPage[i][SCRIPT_NAME]+".inputHidden.value != ''");
	}
	return result;
}


/*******************************************************************************	
 *	GESTION DES EVENEMENTS
 *******************************************************************************
 *	Gestion generale appliquee a tout le document courant 	 
 * */
var _Keys = new Array();
var KEY_BACK = 8
var KEY_TAB = 9;
var KEY_SHIFT = 16;
var KEY_LEFT_ARROW = 37;
var KEY_UP_ARROW = 38;
var KEY_RIGHT_ARROW = 39;
var KEY_DOWN_ARROW = 40;
var KEY_ENTER = 13;
var KEY_ESCAPE = 27;

function onResizeHandler() {
	for(var i=0;i<completionObjectOnPage.length;i++) {
		eval(completionObjectOnPage[i][SCRIPT_NAME] + "._sizePositionContainer()");
	}
}
window.onresize = onResizeHandler;

var _lastKeyCode = null;
var onKeyDownHandler = function(event){
  // accès evenement compatible IE/Firefox
  if(!event&&window.event) {
    event=window.event;
  }
  // on enregistre la touche ayant déclenché l'évènement
  if(event) {
    _lastKeyCode=event.keyCode;
  }
}
document.onkeydown = onKeyDownHandler;

/*******************************************************************************	
 *	Les fonctions ci dessous permettent de gerer les evenements sur les select
 *	qui sont lies aux differents systemes de completions instancies dans la page
 *	en cours  
 * */
function onSelectClickHandler() {
	var currentCompletionObjet = getCompletionObject(this.name);
	eval(currentCompletionObjet[SCRIPT_NAME] + "._setProposition();");
}

function onSelectMouseOverHandler() {
	var currentCompletionObjet = getCompletionObject(this.name);
	eval(currentCompletionObjet[SCRIPT_NAME] + ".clearHideTimeout();");
	eval(currentCompletionObjet[SCRIPT_NAME] + ".setContainerHasFocus(true);");
}

function onSelectMouseOutHandler() {
	var currentCompletionObjet = getCompletionObject(this.name);
	eval(currentCompletionObjet[SCRIPT_NAME] + ".activateHideTimeout();");
	eval(currentCompletionObjet[SCRIPT_NAME] + ".setContainerHasFocus(false);");
}

/*******************************************************************************	
 *	Cette fonction permet de recuperer un tableau contenant les noms des
 *	principaux champs utiles pour un système de completion 
 * */
function getCompletionObject(selectName) {
	for(var i=0;i<completionObjectOnPage.length;i++) {
		if(completionObjectOnPage[i][CONTAINER_NAME] == selectName) {
			return completionObjectOnPage[i];
		}
	}
}

/*******************************************************************************	
 *	Function qui test si on se trouve sur un environnement Mac
 * */
function isMac() {
	var infoNav = navigator.userAgent.toLowerCase();
	return infoNav.indexOf("mac") != -1;	
}


/*******************************************************************************	
 *	Surcharge de l'objet javascript Array afin de lui rajouter une methode de
 *	recherche perfomante 
 * */
Array.prototype._flatArray = "";
Array.prototype._flattern = function() {
	this._flatArray = "|" + this.join("|") + "|";
}
Array.prototype.contains = function(valeur) {
	this._flattern();
	if(this._flatArray.indexOf("|" + valeur + "|") == -1)
		return false;
	else 
		return true;
}

/*******************************************************************************	
 *	Surcharge de l'objet javascript Array afin de lui rajouter la methode push
 *	lorsque celle ci n'existe pas (cas de IE < 5.5) 
 * */
if (!Array.prototype.push) {
  Array.prototype.push = function() {
		var startLength = this.length;
		for (var i = 0; i < arguments.length; i++)
      this[startLength + i] = arguments[i];
	  return this.length;
  }
}

/****************************************************************************/
Completion.prototype.setInputHidden = function(newValue) {
	this.inputHidden.value = newValue;
	if (document.onChangeStopover){
			document.onChangeStopover();
	}
}
