/*  VESPA AJAX-Prototype Framework
 *  T-SYSTEMS - Multimedia Solutions GmbH
 *  Steffen Georgi
 *  (c) 2005-2008 
 *
 *--------------------------------------------------------------------------*/
/**
 * Methoden zu Elementen hinzufügen, um die
 * Quellen zu definieren.
 */
Element.addMethods({
		defineSourceBindingURL: function($super, sourceURL){
			// Definition des Source Bindings.
			this.sourceURL = sourceURL;
			this.hasSourceBinding = true;
		},
		getSourceBindingURL: function(){
			return this.sourceURL;
		},
		hasSourceBinding: function(){
			if (this.sourceURL == null)
				return false
			else
				return true;
		}
});


/**
 * Diese Klasse ist das Kernstueck der AJAX Kommunikation.
 * Die Klasse verwaltet die Include Anforderungen und fuehrt sie innerhalb einer Gruppe aus.
 * 
 * Grundidee:
 * Die der Entwicklung zugrunde liegende Idee entstand aus den Anforderungen der Fussnoten.
 * Dabei ist es wichtig, dass die includes innerhalb der Seite in einer definierten Reihen-
 * folge abgearbeitet werden. Alle Elemente (z.B. teaser) einer Seite besitzen potentiell 
 * Sternvermerke und diese Sternvermerke verweisen auf Fussnoten am Ende der Seiten.
 * 
 * Bsp.:
 * Teaser 1: Vermerk A, Vermerk C,
 * Teaser 2: Vermerk B, Vermerk C,
 * Teaser 3: Vermerk D, Vermerk C,
 * 
 * In den Fussnoten sollen gleiche Vermerkestexte gleich Referenzen enthalten. Texte sollen nicht 
 * mehrfach auftauchen.
 * 
 * Um den Fussnotentext richtig zu gestalten ist es notwendig, dass alle Teaser vor dem Fussnotentexten
 * gerufen werden.
 * 
 * 
 * 
 */
var RequestHandler	= Class.create({
	initialize : function(){
		this.requests = [];
		this.groups = new Hash();
		this.observer = null;
		this.defaultGroup = 'default';
	},
	/*
	 * der observer 
	 */
	registerProgressObserver : function(observer){
			this.observer = observer;
		
	},
	/*
	 * Liefert Information, ob ein Observer registriert ist oder nicht
	 */
	hasProgressObserver : function() {
		if(this.observer!=null) 
			return true;
		else 
			return false;	
	},
	/*
	 * Ruft die Update-Funktion des registrierten Observers 
	 */
	updateProgressObserver : function(groupName) {
		if(this.observer==null)
			return;
		var group = this.groups.get(groupName);	
		this.observer.updateStatus(group);		
	},	
	registerRequest : function(request){
		this.registerParallelRequestForGroup(request, null);
	},
	/**
	 * 
	 */
	registerParallelRequestForGroup : function(request, group){
		info("registriere Element mit ID '" + request.getElement().getAttribute('id') + "' und URL '" + request.getURL() + "' in Gruppe '" + group + "'");
		// falls group==null wird die Default-Gruppe erzeugt und unter dem key null
		// in die Hash gespeichert.
		var includeGroup = this.groups.get(group);
		if (includeGroup == null){
			includeGroup = new IncludeGroup(group);
			includeGroup.setObserverForFinalRequest(this);
			if (this.observer!=null)
			includeGroup.registerProgressObserver(this.observer);
			this.groups.set(group,includeGroup);
		}
		includeGroup.addParallelRequest(request);
		request.setGroup(includeGroup);
		
		
	},
	setFinalRequestForGroup : function(request, group){
		info("registriere FINALES Element mit ID '" + request.getElement().getAttribute('id') + "' und URL '" + request.getURL() + "' in Gruppe '" + group + "'");
		var includeGroup = this.groups.get(group);
		if (includeGroup == null){
			includeGroup = new IncludeGroup(group);
			if (this.observer!=null)
			includeGroup.registerProgressObserver(this.observer);
			this.groups.set(group,includeGroup);
		}
		includeGroup.setFinalRequest(request);
		request.setGroup(includeGroup);
		request.setIsFinalRequest();
	},
	/**
	 * process all request groups
	 */
	processRequestGroups : function(){
		var keys = this.groups.keys().sort()
		
		for (var index = 0, len = keys.length; index < len; ++index) {
			var key = keys[index];
			this.processRequestGroup(key)
		}
	},
	/**
	 * process a single group
	 */
	processRequestGroup : function(groupName){
			var group = this.groups.get(groupName);
			info("Prozessiere Elemente fuer Gruppe '"  + groupName + "'; Gruppe hat " + group.getParallelRequests().length + " Body Elemente");
			// die Gruppe schliessen. Dies
			// ist notwendig um weitere, rekursive Inkludes
			// nicht auch an diese Gruppe zu binden.
			group.setLocked();
			for (var index = 0, len = group.getParallelRequests().length; index < len; ++index) {
				var request = group.getParallelRequests()[index];
				info("Anfrage fuer Element mit ID " + request.getElement().getAttribute('id') + " und URL " + request.getURL() + ".");
				new Ajax.VESPAInclude(request);
			}
			// initial status check
			group.checkStatusChange();
	},
	processFinalRequest : function (group){
		// aussteigen, wenn kein finaler Request definiert ist
		if (group.getFinalRequest() == null){
			info("Abarbeitung der Requestgruppe beendet.");
			if(group.getName()=='default')
				//default-Gruppe entfernen (Ist unter Key=null registriert)
				this.unsetGroup(null); 
			else 	
				this.unsetGroup(group.getName());
			return;
		}
		
		info("Prozessiere finalen Request fuer Gruppe '" + group.getName() + "'");
		// wenn der finale Request auch bei Fehlern
		// oder keine Fehler passiert,
		// dann include
		if (group.getProcessFinalRequestOnError() || !group.getErrorOnParallelRequests()){	
			new Ajax.VESPAInclude(group.getFinalRequest());
		} else {
			error("Fehler beim Aufruf eines ParallelRequests; Gruppe ist so konfiguriert, dass der finale Request nicht ausgeführt wird.");
			// den finalen Request als abgeschlossen kennzeichnen
			group.getFinalRequest().setCompleted();
			// die Error Handling Methode rufen
			handleErrors(group.getFinalRequest(), true);
		}
		group.setIsFinished();
		
		// schaun wir mal, ob alle gruppen zurueck sind
		// fehlerseiten mal ausgeschlossen
		var keys = this.groups.keys();
		var finished = true;
		for (var index = 0, len = keys.length; index < len; ++index) {
			var key = keys[index];
			var group = this.groups.get(key);
			finished = finished && group.getIsFinished();
		}
		// Gruppe Deregistrieren
		info("Abarbeitung der Requestgruppe beendet.");
		if(group.getName()=='default') 
			//default-Gruppe entfernen (Ist unter Key=null registriert)
			this.unsetGroup(null); 
		else 	
			this.unsetGroup(group.getName());
	},
	getGroups : function(){
		return this.groups.values();
	},
	unsetGroup : function(groupName) {
		if(this.groups.get(groupName)) {
			this.groups.unset(groupName);
			info("Gruppe '"+groupName+"' aus Gruppen-Registrierung entfernt.");
		}
	}
});


