/**
 * Définit un ensemble de traitements basés sur l'envoi de requêtes XmlHttpRequest
 * au serveur et le traitement de la réponse.
 */

/**
 * Objet chargé d'envoyer une requête au serveur.
 * Elle fait plusieurs choses :
 * <ul>
 * <li>Elle fournie deux méthodes pour l'envoie de requête post ou get au serveur.</li>
 * <li>Elle prend en charge les traitements standards en réponse au serveur :</li>
 * 	<ul>
 * 		<li>Affichage des boites de dialogue système.</li>
 * 		<li>Mise à jour d'input hidden.</li>
 * 		<li>Mise à jour d'élement HTML de la page.</li>
 * 		<li>Instanciation et lancement de bloc javascripts.</li>
 * 		<li>...</li>
 *		</ul>
 * <li>Dans le cas d'une requête post elle traite le formulaire et envoi l'ensemble
 * 	de ses paramètres au serveur.</li>
 * <li>Pour les champs type checkbox, radio, si les valeurs ne sont cochées
 *   alors on force cette valeur (généralement false).<br />
 *   De cette façon, le binding côté serveur est correctement effectué.<br />
 *   Cette opération est réalisée dans une version modifiée de prototype (Form.serialize).</li>
 * @todo autoriser la sérialisation de plusieurs formulaire dans la page.
 */

/**
 * Cette fonction permet de sérialiser les paramètres d'un formulaire.
 * Utilise la méthode std de prototype (Form.serialize).
 * Par contre, avec cette méthode std, les champs radio et checkbox
 * non renseignés ne sont pas inscrit ds la chaine résultat.
 * cette méthode ajoute donc les champs qui ne sont pas selected sous la forme :
 * champ= (chaine vide).
 * @return une chaine au format chp1=val1&ch2=val2....
 */
	function serialize(form) {
    		var outputString = Form.serialize($(form));


    		var elements = Form.getElements($(form));
    		var lstElts = $(form).getElementsByTagName('input');
    		if (lstElts)
    		{
    			// traitement des radio
    			var aR = $A(lstElts);
    			var mapRadio ={};
    			for (var i = 0;i < aR.length;i++)
    			{
    				var component = aR[i];

    				if (component.type == 'radio')
    				{
    					var valueMap = mapRadio[component.name];
    					if (valueMap == 'undefined')
    					{
	    					valueMap = false;
    					}
    					var isSelected = component.checked;
					valueMap = valueMap || isSelected ;
					mapRadio[component.name] = valueMap;
				}
    			}
			var hshRadio = $H(mapRadio);
			var lst = hshRadio.findAll(function (item)
				{
					return item['value'] == false;
				}
			);
			var strRadio = '';
			lst.each (function (item){
					if (strRadio.length>0)
						strRadio+='&';
					strRadio += item['key'] + '=';
				}
			);

			// traitement des checkbox
			var queryComponents = new Array();
			aR.each(function (item)
    				{
    					var result = item.type == 'checkbox';

    					if (result && !item.checked)
    						queryComponents.push(item.name+"=");
    				}
    			);

    			var strChk = queryComponents.join('&');

    			// assemblage de l'ensemble
    			if (strRadio.length>0)
    				outputString += '&' +strRadio;
    			if (strChk.length>0)
    				outputString += '&' +strChk;
    		}
    		return outputString;
  	}

var internalOavRequest = null;
function getOavRequest()
{
	if (internalOavRequest==null)
	{
		internalOavRequest = new OavRequest();
	}
	return internalOavRequest;
}

var OavRequest = Class.create();

Object.extend(OavRequest.prototype, DHTMLMask.prototype);

