

/**
 * Ce fichier contient l'implémentation d'une completion MEV. Il nécessite
 * l'utilisation du fichier completion.js (qui est mutuel aux version MEV,
 * MEH...) Au début de ce fichier on trouvera notemmment les variables globales
 * completionMEVURLRoot et completionMEVRJS. On devra s'assurer que ces
 * variables contiennent des valeurs convenables. NOTA: Ce script nécessite
 * l'utilisation du script tools.js qui doit donc être invoqué dans la page,
 * ainsi que du fichier CSS par défaut completionMEV.css
 *
 * L'implémentation de la completion MEV est alors la suivante: La page HTML
 * disposant de la completion pour la détermination doit impérativement contenir
 * les trois éléments suivants (pour chaque lieu à déterminer): - Un champ texte
 * de saisie - Un div vide qui permet le positionnement des propositions - Un
 * champ stockant les données 'data' sélectionnées par l'internaute après
 * sélection d'une proposition
 *
 * Rappel sur les URL de MEV: Les deux URL suivantes sont valides, et
 * correpondent à une recherche Paris/Oslo
 *
 * Voici l'url d'une recherche ne tenant compte que de la saisie texte de
 * l'internaute (il n'a pas sélectionné d'élément dans la liste des propositions
 * d'autocompletion).
 * http://www.easyvols.fr/mev/results.jsp?departAller=paris&departAllerData=&arriveeAller=oslo&arriveeAllerData=&...
 *
 * Voici maintenant l'url d'une recherche où l'internaute a sélectionné Paris et
 * Oslo dans les listes de propositions d'autocompletion
 * http://www.easyvols.fr/mev/results.jsp?departAllerData=v%3A5580&departAller=Paris&arriveeAllerData=v%3A5489&arriveeAller=Oslo&...
 *
 * On remarque que dans le premier cas les paramètres departAller et
 * arriveeAller contiennent les textes qui vont servir à la recherche, ils
 * correspondent au champ texte de saisie évoqué plus haut, les paramètres
 * departAllerData et arriveeAllerData sont vides. Cela n'a pas d'importance,
 * les paramètres departAller et arriveeAller vont être lus pour déterminer le
 * parcours.
 *
 * Dans le deuxieme cas on constate que les même champs departAller et
 * arriveeAller contiennent des textes qui ont été modifiés lors de la sélection
 * des propositions associées (ce ne sont plus les textes tapés par
 * l'internaute) en revanche les champs dataDepartAller et dataArriveeAller
 * contiennent les données 'data' correctes. Ces champs correspondent en fait au
 * champ data cité plus haut qui est usuellement un champ hidden. Dans ce cas,
 * les textes de departAller et arriveeAller sont inexploitables, mais en fait
 * c'est les champs data qui ayant la priorité vont permettre de déterminer
 * correctement le parcours. Le contenu de data sera généralement v:VILLE (VILLE
 * étant un identifiant unique numérique de ville), a:AEROPORT (AEROPORT étant
 * un identifiant unique numérique d'aéroport) ou l:ADRESSE (ADRESSE étant un
 * identifiant unique numérique d'adresse) Le contenu de data pourra aussi être
 * un code IATA. (c:IATA ou IATA ; pour assurer la compatibilité avec l'ancien
 * système de paramètrage des recherches)
 *
 * Par exemple pour implémenter la completion sur le lieu de départ aller, on se
 * retrouvera typiquement avec les éléments HTML suivants:
 *
 * <input type="text" id="departAllerMEV" name="departAller"><!-- champ texte
 * de saisie --> <div id="propositionsDepartAller"><!-- Les propositions seront
 * affichées ici --><div> <input type="hidden" id="departAllerMEVData"
 * name="departAllerData"><!-- champ hiden data -->
 *
 * La dernière chose à faire pour rendre la completion disponible, est
 * d'instancier l'objet CompletionMEV en lui passant les références aux éléments
 * précédents. Ceci peut être fait par l'attribut onload du tag body new
 * CompletionMEV(document.getElementById("departAllerMEV"),document.getElementById("departAllerMEVData"),document.getElementById("propositionsDepartAller"));
 * on peut optionnellement, dans la construction précédente, rajouter un dernier
 * paramètre qui détermine le nombre de villes qui vont apparaitre dans les
 * listes de propositions. Pour déterminer le comportement css, se référer à la
 * structure HTML définie en commentaire de la classe CompletionMEV plus bas.
 * Encapsulation de variables locales (privées).
 */