/** definiert einen Request mit dem
  * Element welches ersetzt werden soll 
  * und der zugehörigen URL
  * speichert Informationen uber die Gruppe, der er angehört
*/
var AJAXIncludeRequest = Class.create({
	/*
	 * der Constructor
	 */
	initialize : function(element, url, errorPage){
		this.element = element;
		this.url = url;
		this.isComplete = false;
		this.withError = false;
		this.isFinalRequest = false;
		this.statusCode = -1;
		this.errorStackSize = 0;
		this.errorPage = errorPage;
		this.requestMethod = 'get';
		//this.usePageID=true;
		this.startTime = null;
		this.endTime = null;
		this.status="defined";
		this.postParameters=null;
		this.newElement = null;
		this.isReplaceReceiverWhenError=true;
		this.eventNames = []; // Array mit Event-Namen fuer Handler, die nach Beendigung des Request gerufen werden 
	},
	setNewElement : function(element){
		this.newElement = element;
	},
	getNewElement : function(){
		return this.newElement;
	},
	setPostParameters : function (params){
		this.postParameters = params;
		this.useMethodPost();
		
	},
	addPostParameter : function (aKey, aValue){
		if(this.postParameters==null)
			this.postParameters = new Hash();
			
		this.postParameters.set(aKey,aValue);
		this.useMethodPost();
	},
	getPostParameters : function(){
		return this.postParameters;
	},
	setName : function(name) {
		this.name = name
	},
	getName : function (){
		return this.name
	},
	setStatus : function(status){
		this.status = status;
	},
	getStatus : function(){
		return this.status;
	},
	/*
	 *	Starttime als date 
	 */
	setStartTime : function(startTime){
		this.startTime = startTime;
	},
	/*
	 *	endTime als date
	 */
	getEndTime : function(){
		return this.endTime;
	},
	/*
	 * Starttime als date
	 */
	getStartTime : function(){
		return this.startTime;
	},
	/*
	 *	endtime als date
	 */
	setEndTime : function(endTime){
		this.endTime = endTime;
	},
	/*
	 * laufzeit des requests wenn er
	 * bereits ausgefuehrt worden ist.
	 * ansonsten -1
	 */
	getRuntime : function(){
		if (this.endTime == null){
			return -1;
		}
		return this.endTime.getTime() - this.startTime.getTime();
	},
	/*
	 * setzt die methode fuer den 
	 * http request auf get (default)
	 */
	useMethodGet : function() {
		this.requestMethod = 'get';
	},
	/*
	 * setzt die methode fuer den 
	 * http request auf post (default=get)
	 */
	useMethodPost : function() {
		this.requestMethod = 'post';
	}
	/*
	 * gibt die gewaehlte http request 
	 * methode zurueck
	 */,
	getRequestMethod : function() {
		return this.requestMethod;
	},
	
	/*  
	 * error stack erhoehen
	 * wird verwendet um die Anzahl der
	 * Errors zum Request zu verwalten.
	 * 
	 */
	incErrorStack: function(){
		this.errorStackSize++;	
	},
	/*
	 * Die groesse des Stacks.
	 * Eigentlich nur die Anzahl der bereits aufgetretenen
	 * Fehler im Context dieses Requests.
	 * Falls z.B. die Error Seite angegeben wurde
	 * diese allerdings nicht da ist und der
	 * Request in einen zweiten Fehler laueft
	 * der dann zur endless loop werden koennte.
	 */
	getErrorStackSize : function(){
		return this.errorStackSize;
	},
	/*
	 * setzt die URL zum Error Template.
	 */
	setErrorPage : function(errorUrl){
		this.errorPage = errorUrl;
	},
	/*
	 * gibt die URL zum Error Template zurueck.
	 */
	getErrorPage : function(){
		return this.errorPage;
	},
	/*
	 * setzt den Namen fuer einen Eventhandler, der nach 
	 * Beendigung des Request gerufen wird
	 */
	addEventName : function(evtName){
		this.eventNames[this.eventNames.length] = evtName;
	},
	/*
	 * gibt Namen fuer Eventhandler zurueck
	 */
	getEventNames : function(){
		return this.eventNames;
	},
	/*
	 * setzt die URL
	 */
	setURL : function(aURL){
		this.url = aURL;
	},
	/*
	 * gibt die URL zurueck
	 */
	getURL : function(){
		
		/*
		  
		 if (this.usePageID && pageID!=null && !this.url.include('pageID')){
			if (this.url.include('?')){
				return this.url + "&pageID=" + pageID;
			}else {
				return this.url + "?pageID="+ pageID;
			}
		}*/
		return this.url;
	},
	/*
	 * gibt das Element zurueck, das
	 * durch einen ajax Request zu ersetzten ist
	 */
	getElement : function(){
		return this.element;
	},
	/*
	 * wird auf true gesetzt, wenn der Request erfolgreich beendet wurde
	 */
	setCompleted : function(statusCode){
		// statusCode merken
		this.statusCode = statusCode;
		if (this.group != null){
			this.group.checkStatusChange();
		}
		if (statusCode==200){
			this.setCompleted_internal(); 
		}else {
			this.setCompletedWithError_internal();	
		}
	},
	/*
	 * status der aktuellen Gruppe checken.
	 */
	checkStatusChange : function (){
		if (this.group != null){
			this.group.checkStatusChange();
		}
	},
	/*
	 * wird auf true gesetzt, wenn der Request erfolgreich beendet wurde
	 */
	setCompleted_internal : function(){
		this.isComplete = true;
		// finalen Request anfordern wenn gruppe definiert
		// und dies nicht der finale Request ist
		if (this.group != null && !this.getIsFinalRequest()){
			this.group.checkForFinalRequest();
		}
	}
	,
	/*
	 * wird gerufen, wenn der Request mit Fehlern beendet wurde
	 * Falls eine Gruppe an dem Request haengt, wird der 
	 * Status an die Gruppe uebertragen
	 */
	setCompletedWithError_internal : function(){
		this.isComplete = true;
		this.withError = true;
		if (this.group != null && !this.getIsFinalRequest()){
			this.group.setErrorOnParallelRequests();
		}
	},
	/*
	 * ist wahr, wenn der AjaxRequest beendet wurde
	 */
	getIsComplete : function(){
		return this.isComplete;
	},
	/*
	 * ist wahr, wenn der AjaxRequest mit Fehlern beendet wurde
	 */
	returnedWithError : function(){
		return this.withError;
	},
	/**
	 * gibt den HTTP StatusCode zurück.
	 * wenn != 200 dann Fehler
	 */
	getStatusCode : function(){
		return this.statusCode;
	},
	/*
	 * Die Gruppe in der der Request aufgerufen wurde
	 * sh. Include Group.
	 * An der Gruppe wird die Funktion checkForFinalRequest()
	 * aufgerufen.
	 */
	setGroup : function(group){
		this.group = group;
	},
	/*
	 * wenn dies der finale Request in der Gruppe ist, dann 
	 * muss der observer nicht benachrichtigt werden.
	 * ACHTUNG: endless loop falls nicht gesetzt. 
	 */
	setIsFinalRequest : function(){
		this.isFinalRequest = true;
	},
	getIsFinalRequest : function(){
		return this.isFinalRequest;
	},
	/*
	 * Soll das Empfaenger-Element im Fehlerfall komplett 
	 * ersetzt werden (true) oder 
	 * nur der Inhalt innerhalb des Elements (false) 
	 */	
	setIsReplaceReceiverWhenError: function(isReplace) {
		this.isReplaceReceiverWhenError = isReplace;
	},
	getIsReplaceReceiverWhenError : function() {
		return this.isReplaceReceiverWhenError;
	}
});


