/*[version]2008-08-28 16:52[/version]*/
/**
 * Module ATP.js Anema Transmission Protocol
 *
 * Requires Acu.js
 *
 * Method send():
 * 		params, replyFields, callback
 * 		params: Paare von Namen/Wert; Objekt
 * 		replyFields: Array von Objekten mit den Properties tagname, attributes;
 *			tagname gibt den Tagname in der Server-Antwort an, attributes die Attribute,
 *			die auf dem Tag definiert sind; attributes ist ein Array von Strings und
 *			optional; das aus der Server-Antwort generierte
 *			Objekt sitzt als Property "tagname" im Reply-Object und hat neben den angegebenen
 *			Attributen immer die Property innerValue, in der die vom Tag eingeschlossenen Daten
 *			stehen; gibt es mehrere XML-Elemente mit dem angegebenen Tagname, ist die
 *			"tagname"-Property des Response-Objekts kein einfaches Objekt,sondern ein Array
 *			von Objekten
 *			Array
 * 		callback: Funktion, die nach dem Eintreffen der Server-Antwort aufgerufen wird;
 * 			als einziges Argument wird das aus den Antwortfeldern erzeugte Objekt übergeben;
 * 			das Reply-Object verfügt immer über die Properties 'error' (bool) und 'errmsg'
 * 			(String); Funktion; optional, default null
 *
 *		Wird send() ohne jegliche Argumente aufgerufen, wird die Queue abgearbeitet.
 *
 * Constructor:
 * 		serverScript, method, access_key
 * 		serverScript: Skript, an das gesendet werden soll
 * 		method: post/get; optional, default post
 * 		accessKey: String, der den Client beim Server authentifiziert; dem Server-Skript
 *			wird der Key über den Parameter access_key (access_key=string) mitgegeben;
 *			enthält die Antwort des Servers ein Tag <access_key>, wird der davon eingeschlossene
 *			Wert als neuer Schlüssel übernommen;
 *			optional, default ist der Leerstring
 *		sequentialMode: wenn true werden die gesendeten Requests nicht sofort gesendet, sondern
 *			erst nach Eintreffen der Antwort des vorhergehenden Requests;
 *			boolean, default ist false
 *
 *	Queue, Access-Key-Handling
 *
 *
 * Jede Server-Antwort muss die Felder <status> und <errmsg> haben.
 * Jeder Wert von <status> anders als 'ok' wird als Fehler gewertet.
 *
 * XML-Einträge können über ein encoding-Attribut angeben, wie die eingeschlossenen
 * Daten codiert wurden. Momentan wird nur der Wert "url" für URLencoded unterstützt.
 *
 * Das Protokoll schickt (momentan) NUR asynchrone Requests.
 *
 * Dem Server-Skript werden im Parameter atp_info immer Infos über das Protokoll
 * mitgegeben (momentan nicht, könnte später nützlich sein und erleichtert
 * momentan den Zusammenbau des Parameter-Strings).
 *
 * Wird ein encoding bei einem XML-Element angegeben, müssen auch die Attribue
 * des betreffenden Elements dementsprechend codiert sein!
 *
 */

function ATP( serverScript/*, method="post", accessKey="", sequentialMode=false*/ ){
	this.serverScript = serverScript;
	this.method = (arguments[1]=="get") ? "get" : "post";
	this.accessKey = arguments[2] ? arguments[2] : "";
	this.sequentialMode = (arguments[3]===true);

	this.queue = new Array();
	this.activeRequest = null;	// Nur damit man weiß, ob gerade einer unterwegs ist.
}

/**
 *
 * Wird send() ohne irgendwelche Argumente aufgerufen, wird die
 * Queue abgearbeitet.
 *
 */
ATP.prototype.send = function( /* params, replyFields, callback=null */ ){
	if( arguments.length == 0 ){
		// Falls kein Request unterwegs und die Schlange nicht leer ist,
		// schicken wir den ersten aus der Queue ab.
		if( this.activeRequest == null && this.queue.length > 0 ){
			var data = this.queue.shift();
			this.dispatchRequest( data );
		}else{
			// Gibt's nix zu tun.
			return;
		}
	}

	// Wir haben Argumente, also fassen wir die Daten zusammen.
	var data = {
		params 		: arguments[0],
		replyFields	: arguments[1],
		callback 	: (arguments[2] ? arguments[2] : null)
	};

	// Jetzt müssen wir prüfen, ob wir in sequentialMode sind.
	// Falls ja, und falls ein Request unterwegs ist, speichern wir die Daten.
	if( this.sequentialMode && this.activeRequest != null ){
		this.storeRequest( data );
	}else{
		// Ansonsten ab mit dem Mist zum Server.
		this.dispatchRequest( data );
	}
};

ATP.prototype.storeRequest = function( data ){
	this.queue.push( data );
};

ATP.prototype.dispatchRequest = function( data ){
	var self = this;
	var request = Acu.getHttpRequest();
	if( !request ){
		throw "[ATP.dispatchRequest] Fetching request failed.";
	}
	request = this._prepareRequest( request, data );
	var params = this._compileParams( data.params );

	this.activeRequest = request;
	request.send( params );
};

