/**
 * Le gestionnaire d'id de session.<br>
 * <br>
 * Il permet de conserver le lien avec le serveur
 * qui a créé la session pour l'internaute courant.<br>
 * <br>
 * A chaque nouvelle redirection, il est
 * recommandé d'encoder le lien via la méthode
 * {ev.requestManager#encodeUrl(String)}
 * afin de dirigé chacune des requêtes HTTP vers le
 * même serveur.<br>
 * Pour effectuer une requête asynchrone (RJS, ERA)
 * il faut impérativement utiliser une des méthodes du
 * manager : invokeRjs() invokeRjsEra().<br>
 * Cela permet de s'assurer qu'au retour de chaque
 * requête, le numéro de session renvoyé par le serveur
 * est vérifié.<br>
 * Cela se fait grâce à la méthode
 * {ev.requestManager#checkSession(String)}.
 */
(function(){
	// Si les namespaces/classes nécessaires ne sont pas chargées : exception
	if(!window.ev){ window.ev={}; }
	if(!ev.rjs){throw new Error("Le namespace 'ev.rjs' doit exister");}

	// nom du cookie ou du paramètre à passer dans les url (pour maintenir le sessionId)
	var sIdName='ev_SID',

	/**
	 * On stocke le numéro de session.
	 * Cela permet d'éviter de fonctionner avec les cookies
	 * tant qu'on reste dans la même page.
	 */
	sId,
//
//	/**
//	 * Expression régulière permettant de trouver l'id de
//	 * session dans l'url.
//	 */
////	regexUrlSession=/;jsessionid=([^#\?]*)[#\?]?/,
//	regexUrlSession=new RegExp("/"+sIdName+"=([^#\?]*)[#\?]?/"),
////	regexUrlSession=new RegExp("#"+sIdName+":([^|]*)"),

	/**
	 * Expression régulière représentant le nom de domaine
	 * d'une URL
	 */
	RE_HOST=/http:\/\/([^:\/]*)(:[0-9]+)?/,

	/**
	 * Expression régulière permettant de trouver le début des paramètres.
	 */
	regexQuery=/^[^#]*\?/,

	/**
	 * Expression régulière représentant le '?' (pour remplacement).
	 */
	regexQueryMark=/(\?|$)/,

	/**
	 * Expression régulière représentant le '?' avec l'attribut
	 * "jsessionid=" s'il y est (pour remplacement).
	 */
	regexJSessionId=/^([^#\?;]*)(;jsessionid=[^#\?]+)?(\?|#|$)/,

	/**
	 * Expression régulière représentant le '#' (pour remplacement).
	 */
	regexAnchorMark=/(#|$)/,

	/**
	 * Expression régulière permettant de trouver l'id de session dans l'url.
	 */
	regexCookieSession=new RegExp("(?:; )?"+sIdName+"=([^;]*);?"),

	/**
	 * Identifiant du locator utilisé par le gestionnaire
	 * d'id de sessions.
	 */
	RJS_ID='ReqMngr',

	/**
	 * Définition d'un seul Locator RJS (au cas où il y
	 * ait plusieurs requête sur le numéro de session).
	 */
	RJS_LOCATOR=new ev.rjs.Locator(8000, RJS_ID),

	/**
	 * Chemin vers la page de verification de session.
	 */
	PATH_ERA_SESSION='/session.rjs';

	/**
	 * File d'attente des requetes ERA.
	 * Utilisé pour stocker les requetes ERA tant que la session n'est pas validée.
	 */
	var queuedEraRequest = [];
	
	/**
	 * Permet d'encoder l'url passée en paramètre en
	 * lui ajoutant le 'jsessionid' permettant de
	 * contacter le bon serveur (load balancing
	 * d'Apache + ModJK) et de retrouver la session
	 * courante dans une requête asynchrone.
	 * 
	 * @param u url à encoder
	 */
	function encodeUrl(u){
		if(!sId){
			// rien à encoder si pas d'id de session en mémoire
			return u;
		}
		return u.replace(regexJSessionId, '$1;jsessionid='+sId+'$3');
	}

	/**
	 * Permet d'encoder l'ancre de l'url passée en
	 * paramètre en lui ajoutant l'id de session
	 * permettant de le conserver entre 2 pages
	 * différentes dans le cas d'un changement de
	 * domaine ou dans le cas ou les cookies sont
	 * complètement bloqués.
	 * 
	 * @param u url à encoder (si besoin)
	 * @param id numéro de session
	 * @param force permet de forcer l'encodage si l'on
	 *   souhaite le faire sans condition
	 */
	function encodeAnchor(u, id, force){
		var windowHost=window.location.host,
		urlHost=RE_HOST.test(u)&&(RegExp.$1+RegExp.$2)||null;
//		ev.log.debug('ev.requestManager.__encodeAnchor()> cookieEnabled: '+ev.tools.isCookieEnabled());
//		ev.log.debug('ev.requestManager.__encodeAnchor()> location.hostname: '+windowHost);
//		ev.log.debug('ev.requestManager.__encodeAnchor()> url hostname: '+urlHost);
//		ev.log.debug('ev.requestManager.__encodeAnchor()> url anchor: '+u.replace(/^[^#]*(#.*)?$/, '$1'));
		// on insère / met à jour l'ancre seulement dans 1 des 2 cas suivants :
		// - si on précise le nom de domaine dans l'URL et celui-ci est différent du nom de domaine actuel (window)
		// - si les cookies sont inutilisables
		if(force||urlHost&&windowHost!==urlHost||!ev.tools.isCookieEnabled()){
			return ev.tools.setParamInAnchor(u, sIdName, id);
		}
		return u;
	}

	/**
	 * Inscrit l'id de session courant dans l'URL
	 * et dans le cookie le cas échéant (si les
	 * cookies sont autorisés).
	 */
	function saveSession(){
		// Mise à jour de l'URL avec numéro de session (pour être sûr de conserver la session sur BACK du navigateur)
		// FIXME [ygally] désactivé en attendant de savoir si on le fait ou pas (cf. référencement)
		//window.location.replace(encodeAnchor(window.location.href, sId, 1));

		// Si les cookies ne sont pas utilisables, on sort
		if(!ev.tools.isCookieEnabled()){return;}

		// Sinon, on enregistre l'id de session dans le cookie
		document.cookie=sIdName+'='+sId+';path=/';
	}

	/**
	 * Permet de comparer le numéro de session donné
	 * à celui qui était précédemment stocké.<br>
	 * S'il est différent, le numéro de session donné
	 * sera retenu et remplacera le numéro courant
	 * (variable locale + cookie).
	 * 
	 * @param {String} sId2 id de session à comparer à la dernière session
	 *   connue du gestionnaire
	 * @return {boolean} true si la session est inchangée ; false si la session
	 *   donnée est différente ou nulle
	 */
	function chkAndUpd(sId2){
//		ev.log.debug('requestManager.__chkAndUpd()> Checking session Id : '+sId2);
		if(!sId2){
			ev.log.error('requestManager.__chkAndUpd()> Given session Id is undefined');
			return false;
		}
		if(sId){
			if(sId2===sId){
	//			ev.log.info('requestManager.__chkAndUpd()> Session Id is good : '+sId);
				return true;
			}
			ev.log.warn('requestManager.__chkAndUpd()> Session Id \''+sId+'\' replaced by \''+sId2+'\'');
		}
		// Si le numéro de session donné est différent, on le garde en remplacement de l'ancien
		sId=sId2;
		// ... et on le sauvegarde
		saveSession();
		return false;
	}

	/**
	 * Permet d'ajouter le bon identifiant RJS
	 * pour une requête sur l'ERA via le locator
	 * local.
	 * 
	 * @param u url à complèter
	 */
	function appendRjsId(u){
		return u&&(regexQuery.test(u)&&u.replace(regexQueryMark, '?id='+RJS_ID+'&')||u.replace(regexAnchorMark, '?id='+RJS_ID+'$1'));
	}

	/**
	 * @see ev.requestManager#invokeRjs(locator, path, callback, force)
	 */
	function invokeRjs(l, p, c, f){
		if(typeof(l)==='string'){
			// si l(ocator) est un string, c'est qu'on a choisi de ne pas donner de locator, donc on décale les params
			f=c;
			c=p;
			p=appendRjsId(l); // pour le (p)ath (chemin), on doit préciser l'ID de la requête RJS
			// et le locator est celui par défaut
			l=RJS_LOCATOR;
		}
//		ev.log.debug('invokeRjs()> '+p);
		ev.rjs.simpleInvoke(l, ev.rjs.URL_ROOT+p, function(r){
			chkAndUpd(r&&r.sessionId);
			if(typeof(c)==='function'){
				c(r);
			}
		}, f);
	}

	/**
	 * @see ev.requestManager##invokeEra(locator, path, callback, force)
	 */
	function invokeEra(l, p, c, f){
		if(typeof(l)==='string'){
			// si l(ocator) est un string, c'est qu'on a choisi de ne pas donner de locator, donc on décale les params
			f=c;
			c=p;
			p=appendRjsId(l); // pour le (p)ath (chemin), on doit préciser l'ID de la requête RJS
			// et le locator est celui par défaut
			l=RJS_LOCATOR;
		}
//		ev.log.debug('invokeEra()> '+p);
		ev.rjs.simpleInvoke(l, encodeUrl(ev.rjs.URL_ERA_ROOT+p), function(r){
			chkAndUpd(r&&r.sessionId);
			if(typeof(c)==='function'){
				c(r);
			}
		}, f);
	}

	/**
	 * Objet de stockage de requete
	 */
	function QueuedRequest(locator, path, callback, force) {
		this.call = function() {
			invokeEra(locator, path, callback, force);
		};
	}

	/**
	 * Parcours la pile de requete mise en attente, et les execute les unes après les autres.	 
	 */
	function makeQueued(){		
		ev.log.info('ev.requestManager_makeQueued...');		
		for(var i=0;i<queuedEraRequest.length;i++) {					
			queuedEraRequest[i].call();		
		}
		queuedEraRequest = [];			
	}


	/**
	 * Récupère l'identifiant de la session courante
	 * ou de la nouvelle session.
	 */
	function restoreSession(){
		// on recupere l'id de session dans l'url de la page s'il existe
		sId=ev.tools.getAnchorParameters&&ev.tools.getAnchorParameters(window.location.href)[sIdName];
//		ev.log.debug('requestManager.__restoreSession()> Session found in url : '+sId);

		// sinon, on recupere l'id de session dans un cookie s'il existe
		if(!sId&&ev.tools.isCookieEnabled()){
			sId=regexCookieSession.test(document.cookie)&&RegExp.$1;
//			ev.log.debug('requestManager.__restoreSession()> Session found in cookie : '+sId);
		}

		// Si une session est dans l'url ou les cookies. on la sauvegarde
		if(sId){		
			saveSession();
		}
		// on déclare une fonction de callback (pour exécution après l'appel à l'ERA)
		function cb(r){
			ev.log.info('requestManager.__restoreSession()> Session Id retreived : '+r.sessionId);
			chkAndUpd(r.sessionId);
			if(!r.exception){			
				ev.log.info('requestManager.__restoreSession()> FIFO off');															
				ev.requestManager.invokeEra=function(locator, path, callback, force){										
					invokeEra(locator, path, callback, force);							
				};								
				makeQueued();
			}else {
				// FIXME system de retry (3 fois?) si r.exception
				ev.log.warn('requestManager.__restoreSession()> Exception '+r.exception);
			}				
		}
//		ev.log.debug('requestManager.__restoreSession()> Retreiving Session Id...');
		invokeEra(PATH_ERA_SESSION, cb);					
	}

	/**
	 * Déclaration de la partie visible (publique)
	 * du gestionnaire d'id de session.
	 */
	ev.requestManager={
		/**
		 * Permet d'encoder l'url passée en paramètre pour
		 * retrouver la session courante dans la page
		 * suivante (ou requête asynchrone).
		 * 
		 * @param u url à encoder
		 */
		encodeUrl: function(u){
			return encodeUrl(u);
		},

		/**
		 * Permet d'encoder l'url passée en paramètre en
		 * utilisant l'ancre (pour éviter d'entrer en
		 * conflit avec des systèmes de session tiers ;
		 * ex: affiliations).
		 * L'encodage ne se fait que si le nom de domaine
		 * de la requête est différent de celui de la page
		 * courante, ou si les cookies ne sont pas accepté.
		 * Dans les autres cas, l'identifiant de la session
		 * sera conservé dans un cookie.
		 * 
		 * @param u url à encoder
		 */
		encodeAnchor: function(u){
			return encodeAnchor(u, sId);
		},

		/**
		 * Méthode permettant d'invoquer une requête en
		 * RJS en prenant en compte l'information sur la
		 * session courante.<br>
		 * L'invocation de RJS est considérée comme
		 * "stateless", c'est-à-dire qu'on ne tient pas
		 * compte des sessions.<br>
		 * 
		 * @see #invokeEra(locator, url, callback, force)
		 *
		 * @param {ev.rjs.Locator} locator [optionel] Locator spécifique à utiliser pour la requête
		 * @param {String} path chemin du service demandé (avec les paramètres à traiter par le service)
		 * @param {Function} callback [optionel] fonction de callback à appeler lorsque le service renvoi un résultat
		 * @param {Boolean} force [optionel] parametre permettant de préciser
		 *   s'il on souhaite absoluement faire la requete (true) ou s'il on
		 *   souhaite utiliser le cache (false) lorsque la requête a déjà été
		 *   exécutée
		 */
		invokeRjs: function (locator, path, callback, force){
			invokeRjs(locator, path, callback, force);
		},

		/**
		 * Méthode permettant d'invoquer une requête en
		 * RJS sur le serveur ERA. (Encodage d'URL avec
		 * id de session + vérification de l'id renvoyé
		 * si présent dans la réponse)
		 * 
		 * @see #invokeRjs(locator, url, callback, force)
		 *
		 * @param {ev.rjs.Locator} locator [optionel] Locator spécifique à utiliser pour la requête
		 * @param {String} path chemin du service demandé (avec les paramètres à traiter par le service)
		 * @param {Function} callback [optionel] fonction de callback à appeler lorsque le service renvoi un résultat
		 * @param {Boolean} force [optionel] parametre permettant de préciser
		 *   s'il on souhaite absoluement faire la requete (true) ou s'il on
		 *   souhaite utiliser le cache (false) lorsque la requête a déjà été
		 *   exécutée
		 */
		invokeEra: function (locator, path, callback, force){
			ev.log.info('ev.requestManager_invoke_era_session_not_ready_request_queued...');										
			var temp = new QueuedRequest(locator, path, callback, force);
			queuedEraRequest[queuedEraRequest.length] = temp;	
		}
	};

	// initialisation
	restoreSession();

	ev.log.debug('ev.requestManager ok');
	ev.tools.onFileLoad('ev/requestManager.js');
}());