/**
 * Beispiel fuer einen ProgressObserver
 * Der Observer muss an den requestHandler gebunden werden.
 * requestHandler.registerObserver(new SimpleProgressObserver('status'));
 * @param {Object} elementID ID des Elements, in den der Status geschrieben wird
 */
var SimpleProgressObserver = Class.create({
	initialize : function(elementID){
		this.elementID = elementID;
	},
	/*
	 * Einfache Beispielfunktion.
	 * Damit wird in das Element einfach der aktuelle Status geschrieben.
	 */ 
	updateStatus : function(group){
		// toDO: hier die prozentuale ergebnis berechnen und das element updaten
		// siehe DebuggingProgressObserver
	}
});

/**
 * Beispiel fuer einen ProgressObserver
 * Der Observer muss an den requestHandler gebunden werden.
 * requestHandler.registerProgressObserver(new DebuggingProgressObserver('status'));
 * @param {Object} elementID ID des Elements, in den der Status geschrieben wird
 * 	<div id="progress_state">
 *		<table>
 *		<thead>
 *			<tr><td>url1</td><td>status</td><td>runtime</td><td>typ</td></tr>
 *		</thead>
 *		<tbody>	
 *			<tr><td>/Apache5/aaa</td><td>defined</td><td>-1</td><td>P</td></tr>
 *		</tbody>
 *		</table>
 *	</div>
 */
 
 var DebuggingProgressObserver = Class.create({
	initialize : function(elementID){
		this.elementID = elementID;
	},
	updateStatus : function(group){

		if (group==null){
			return;
		}
		var fullElementID = this.elementID + "_" + group.getName();
		info("suche element mit ID '" + fullElementID + "'");
		var element = $(fullElementID);
		if (element==null){
			error("element " + fullElementID + " nicht gefunden");
			return;
		}
		var htmlText = "<table border='1'><thead><tr><td>RequestName</td><td>Status</td><td>URL</td><td>Start</td><td>Runtime</td><td>typ</td></tr></thead><tbody>";
		// parallelRequests
		var parallelRequests = group.getParallelRequests();
		for (var index = 0, len = parallelRequests.length; index < len; ++index) {
			var parallelRequest = parallelRequests[index];
			htmlText = htmlText + "<tr class='observerTableContent'>";
				// Name
				htmlText = htmlText + "<td>" ; 
				htmlText = htmlText + (parallelRequest.getName()== null ? "unnamed" : parallelRequest.getName());
				htmlText = htmlText + "</td>";
				// status
				htmlText = htmlText + "<td>" + parallelRequest.getStatus() + "</td>";
				// url
				htmlText = htmlText + "<td>" + truncStr(parallelRequest.getURL(),100) + "</td>";
				// start
				htmlText = htmlText + "<td>" ; 
				htmlText = htmlText + (parallelRequest.getStartTime()== null ? "-" : parallelRequest.getStartTime());
				htmlText = htmlText + "</td>";
				// runtime
				htmlText = htmlText + "<td>" + parallelRequest.getRuntime() + "</td>";
				// typ
				htmlText = htmlText + "<td>p</td>";
			htmlText = htmlText + "</tr>";
		}
		// finalen Request verarbeiten
		var finalRequest = group.getFinalRequest();
		if (finalRequest!=null){
			htmlText = htmlText + "<tr class='observerTableContent'>";
				htmlText = htmlText + "<td>" ; 
				htmlText = htmlText + (finalRequest.getName()== null ? "unnamed" : finalRequest.getName());
				htmlText = htmlText + "</td>";
				// status
				htmlText = htmlText + "<td>" + finalRequest.getStatus() + "</td>";
				// url
				htmlText = htmlText + "<td>" + truncStr(finalRequest.getURL(),100) + "</td>";
				// start
				htmlText = htmlText + "<td>" ; 
				htmlText = htmlText + (finalRequest.getStartTime()== null ? "-" : finalRequest.getStartTime());
				htmlText = htmlText + "</td>";
				// runtime
				htmlText = htmlText + "<td>" + finalRequest.getRuntime() + "</td>";
				// typ
				htmlText = htmlText + "<td>f</td>";
			htmlText = htmlText + "</tr>";
		}
		htmlText = htmlText + "<tr><td colspan='6'><input type='button' value='Clear Table' onclick='clearObserverTable(\"observerTableContent\",\""+group.getName()+"\")'/></td></tr>"
		htmlText = htmlText + "</tbody></table>";
		
		element.update(htmlText);
	}
});