Object.extend(OavRequest.prototype,
{
	/**
	 * Constructeur
	 */
	initialize: function()
	{
		this.ajaxRequest = null;
	},

	/**
	 * Envoi une requete de formulaire au serveur
	 * @param formAction : L'action du formulaire (optionnel, prend l'action courante du formulaire sinon)
	 * @param formId : Le nom du formulaire (optionnel, par défaut le 1er formulaire de la page)
	 * @param p_paramAdditionnels : Liste de paramètres à ajouter dynamiquement
	 */
	sendFormRequest:function(formAction, formId, p_paramAdditionnels, ignoreReturn)
	{
		var strFormId = null;
		if(formId)
		{
			strFormId = formId;
		}
		// Si le formulaire n'est pas paramétré, prend le premier formulaire trouvé
		// dans le document
		else
		{
			strFormId = document.forms[0];
		}

		var formToUse = $(strFormId)

		// Prend par défaut l'action du formulaire
		if(!formAction)
		{
			formAction = formToUse.action;
		}

		var formParams = '';

		var parametersInitialized = false;

		// Ajoute les paramètres à la requête
		if(p_paramAdditionnels)
		{
			// Converti le tableau de paramètres en une chaîne de paramètre de requete
			paramAdditionnels = $H(p_paramAdditionnels).toQueryString();
			if(paramAdditionnels)
			{
				// Utilise la chaine de paramètres comme une map
				var paramsMap = paramAdditionnels.toQueryParams();
				for(var paramName in paramsMap)
				{
					// Ajoute ou initialise les paramètres additionnels au formulaire de la page
					setFormParameter(formToUse, paramName, "hidden", paramsMap[paramName]);
				}
			}
		}

		// Créé la chaîne de paramètres finale à passer à Prototype.
		// C'est une chaine du type : param1=valeur1&param2=valeur2...
		formParams += serialize($(strFormId));//Form.serialize($(strFormId));
		if(formParams.length > 0)
		{
			parametersInitialized = true;
		}

		this.internalSendRequest(formAction, formParams, ignoreReturn);
	},

	/**
	 * Envoi une requete simple au serveur sur une url.
	 */
	sendRequest:function(url, ignoreReturn)
	{
		this.internalSendRequest(url, null, ignoreReturn);
	},

	internalSendRequest:function(url, params, ignoreReturn)
	{
		this.sentUrl = url;

		// Les différentes méthodes en fonction du résultat de traitement
		// de la réponse.
		// Erreur réponse serveur : 500, 40X, ...
		onServerErrorFunction = createMethodReference(this, "onServerError");
		// Exception javascript lors du traitement de la réponse
		onResponseExceptionFunction = createMethodReference(this, "onResponseException");
		// Méthode chargée de parser/traiter la réponse du serveur
		responseHandler = createMethodReference(this, "handleServerResponse");

		// Affiche le masque empéchant l'utilisateur d'intéragir avec l'interface
		// utilisateur en attendant le résultat de la requête.
		this.initMask("RequestMask");
		this.showMask();

		var options = null;
		if(!params || params == null)
		{
			params = "reqtype=xmlhttpreq";
			if(ignoreReturn)
			{
				options = {parameters:params,
					asynchronous:false};
			}
			else
			{
				options = {
				parameters:params,
					onComplete:responseHandler,
					onFailure:onServerErrorFunction,
					onException:onResponseExceptionFunction};
			}
		}
		else
		{
			params += "&reqtype=xmlhttpreq";
			if(ignoreReturn)
			{
				options = {
					parameters:params,
					asynchronous:false};
			}
			else
			{
				options = {
	      		parameters:params,
					onComplete:responseHandler,
					onFailure:onServerErrorFunction,
					onException:onResponseExceptionFunction};
			}
		}
		document.body.style.cursor = 'wait';

		// Lance la requête serveur via prototype
		if (this.ajaxRequest==null)
		{
			this.ajaxRequest = new Ajax.Request(url, options);
		}
		else
		{
			this.ajaxRequest.setOptions(options);
    		this.ajaxRequest.request(url);
		}
	},

	/**
	 * Cette méthode gère le flux de réponse (au format XML).
	 * Cette fonction s'appuie sur xmldom.js pour le parcours du flux.
	 * En fonction des balises rencontrées, l'application délègue le traitement à une méthode spécifique
	 * Chaque balise est ensuite traitée dans des méthodes handleXXX spécifiques.<br />
	 * Les bouts de code HTML doivent être encodés côté serveur en base64 (ils sont décodés côté client).
	 * @param request objet XmlHttpRequest contenant notamment la réponse (request.responseText).
	 */
	handleServerResponse:function(request)
	{
		var content = request.responseText;

		// Enlève le masque bloquant l'interface utilisateur
		this.hideMask();

		var result = null;

		// Si le status de la réponse n'est pas le code HTTP OK : 200
		if(request.status != 200)
		{
			this.doServerError(request, "Invalid request status.");
			return;
		}

		// Contenu de la réponse du serveur
		response = request.responseText;

		// Controle que la réponse est bien une réponse ajax oav : elle doit
		// être encadrée par des balises ajax-response.
		if(!response.leftTrim().startsWith("<ajax-response>"))
		{
			this.doServerError(request, "Invalid server response start");
			return;
		}
		else if(!response.rightTrim().endsWith("</ajax-response>"))
		{
			this.doServerError(request, "Invalid server response end");
			return;
		}

		this.jsFctLoadList = new Array();

		// Une réponse serveur AJAX est un document XML.
		// Créé un nouveau parseur du document XML
		theDocument = new XMLDoc(response);

		rootNodeForDoc = theDocument.docNode;

		var rootNodeElements = rootNodeForDoc.getElements();

		// Tri les noeuds à la racine par ordre de traitement (attribut order).
		rootNodeElements = sortArray(rootNodeElements, nodeSortPredicate);

		// Evalue chaque élément racine du résultats ajax
		for(var i=0; i<rootNodeElements.length; i++)
		{
			rootChild = rootNodeElements[i];

			// Détermine la fonction chargée de traitée cette section du document
			var nodeFunctionName = rootChild.getAttribute("function");
			var functionCall = null;
			if(nodeFunctionName)
			{
				functionCall = nodeFunctionName;
			}
			else
			{
				// Si le nom de la fonction n'est pas donné, le construit automatiquement
				// à partir du nom du tag.
				functionCall = this.buildNodeFunctionName(rootChild.tagName);
			}

			var functionRef = null;
			// Recherche d'abord une fonction de la classe
			try
			{
				functionRef = eval("this."+functionCall);
				if(functionRef)
				{
					functionRef = createMethodReference(this, functionCall);
				}
			}
			catch(e)
			{
			}

			// Sinon une fonction globale
			if(!functionRef)
			{
				try
				{
					functionRef = eval(functionCall);
				}
				catch(e)	{}
			}

			if(functionCall == null || !functionRef)
			{
				alert("Function " + functionCall + " not found for tag " + rootChild.tagName);
			}
			else
			{
				var result = functionRef(rootChild);
				// Une fonction peut intérompre le traitement ajax en retoutnant exit
				if("exit" == result)
				{
					return;
				}
			}
		}

		// Exécute toutes les fonctions onLoad des blocs javascripts
		for(var i=0; i<this.jsFctLoadList.length; i++)
		{
			eval(this.jsFctLoadList[i]+"()");
		}

		// Le layout peut ne pas etre défini
		try
		{
			layout.doLayout();
		}
		catch(e)
		{}
		document.body.style.cursor = 'default';
	},

	/**
	 * Construit le nom de la fonction de traitement d'une section u document
	 * XML à partir du nom de son tag, ex : html-content devient handleHtmlContent
	 */
	buildNodeFunctionName:function(nodName)
	{
		if(!nodName || nodName.length ==0)
		{
			return null;
		}
		var nodeChars = nodName.toArray();
		var functionName = "handle" + nodeChars[0].toUpperCase();
		for(var i=1; i<nodeChars.length; i++)
		{
			if(nodeChars[i] == '-')
			{
				if(i != nodeChars.length - 1)
				{
					i++;
					functionName += nodeChars[i].toUpperCase();
				}
			}
			else
			{
				functionName += nodeChars[i];
			}
		}

		return functionName;
	},

	/*
	 * Méthode appelée lorsqu'une exception se produit dans le javascript de
	 * traitement de la réponse serveur.
	 */
	onResponseException:function(request, exception)
	{
		this.hideMask();

		// Efface la valeur du token pour ne pas bloquer l'utilisateur
		cancelToken();

		errorText = '<table><tr>'
			+ '<td align=\"left\" colspan=\"2\">Une exception est survenue lors du traitement de la réponse serveur.</td>'
			+'</tr><tr><td>&nbsp;&nbsp;</td><td align=\"left\">'
			+'<br>Exception message : '+exception.message
			+'<br>Request Url:'+this.sentUrl.substr(0, Math.min(80, this.sentUrl.length));
		if(request.transport)
		{
		/*	var readyState = null;
			try { readyState = request.transport.readyState; }	catch(e){}
			var status = null;
			try { status = request.transport.status; }	catch(e){}
			var statusText = null;
			try { statusText = request.transport.statusText; }	catch(e){}

			errorText+=
				((readyState)?'<br>-Ready state:'+readyState:'')
				+((status)?'<br>  -Status:'+status:'')
				+((statusText)?'-'+statusText:'');*/
		}
		errorText+='</td></tr></table>';

		this.displayOavRequestError(errorText);

		// Propage l'exception pour que l'avoir dans la console et empécher
		// le client d'aller plus loin
		throw exception;
	},

	/**
	 * Méthode appelée lorsqu'une erreur est détectée dans la réponse du serveur.
	 */
	onServerError:function(request)
	{
		this.hideMask();
		this.doServerError(request, 'Une erreur est survenue lors de l\'appel au serveur.');
	},

	/**
	 * Méthode appelée lorsqu'une erreur est détectée dans la réponse du serveur.
	 */
	doServerError:function(request, message)
	{
		// Efface la valeur du token pour ne pas bloquer l'utilisateur
		cancelToken();

		var description =
			'<br>' + message
			+'<br>Request Url:'+this.sentUrl.substr(0, Math.min(80, this.sentUrl.length))
		  	+'<br>Request status:'+request.status+'-'+request.statusText
		  	+'<br>Request readyState:'+request.readyState;

		this.displayOavRequestError(description, request.responseText);
	},



/**
 * Affichage d'une erreur
 */
displayOavRequestError :function (description, details)
{
	var winInstance = new DHTMLDialog("error");
	// Pour avoir les barre de défilement automatiques
	winInstance.setOverflow("auto");
	description = "<table width='100%' border='0'><tr><td align='left'>" + description + "</td></tr>";
	if(details)
	{
		description += "<tr><td align='left'>Details :</td></tr>";
		description += '<tr><td valign="top" id="erriframediv" height="300px">';
		description += "</td></tr>";
	}
	description += '</table>';
	winInstance.showDialog(
		"Erreur", description, "error", "default", null, null, null);

	// Ajoute une iframe au div erriframediv de la boite de dialogue.
	// On utilise une iframe car la réponse du serveur est un contenu HTML
	// complet (html, head, body, etc...).
	// Si on ne réutilise pas l'iframe, elle n'est jamais supprimée du coup
	// quand on en recréé une, c'est toujours la première créée qui est référencée.
	if(details)
	{
		var iframe = document.getElementById('errorContent');
		if(!iframe)
		{
			iframe = document.createElement('iframe');
			iframe.id = "errorContent";
			iframe.frameborder = 0;
		}
		iframe.width = "100%";
		iframe.height = "300px";
		document.getElementById('erriframediv').appendChild(iframe);

		var frameDoc = null;
		if(iframe.contentDocument) { // For NS6
			frameDoc = iframe.contentDocument;
		} else if (iframe.contentWindow) { // For IE5.5 and IE6
			frameDoc = iframe.contentWindow.document;
		} else if (iframe.document) { // For IE5
			frameDoc = iframe.document;
		}
		frameDoc.write(details);
	}
},


/**
 * traite les balises "js" ds lesquelles sont situées les fragments <script>.
 * par défaut, une méthode pageLoad() est lancée.
 * @todo permettre de lancer une méthode à l'initialisation (ou non).
 */
handleJs:function(xmlNode)
{
	if (xmlNode)
	{
   	var scriptTxt = unescape(xmlNode.getText());
      scriptTxt.evalScripts();
	}

	var onLoadFunction = xmlNode.getAttribute("onLoad");

	if(onLoadFunction)
	{
		this.jsFctLoadList.push(onLoadFunction);
	}

   return
},

/*
 * Prise en charge de la mise à jour d'inputs hidden depuis le serveur
 */
handleHiddenParams:function(xmlNode)
{
	// Traitement de l'ajout dynamique de paramètres hidden
	if(xmlNode.getElements("hiddenParam"))
	{
  		var formId = document.forms[0];
		if(!formId)
		{
			alert("Form not found, can't append hidden parameters");
			return;
		}

		xmlNode.getElements("hiddenParam").each(
			function(item)
			{
				webOavParamName = item.getAttribute('name');
				webOavParamValue = item.getAttribute('value');
				setFormParameter(formId, webOavParamName, "hidden", webOavParamValue);
		});
	}
},


/**
 * Prise en charge d'une section message oav dans le document xml de la requête ajax
 */
handleOavMessage:function(xmlNode)
{
	var dialogId = xmlNode.getAttribute('id');
	// L'identifiant de message DIAG_CLOSEWINDOW_CLOSE est spécial, il indique
	// que le serveur veut fermer la fenêtre (fonction quitter/fermer la fenêtre
	// du framework).
	if(dialogId &&
		dialogId == 'DIAG_CLOSEWINDOW_CLOSE')
	{
		setTimeout("window.close()", 100);
		return "exit";
	}
	else
	{
		// Affiche la boite de dialogue à l'utilisateur
		// Une boite de dialogue oav respecte les même principes de configuration
		// qu'une boite de dialogue standard.
		handleDialog(xmlNode);
	}
},


/**
 * Met à jour un zone avec du contenu html.
 * Le contenu est décodé en utilisant la méthode base64.
 * il faut donc que ce soit encodé en base64 côté serveur.
 */
handleHtmlContent:function(xmlNode)
{
	var contentElement = xmlNode.getAttribute('element');

	if(!contentElement)
	{
		alert("Attribute element must be present to update its content");
	}
	else
	{
		var docElement = $(contentElement);
		if(!docElement)
		{
			alert("Div " + contentElement + " not found, can't update its content");
		}
		else
		{
			// mise à jour du contenu HTML du bloc
			if(xmlNode.getText() &&
				xmlNode.getText().length > 0)
			{
				var strHtml = unescape(xmlNode.getText());
//				alert(strHtml);
	  			docElement.innerHTML = strHtml;

				// Branche le FormController sur le formulaire (controles de saisies, etc, ...)
				var formElt = null;
				var forms = docElement.getElementsByTagName('form');
				if (forms!=null && forms.length>=1)
				{
					formElt = $A(forms)[0];
					if(formCtrler)
					{
						if (formElt.id == null || formElt.id.length==0)
						{
							 formElt.id =  formElt.name;
						}
						var strFormId = formElt.id;
						if(!strFormId || strFormId == null)
						{
							alert("Form id must be defined");
							return;
						}
						if (formCtrler.isFormId()==false || (strFormId != null && strFormId != formCtrler.getFormId()))
						{
							formCtrler.initialize(strFormId);
						}
						else
						{
							formCtrler.updateForm(strFormId, formElt);
						}
					}
				}
			}

			// test portant sur la visibilité ou non du bloc
			if(xmlNode.getAttribute('visible') == "true")
			{
				Element.show(docElement);
			}
			else	if(xmlNode.getAttribute('visible') == "false")
			{
				Element.hide(docElement);
			}
		}
	}
}
});

