/**
 * Classe PAFClient
 * 
 * Gestion côté client pour les requêtes vers le serveur. Voir la documentation pour savoir
 * comment utiliser la classe de façon approprié
 * 
 * @author Olivier Arteau
 */

/**
 * Constructeur de classe
 */

function PAFClient () {
	this.createXHR();
}

/**
 * Classe PAFClient
 * 
 * Gestion côté client de l'AJAX
 */

PAFClient.prototype = {
	// public static final int NO_TIMEOUT //
	'NO_TIMEOUT' : -1,
	
	// Connexion XMLHTTP //
	'connexion' : null,
	
	// Paramètre de la requête //
	'asynchronus' : true,
	'url' : '',
	'method' : 'GET',
	'callback' : null,
	
	// Pour le queuing des requêtes //
	'queue' : new Array(),
	
	// Pour l'option timeout de requête //
	'timeout' : 3000,
	'timeoutTimer' :  null,
	
	// Pour l'option retry on fail //
	'retryOnFail' : true,
	'isFirstAttempt' : true,
	'currentParameter' : new Array(),
	
	// Pour les auto-assign en fonction des headers //
	'lstListenerHeader' : new Array(),
	
	/**
	 * private void createXHR
	 * 
	 * Crée l'objet XHR et le garde en mémoire
	 */
	
	'createXHR' : function () {
		this.connexion = null;
		if (window.XMLHttpRequest)
			this.connexion = new XMLHttpRequest();
		else if (window.ActiveXObject)  // if IE
		{
			var ieversions = ['Msxml2.XMLHTTP','Microsoft.XMLHTTP','Msxml2.XMLHTTP.5.0','Msxml2.XMLHTTP.4.0','Msxml2.XMLHTTP.3.0'];
			for(var i=0; !this.connexion && i<ieversions.length; i++)
			{
				try
				{
					this.connexion = new ActiveXObject(ieversions[i]);
					break;
				}
				catch(e)  { }
			}
		}
	}
	,
	
	/**
	 * public void executerCommande
	 * 
	 * Demande d'exécution de commande vers le serveur
	 * 
	 * @param (String)   Commande
	 * @param (String[]) Paramètre
	 * @param (Function) Callback
	 */
	
	'executerCommande' : function (cmd, param, callback) {
		
		// Si une ou plusieurs commandes sont en train d'être fait, on met en queue //
		if (this.connexion.readyState > 0 && this.connexion.readyState < 4) {
			this.queue.push(new Array(cmd, param, callback));
			return;
		}
		
		// Garde en mémoire les paramètres pour le retry on fail //
		this.currentParameter = new Array(cmd, param, callback);
		
		// Converti la liste des paramètres en String //
		parametre = '&cmd=' + cmd;		
		for (var i in param) {
			if ((typeof param[i]).toLowerCase() == 'function')
				continue;
			parametre += '&' + i + '=' + escape(param[i]);
		}
		
		// Pour le GET, les paramètres sont ajoutés dans l'URL //
		url = this.url;
		if (this.method.toUpperCase() == "GET") {
			url += ((this.url.indexOf('?') == -1) ? '?' : '') + parametre;
		}
		
		this.callback = callback;
		this.connexion.open(this.method, url, this.asynchronus);
		
		// Pour le POST en doit mettre un header spécial //
		if (this.method.toUpperCase() == "POST") {
			this.connexion.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
		}
		
		if (this.timeout != this.NO_TIMEOUT)
			this.timeoutTimer = setTimeout(this.abortRequest.bind(this), this.timeout);
		
		if (this.asynchronus) {
			this.connexion.onreadystatechange = this.receiveResultat.bind(this);
			this.connexion.send((this.method.toUpperCase() == "GET") ? null : parametre);
		} else {
			this.connexion.send((this.method.toUpperCase() == "GET") ? null : parametre);
			this.receiveResultat();
		}
	}
	,
	
	/**
	 * private void receiveResultat
	 * 
	 * Quand un résultat est reçu, il est renvoyé au callback et la prochaine commande est traité
	 */
	
	'receiveResultat' : function () {
		if (this.connexion.readyState == 4) {
			if (this.connexion.status == 200) {
				this.doCallback(new Resultat(
					this.connexion.responseText,
					this.connexion.responseXML,
					this.connexion.status
				));
				
				this.proceedNextCommand();
			} else {
				this.failed(new Resultat(
					this.connexion.responseText,
					this.connexion.responseXML,
					this.connexion.status
				));
			}
		}
	}
	,
	
	/**
	 * private void failed
	 * 
	 * Quand la requête ne réussit pas. Si l'option retryOnFail est activé, on réessaie la même requête
	 * 
	 * @param (Objet) Résultat de la requête
	 */
	
	'failed' : function (resultat) {
		if (this.isFirstAttempt && this.retryOnFail) {
			this.isFirstAttempt = false;
			this.executerCommande(this.currentParameter[0], this.currentParameter[1], this.currentParameter[2]);
		} else {
			this.doCallback(resultat);
			this.proceedNextCommand();
		}
	}
	,
	
	'doCallback' : function (resultat) {
		/**
		 * Analyse des headers
		 */
		try {
			var header = this.connexion.getAllResponseHeaders().split("\n");
			var tmp = new Array();
			
			for (var i=0; i<header.length; i++) {
				if (header[i].indexOf(": ") != -1) {
					var ligneData = new Array(
						header[i].substring(0, header[i].indexOf(": ")),
						header[i].substr(header[i].indexOf(": ") + 2)
					);
					tmp[ligneData[0]] = ligneData[1];
				}
			}
			
			header = tmp;
			
			/**
			 * Analyse de certain header spécifique
			 * 
			 * L'analyse se fait par des modules externes au core de la librairie côté client
			 * pour que l'ajout de module soit plus simple et qu'il ne crée pas de dépendance.
			 */
			
			for (var i in PAFClient.prototype.lstListenerHeader) {
				if (isset(header[i]) && typeof PAFClient.prototype.lstListenerHeader[i] == "object") {
					PAFClient.prototype.lstListenerHeader[i].analyse(tmp[i]);
				}
			}
			
			resultat.setHeader(header);
		} catch (e) {
			resultat.setHeader(new Array());
		}
		this.callback(resultat);
	}
	,
	
	/**
	 * public void abortRequest
	 * 
	 * Annule un requête en cours
	 */
	
	'abortRequest' : function () {
		this.reset(); // Pour éviter effets indésirables //
		this.failed(new Resultat('', '', 408));
	}
	,
	
	/**
	 * private void proceedNextCommand
	 * 
	 * S'il reste des commandes en queue, la prochaine commande est traité
	 */
	
	'proceedNextCommand' : function () {
		// Enlève le timeout de la requête précédente et reset la connexion //
		this.totalReset();
		
		// Si il reste des éléments à exécuter, on les passe //
		if (this.queue.length > 0) {
			toExec = this.queue.shift();
			this.executerCommande(toExec[0], toExec[1], toExec[2]);
		}
	}
	,
	
	/**
	 * private void clearTimeout
	 * 
	 * Enlève le timeout pour la requête courrant et reset la connexion.
	 * 
	 * Note: pour annuler une requête, il faut utiliser la méthode publique abortRequest
	 */
	
	'reset' : function () {
		clearTimeout(this.timeoutTimer);
		this.connexion.onreadystatechange = function () {}; // Pour éviter que onreadystatechange soit appelé //
		this.connexion.abort();
	}
	,
	
	/**
	 * Reset la même chose que la fonction reset et il reset le flag isFirstAttempt.
	 * Ceci est à exécuter seulement quand le requête est totalement fini d'être exécuté
	 */
	
	'totalReset' : function () {
		this.reset();
		this.isFirstAttempt = true;
	}
	,
	
	/**
	 * public void setAsynchronus
	 * 
	 * @param (Boolean) Is asynchronus
	 */
	
	'setAsynchronus' : function (asyn) {
		this.asynchronus = asyn;
	}
	,
	
	/**
	 * public boolean isAsynchronus
	 * 
	 * @return (Boolean) Is asynchronus
	 */
	 
	'isAsynchronus' : function () {
		return this.asynchronus;
	},
	
	/**
	 * public void setURL
	 * 
	 * @param (String) URL
	 */
	
	'setURL' : function (url) {
		this.url = url;
	}
	,
	
	/**
	 * public boolean getURL
	 * 
	 * @return (String) URL
	 */
	 
	'getURL' : function () {
		return this.url;
	}
	,
	
	/**
	 * public int getTimeoutTime
	 * 
	 * Retourne le temps avec qu'une requête soit annulé automatiquement
	 * 
	 * @return (int)Temps en millisecondes
	 */
	
	'getTimeoutTime' : function () {
		return this.timeout;
	}
	,
	
	/**
	 * public void setTimeoutTime
	 * 
	 * Définit le temps avec qu'une requête soit annulé automatiquement
	 * 
	 * @param (int) Temps en millisecondes
	 */
	
	'setTimeoutTime' : function (temps) {
		this.timeout = temps;
	}
	,
	
	/**
	 * Définit si la requête va être réessayé si elle échoue
	 * 
	 * @param (Boolean)
	 */
	
	'setRetryOnFail' : function (retryOnFail) {
		this.retryOnFail = retryOnFail;
	}
	,
	
	/**
	 * Définit si la requête va être réessayé si elle échoue
	 * 
	 * @return (Boolean)
	 */
	
	'isRetryOnFail' : function () {
		return this.retryOnFail;
	}
	,
	
	/**
	 * Définit la méthode utilisé pour envoyer les données
	 * 
	 * @param (String) Méthode "GET" ou "POST"
	 */
	
	'setMethod' : function (method) {
		this.method = (method.toUpperCase() == "GET" || method.toUpperCase() == "POST") ? method.toUpperCase() : "GET";
	}
	,
	
	/**
	 * Retourne la méthode utilisé pour envoyer les données
	 * 
	 * @return (String) Méthode "GET" ou "POST"
	 */
	
	'getMethod' : function () {
		return this.method;
	}
	,
	
	/**
	 * 
	 */
	
	'addHeaderListener' : function (classe, header) {
		PAFClient.prototype.lstListenerHeader[header] = classe;
	}
};