function clearObserverTable(rowName, requestGroupName) {
	var elems = document.getElementsByClassName(rowName);
	for (var index = 0, len = elems.length; index < len; ++index) {
		var elem = elems[index];
		if($(elem))  {
			$(elem).remove();
		}
	}
	if(requestHandler!=null) {
		requestHandler.unsetGroup(requestGroupName);
	}
}

// Zerschneidet die übergebene Zeichenkette (durch Einfügen von <br>)
// in Stücke mit der übergebenen Länge
function truncStr(str, truncateLength) {
	var counter = 0;
	var retStr = "";
	
	if(str.length<truncateLength)
		return str;
		
	while(counter<str.length) {
		var z = eval(counter+truncateLength);
		retStr = retStr + str.substr(counter, z)+"<br/>";
		counter = z;
	}
	return retStr;
}


/**
 * Definition einer Gruppe von Requests
 * Wobei die Gruppe aus einer beliebigen 
 * Anzahl von parallelen Requests und (0-) einem
 * finalen Request bestehen kann. 
 * 
 */
var IncludeGroup = Class.create({
	initialize : function(groupName){
		this.parallelRequests = [];
		this.finalRequestOnError = false;
		this.isFinished = false;
		this.progressObservers = [];
		if (groupName!=null){
			this.name = groupName;
		}else{
			this.name = "default";
		}
		this.locked = false;
	},
	/**
	 * Diese Funktion sollte gerufen werden
	 * um die Erweiterung der Gruppe zu verhindern
	 */
	setLocked : function (){
		this.locked = true;
	},
	/**
	 * Gibt true zurueck, wenn die
	 * Gruppe geschlossen wurde und keine Requests
	 * mehr annehmen kann
	 */
	getISLocked : function(){
		return this.locked;
	},
	/*
	 * gibt den Namen der Request gruppe zurueck
	 */
	getName : function(){
		return this.name;
	},
	/*
	 * prueft den aktuellen Status der Requests
	 * und gibt die Informationen an die registrierten
	 * progressObserver in Form der Klasse ProgressStatus weiter
	 */
	checkStatusChange : function(){
		// inform observers
		for (var index = 0, len = this.progressObservers.length; index < len; ++index) {
			var progressObserver = this.progressObservers[index];
			progressObserver.updateStatus(this);
		}
		
	},
	/*
	 * hinzufuegen von observern, die den status
	 * der includes verfolgen.
	 */
	registerProgressObserver : function(observer){
		if (observer==null){ 
			error("observer is null");
			return;
		}
		this.progressObservers[this.progressObservers.length] = observer;
	},
	/*
	 * Ist true, wenn alle requests abgearbeitet worden sind.
	 * Wenn Fehler auftreten, dann erfolgt die Fehlerbehandlung
	 * nach diesem Zeitpunkt.
	 */
	getIsFinished : function(){
		return this.isFinished;
	},
	/*
	 * methode rufen, um auf der gruppe den finished
	 * status zu setzten
	 */
	setIsFinished : function(){
		this.isFinished = true;
	},
	/*
	 * hinzufuegen eines Requests zur parallelen Ausfuehrung
	 * im Body
	 */
	addParallelRequest : function(request){
		if (!this.locked){
			this.parallelRequests[this.parallelRequests.length] = request;	
		}else {
			error("Group "+this.getName()+" is already locked; cannot add parallelRequest");
		}
	},
	/*
	 * hinzufuegen eines finalen Requests z.B. fuer den footer
	 */
	setFinalRequest : function(request){
		if (!this.locked){
			this.finalRequest = request;
		}else {
			error("Group "+this.getName()+" is already locked; cannot add finalRequest");
		}
	},
	/*
	 * gibt alle parallelen Requests zurueck
	 */
	getParallelRequests : function(){
		return this.parallelRequests;
	},
	/*
	 * gibt den finalen Request zurueck, kann null sein
	 */
	getFinalRequest : function(){
		return this.finalRequest;
	},
	/*
	 * prueft, ob alle parallelen requests beendet wurden
	 * und ruft den observer um den finalen auszuführen.
	 * Falls einer der Requests nicht erfolgreich war, dann wird auch der
	 * finale Request an den Observer gemeldet.
	 * Ist kein Observer registriert, dann findet keine Überprüfung statt.
	 * 
	 */
	checkForFinalRequest : function(){
		// ist ein observer registriert?
		if (this.observer!=null){
			// dann alle subRequests checken ob abgeschlossen
			// wenn ja, dann finalen request ausfuehren
			var finalize = true;
			for (var index = 0, len = this.parallelRequests.length; index < len; ++index) {
				var item = this.parallelRequests[index];
				finalize = finalize && (item.getIsComplete());
			}
			// alle fertig
			if (finalize){
				this.observer.processFinalRequest(this);
			}		
		}
	},
	/*
	 * hier ein Object registieren, dass dann gerufen wird, wenn alle parallelen
	 * Requests abgeschlossen sind.
	 * Am object wird die methode processFinalRequest() gerufen
	 * Im Standard wird die Function vom Request Handler
	 * verwendet. 
	 */
	setObserverForFinalRequest : function(observer){
		this.observer = observer;
	},
	/**
	 * ist true, falls bei den Requests 
	 * ein Fehler auftrat. In diesem Fall 
	 * kann der finale Request eventuell nicht ausgefuehrt 
	 * werden.
	 */
	getErrorOnParallelRequests : function(){
		return this.errorOnParallelRequest;
	},
	setErrorOnParallelRequests : function(){
		this.errorOnParallelRequest = true;
	},
	/**
	 * wenn FinalRequestOnError= true, dann
	 * wird der finale Request (z.B. FootNote) ausgefuehrt auch wenn 
	 * die parallelen Requests (z.B. teaser) auf Fehler
	 * laufen
	 */
	setProcessFinalRequestOnError : function(){
		this.finalRequestOnError = true;
	},
	/**
	 * sh. getFinalRequestOnError
	 */
	getProcessFinalRequestOnError : function(){
		return this.finalRequestOnError;
	}
});