/**
 * Désactive le token dans le formulaire courant.
 */
function cancelToken()
{
	var token = getFormElement(null, "tok");
	if(token)
	{
		token.value='';
	}
}

/*function debugFormParams(formToUse)
{
	var debugMess = "Action " + formToUse.action + "\n";
	var elements = Form.getElements(formToUse);
	for (var i = 0; i < elements.length; i++)
	{
		element = $(elements[i]);
		debugMess += "[" + element.name + ":" + element.value + "]\n";
	 }
	 return debugMess;
}
*/
/**
 * Méthode de traitement de la réponse de l'utilisatauer à une boite de dialogue
 * système (lors du processus de sauvegarde, ...
 */
function handleOavDialogResponse(dialogId, userResponse)
{
	// si l'utilisateur ferme la fenêtre sans répondre ou sis il fait annuler
	// on revient à la page dans l'état courant sans faire plus de traitements.
	if(userResponse != "cancel")
	{
		getOavRequest().sendFormRequest(null, document.forms[0], {_oavprompt:dialogId, _oavanswer:userResponse});
	}
}

/**
 * Prédicat de tri des noeuds des sections du document xml en réponse à une requête
 * ajax.
 */
function nodeSortPredicate(node1, node2)
{
	var node1Order = (node1.getAttribute("order"))?parseInt(node1.getAttribute("order"), 10):0;
	var node2Order = (node2.getAttribute("order"))?parseInt(node2.getAttribute("order"), 10):0;
	if(node1Order > node2Order) return 1;
	else	if(node1Order < node2Order) return -1;
	else	return 0;
}

function debugQueryString(queryString)
{
	var paramsMap = queryString.toQueryParams();
	var content = "";
	for(var paramName in paramsMap)
	{
		content += paramName + ":" + paramsMap[paramName] + "\n";
	}
	return content;
}