ATP.prototype._prepareRequest = function( request, data ){
	var self = this;

	request.open( this.method, this.serverScript, true );
	request.setRequestHeader( "Content-type","application/x-www-form-urlencoded" );
	request.setRequestHeader( "Connection","close" );

    request.onreadystatechange = function() {
		if( request.readyState == 4 ){
			var responseObject = self._createResponseObject( request, data );
			if( data.callback ) data.callback( responseObject );
			self.activeRequest = null;	// Wir haben fertig.
			self.send();	// Arbeitet, wenn ohne Argumente aufgerufen, die Queue ab.
		}
    };

    return request;
};

ATP.prototype._compileParams = function( pObj ){
	// Erzeugt den Parameter-String und hängt den Access-Key, so vorhanden, am Ende an.
	var params = "atp_info=none";
	for( var p in pObj ){
		params += "&" + p + "=" + Acu.phpUrlEncode( pObj[p] );
	}
	if( this.accessKey != "" ) params += "&access_key=" + Acu.phpUrlEncode( this.accessKey );

	return params;
};

ATP.prototype._createResponseObject = function( request, data ){
	var responseObject = { error : true, errmsg : "" };

	if( request.status != 200 ){
		responseObject.errmsg = "[ATP._createResponseObject] Request failed: " + request.statusText;
		return responseObject;
	}

	try{
		var xmlDoc = request.responseXML;
alert( request.responseXML );
	}catch( e ){
		responseObject.errmsg = "[ATP._createResponseObject] Unable to retrieve XML: " + e;
		return responseObject;
	}

	if( !xmlDoc ){
		responseObject.errmsg = "[ATP._createResponseObject] No XML in server response: " + request.responseText;
		return responseObject;
	}

	var status = xmlDoc.getElementsByTagName( 'status' )[0].firstChild.nodeValue;
	if( !status ){
		responseObject.errmsg = "[ATP._createResponseObject] No status defined in XML.";
		return responseObject;
	}
	if( status != 'ok' ){
		var node = this.xmlNode2ATPObject( xmlDoc.getElementsByTagName( 'errmsg' )[0] );
		var errmsg = node.innerValue;
		responseObject.errmsg = errmsg;
		return responseObject;
	}

	var fields = data.replyFields;
	// Hole Felder. Erzeuge zu jedem ein Objekt und setze die in attributes
	// angegebenen Attribute sowie in innerValue den (korrekt decodierten)
	// inneren Wert des Tags.
	// Setze das Objekt als Property "field" im responseObject.
	// Zuletzt aktualisiere den Access-Key, falls notwendig.
	for( var i=0; i<fields.length; i++ ){
		var field = fields[i];
		try{
			responseObject[field.tagname] = this._extractResponsePropertyFromXML( xmlDoc, field );
		}catch( e ){
			responseObject.errmsg = "[ATP._createResponseObject] Error parsing server response: " + e;
			return responseObject;
		}
	}

	var key = xmlDoc.getElementsByTagName( 'access_key' );
	// Wir bekommen als Ergebnis (wenn überhaupt) einen Array
	// zurückgeliefert. Brauchen allerdings nur den ersten Eintrag.
	if( key && key.length > 0 ) key = key[0];

	responseObject.error = false;
	return responseObject;
};

ATP.prototype._extractResponsePropertyFromXML = function( xml, field ){
	var responseEntries = new Array();

	var entries = xml.getElementsByTagName( field.tagname );
	for( var i=0; i<entries.length; i++ ){
		responseEntries.push( this.xmlNode2ATPObject(entries[i], field.attributes) );
	}

	if( responseEntries.length == 0 ){
		return null;
	}else if( responseEntries.length == 1 ){
		// Bei nur einem Eintrag liefere den ersten zurück.
		return responseEntries[0];
	}else{
		return responseEntries;
	}
};

ATP.prototype.xmlNode2ATPObject = function( xmlNode/*, attributes=[]*/ ){
	var atpObj = {};
	attributes = arguments[1] ? arguments[1] : [];

	if( !xmlNode ) throw "[ATP.xmlNode2ATPObject] No xml node specified.";

	// FF zerlegt den Inhalt in 4096 Byte lange Stücke.
	// Acu.extractCompleteXMLNodeData setzt sie wieder zusammen.
	//var value = Acu.extractCompleteXMLNodeData( xmlNode.firstChild.nodeValue );
	var value = Acu.extractCompleteXMLNodeData( xmlNode );
	var encoding = null;
	try{
		encoding = xmlNode.getAttribute( 'encoding' );
		if( encoding == "url" ) value = Acu.phpUrlDecode( value );
	}catch( e ){}	// Falls das Attribut nicht gesetzt wurde.
	atpObj.innerValue = value;

	for( var i=0; i<attributes.length; i++ ){
		attribute = attributes[i];
		var value = xmlNode.getAttribute( attribute );
		if( encoding ){
			switch( encoding ){
				case "url":
					value = Acu.phpUrlDecode( value );
					break;
				default:
					throw "[ATP.xmlNode2ATPObject] Encoding not supported (" + encoding + ")";
			}
		}
		atpObj[attribute] = value;
	}

	return atpObj;
};

ATP.prototype.xy = function(  ){
	//
};