Ajax.VESPAInclude = Class.create(Ajax.Request, {
  initialize: function($super, request, options) {
	if (request==null || request.getURL()==null || request.getElement()==null){
		error("Ajax.VESPAInclude: given request or URL or Element is null");
		return;
	}
	info("VESPAInclude: " + request.getURL());
	// aktuellen Request merken
	this.includeRequest = request;
    // container definieren
    this.container = {
      success: (request.getElement().success || request.getElement()),
      failure: (request.getElement().failure || (request.getElement().success ? null : request.getElement()))
    };
	// options ueberladen um eigene methode zu rufen
    options = Object.clone(options);
    // methode setzten.
    options.method = request.getRequestMethod();
    var onComplete = options.onComplete;
    options.onComplete = (function(transport, json) {
      this.requestComplete(transport);
      if (Object.isFunction(onComplete)) onComplete(transport, json);
    }).bind(this);
	// laufzeit ermitteln
	if (request.getRequestMethod()=='post'){
		 options.parameters=request.getPostParameters();
	}	 
	options.evalJS='force';
	
	this.includeRequest.setStartTime(new Date());
	this.includeRequest.setStatus('started');
	// super rufen
    $super(request.getURL(), options);
  },
  /* 
   * wird aufgerufen, wenn der Aufruf abgeschlossen ist
   */	
  requestComplete: function(transport) {
	// laufzeit ermitteln
   	this.includeRequest.setEndTime(new Date());
   	this.includeRequest.setStatus('received');
   	// ausgeben
   	info (this.includeRequest.getURL() + " needs " + this.includeRequest.getRuntime() + "ms") 

   	this.includeRequest.setCompleted(transport.status);
    // wenn die antwort ok war, dann Text ersetzten
    if (transport.status == 200){
	    info("Request " + this.includeRequest.getURL() + " completed with " + transport.status);
    	
	    var responseText = transport.responseText;
	    var receiver = this.container[this.success() ? 'success' : 'failure'],
	        options = this.options;
		var elementsParentNode = this.includeRequest.getElement().parentNode;
		
	    if (!options.evalScripts) responseText = responseText.stripScripts();
	    
	    // ersetzten des Textes
	    if (receiver = $(receiver)) {
	    	if(!this.includeRequest.getIsReplaceReceiverWhenError() &&
	    		this.includeRequest.getErrorStackSize()>0)
	    		// wenn bei vorhergehenden Request Fehler aufgetreten sind und
	    		// Receiver-Element als Rahmen-Element erhalten werden soll. 
	    		receiver.update(responseText);
		    else 
	    		receiver.replace(responseText);
	    }	
	    
	    // scripts innerhalb der geladenen Response ausführen
		var scripts = transport.responseText.extractScripts();
		scripts.map(function(script) {
			try {
				return eval(script);
			} catch (e) {
				error(e);
			}	
		});
    } else {
    	// Fehlerseite anzeigen
    	error(" statuscode " + transport.status + " beim Aufruf von " + this.includeRequest.getURL());
   		this.includeRequest.setStatus('errorHandling (Statuscode: '+transport.status+')');
   		this.includeRequest.checkStatusChange();
    	handleErrors(this.includeRequest);
    }
    try {
    	// fuehrt registierte Events aus
    	var evtNames = this.includeRequest.getEventNames();
    	for (var index = 0, len = evtNames.length; index < len; ++index) {
			var eventName = evtNames[index];
			if (eventName != null){
	    		info("EventHandler mit Namen " + eventName + " wird gerufen.");
    			document.fire(eventName);
    			document.stopObserving(eventName);
    		}	
    	}
    } catch(e) {}	
  }
});