(function() {
	//'use strict';
	// Raccourci vers window
	var WIN = this,
			// Raccourci vers document
			DOC = WIN.document,
			// synonymes de true et false
			FALSE = !WIN, TRUE = !FALSE,
			// Raccourci vers window.ev
			EASY = WIN.ev,
			// Raccourci vers window.ev.log
			LOG = EASY && EASY.log,
			// Raccourci vers window.ev.dom
			DOM = EASY && EASY.dom,
			addClass = DOM && DOM.addClass,
			removeClass = DOM && DOM.removeClass,
			// Raccourci vers fonctions utiles
			setTimeout = WIN.setTimeout,
			addJavascriptToAttribute = WIN.addJavascriptToAttribute,
			genericNavigator = WIN.genericNavigator,
			MSIE = WIN.MSIE,

			// Constante globale qui identifie l'URL racine des fichiers rjs.
			// Il faut vérifier que la source ici définie, correspond effectivement
			// à une localisation valable de fichiers rjs.
			completionMEVURLRoot,
			// Chemin du CDN
			dn = EASY.path.cdn,
			// Variables de trad de "tous aéroports"
			TOUS_AEROPORTS,
			// Constante globale l'instance RemoteScript qui sera utilisée par toutes les instances de CompletionMEV
			completionMEVRJS,
			// tableau permettant de stocker plusieurs complétions MEV
			poolArray = [], completionPool;

	if (!EASY || !DOM) { throw 'Needs ev.core and ev.dom modules!'; }

	// Dimensions des lignes pour les MSIE<=6.9
	WIN.MSIE6_LINE_HEIGHT = 20;
	WIN.MSIE6_LINE_WIDTH = 300;

	switch (EASY.lang.current) {
		case 'es_ES':
			completionMEVURLRoot = dn + '/rjs/completion/MEV/es_ES';
			TOUS_AEROPORTS = 'todos aeropuertos';
			break;
		case 'it_IT':
			completionMEVURLRoot = dn + '/rjs/completion/MEV/it_IT';
			TOUS_AEROPORTS = 'tutti aeroporti';
			break;
		case 'en_GB':
			completionMEVURLRoot = dn + '/rjs/completion/MEV/en_GB';
			TOUS_AEROPORTS = 'all airports';
			break;
		case 'de_DE':
			completionMEVURLRoot = dn + '/rjs/completion/MEV/de_DE';
			TOUS_AEROPORTS = 'alle Flughäfen';
			break;
		//case 'fr_FR': // par défaut
		default:
			completionMEVURLRoot = dn + '/rjs/completion/MEV/fr_FR';
			TOUS_AEROPORTS = 'tous aéroports';
			break;
	}

	completionMEVRJS = new WIN.RemoteScript(completionMEVURLRoot + '/load_default.rjs');

	/**
	 * Extrait le type de lieu d'un champ data.
	 *
	 * @param {!string} data champ à lire pour retrouver le type de lieu.
	 */
	function extractTypeFromData(data) {
		return data.split('|')[0].split(':')[0] || '';
	}

	/**
	 * Récupère la ville dans un tableau donné,
	 * en fonction de la ligne sélectionnée.
	 *
	 * @param {!Object} _selectedLine ligne sélectionnée.
	 * @param {!Array.<Object>} _towns tableau des villes.
	 * @return {?Object} la ville (ou null si non trouvée).
	 */
	function getTown(_selectedLine,_towns) {
		var nom = _selectedLine.nom, i, j, ville, vcnt, acnt;
		vcnt = _towns.length;
		for (i = 0; i < vcnt; i++) {
			ville = _towns[i];
			acnt = ville.aeroports.length;
			for (j = 0; j < acnt; j++) {
				if (ville.aeroports[j].nom === nom) {
					return ville;
				}
			}
		}
		return null;
	}

	/**
	 * Fixe les attributs du champ texte donné en fonction
	 * de la ligne sélectionnée.
	 *
	 * @param {!Element} _elementText élément texte visible sur le formulaire.
	 * @param {!Array.<Object>} _towns tableau des villes.
	 * @param {!Object} _line ligne sélectionnée.
	 */
	function setLineData(_elementText, _towns, _line) {
		if (extractTypeFromData(_line.data) === 'a') {
			var town = getTown(_line, _towns);
			if (town) {
				_elementText.setAttribute('vdata', town.data);
				_elementText.setAttribute('adata', _line.data);
			}
		}
		else {
			_elementText.setAttribute('vdata', _line.data);
			_elementText.removeAttribute('adata');
		}
	}

	/**
	 * Objet définissant une proposition ville. Cet objet contient essentiellement le nom de la ville et le pays pour affichage et matching, les synonymes
	 * qui lui sont éventuellement associés (keywords) et le iata qui lui est associé. A l'instanciation, l'objet construit le div qui permettra son
	 * affichage.
	 * @param {!string} nom nom de la ville.
	 * @param {!string} pays nom du pays.
	 * @param {!string} code iata de la ville.
	 * @param {(string|number)=} id identifiant unique numérique de la ville (peut être 'undefined' car l'ancienne completion doit rester compatible pendant la migration).
	 *
	 * Une propriété 'data' est créée à partir des infos disponibles :
	 *  - dans le meilleur des cas l'identifiant, le code AITA et le nom de la ville sont insérés dedans (ex.: "v:5580|c:PAR|t:Paris")
	 *  - si une ou plusieurs donnée(s) manque(nt), on utilise dans l'ordre (le premier valide) :
	 *    - l'identifiant de la ville
	 *    - le code AITA de la ville
	 *    - le texte (nom de la ville)
	 * REMARQUE : on part du principe de le 'nom' de la ville existe toujours
	 *
	 * La propriété keywords est directement affectée s'il y a lieu. Pour le cas où aucun keyword ne serait affecté, par defaut keywords est un  tableau
	 * vide.
	 * La propriété div est caluclée à la construction.
	 *
	 * @constructor
	 */
	WIN.PropositionMEVVille = function(nom, pays, code, id) {
		var thisPropVille = this;
		thisPropVille.nom = nom;
		thisPropVille.pays = pays;
		thisPropVille.code = code;
		thisPropVille.id = id;
		if (id && code && code !== 'null') {
			thisPropVille.data = 'v:' + id + '|c:' + code + '|t:' + nom;
		}
		else if (id) {
			thisPropVille.data = 'v:' + id;
		}
		else if (code && code !== 'null') {
			thisPropVille.data = 'c:' + code;
		}
		else {
			thisPropVille.data = nom;
		}

		// Calcul du div
		thisPropVille.div = DOC.createElement('DIV');
		thisPropVille.div.className = 'propositionVille';
		thisPropVille.div.appendChild(DOC.createTextNode(nom + ', ' + pays));
		thisPropVille.div.style.display = 'none';

		thisPropVille.index = -1;

		// Tableaux d'aéroports et mots-clés
		thisPropVille.aeroports = [];
		thisPropVille.keywords = [];
	};

	WIN.PropositionMEVVille.prototype = {
		/**
		 * Méthode permettant d'ajouter un mot-clé
		 * dans la liste de cette ville.
		 * @param {Object} kw le mot-clé à ajouter.
		 */
		add: function(kw) {
			this.keywords.push(kw);
		},

		/**
		 * Méthode permettant d'ajouter un aéroport
		 * dans la liste de cette ville.
		 * @param {!Object} a l'aéroport à ajouter.
		 */
		addAeroport: function(a) {
			this.aeroports.push(a);
		},

		/**
		 * Méthode déterminant si la proposition ville correspond à l'expression régulière passée en paramètre, i.e. l'une de ses données (nom, pays, code,
		 * ou keyword) débute par le texte value.
		 * @param {!RegExp} _re expression à comparer aux nom, pays, code et mots-clés.
		 * @return {boolean} true si une expression est reconnue, sinon false.
		 **/
		matches: function(_re) {
			if (WIN.textMatch(this.nom, _re)) {return TRUE;}
			if (WIN.textMatch(this.pays, _re)) {return TRUE;}
			if (WIN.textMatch(this.code, _re)) {return TRUE;}
			var i = this.keywords.length;
			while (i) {
				--i;
				if (WIN.textMatch(this.keywords[i], _re)) {return TRUE;}
			}
			return FALSE;
		},

		/**
		 * Méthode déterminant si la proposition ville correspond à l'expression régulière passée en paramètre, i.e. l'une de ses données (nom, pays, code,
		 * ou keyword) débute par le texte value.
		 * @param {!RegExp} _re expression à comparer aux nom, pays, code et mots-clés.
		 * @return {?string} l'expression reconnue (ou null si non reconnue).
		 **/
		retournMatch: function(_re) {
			var i = this.keywords.length;
			while (i) {
				--i;
				if (WIN.textMatch(this.keywords[i], _re)) {
					return this.keywords[i];
				}
			}
			return null;
		}
	};

	/**
	 * Objet définissant une proposition aéroport. Cet objet contient essentiellement le nom de l'aéroport pour affichage et matching, les synonymes
	 * qui lui sont éventuellement associés (keywords) et le iata qui lui est associé. A l'instanciation, l'objet construit le div qui permettra son
	 * affichage.
	 * @param {!string} nom nom de l'aéroport.
	 * @param {!string} code iata de l'aéroport.
	 * @param {(string|number)=} id identifiant unique numérique de l'aéroport (peut être 'undefined' car l'ancienne completion doit rester compatible pendant la migration).
	 *
	 * Une propriété 'data' est créée à partir des infos disponibles :
	 *  - dans le meilleur des cas l'identifiant, le code AITA et le nom de l'aéroport sont insérés dedans (ex.: "a:5829|c:CDG|t:Ch. De Gaulle")
	 *  - si une ou plusieurs donnée(s) manque(nt), on utilise dans l'ordre (le premier valide) :
	 *    - l'identifiant de l'aéroport
	 *    - le code AITA de l'aéroport
	 *    - le texte (nom de l'aéroport)
	 * REMARQUE : on part du principe de le 'nom' de l'aéroport existe toujours
	 *
	 * La propriété keywords est directement affectée s'il y a lieu. Pour le cas où aucun keyword ne serait affecté, par defaut keywords est un  tableau
	 * vide.
	 * La propriété div est caluclée à la construction.
	 *
	 * @constructor
	 */
	WIN.PropositionMEVAeroport = function(nom, code, id) {
		var thisPropAero = this;
		thisPropAero.nom = nom;
		thisPropAero.code = code;
		thisPropAero.id = id;
		if (id && code && code !== 'null') {
			thisPropAero.data = 'a:' + id + '|c:' + code + '|t:' + nom;
		}
		else {
			if (id) {
				thisPropAero.data = 'a:' + id;
			}
			else if (code && code !== 'null') {
				thisPropAero.data = 'c:' + code;
			}
			else {
				thisPropAero.data = nom;
			}
		}

		// Calcul du div
		thisPropAero.div = DOC.createElement('DIV');
		thisPropAero.div.className = 'propositionAeroport';
		thisPropAero.div.appendChild(DOC.createTextNode(nom));

		// Tableau de mots-clés
		thisPropAero.keywords = [];
	};

	WIN.PropositionMEVAeroport.prototype = {
		/**
		 * Méthode permettant d'ajouter un mot-clé
		 * dans la liste de cet aéroport.
		 * @param {!string} kw le mot-clé à ajouter.
		 */
		add: function(kw) {
			this.keywords.push(kw);
		},
		/**
		 * Méthode déterminant si la proposition aéroport correspond à l'expression régulière passée en paramètre, i.e. l'une de ses données (nom, code,
		 * ou keyword) débute par le texte value.
		 * @param {!RegExp} _re expression à comparer aux nom, code et mots-clés.
		 * @return {boolean} true si une expression est reconnue, sinon false.
		 */
		matches: function(_re) {
			if (WIN.textMatch(this.nom, _re)) {return TRUE;}
			if (WIN.textMatch(this.code, _re)) {return TRUE;}
			var i;
			for (i = 0; i < this.keywords.length; i++) {
				if (WIN.textMatch(this.keywords[i], _re)) {return TRUE;}
			}
			return FALSE;
		}
	};

	/**
	 * Objet qui référence toutes les intances de
	 * CompletionMEV présentes sur la page.
	 **/
	completionPool = (WIN.completionMEVPool = {
		/**
		 * Ajoute une complétion au pool.
		 *
		 * @param {!Object} comp complétion MEV à ajouter.
		 */
		add: function(comp) {
			poolArray.push(comp);
			return poolArray.length - 1;
		},
		/**
		 * Récupère une complétion dans le pool.
		 *
		 * @param {!number} index indice de la complétion MEV à récupérer.
		 */
		get: function(index) {
			return poolArray[index];
		}
	});

	/**
	 * Nombre maximal par défaut de lignes dans la complétion.
	 */
	var DEFAULT_MAX_TOWNS_IN_LIST = 6;
	/**
	 * Objet qui gère l'autocompletion MEV, en utilisant le texte saisi dans le champ texte qui lui est associé, affiche les propositions dans le div
	 * de positionnement qui lui est affecté, et en fonction de la sélection effectuée par l'internaute modifie le champ data qui lui est associé.
	 * @param {!Element} elementText Champ texte de saisie.
	 * @param {!Element} elementData Champ data.
	 * @param {!Element} positionDiv Div de positionnement des propositions.
	 * @param {number=} maxTownsInList (optionnel) limite le nombre de villes affichées dans une liste de propositions.
	 * @param {boolean=} townRequest (optionnel) true ajoute vdata contenant le champs data de la selection si ville, et vdata et adatacontenant le champs data de la selection si un aeroport.
	 * La structure HTML générée pour afficher les propositions est la suivante:
	 * elle s'inscrit complètement dans le div de positionnement (je reprends la dénomination proposée en commentaire liminaire où le positionnement
	 * des propositions est assuré par le div propositionsDepartAller.
	 *   <!-- ce div sert donc à positionner l'affichage à un endroit précis de la page il est passé à l'instance par le paramètre positionDiv, il n'est
	 *        pas référencé en tant que propriété
	 *   -->
	 *   <div id="propositionsDepartAller">
	 *     <!-- ce div est créé à l'instanciation de CompletionMEV, il est référencé par la variable propositionsDiv. Les méthodes
	 *          theComp.showPropositions() et theComp.hidePropositions() utilisent son style visibility pour faire apparaitre et disparaitre les propositions.
	 *          Il est affecté de la classe CSS completion qui de préférence va définir un positionnement absolute (afin de donner à la liste de
	 *          propositions l'apparence d'une fenêtre flotante, et d'éviter qu'elle ne pousse vers le bas les éléments qui se trouvent sous elle,
	 *          mais il n'est pas interdit de faire autre chose... Je recommande aussi de définir un fond pour que la liste ne soit pas transparente!
	 *     -->
	 *     <div class="completionMEV">
	 *       <!-- Chaque ville dispose alors d'un div propre, que cette ville dispose ou non d'aéroports ces div sont affectés de la classe
	 *            propositionVille-->
	 *       <div class="propositionVille">Ciudad del Este, Paraguay</div>
	 *       <div class="propositionVille">Asuncion, Paraguay</div>
	 *       <!-- Les villes disposant en plus d'aéroports voient leur div contenir des div supplémentaires -->
	 *       <div class="propositionVille">
	 *         Paris, France
	 *         <div class="propositionAeroport">Beauvais-Tille</div>
	 *         <div class="propositionAeroport">Ch. De Gaulle</div>
	 *         <div class="propositionAeroport">Orly</div>
	 *       </div>
	 *       <div class="propositionVille">Paros, Grèce</div>
	 *       <!-- l'élément sélectionné se voit affecté la classe selected qui peut être déclarée dans la feuille de style sous la dénomination
	 *            .completionMEV .selected pour ne pas faire mélange avec d'autres selected. En revanche il peut être nécessaire de surcharger une
	 *            classe .selected plus générale qui aurait été définie par ailleurs.
	 *       -->
	 *       <div class="propositionVille selected">
	 *         Nassau, Bahamas
	 *         <div class="propositionAeroport">Paradise Island</div>
	 *       </div>
	 *     </div>
	 *    <!-- Pour IE 6 <iframe frameborder="0"></iframe> -->.
	 *   </div>
	 *
	 * @constructor
	 */
	WIN.CompletionMEV = function(elementText, elementData, positionDiv, maxTownsInList, townRequest) {
		var theComp = this,
				// On référence l'instance dans le pool
				poolIndex = (theComp.poolIndex = completionPool.add(theComp)),
				// Div qui contiendra réellement les propositions
				propositionsDiv,
				// Cette variable contient la ville sélectionnée par les flêches (mémorise la dernière ville sélectionnée, mais pas les aéroports).
				townSelected = 0;
		// On définit l'état de base du panneau de propositions à "off"
		theComp.status = 'off';

		// On applique l'attribut autocomplete="off" au champ input pour bloquer l'autocompletion du navigateur
		elementText.setAttribute('autocomplete', 'off');
		// On référence l'élément text et on wrappe les événements onfocus, onblur, onkeydown et onkeyup sur les méthodes appropriées
		theComp.elementText = elementText;
		// On référence l'élément data
		theComp.elementData = elementData;
		// IE ne supporte pas le mot-clé 'return' hors des méthodes
		if (genericNavigator.navigator.id === MSIE) {
			addJavascriptToAttribute(elementText, 'onkeydown', 'completionMEVPool.get(' + poolIndex + ').toucheDown(event);');
			addJavascriptToAttribute(elementText, 'onkeyup', 'completionMEVPool.get(' + poolIndex + ').toucheUp(event);');
			addJavascriptToAttribute(elementText, 'onfocus', 'completionMEVPool.get(' + poolIndex + ').updatePropositions(event);');
			// Sur la ligne suivante, il est indispensable de délayer le masquage pour faire en sorte que le onblur ne provoque pas un hidePropositions
			// trop rapide, dans le cas où justement le onblur serait provoqué par un onclick dans l'une de ces propositions. Dans un tel cas, l'absence de
			// delai d'attente ne permet pas le clic avant la disparition des propositions
			addJavascriptToAttribute(elementText, 'ondeactivate', 'completionMEVPool.get(' + poolIndex + ').delayedHidePropositions(500);');
		}
		else {
			addJavascriptToAttribute(elementText, 'onkeydown', 'return completionMEVPool.get(' + poolIndex + ').toucheDown(event);');
			addJavascriptToAttribute(elementText, 'onkeyup', 'return completionMEVPool.get(' + poolIndex + ').toucheUp(event);');
			addJavascriptToAttribute(elementText, 'onfocus', 'return completionMEVPool.get(' + poolIndex + ').updatePropositions(event);');
			// Sur la ligne suivante, il est indispensable de délayer le masquage pour faire en sorte que le onblur ne provoque pas un hidePropositions
			// trop rapide, dans le cas où justement le onblur serait provoqué par un onclick dans l'une de ces propositions. Dans un tel cas, l'absence de
			// delai d'attente ne permet pas le clic avant la disparition des propositions
			addJavascriptToAttribute(elementText, 'onblur', 'completionMEVPool.get(' + poolIndex + ').delayedHidePropositions(500);');
		}
		// On initialise le div de positionnement des propositions
		propositionsDiv = DOC.createElement('DIV');
		propositionsDiv.style.visibility = 'hidden';
		propositionsDiv.className = 'completionMEV';
		if (genericNavigator.navigator.id === MSIE && genericNavigator.navigator.version <= 6.9) {
			propositionsDiv.style.width = WIN.MSIE6_LINE_WIDTH + 'px';
			propositionsDiv.style.padding = '5px';
		}
		positionDiv.appendChild(propositionsDiv);

		// Nombre maximum de villes affichées dans la liste de propositions
		maxTownsInList = maxTownsInList || DEFAULT_MAX_TOWNS_IN_LIST;

		// Cette propriété est mise à jour par les rjs et contient l'ensemble des propositions associée au préfixe de la saisie utilisateur
		theComp.propositions = [];

		// Cette propriété contient la ligne sélectionnée par l'intermédiaire des flêches du clavier. Par défaut aucune ligne n'ayant été sélectionnée
		// cette propriété contient -1. Elle permet un lien immédiat avec les objets référencés dans theComp.lines
		theComp.lineSelected = -1;
		// Lorsque la liste de propositions est construite, chaque élémeconvertVilleTextToDivnt affiché est référencé dans l'ordre dans ce tableau, ce qui permet de conserver
		// un ordre d'affichage des données. Il faut se rappeler que les données sont organisées hiérarchiquement entre villes et aéroports, ce tableau
		// linéarise tout cela
		theComp.lines = [];

		// Cette propriété contient le tableau des villes, uniquement des villes.
		theComp.towns = [];
		// Cette propriété mainitent la page affichée
		theComp.pageIndex = 0;
		// Cette propriété sert de jeton, elle permet de savoir si une requête de rjs est en cours avant d'en lancer une autre
		theComp.updating = FALSE;

		/**
		 * Cette méthode permet lancer la récupération de la liste de propositions.
		 * La valeur de retour permet de préciser si l'on doit laisser remonter l'évenement au niveau du navigateur (toujours oui : true)
		 **/
		theComp.toucheUp = function(event) {
			if (!event) {return FALSE;}
			if (WIN.shouldUpdatePropositions(event)) {
				theComp.updatePropositions(event);
			}
			return TRUE;
		};

		/**
		 * Cette méthode utilise une méthode générique qui traite les déplacements, validations et annulation de proposition(s) à partir du clavier.
		 * Elle gère également les caractères entrés dans le champ texte.
		 * Elle utilise des méthodes et propriétés de l'objet CompletionMEV courant (en cas de besoin).
		 * (Ces méthodes et/ou propriétés sont les mêmes pour les CompletionMEH et CompletionMEC)
		 * La valeur de retour permet de préciser si l'on doit laisser remonter l'évenement au niveau du navigateur (oui : true ; non : false)
		 * Pour IE le système est différent et on doit modifier l'évènement lorsque celui-ci doit être bloqué (car il ne gère pas et n'accepte pas la valeur de retour)
		 * (cf. completionToucheDown(completion, event)
		 **/
		theComp.toucheDown = function(event) {
			if (!event) {return FALSE;}
			return WIN.completionToucheDown(theComp, event);
		};

		var lock = FALSE;

		/**
		 * Crée un callback à exécuter après un download de complétion.
		 *
		 * @param {!string} prefix préfixe de ce qu'a tapé l'internaute.
		 * @return {Function} le callback créé.
		 */
		function createOnAfterUpdateHandler(prefix) {
			return function() {
				LOG.debug('completionMEV: /load_' + prefix + '.rjs reçu : ' + theComp.propositions.length + ' proposition(s)');
				// On signale la fin de mise à jour
				theComp.updating = FALSE;
			};
		}

		/**
		 * Cette méthode a été wrappée à l'instanciation sur le onfocus de l'élément texte ou appelée à partir du onkeydown. C'est elle qui va "décider" des affichages de
		 * propositions qui devront être faits (ou non).
		 * Elle retourne toujours true (lorsque l'appel vient de 'onfocus' l'évenement remonte au navigateur).
		 **/
		theComp.updatePropositions = function(event) {
			if (lock) {return FALSE;}
			if (!event) {return FALSE;}
			// Si une requête de mise à jour est déjà en cours on sort
			if (theComp.updating) {return FALSE;}
			// Sinon, on signale qu'une mise à jour se prépare
			theComp.updating = TRUE;
			// Toute saisie de caractère invalide la précédente sélection de data
			if (event.type !== 'focus') {
				elementData.value = '';
				/* Mode iterogation par ville les attributs suivants doivent aussi etre netoyés.*/
				if (townRequest) {
					if (elementText.getAttribute('vdata')) {
						elementText.removeAttribute('vdata');
					}
					if (elementText.getAttribute('adata')) {
						elementText.removeAttribute('adata');
					}
				}
			}
			var elementTextValue = elementText.value;
			if (elementTextValue.length >= 3) {
				var prefix = WIN.getPrefix(elementTextValue);
				theComp.propositions = [];
				// invocation distante
				completionMEVRJS.invoke(completionMEVURLRoot + '/load_' + prefix + '.rjs', 'initMEVPropositions(' + poolIndex + ')', createOnAfterUpdateHandler(prefix));
				// L'invocation précédente provoque aussi l'appel à showPropositions, mais de façon asynchrone, si par malheur, elle tardait
				// à venir celle ci-dessous nous assure d'une execution (rapide).
				// Supprimé jusqu'à preuve du contraire
				// theComp.showPropositions();
			}
			else {
				// Par défaut, on cache les propositions si moins de 3 caractères
				theComp.hidePropositions();
				//      LOG.debug("Par défaut, on cache les propositions si moins de 3 caractères");
				// On signale la fin de mise à jour
				theComp.updating = FALSE;
			}
			return TRUE;
		};

		/**
		 * Cette méthode affiche les propositions en tenant compte de leur effective correspondance avec le texte saisi dans l'élément texte. Elle
		 * lancée en cas de modification de la liste de propositions.
		 **/
		theComp.showPropositions = function() {
			// Si le div de positionnement des propositions n'est pas présent, inutile d'aller plus loin
			if (!propositionsDiv) {return;}

			if (elementText.$$evAutoFilled) {
				// Si l'élément texte a été pré-rempli automatiquement...
				// ... on supprime le flag d'information à ce sujet
				elementText.$$evAutoFilled = FALSE;
				// ... mais on n'affiche pas les propositions de suite
				return;
			}

			// On vide le div de positionnement de tous les éléments précédements affichés
			var node = propositionsDiv.lastChild;
			while (node) {
				propositionsDiv.removeChild(node);
				node = propositionsDiv.lastChild;
			}

			// On calcule l'expression régulière qui correspond au texte tapé par l'utilisateur
			var regExp = new RegExp('^' + WIN.formateText(elementText.value).replace(/\s+/, '\\s+') + '.*'),

					// Cette variable va tenir le décompte des lignes des éléments affichés (villes + aéroports)
					line = 0,
					// Cette variable va faire le même travail dans le tableau des villes
					townIndex = 0,
					// variables de travail
					i, j, height, reg, chaine, chaineSansAccent;
			// On réinitialise les tableaux
			theComp.lines = [];
			theComp.towns = [];
			// On passe en revue toutes les propositions villes
			for (i = 0; i < theComp.propositions.length; ++i) {
				// Si la proposition ville matche avec le champ texte
				if (theComp.propositions[i].matches(regExp)) {
					// On place le div correspondant dans la liste de propositions
					// Ajout d'événements de sélection (onmouseover) et validation (onmousedown) de la ville
					addJavascriptToAttribute(theComp.propositions[i].div, 'onmouseover', 'completionMEVPool.get(' + poolIndex + ').selectLine(' + line + ')');
					addJavascriptToAttribute(theComp.propositions[i].div, 'onmousedown', 'completionMEVPool.get(' + poolIndex + ').validLine(' + line + ',true)');
					theComp.propositions[i].index = townIndex;
					// On insère la ville dans le tableau des lignes
					theComp.lines[line] = theComp.propositions[i];
					// On insère ma ville dans le tableau des villes
					theComp.towns[townIndex] = theComp.propositions[i];
					line++;
					height = WIN.MSIE6_LINE_HEIGHT;
					townIndex++;

					// Initialisation des variables utilisé pour l'ajout du synonymes, du IATA et la mise en gras dasn les proposition du texte saisie.
					reg = new RegExp('(' + WIN.formateText(elementText.value).replace(/\s+/, '\\s+') + ')', 'gi');
					chaine = theComp.propositions[i].div.childNodes[0].nodeValue;
					chaineSansAccent = WIN.replaceAccents(chaine);
					// Affichage du synonyme ?
					chaine = WIN.ajoutSynonyme(theComp.propositions[i], chaine, chaineSansAccent, regExp, reg);
					// Affichage du code ISO du pays si le match dessus est bon.
					if (theComp.propositions[i].code.match(reg)) {
						chaine = theComp.propositions[i].nom + ' [' + theComp.propositions[i].code + '], ' + theComp.propositions[i].pays;
					}
					// Creation d'une chaine sans accent pour la mise en évidance.
					chaineSansAccent = WIN.replaceAccents(chaine);
					// S'il y a au mois 1 aéroport dans la ville, on met à jour le texte en ajoutant " (tous aéroports)"
					if (theComp.propositions[i].aeroports.length > 0) {
						chaine = theComp.ajoutTousAeroports(chaine);
					}
					// Modification du DOM pour mettre en évidance la correspondance entre le texte fourni et celui proposé.
					theComp.propositions[i].div.childNodes[0].nodeValue = '';
					WIN.modifDomWhithSearch(chaine, chaineSansAccent, elementText.value, theComp.propositions[i].div, reg);
					propositionsDiv.appendChild(theComp.propositions[i].div);

					if (theComp.propositions[i].aeroports.length > 0) {
						// S'il y a au mois 1 aéroport dans la ville, on met à jour le texte en ajoutant " (tous aéroports)"
						// On y ajoute toutes les propositions aéroports éventuellement contenues dans la ville
						for (j = 0; j < theComp.propositions[i].aeroports.length; j++) {
							theComp.propositions[i].div.appendChild(theComp.propositions[i].aeroports[j].div);
							// Ajout d'événements de sélection (onmouseover) et validation (onmousedown) de l'aéroport
							addJavascriptToAttribute(theComp.propositions[i].aeroports[j].div, 'onmouseover', 'completionMEVPool.get(' + poolIndex + ').selectLine(' + line + ')');
							addJavascriptToAttribute(theComp.propositions[i].aeroports[j].div, 'onmousedown', 'completionMEVPool.get(' + poolIndex + ').validLine(' + line + ',true)');
							theComp.lines[line] = theComp.propositions[i].aeroports[j];
							line++;
							height += WIN.MSIE6_LINE_HEIGHT;
						}
					}
					if (genericNavigator.navigator.id === MSIE && genericNavigator.navigator.version <= 6.9) {
						theComp.propositions[i].div.style.height = height + 'px';
						theComp.propositions[i].div.style.width = WIN.MSIE6_LINE_WIDTH + 'px';
					}
					
				}else {
					// Si le matching ne se fait pas sur la ville
					// On considère d'emblée que la proposition de ville ne sera pas visible
					var propositionVilleVisible = FALSE;
					// On teste tous les aéroports
					for (j = 0; j < theComp.propositions[i].aeroports.length; j++) {
						// Si l'aéroport en cours matche le texte entré par l'utilisateur son div est ajouté au div de la ville, et cette dernière
						// est prévue pour affichage par propositionVilleVisible=TRUE
						if (theComp.propositions[i].aeroports[j].matches(regExp)) {
							// Initialisation des variables utilisé pour l'ajout du synonymes, du IATA et la mise en gras dasn les proposition du texte saisie.
							reg = new RegExp('(' + WIN.formateText(elementText.value).replace(/\s+/, '\\s+') + ')', 'gi');
							chaine = theComp.propositions[i].aeroports[j].div.childNodes[0].nodeValue;
							chaineSansAccent = WIN.replaceAccents(chaine);
							// Affichage du code IATA de l'aéroport si le match dessus est bon.
							if (theComp.propositions[i].aeroports[j].code.match(reg)) {
								chaine = theComp.propositions[i].aeroports[j].nom + ' [' + theComp.propositions[i].aeroports[j].code + ']';
							}
							// Modification du DOM pour mettre en évidance la correspondance entre le texte fourni et celui proposé.
							chaineSansAccent = WIN.replaceAccents(chaine);
							theComp.propositions[i].aeroports[j].div.childNodes[0].nodeValue = '';
							WIN.modifDomWhithSearch(chaine, chaineSansAccent, elementText.value, theComp.propositions[i].aeroports[j].div, reg);
							theComp.propositions[i].div.appendChild(theComp.propositions[i].aeroports[j].div);
	
							// Si la ville n'a pas déjà été prévue pour insertion
							height = 0;
							if (!propositionVilleVisible) {
								// On met à jour le texte de la proposition de ville, en ajoutant " (tous aéroports)"
								theComp.convertVilleTextToDiv(theComp.propositions[i]);
								// Ajout d'événements de sélection (onmouseover) et validation (onmousedown) de la ville
								addJavascriptToAttribute(theComp.propositions[i].div, 'onmouseover', 'completionMEVPool.get(' + poolIndex + ').selectLine(' + line + ')');
								addJavascriptToAttribute(theComp.propositions[i].div, 'onmousedown', 'completionMEVPool.get(' + poolIndex + ').validLine(' + line + ',true)');
								theComp.propositions[i].index = townIndex;
								// On insère la ville dans le tableau des lignes
								theComp.lines[line] = theComp.propositions[i];
								// On insère ma ville dans le tableau des villes
								theComp.towns[townIndex] = theComp.propositions[i];
								line++;
								height += WIN.MSIE6_LINE_HEIGHT;
								townIndex++;
								propositionVilleVisible = TRUE;
							}
							// Ajout d'un événement de validation (onmousedown) de l'aéroport
							addJavascriptToAttribute(theComp.propositions[i].aeroports[j].div, 'onmousedown', 'completionMEVPool.get(' + poolIndex + ').validLine(' + line + ',true)');
							theComp.lines[line] = theComp.propositions[i].aeroports[j];
							line++;
							height += WIN.MSIE6_LINE_HEIGHT;
						}
					}
					// Si l'affichage de la proposition ville est prévu par la présence d'un ou plusieurs de ses aéroports
					if (propositionVilleVisible) {
						propositionsDiv.appendChild(theComp.propositions[i].div);
						if (genericNavigator.navigator.id === MSIE && genericNavigator.navigator.version <= 6.9) {
							theComp.propositions[i].div.style.height = height + 'px';
							theComp.propositions[i].div.style.width = WIN.MSIE6_LINE_WIDTH + 'px';
						}
					}
				}
			}
			// Par défaut on n'a pas de ligne sélectionnée (-1), tout action sur les flêches du clavier va faire apparitre la selection grâce aux tests
			// su theComp.lineSelected dans les méthodes decreaseSelectedPosition() ou increaseSelectedPosition()
			theComp.hidePage();
			theComp.lineSelected = -1;
			townSelected = 0;
			theComp.pageIndex = 0;
			theComp.showPage();

			if (theComp.propositions.length > 0 && propositionsDiv && propositionsDiv.lastChild) {
				theComp.status = 'on';
				WIN.showPropositionsElement(propositionsDiv, line);
			}
			else {
				theComp.hidePropositions();
			}
		};

		/**
		 * Cette méthode met à jour le texte de la proposition de ville, en ajoutant " (tous aéroports)" et en transformant le noeud Text en noeud Span.
		 * Elle n'a aucun effet si le premier noeud n'est pas de type TEXT.
		 */
		theComp.convertVilleTextToDiv = function(villeProp) {
			var textNodeVille = villeProp.div.firstChild;
			if (textNodeVille.nodeType === 3) {
				textNodeVille.nodeValue = theComp.ajoutTousAeroports(villeProp.nom + ', ' + villeProp.pays);
				var divNode = DOC.createElement('div');
				villeProp.div.replaceChild(divNode, textNodeVille);
				divNode.appendChild(textNodeVille);
			}
		};

		/**
		 * Cette méthode met à jour le texte en paramêtre , en ajoutant " (tous aéroports)".
		 */
		theComp.ajoutTousAeroports = function(chaine) {
			return chaine + ' (' + TOUS_AEROPORTS + ')';
		};

		/**
		 * Cette méthode cache la liste des propositions.
		 */
		theComp.hidePropositions = function() {
			//on force le focus sur le champ de la ville qui affichait la completion, NB : FF le fait par defaut, IE non
			theComp.status = 'off';
			WIN.hidePropositionsElement(propositionsDiv);
		};

		/**
		 * Cette méthode permet de délayer le masquage des propositions via un timeout sur la méthode #hidePropositions()
		 */
		theComp.delayedHidePropositions = function(delay) {
			setTimeout('completionMEVPool.get(' + poolIndex + ').hidePropositions();', delay);
		};

		/**
		 * Cette méthode modifie éventuellement la page de propositions affichées.
		 **/
		theComp.updatePage = function() {
			// On détermine l'éventuel nouveau pageIndex
			var newPageIndex = Math.floor(townSelected / maxTownsInList);
			// S'il a changé on cache la page courante et on affiche la nouvelle
			if (theComp.pageIndex !== newPageIndex) {
				theComp.hidePage();
				theComp.pageIndex = newPageIndex;
				theComp.showPage();
			}
			//    LOG.info("on est ds updatePage");
		};

		/**
		 * Montre les villes de la page courante.
		 **/
		theComp.showPage = function() {
			var i;
			for (i = theComp.pageIndex * maxTownsInList; i < (theComp.pageIndex + 1) * maxTownsInList && i < theComp.towns.length && i >= 0; i++) {
				theComp.towns[i].div.style.display = 'block';
			}
		};

		/**
		 * Cache les villes de la page courante.
		 **/
		theComp.hidePage = function() {
			var i;
			for ( i = theComp.pageIndex * maxTownsInList; i < (theComp.pageIndex + 1) * maxTownsInList && i < theComp.towns.length && i >= 0; i++) {
				theComp.towns[i].div.style.display = 'none';
			}
			//    LOG.info("on est ds hidePage");
		};

		/**
		 * Cette méthode est invoquée lorsque l'on tape sur la touche flêche vers le haut du clavier, elle déplace alors la sélection vers le haut. Cela
		 * est réalisé en déselectionnant la précédente sélection, puis en modifiant le numéro de la ligne sélectionnée, puis en sélectionnant la donnée
		 * correspondant à cette ligne.
		 **/
		theComp.decreaseSelectedProposition = function() {
			theComp.deselectProposition();
			theComp.lineSelected--;
			if (theComp.lineSelected < 0) {
				theComp.lineSelected = theComp.lines.length - 1;
			}
			theComp.selectProposition();
		};

		/**
		 * Cette méthode est invoquée lorsque l'on tape sur la touche flêche vers le bas du clavier, elle déplace alors la sélection vers le bas. Cela
		 * est réalisé en déselectionnant la précédente sélection, puis en modifiant le numéro de la ligne sélectionnée, puis en sélectionnant la donnée
		 * correspondant à cette ligne.
		 **/
		theComp.increaseSelectedProposition = function() {
			theComp.deselectProposition();
			theComp.lineSelected++;
			if (theComp.lineSelected >= theComp.lines.length) {
				if (theComp.lines.length > 0) {
					theComp.lineSelected = 0;
				}
				else {
					theComp.lineSelected = -1;
				}
			}
			theComp.selectProposition();
		};

		/**
		 * Sélectionne la ligne donnée 'line'
		 */
		theComp.selectLine = function(line) {
			theComp.deselectProposition();
			theComp.lineSelected = line;
			if (theComp.lineSelected >= theComp.lines.length) {
				if (theComp.lines.length > 0) {
					theComp.lineSelected = 0;
				}
				else {
					theComp.lineSelected = -1;
				}
			}
			else if (theComp.lineSelected < 0) {
				theComp.lineSelected = theComp.lines.length - 1;
			}
			theComp.selectProposition();
		};

		/**
		 * Sélectionne la donnée désignée par la propriété theComp.lineSelected
		 */
		theComp.selectProposition = function() {
			// Le test préliminaire est obligatoire au cas où aucune liste de propostions n'ait été chargée, et par voie de conséquence où theComp.lines soit
			// vide
			if (theComp.lines[theComp.lineSelected]) {
				addClass(theComp.lines[theComp.lineSelected].div, 'selected');
				if (theComp.lines[theComp.lineSelected] instanceof WIN.PropositionMEVVille) {
					townSelected = theComp.lines[theComp.lineSelected].index;
				}
			}
		};

		/**
		 * Désélectionne la donnée désignée par la propriété theComp.lineSelected
		 */
		theComp.deselectProposition = function() {
			// Le test préliminaire est obligatoire au cas où aucune liste de propostions n'ait été chargée, et par voie de conséquence où theComp.lines soit
			// vide
			if (theComp.lines[theComp.lineSelected]) {
				removeClass(theComp.lines[theComp.lineSelected].div, 'selected');
			}
		};

		var validatingLine = FALSE;

		/**
		 * Permet de redonner le focus au champ correspondant à l'objet
		 * completion donné.
		 *
		 * @param {!number} lineIndex l'indice de la ligne validée.
		 */
		function onClickFocusAgain(lineIndex) {
			function unlock() {
				lock = FALSE;
				validatingLine = FALSE;
				//      LOG.info('onClickFocusAgain()> unlocked!');
			}
			function doFocus() {
				//      LOG.info('onClickFocusAgain()> try to focus...');
				lock = TRUE;
				elementText.focus();
				// on fixe à nouveau les champs texte après le focus
				// pour que le curseur soit à la bonne place
				elementText.value = theComp.lines[lineIndex].nom;
				elementData.value = theComp.lines[lineIndex].data;
				setTimeout(unlock, 200);
				//      unlock();
			}
			setTimeout(doFocus, 200);
		}

		/**
		 * Cette méthode est invoquée par l'appui sur la touche ENTER si on a sélectionné une proposition, dans ce cas on valide au clavier la sélection en
		 * cours.
		 *
		 * @param {!number} lineIndex l'indice de la ligne validée.
		 * @param {boolean=} isClick [optionel] 'true' si on veut préciser qu'il s'agit d'un clic
		 *     (le curseur retournera alors vers le champ concerné).
		 **/
		theComp.validLine = function(lineIndex, isClick) {
			if (validatingLine) {return;}
			validatingLine = TRUE;
			if (townRequest) {
				setLineData(elementText, theComp.towns, theComp.lines[lineIndex]);
			}
			// Par défaut, les villes ne sont pas visibles car il faut tester leur page d'appartenance
			theComp.hidePropositions();
			elementText.value = theComp.lines[lineIndex].nom;
			elementData.value = theComp.lines[lineIndex].data;
			if (isClick) {
				onClickFocusAgain(lineIndex);
			}
			else {
				validatingLine = FALSE;
			}
		};
	}; // fin du constructeur CompletionMEV
}()); // Exécution ici