/**
 * Diese Funktion kuemmert sich um die
 * Anzeige der Fehlerseiten fuer den Fall, dass der
 * Request mit einem StatusCode != 200 zurueckkommt.
 * 
 */
function handleErrors(request, causedPreventFinalRequest){
	// nichts zu tun wenn der returnCode =200 ist und
	// der Aufruf auch nicht auf Grund fehlgeschlagener 
	// finaler
	if ( request.getErrorStackSize() > 1 ||
		((request.getStatusCode()== 200) && ! causedPreventFinalRequest)) return;


 	// die default Fehlerseite
 	var theErrorURL = defaultErrorURL;
	// nur wenn dies erst der erste Aufruf ist, dann wird versucht eine spezifische Seite zu finden
	if (request.getErrorStackSize() < 1){
			// erst einmal am Include nachschauen,
			// dort koennte eine errorPage definiert sein.
			var temp = request.getErrorPage();
			info("ErrorPage: " + request.getURL() + " " + request.getErrorPage());
			if (temp== null && causedPreventFinalRequest){
				// spezielles Default Handling/Seite fuer die Fussnoten
				// z.B. FootNote wenn einer der Requests im Body nicht kam.
				temp = preventFinalRequestErrorURL;	
			}
			if (temp==null){
				// dann schauen wir mal, ob eine spezielle Seite fuer den
				// Fehlercode da ist.
				temp = this.errorURLs.get(request.getStatusCode());
				if (temp!=null)
					info(request.getStatusCode() + ":" + temp);
			}
			// wenn was gefunden wurde, dann 
			// die standard URL damit ueberschreiben.
			if (temp!=null && temp.length > 0){
				theErrorURL = temp;
		}
	}	

	// URL gefunden, dann los
	// remember origin URL
	var tempURL = request.getURL();
	request.setURL(theErrorURL);
	// Fehlerversuche merken um endless loop zu vermeiden
	var errorRequest = new AJAXIncludeRequest(request.getElement(),request.getURL());
	errorRequest.incErrorStack();
	errorRequest.setIsReplaceReceiverWhenError(request.getIsReplaceReceiverWhenError());
	new Ajax.VESPAInclude(errorRequest);
}

/**
 * Registriert neuen EventHandler am Request.
 * Dieser wird nach Beendigung des Ajax-Requests ausgeführt
 * (EventHandler sollte am Request-Objekt registriert werden, damit
 *  bei parallelen AJAX-Requests Eindeutigkeit gewährt ist 
 * 	-> eventName muss eindeutig sein)
 * Parameter:
 * - AJAXIncludeRequest-Objekt
 * - Name unter welchen der Event registiert werden soll
 * - Funktion die beim Auslösen des Events ausgeführt werden soll
 */
function registerEventHandler(requestObj, eventName, fkt) {
	if(requestObj==null) return;

	requestObj.addEventName(eventName);
	// Fktaufruf in try/catch Block einschließen, wenn nicht garantiert ist,
	// dass Fktimplementierung vorhanden ist -> Unterdrücken von JS-Fehlern
	// -> Nutzen der Fkt 'tryCatchWrapper(fktname)'
	document.observe(eventName, fkt);
	info("Eventhandler mit Namen "+eventName+" am Request registriert ");
}

// Wrapper für Funktionsaufrufe, wo nicht sicher 
// ist, dass Fktimplementierung vorhanden ist.
// Wird verwendet bei Events nach Ajax-Calls
function updateFunctionWrapper() {
	// Bestimmte CMS-Komponenten bei gesetztem callback=off wieder ausblenden
	try {
		hideCallbackComponents();
	} catch(e) {}
  
	// Startzeit für den StandbyLayer zurücksetzen
	try {
		resetStandbyLayer();
	} catch(e) {}
	
	// kleinen Warenkorb aktualisieren
	try {
		updateBasketPositions();
	} catch(e) {}
	
	// Event Handler für Klick auf Hilfe-Link registrieren
	try {
		registerHelpLink();
	} catch(e) {}
	
	// Event Handler für Klick auf Footnote-Link registrieren
	try {
		registerFootnoteLink();
	} catch(e) {} 
}

/**
 * dies ist eine einfache Funktion zum Ersetzen 
 * eines elements in durch das Ergebnis eines
 * Requests
 * Es wird die HTTP-get methode verwendet.
 */
function vespa_replace (elementID, url, errorURL){
	if(!$(elementID)) {
		error("[vespa_replace];elementID '"+elementID+"' nicht vorhanden.");
		return;
	}	
	var decodeUrl = decodeAmpersand(url);
	
	// Neues Requestobjekt erzeugen
	var request = new AJAXIncludeRequest($(elementID), decodeUrl, decodeAmpersand(errorURL));
	
	info("[vespa_replace];ersetzte: " + $(elementID).getAttribute('id') + " durch " + decodeUrl);
	sendReplaceCall(request, elementID, decodeUrl, decodeAmpersand(errorURL));
}

/**
 * dies ist eine einfache Funktion zum Ersetzen 
 * eines elements in durch das Ergebnis eines
 * Requests
 * Es wird die HTTP-post methode verwendet.
 * ACHTUNG: bessere Sicherheit aber kein Caching
 * Für die Verwendung der post-methode muessen die parameter als 
 * Hash uebergeben werden.
 *
 * elementID ... Element, das ersetzt werden soll
 * url ... Auszuführende URL
 * params ... Parameter, die im Request mitgesendet werden
 * errorUrl ... Url für Anzeige von ErrorMeldung
 * elemLoader ... Element, das Ladeanzeige-Nachricht enthält
 * loaderPosition ... Element, an dessen Stelle die Ladeanzeige angezeigt werden soll
 *                        (wenn abweichend von Ersetzungstelle) 
 * jsResultFunctions ... Auflistung von Funktionsnamen, die nach dem Ausführen des Ajax-Request 
 *						   ausgeführt werden sollen
 */
function vespa_replaceWithPost (elementID, url, params, errorURL, elemLoader, loaderPosition, jsResultFunctions){
	if(!$(elementID)) {
		error("[vespa_replaceWithPost];elementID '"+elementID+"' nicht vorhanden.");
		return;
	}
	
	if(params)
		var parameters = params.toObject();
		
	var decodeUrl = decodeAmpersand(url);	
		
	// Neues Requestobjekt erzeugen
	var request = new AJAXIncludeRequest($(elementID), decodeUrl, decodeAmpersand(errorURL));
	
	// Ladeanzeige... 
	processReloader(elementID, elemLoader, loaderPosition, request);
	
	// registerEvents
	registerEvents(request, elementID, jsResultFunctions);	
	
	info("[vespa_replaceWithPost];ersetzte: " + $(elementID).getAttribute('id') + " durch " + decodeUrl + " mit parameter " + parameters);
	sendReplaceCall(request, elementID, decodeUrl, decodeAmpersand(errorURL), true, parameters, false);
}

/**
 * Dies ist eine Erweiterung zur Funktion "vespa_replaceWithPost"
 * Dabei werden die parameter von der uebergebenen Form
 * selbststaendig extrahiert.
 * elementID ... Element, das ersetzt werden soll
 * url ... Auszuführende URL
 * form	... Id des Formulars für Extraction von Parametern
 * errorUrl ... Url für Anzeige von ErrorMeldung
 * elemLoader ... Element, das Ladeanzeige-Nachricht enthält
 * loaderPosition ... Element, an dessen Stelle die Ladeanzeige angezeigt werden soll
 *                        (wenn abweichend von Ersetzungstelle) 
 * jsResultFunctions ... Auflistung von Funktionsnamen, die nach dem Ausführen des Ajax-Request 
 *						   ausgeführt werden sollen
 */
function vespa_replaceWithPostFromForm (elementID, url, form, errorURL, elemLoader, loaderPosition, jsResultFunctions){
	if(!$(elementID)) {
		error("[vespa_replaceWithPostFromForm];elementID '"+elementID+"' nicht vorhanden.");
		return;
	}	
	
	// Parameter auslesen
	if(form!=null)
		var parameters = form.serialize(true);
	
	var decodeUrl = decodeAmpersand(url);
	
	// Neues Requestobjekt erzeugen
	var request = new AJAXIncludeRequest($(elementID), decodeUrl, decodeAmpersand(errorURL));
	
	// Ladeanzeige... 
	processReloader(elementID, elemLoader, loaderPosition, request);
	
	// registerEvents
	registerEvents(request, elementID, jsResultFunctions);
		
	info("[vespa_replaceWithPostFromForm];ersetzte: " + $(elementID).getAttribute('id') + " durch " + decodeUrl);
		
	sendReplaceCall(request, elementID, decodeUrl, decodeAmpersand(errorURL), true, parameters, false);
}

// +++++ Interne Funktionen +++++

/*
 * Registriert neue EventHandler am Request.
 * Die Namen der zu rufenden Js-Funktionen werden 
 * als Array in 'jsFunctions' übergeben
 * requestObj ... Request-Objekt
 * uniqueID ... für eindeutigen EventNamen
 * jsFunctions ... Array mit Funktionsname 
 */
function registerEvents(requestObj, uniqueID, jsFunctions) {
	if(jsFunctions==null) return;
	
	for (var index = 0, len = jsFunctions.length; index < len; ++index) {
		var jsFkt = jsFunctions[index];
		var evtName = 'ajax:RequestCompleteFkt'+index+'_'+uniqueID;
		if (jsFkt != null && jsFkt != "") {
			// Event am Request registrieren
	    	registerEventHandler(requestObj, evtName, jsFkt);
    	}	
    }
}

// Wandelt alle '&amp;' im URL in '&' um.
function decodeAmpersand(url) {
  return (typeof(url) == "string") ? url.replace(/&amp;/gi, "&") : null;
}

// befüllt anhand der übergebenen Parameter
// AJAXIncludeRequest und sendet ihn ab
function sendReplaceCall(request, elementID, url, errorURL, isPostRequest, parameters, isReplaceReceiverWhenError) {
	if(isPostRequest)
		request.useMethodPost();
	if(parameters!=null)
		request.setPostParameters(parameters);
	 		
	if(isReplaceReceiverWhenError==false)
		// ersetzt im Fehlerfall nur den Elementinhalt des Elements mit ID "elementID" 
		request.setIsReplaceReceiverWhenError(false);
		
	// Wenn Observer vorhanden ist, Gruppe erzeugen
	if(requestHandler.hasProgressObserver()) {
		requestHandler.registerParallelRequestForGroup(request, "replace")
		// Anzeige des Observers aktualisieren
		requestHandler.updateProgressObserver("replace");
	}	
	
	new Ajax.VESPAInclude(request);
}

/** 
 * Verarbeitung der Reload-Anzeige
 */ 
function processReloader(replaceElemID, reloaderElemID, reloaderPositionElemID, requestObj) {
	if($(reloaderPositionElemID)) {
		// Anzeige an anderer Stelle als Replace-Element 
		var newElemID = replaceElemID+"_"+reloaderPositionElemID+"_TmpInfo";
		var newElemContent ="<div id='"+newElemID+"'></div>";
		$(reloaderPositionElemID).update(newElemContent);
			
		showReloader(newElemID, reloaderElemID);

		// EventHandlerName registrieren, für Event, dass später
		// Ladeanzeige wieder ausblendet, wenn diese an anderer Stelle als 
		// Replace-Element steht!
		// Achtung evtName muss eindeutig sein!
		registerEventHandler(requestObj, 'ajax:RequestComplete_'+reloaderPositionElemID+'_'+replaceElemID, function() {try{$(newElemID).remove();}catch(e){}});
	} else 
		showReloader(replaceElemID, reloaderElemID);
}

// Ladeanzeige während des AJAX-Requests
function showReloader(replaceElemID, reloaderElemID) {
	// Custom-Element für Ladeanzeige vorhanden? 
	if(reloaderElemID!=null && $(reloaderElemID))
		changeElemContent(replaceElemID, reloaderElemID);
	else // Fallback: Default-Einstellung nutzen wenn möglich	
		changeElemContent(replaceElemID, 'ajaxDefaultReloaderView');
}

// tauscht den Content des Elements 'elemID'
// mit dem Content des Elements 'changeElemID'
function changeElemContent(elemID, changeElemID) {
	if($(elemID) && $(changeElemID)) {
		$(elemID).update($(changeElemID).innerHTML);
	}
}

/**
 * wrapper function fuer das
 * um die methode von RequestHandler auf das Event load 
 * binden zu koennen
 */
function callRequestHandler(){
	requestHandler.processRequestGroups();
}

/**
 * logger methoden
 * koennen durch projektweite Definitionen ueberladen werden.
 */
function info(message){
	if (myConsole != null){
		myConsole.info(message);
	}
}

function log(message){
	if (myConsole!=null)
	myConsole.log(message);
}

function error(message){
	if (myConsole!=null)
	myConsole.error(message);
}

/**
 * wird noch nicht unterstützt
 */
function markIncludes(){
	//style="border-style: double; border-color: red"
	var groups = requestHandler.getGroups();
	for (var index = 0, len = groups.length; index < len; ++index) {
			var group = groups[index];
			if (group != null){
				info(group.getName());
				var parallelRequests = group.getParallelRequests();
				for (var includeIndex = 0, len = parallelRequests.length; includeIndex < len; ++includeIndex) {
					info(group.getName() + " request " + includeIndex);
					var parallelRequest = parallelRequests[includeIndex];
						//alert(parallelRequest);
						error("[markIncludes] wird noch nichtunterstützt");
						//parallelRequest.getNewElement().setStyle({background:'lemonchiffon', borderColor:'red'});
				
				}	
			}
	}
	
}
/**
 * schaltet einen Cookie zwischen verschiedenen Zuständen um
 * existiert der cookie nicht, dann wird value1 gesetzt
 * der Cookie witd mit path=/ gesetzt.
 */
function vespa_toogleCookie(name,value1,value2)
{
 info("[toogleCookie]" + document.cookie);
 var cookie1 = name + '=' + value1 + ';path=/';
 var cookie2 = name + '=' + value2 + ';path=/';
 
 var value = vespa_getCookie(name);
 if (value==null || value == value2){
 	document.cookie = cookie1;
 }else {
 	document.cookie = cookie2;
 }
}
/**
 * gibt den Wert eines Cookie zurück.
 */
function vespa_getCookie(name)
{
if (document.cookie.length>0)
  {
  var start=document.cookie.indexOf(name + "=");
  if (start!=-1)
    { 
    start=start + name.length+1; 
    var end=document.cookie.indexOf(";",start);
    if (end==-1) end=document.cookie.length;
    return unescape(document.cookie.substring(start,end));
    } 
  }
return "";
}

// Fügt dem Listenelement mit der ID (elemID)  
// einen Verweis auf die übergebene Style-Klasse
// hinzu und löscht bei allen anderen Elementen der Listen 
// den Verweis auf diese Style-Klasse 
function changeListElementStyleClass(elemID, styleClass) {
	if(!$(elemID))
		return;

	var parentElem = $(elemID).up('ul');
	parentElem.childElements().each(function(item) {
		item.removeClassName(styleClass);
		if(item==$(elemID)) {
			item.toggleClassName(styleClass);
		} 
	}); 
}

function initializeAJAXHandling(){
	Event.observe(window, 'load', callRequestHandler);
	progessCookieValue = vespa_getCookie(progressCookieName);
}

/**
 * ##########################################################################################################
 * Hier die Definition der globalen Variablen und Registierung der Events. 
 */
var requestHandler = new RequestHandler();
// hier die standard error URL's erweitern
var errorURLs = new Hash();
var defaultErrorURL = null;					
var preventFinalRequestErrorURL = null; 	
var myConsole = window.console;
var pageID = null;
var progressCookieName = 'X-MMS-PROGRESS_OBSERVER';
var progressCookieValue = null;
