/**************************************************************
 * Class AjaxModule
 *
 * Simple asynchronous module loader.
 **************************************************************/

function AjaxModule(uid, htmlContEl, hrefXml, hrefXsl) {
	// register me
	AjaxModule.all[uid] = this;

	// init properties
	this.uid = uid;
	this.htmlContEl = htmlContEl || $(uid);
	this.hrefXml = hrefXml;
	this.hrefXsl = hrefXsl;

	this.xmlParams = $H();
	this.xslParams = $H();
	this.xsltParams = $H();
	this.notEmpty = true;
	this.debug = AjaxModule.debug;
	this.links = $H(); // { string linkName => CmsLinkReplacement }

	// emitters are CallbackLists, whose callbacks are invoked with the sender as last param
	this.emitters = $H();
	this.emitters.xmlLoaded = new CallbackList(); // emits the xml document
	this.emitters.xslLoaded = new CallbackList(); // emits the xsl document
	this.emitters.rendered = new CallbackList(); // emits nothing but the module
	this.emitters.itemIds = new CallbackList(); // emits all available items in the render() method
	this.emitters.displayedItemIds = new CallbackList(); // emits all processed items in the render() method

	/* if one entry in loadConditions evaluates to false, load() does nothing */
	this.loadConditions = $H();
	/* if one entry in loadConditionsXml evaluates to false, loadXml() does nothing */
	this.loadConditionsXml = $H();
	/* if one entry in loadConditionsXsl evaluates to false, loadXsl() does nothing */
	this.loadConditionsXsl = $H();
	/* if one entry in renderConditions evaluates to false, render() does nothing */
	this.renderConditions = $H();

	/* PRIVATE */
	this._xmlDoc = null;
	this._xslDoc = null;
	this._loading = false; // prevents from concurrent reloads
	this._loadingXml = false; // prevents from concurrent requests
	this._loadingXsl = false; // prevents from concurrent requests
	this._firstLoad = true;
	this._nestedContent = [];
	this._skelContents = $H();
}

/* Starts asynchronous loading.
 * If loadXml is false, the xml won't be loaded.
 * If loadXsl is false, the xsl won't be loaded.
 * If both loadXml and loadXsl are false, nothing will be loaded, but the module gets rendered.
 */
function AjaxModule_load(/* boolean */loadXml/* = true */, /* boolean */loadXsl/* = true */) {
	if (!this.loadConditions.all(function(pair){return pair[1]})) return false;
	var result = false;
	this.preserveSkeletons();

	if (loadXml == null) loadXml = true;
	if (loadXsl == null) loadXsl = true;
	if (loadXml || loadXsl) {
		this.addLoadingMessage();
		if ((!loadXml || this.loadXml()) & (!loadXsl || this.loadXsl())) result = true;
		this._loading = this._loading || result;
	}

	if (!loadXml && !loadXsl /*&& !this._loading*/) {
		this.addLoadingMessage();
		result = this.doOnRequestsComplete();
	}

	return result;
}

/* Starts asynchronous loading of xml.
 * @PRIVATE
 */
function AjaxModule_loadXml() {
	if ((this.hrefXml || '' != '') && !this._loadingXml && this.loadConditionsXml.all(function(pair){return pair[1]})) {
		this._loadingXml = true;
		this._xmlDoc = null;
		new Ajax.Request(
			this.hrefXml,
			{
				method: 'get',
				parameters: this.xmlParams.toNormalizedQueryString(),
				onComplete: this.doOnXmlRequestComplete.bind(this),
				onFailure: this.doOnFailure.bind(this),
				onException: this.doOnException.bind(this)
			});
		return true;
	}
	else return false;
}

/* Starts asynchronous loading of xsl.
 * @PRIVATE
 */
function AjaxModule_loadXsl() {
	if ((this.hrefXsl || '' != '') && !this._loadingXsl && this.loadConditionsXsl.all(function(pair){return pair[1]})) {
		this._loadingXsl = true;
		this._xslDoc = null;
		new XslLoader(
			this.hrefXsl,
			{
				method: 'get',
				parameters: this.xslParams.toNormalizedQueryString(),
				onComplete: this.doOnXslRequestComplete.bind(this),
				onFailure: this.doOnFailure.bind(this),
				onException: this.doOnException.bind(this)
			});
		return true;
	}
	else return false;
}

function AjaxModule_doOnXmlRequestComplete(request) {
	if (request.responseXML && !AjaxModule.getXmlParseError(request.responseXML)) {
		this._loadingXml = false;
		this._xmlDoc = request.responseXML;
		this.emitters.xmlLoaded.invoke(this._xmlDoc, this);
		if (this._xslDoc) this.doOnRequestsComplete();
	}
}

function AjaxModule_doOnXslRequestComplete(request) {
	if (request.responseXML && !AjaxModule.getXmlParseError(request.responseXML)) {
		this._loadingXsl = false;
		this.convertSkeletons(request.responseXML); // do this before assigning _xsl - want to see exceptions
		this._xslDoc = request.responseXML;
		this.emitters.xslLoaded.invoke(this._xslDoc, this);
		if (this._xmlDoc) this.doOnRequestsComplete();
	}
}

function AjaxModule_doOnRequestsComplete() {
	var result = false;
	this._loading = false;
	if (this.renderConditions.all(function(pair){return pair[1]}) && this.render()) {
		this.replaceLinks();
		this.replaceSkeletons();
		this.emitters.rendered.invoke(this);
		result =  true;
	}
	else this.preserveNestedContent();
	this._firstLoad = false;
	return result;
}

/* Renders the content.
 * Do not call this directly, use load() instead.
 */
function AjaxModule_render() {
	if (this._xmlDoc && this._xslDoc) {
		if (this._xmlDoc.documentElement.childNodes.length > 0 || this._xmlDoc.documentElement.attributes.length > 0 || !this.notEmpty) {
			this.emitItemIds();
			this.emitDisplayedItemIds();
			this.addXsltParams();
			var html = xsltProcess(this._xmlDoc, this._xslDoc);
			this.preserveNestedContent();
			this.htmlContEl.innerHTML = html;
			evalJsNodes(this.htmlContEl);
			return true;
		}
	}
	return false;
}

function AjaxModule_doOnException(request, e) {
	var s = e + '\n';
	if (e.message) s += e.message + '\n';
	if (e.stack) s += e.stack + '\n';
	this.log(s);
}

function AjaxModule_doOnFailure(request) {
	if (request.status == 304) return; // should be handled by prototype, but is not done yet (occurs in Opera)
	var s = 'HTTP Status ' + request.status.toString() + ' ' + (request.statusText ? request.statusText : '') + '\n';
	if (request.status == 500) s += request.responseText.replace(/<.+?>/g, '').substr(0, 300);
	this.log(s);
}

function AjaxModule_log(message) {
	if (this.debug) this.htmlContEl.innerHTML += '<pre>' + message + '</pre>';
}

function AjaxModule_replaceLinks(/* DomElement */rootEl) {
	var anchors = getFlatTree(rootEl ? rootEl : this.htmlContEl,
		function(el) { return el.nodeType == 1 && el.getAttribute('CMSLinkName') });
	var linkName, link;
	for (var i = 0; i < anchors.length; i++) {
		linkName = anchors[i].getAttribute('CMSLinkName');
		link = this.links[anchors[i].getAttribute('CMSLinkName')];
		if (link) {
			anchors[i].removeAttribute('CMSLinkName');
			anchors[i].href = link.replace(anchors[i].href);
		}
	}
}

function AjaxModule_convertSkeletons(doc) {
	doc = doc ? doc : this._xslDoc;
	var nodeSet = getFlatTree(doc, function(el){return el.nodeName.toLowerCase() == 'cms:skeleton'});
	var el;
	for (var i = 0; i < nodeSet.length; i++) {
		el = doc.createElement('span');
		el.setAttribute('skid', nodeSet[i].getAttribute('skid'));
		el.setAttribute('style', 'display:none');
		el.appendChild(doc.createTextNode('I am just a placeholder, please replace me')); // element must not be empty
		nodeSet[i].parentNode.replaceChild(el, nodeSet[i]);
	}
}

function AjaxModule_replaceSkeletons(rootEl) {
	var nodeSet = getFlatTree(rootEl ? rootEl : this.htmlContEl,
		function(el){return el.nodeName.toLowerCase() == 'span' && el.getAttribute('skid')});
	var skel, skid;
	for (var i = 0; i < nodeSet.length; i++) {
		skid = this.uid + '_' + nodeSet[i].getAttribute('skid');
		skel = this._skelContents[skid];
		if (skel) {
			nodeSet[i].parentNode.replaceChild(skel, nodeSet[i]);
			skel.style.display = '';
		}
	}
}

function AjaxModule_addLoadingMessage() {
	if (!this._firstLoad) {
		this.preserveNestedContent();
		this.htmlContEl.innerHTML = '';
		this._nestedContent.each(function(el) {
			this.htmlContEl.appendChild(el);
		}.bind(this));
	}
}
/* Preserves elements contained by the modules root element,
 * but only those without id (they count as 'nested content').
 * Elements are moved to AjaxModule_preserveElement, so they are definitely kept in memory.
 */
function AjaxModule_preserveNestedContent() {
	if (this._firstLoad) {
		$A(this.htmlContEl.childNodes).each(function(el) {
			if (!(el.nodeType == 1 && el.id) && el.nodeName.toLowerCase() != 'script') {
				this._nestedContent.push(el);
				stripJS(el);
				AjaxModule.preserveElement.appendChild(el);
			}
		}.bind(this));
	}
	else {
		this._nestedContent.each(function(el) {
			stripJS(el)
			AjaxModule.preserveElement.appendChild(el);
		}.bind(this));
	}
}
/* Preserves elements contained by the modules root element,
 * but only those with id (they count as 'skeleton').
 */
function AjaxModule_preserveSkeletons() {
	if (this._firstLoad) {
		$A(this.htmlContEl.childNodes).each(function(el) {
			if (el.nodeType == 1 && el.id) {
				this._skelContents[el.id] = el;
				stripJS(el);
				AjaxModule.preserveElement.appendChild(el);
			}
		}.bind(this));
	}
	else {
		this._skelContents.each(function(pair) {
			stripJS(pair[1]);
			AjaxModule.preserveElement.appendChild(pair[1]);
		}.bind(this));
	}
}

function AjaxModule_addXsltParams() {
	this.xsltParams['uid'] = this.uid;
	this.xsltParams['notEmpty'] = this.notEmpty ? 1 : 0;
	var nodeSet = $A(this._xslDoc.documentElement.childNodes).findAll(
		function(el){return el.nodeName.toLowerCase() == 'xsl:param'});
	var value, node;
	for (var i = 0; i < nodeSet.length; i++) {
		value = this.xsltParams[nodeSet[i].getAttribute('name')];
		if (value != null) {
			$A(nodeSet[i].childNodes).each(function(node){ node.parentNode.removeChild(node) });
			try {
				if (value.ownerDocument == this._xslDoc) nodeSet[i].appendChild(value)
				else {
					node = cloneDomNode(this._xslDoc, value);
					if (node) nodeSet[i].appendChild(cloneDomNode(this._xslDoc, value));
					else nodeSet[i].appendChild(this._xslDoc.createTextNode(value.toString())); }
				}
			catch (e) { nodeSet[i].appendChild(this._xslDoc.createTextNode(value.toString())); }
		}
	}
}
function AjaxModule_emitItemIds() {
	this.emitters.itemIds.invoke([this._xmlDoc.documentElement.getAttribute('id')], this);
}
function AjaxModule_emitDisplayedItemIds() {
	this.emitters.displayedItemIds.invoke([this._xmlDoc.documentElement.getAttribute('id')], this);
}

function AjaxModule_getXmlParseError(doc) {
	if (doc.parseError && doc.parseError.errorCode != 0) {
		with (doc.parseError) return reason + '\n' + 'Line ' + line + '\n' +  srcText;
	}
	else if (doc.documentElement.nodeName == 'parsererror') {
		return doc.documentElement.textContent;
	}
}

AjaxModule.getXmlParseError = AjaxModule_getXmlParseError;
AjaxModule.prototype.addLoadingMessage = AjaxModule_addLoadingMessage;
AjaxModule.prototype.addXsltParams = AjaxModule_addXsltParams;
AjaxModule.prototype.convertSkeletons = AjaxModule_convertSkeletons;
AjaxModule.prototype.doOnException = AjaxModule_doOnException;
AjaxModule.prototype.doOnFailure = AjaxModule_doOnFailure;
AjaxModule.prototype.doOnRequestsComplete = AjaxModule_doOnRequestsComplete;
AjaxModule.prototype.doOnXmlRequestComplete = AjaxModule_doOnXmlRequestComplete;
AjaxModule.prototype.doOnXslRequestComplete = AjaxModule_doOnXslRequestComplete;
AjaxModule.prototype.emitItemIds = AjaxModule_emitItemIds;
AjaxModule.prototype.emitDisplayedItemIds = AjaxModule_emitDisplayedItemIds;
AjaxModule.prototype.load = AjaxModule_load;
AjaxModule.prototype.loadXml = AjaxModule_loadXml;
AjaxModule.prototype.loadXsl = AjaxModule_loadXsl;
AjaxModule.prototype.log = AjaxModule_log;
AjaxModule.prototype.preserveNestedContent = AjaxModule_preserveNestedContent;
AjaxModule.prototype.preserveSkeletons = AjaxModule_preserveSkeletons;
AjaxModule.prototype.render = AjaxModule_render;
AjaxModule.prototype.replaceLinks = AjaxModule_replaceLinks;
AjaxModule.prototype.replaceSkeletons = AjaxModule_replaceSkeletons;

/**************************************************************
 * Module Management
 **************************************************************/
AjaxModule.all = $H();
AjaxModule.initStack = [];
AjaxModule.initStack.findEmitter = function(name, yield) {
	var emitter;
	for (var i = this.length - 1; i >=0; i--) {
		emitter = this[i].emitters[name];
		if (emitter) {
			if (yield) yield(emitter, this[i]);
			return emitter;
		}
	}
	return null;
}
AjaxModule.preserveElement = document.documentElement.insertBefore(document.createElement('span'), document.documentElement.firstChild);
AjaxModule.preserveElement.id = 'AjaxModule_preserveElement';
AjaxModule.preserveElement.style.display = 'none';
AjaxModule.debug = false;

/**************************************************************
 * Class AjaxList inherits AjaxModule
 *
 * After rendering a "stub", AjaxListItems are created that will load the data.
 * When the data is received, the items are rendered into that "stub".
 **************************************************************/

function AjaxList(uid, htmlContEl, hrefXml, hrefXsl) {
	AjaxModule.call(this, uid, htmlContEl, hrefXml, hrefXsl);

	this.hrefItemXml = null;
	this.autoLoadItems = true;
	this.excludeItemIds = []; // list of ids that have to be excluded (applied in the render() method)

	this.pager = new Pager();
	this.pager.onPageChange = this.doOnPageChange.bind(this);

	this.emitters.currentPage = new CallbackList();
}

function AjaxList_loadXml() {
	var result = AjaxModule.prototype.loadXml.apply(this, arguments);
	if (result) this._allItems = null;
	return result;
}

function AjaxList_createPagerNode(doc) {
	var pagerNode = doc.createElement('tabs');
	pagerNode.setAttribute('currentTab', this.pager.getCurrentTab());
	pagerNode.setAttribute('currentPage', this.pager.getCurrentPage());
	pagerNode.setAttribute('pagesPerTab', this.pager.getPagesPerTab());
	pagerNode.setAttribute('pageCount', this.pager.getPageCount());
	var tabNode, pageNode, iTabPage, pageIndex;
	for (var iTab = 0; iTab < this.pager.getTabCount(); iTab++) {
	  pagerNode.appendChild(tabNode = doc.createElement('tab'));
	  if (iTab == this.pager.getCurrentTab()) tabNode.setAttribute('current', 1);
	  for (iTabPage = 0; iTabPage < this.pager.getTabPageCount(iTab); iTabPage++) {
		  tabNode.appendChild(pageNode = doc.createElement('page'));
		  pageIndex = iTab * this.pager.getPagesPerTab() + iTabPage;
		  pageNode.setAttribute('index', pageIndex);
		  if (pageIndex == this.pager.getCurrentPage()) pageNode.setAttribute('current', 1);
	  }
	}
	return pagerNode;
}

function AjaxList_addPager() {
	var items = this._xmlDoc.documentElement.childNodes;
	if (this.pager) this.pager.setItemCount(items.length);
	if (this.pager && this.pager.getPageCount() > 1) {
		this.xsltParams['firstIndex'] = this.pager.getFirstItem();
		this.xsltParams['lastIndex'] = this.pager.getLastItem();
		this.xsltParams['pager'] = this.createPagerNode(this._xslDoc);
	}
	else {
		this.xsltParams['firstIndex'] = 0;
		if (this.pager) {
			if (this.pager.getIsFiltered()) {
				this.xsltParams['lastIndex'] = this.pager.getItemCount();
			}
			else this.xsltParams['lastIndex'] = items.length;
		}
		else this.xsltParams['lastIndex'] = items.length;

		this.xsltParams['pager'] = this.createPagerNode(this._xslDoc);
//		delete this.xsltParams['pager'];
	}
}

function AjaxList_render() {
	if (this._xmlDoc && this._xslDoc) {
		this.assembleItems();
		var items = this._xmlDoc.documentElement.childNodes;
		this.emitItemIds(items);
		if (items.length > 0 || !this.notEmpty) {
			this.addPager();
			this.emitDisplayedItemIds(items);
			this.addXsltParams();
			var html = xsltProcess(this._xmlDoc, this._xslDoc);
			this.preserveNestedContent();
			this.htmlContEl.innerHTML = html;
			evalJsNodes(this.htmlContEl);
			this.loadItems(this._xmlDoc.getElementsByTagName('item'));
			return true;
		}
		else this.emitDisplayedItemIds([]);
	}
	return false;
}

function AjaxList_loadItems(nodes) {
	if (this.autoLoadItems && (this.hrefItemXml || '') != '') {
		$A(nodes).each(this.loadItem.bind(this));
	}
}

function AjaxList_loadItem(node) {
	var item = new this.itemClass(this, node);
	if ($(this.uid + '_' + item.id)) {
		item.onRequestComplete = this.doOnItemRequestComplete.bind(this);
		item.onFailure = this.doOnFailure.bind(this);
		item.onException = this.doOnException.bind(this);
		item.load();
	}
}

function AjaxList_loadItemById(id) {
	var node = $A(this._xmlDoc.documentElement.childNodes).find(
		function(el){return el.nodeName.toLowerCase() == 'item' && el.getAttribute('id') == id.toString()});
	if (node) this.loadItem(node);
}

function AjaxList_doOnItemRequestComplete(request, item) {
	var htmlElement = $(this.uid + '_' + item.id);
	if (htmlElement && item._xmlDoc) {
		var html = xsltProcess(item._xmlDoc, this._xslDoc);
		htmlElement.innerHTML = html;
		evalJsNodes(htmlElement);
		this.replaceLinks(htmlElement);
	}
	if (item._xmlDoc) {
		var i = this._allItems.indexOf(item.ownerNode);
		if (i >= 0) this._allItems[i] = cloneDomNode(this._xmlDoc, item._xmlDoc.documentElement);
	}
}
function AjaxList_doOnPageChange() {
	this.emitters.currentPage.invoke(this.pager.getCurrentPage(), this);
	this.load(false, false);
}

/* Applies excludeItemIDs and sets idx attribute for the nodes */
function AjaxList_assembleItems() {
	if (!this._allItems) this._allItems = $A(this._xmlDoc.documentElement.childNodes).findAll(function(el){return el.nodeType == 1});
	$A(this._xmlDoc.documentElement.childNodes).each(function(node){node.parentNode.removeChild(node)});
	var exclIdHash = {}; this.excludeItemIds.each(function(id){exclIdHash[id] = true});
	this._allItems.each(function(node, idx){
		node.setAttribute('idx', idx);
		if (!exclIdHash[node.getAttribute('id')]) this._xmlDoc.documentElement.appendChild(node);
	}.bind(this));
}

function AjaxList_emitItemIds(nodes) {
	if (this.emitters.itemIds.getLength() > 0) {
		this.emitters.itemIds.invoke($A(nodes).collect(function(node){return node.getAttribute('id')}), this);
	}
}
function AjaxList_emitDisplayedItemIds(nodes) {
	if (this.emitters.displayedItemIds.getLength() > 0) {
		var itemIds = [];
		for (var i = this.xsltParams['firstIndex']; i <= this.xsltParams['lastIndex'] && i < nodes.length; i++)
			itemIds.push(nodes[i].getAttribute('id'));
		this.emitters.displayedItemIds.invoke(itemIds, this);
	}
}

Object.extend(AjaxList.prototype, AjaxModule.prototype);
AjaxList.prototype.addPager = AjaxList_addPager;
AjaxList.prototype.assembleItems = AjaxList_assembleItems;
AjaxList.prototype.createPagerNode = AjaxList_createPagerNode;
AjaxList.prototype.doOnItemRequestComplete = AjaxList_doOnItemRequestComplete;
AjaxList.prototype.doOnPageChange = AjaxList_doOnPageChange;
AjaxList.prototype.emitItemIds = AjaxList_emitItemIds;
AjaxList.prototype.emitDisplayedItemIds = AjaxList_emitDisplayedItemIds;
AjaxList.prototype.loadItem = AjaxList_loadItem;
AjaxList.prototype.loadItemById = AjaxList_loadItemById;
AjaxList.prototype.loadItems = AjaxList_loadItems;
AjaxList.prototype.loadXml = AjaxList_loadXml;
AjaxList.prototype.render = AjaxList_render;

/**************************************************************
 * Class AjaxListItem
 *
 * Does only communication. Actual rendering is performed be the ownerList on callback.
 **************************************************************/

function AjaxListItem(/* AjaxList */ownerList, /* DomNode */ownerNode, /* String */id/* = NULL */, /* String */revision/* = NULL */) {
	this.ownerList = ownerList;
	this.ownerNode = ownerNode;

	this.id = id || ownerNode.getAttribute('id');
	this.revision = revision || ownerNode.getAttribute('rev');
	this.onRequestComplete = null; /* function(XMLHttpRequest, NewsItem) */
	this.onFailure = null; /* function(XMLHttpRequest, NewsItem) */
	this.onException = null; /* function(XMLHttpRequest, Exception, NewsItem) */

	/* PRIVATE */
	this._loading = false;
	this._xmlDoc = null
}

function AjaxListItem_load() {
  if (!this._loading) {
		this._loading = true;
		this._xmlDoc = null;
		new Ajax.Request(
			this.ownerList.hrefItemXml,
			{
				method: 'get',
				parameters: {'id': this.id, 'rev' : this.revision },
				onComplete: this.doOnRequestComplete.bind(this),
				onFailure: this.doOnFailure.bind(this),
				onException: this.doOnException.bind(this)
			});
	}
}

function AjaxListItem_doOnRequestComplete(request) {
	this._loading = false;
	if (request.responseXML && !AjaxModule.getXmlParseError(request.responseXML)) {
		this._xmlDoc = request.responseXML;
		for (var i = 0; i < this.ownerNode.attributes.length; i++)
			if (!this._xmlDoc.documentElement.getAttribute(this.ownerNode.attributes[i].name))
				this._xmlDoc.documentElement.setAttribute(this.ownerNode.attributes[i].name, this.ownerNode.attributes[i].value);
		for (var i = 0; i < this.ownerNode.childNodes.length; i++)
			this._xmlDoc.documentElement.appendChild(xmlImportNode(this._xmlDoc, this.ownerNode.childNodes[i]));
	}
	this.onRequestComplete(request, this);
}

function AjaxListItem_doOnFailure(request) {
	if (this.onFailure) this.onFailure(request, this);
}

function AjaxListItem_doOnException(request, e) {
	if (this.onException) this.onException(request, e, this);
}

AjaxList.prototype.itemClass = AjaxListItem;
AjaxListItem.prototype.doOnFailure = AjaxListItem_doOnFailure;
AjaxListItem.prototype.doOnException = AjaxListItem_doOnException;
AjaxListItem.prototype.doOnRequestComplete = AjaxListItem_doOnRequestComplete;
AjaxListItem.prototype.load = AjaxListItem_load;

/**************************************************************
 * Class Pager
 **************************************************************/

function Pager(
		/* uint */itemCount/* = 0 */,
		/* uint */itemsPerPage/* = 0 */,
		/* uint */pagesPerTab/* = 0 */,
		/* uint */currentPage/* = 0 */
	) {
	this.onPageChange = null; /* function(Pager) */
	this.repeat = false; /* for selectNextPage() / selectPreviousPage() */

	/* PRIVATE */
	this._currentTab = 0;
	this._pageCount = 1;
	this._tabCount = 1;
	this._filtered = false;

	this.init(itemCount, itemsPerPage, pagesPerTab, currentPage);
}
function Pager_init(
		/* uint */itemCount/* = 0 */,
		/* uint */itemsPerPage/* = 0 */,
		/* uint */pagesPerTab/* = 0 */,
		/* uint */currentPage/* = 0 */,
		/* bool */filtered/* = false */
	) {
	this._itemCount = itemCount || 0;
	this._itemsPerPage = itemsPerPage || 0;
	this._pagesPerTab = pagesPerTab || 0;
	this._currentPage = currentPage || 0;
	this._filtered = filtered || false;
	this._updateValues(false);
}
function Pager_setItemCount(/* uint */value, /* boolean */ force/* = false */) {
	if ((!this._filtered) || force) {
		this._itemCount = value;
	};
	this._updateValues(true);
}
function Pager_resetItemCount(/* uint */value, /* boolean */ fireEvents/* = true */) {
	if (fireEvents == null) fireEvents = true;
	var oldVal = this._itemCount;
	this._itemCount = value;
	this._filtered = true;
	this._currentPage = 0;
	this._updateValues();
	if (oldVal != value && fireEvents && this.onPageChange) this.onPageChange(this);
}
function Pager_getItemCount() {
	return this._itemCount;
}
function Pager_getIsFiltered() {
	return this._filtered;
}
function Pager_setItemsPerPage(/* uint */value) {
	this._itemsPerPage = value;
	this._updateValues(true);
}
function Pager_getItemsPerPage() {
	return this._itemsPerPage;
}

function Pager_resetItemsPerPage(/* uint */value, /* boolean */ fireEvents/* = true */) {
	if (fireEvents == null) fireEvents = true;
	var oldVal = this._itemsPerPage;
	this._itemsPerPage = value;
	this._updateValues();
	if (oldVal != value && fireEvents && this.onPageChange) this.onPageChange(this);
}

function Pager_setCurrentPage(/* uint */value, /* boolean */ fireEvents/* = true */) {
	if (fireEvents == null) fireEvents = true;
	if (value < 0) value = 0;
	else if (value >= this._pageCount) value = this._pageCount - 1;
	var oldVal = this._currentPage;
	this._currentPage = value;
	this._updateCurrentTab();
	if (oldVal != value && fireEvents && this.onPageChange) this.onPageChange(this);
}
function Pager_getCurrentPage() {
	return this._currentPage;
}
function Pager_setPagesPerTab(/* uint */value) {
	this._pagesPerTab = value;
	this._updateValues(true);
}
function Pager_getPagesPerTab() {
	return this._pagesPerTab;
}
function Pager_getCurrentTab() {
	return this._currentTab;
}
function Pager_getPageCount() {
	return this._pageCount;
}
function Pager_getTabCount() {
	return this._tabCount;
}
function Pager_getTabPageCount(tab) {
	var result = this._pagesPerTab;
	if (tab == this._tabCount - 1) result -= this._pagesPerTab * this._tabCount - this._pageCount;
	return result;
}
function Pager_getFirstItem() {
	return this._currentPage * this._itemsPerPage;
}
function Pager_getLastItem() {
	var result = this.getFirstItem() + this._itemsPerPage - 1;
	if (result < 0 || result >= this._itemCount) result = this._itemCount - 1;
	return result;
}
function Pager_selectNextPage() {
	if (this._currentPage == this._pageCount - 1) this.setCurrentPage(0);
	else this.setCurrentPage(this._currentPage + 1);
}
function Pager_selectPreviousPage() {
	if (this._currentPage == 0) this.setCurrentPage(this._pageCount - 1);
	else this.setCurrentPage(this._currentPage - 1);
}
/* private */function Pager_updateValues(/* boolean */fireEvents/* = false */) {
	this._currentTab = 0;
	this._pageCount = 1;
	this._tabCount = 1;
	if (this._itemsPerPage > 0 && this._itemCount > 0) {
		this._pageCount = Math.ceil(this._itemCount / this._itemsPerPage);
		if (this._pagesPerTab > 0) {
		  this._tabCount = Math.ceil(this._pageCount / this._pagesPerTab);
		  this._updateCurrentTab();
		}
	}
	this.setCurrentPage(this._currentPage, fireEvents);
}
/* private */function Pager_updateCurrentTab() {
	if (this._pagesPerTab > 0) this._currentTab = Math.floor(this._currentPage / this._pagesPerTab);
	else this._currentTab = 0;
}
Pager.prototype.init = Pager_init;
Pager.prototype.setItemCount = Pager_setItemCount;
Pager.prototype.getItemCount = Pager_getItemCount;
Pager.prototype.getIsFiltered = Pager_getIsFiltered;
Pager.prototype.resetItemCount = Pager_resetItemCount;
Pager.prototype.setItemsPerPage = Pager_setItemsPerPage;
Pager.prototype.getItemsPerPage = Pager_getItemsPerPage;
Pager.prototype.resetItemsPerPage = Pager_resetItemsPerPage;
Pager.prototype.setCurrentPage = Pager_setCurrentPage;
Pager.prototype.getCurrentPage = Pager_getCurrentPage;
Pager.prototype.setPagesPerTab = Pager_setPagesPerTab;
Pager.prototype.getPagesPerTab = Pager_getPagesPerTab;
Pager.prototype.getCurrentTab = Pager_getCurrentTab;
Pager.prototype.getPageCount = Pager_getPageCount;
Pager.prototype.getTabCount = Pager_getTabCount;
Pager.prototype.getTabPageCount = Pager_getTabPageCount;
Pager.prototype.getFirstItem = Pager_getFirstItem;
Pager.prototype.getLastItem = Pager_getLastItem;
Pager.prototype.selectNextPage = Pager_selectNextPage;
Pager.prototype.selectPreviousPage = Pager_selectPreviousPage;
/* private */Pager.prototype._updateValues = Pager_updateValues;
/* private */Pager.prototype._updateCurrentTab = Pager_updateCurrentTab;

/**************************************************************
 * Class CmsLinkReplacement
 *
 * Defines a link replacement rule.
 **************************************************************/

function CmsLinkReplacement(/* string */targetHref, /* { string name => string localName } */locals, /* string[] */globals, /* boolean */friendly/* = CmsLinkReplacement.friendly */) {
	if (targetHref.indexOf('javascript:') != 0) {
		this.targetUri = new URI(targetHref);
		CmsLinkReplacement.makeUnFriendly(this.targetUri);
	}
	else {
		this.targetUri = new URI('');
		this.targetUri.pathname = targetHref;
		this.isJS = true;
	}
	this.locals = $H(locals);
	this.globals = globals;
	this.staticParamHash = CmsLinkReplacement.paramStringToHash(this.targetUri.search, true);

	var currentUri = new URI(document.location.href);
	CmsLinkReplacement.makeUnFriendly(currentUri);
	this.currentParamHash = CmsLinkReplacement.paramStringToHash(currentUri.search, true);

	this.friendly = friendly == null ? CmsLinkReplacement.friendly : friendly;
}
/* string */function CmsLinkReplacement_replace(/* string */href) {
	if (this.isJS) return this.targetUri.pathname;

	var uri = new URI(href);
	var uriParamHash = CmsLinkReplacement.paramStringToHash(uri.search, true);
	var targetParamHash = $H();
	var name, value;
	for (var i = 0; i < this.globals.length; i++) {
		name = this.globals[i].toLowerCase();
		value = this.currentParamHash[name];
		if (value) targetParamHash[name] = value;
		else {
			value = this.staticParamHash[name];
			if (value) targetParamHash[name] = value;
		}
	}
	this.locals.each(function (pair) {
		if (uriParamHash[pair.key.toLowerCase()]) targetParamHash[pair.value.toLowerCase()] = uriParamHash[pair.key.toLowerCase()];
	});
	uri.protocol = this.targetUri.protocol;
	uri.hostname = this.targetUri.hostname;
	uri.port = this.targetUri.port;
	uri.pathname = this.targetUri.pathname;
	if (this.friendly) CmsLinkReplacement.makeFriendly(uri, targetParamHash);
	else uri.search = targetParamHash.toNormalizedQueryString();
	return uri.getHref();
}
function CmsLinkReplacement_paramStringToHash(paramsStr, nameToLowerCase/* = false */) {
	var hash = $H();
	var pairs = paramsStr.split('&');
	var pair;
	for (var i = 0; i < pairs.length; i++) {
		pair = pairs[i].split("=");
		hash[nameToLowerCase ? pair[0].toLowerCase() : pair[0]] = pair[1];
	}
	return hash;
}
function CmsLinkReplacement_makeFriendly(uri, paramHash/* = NULL */) {
	if (!paramHash) paramHash = CmsLinkReplacement.paramStringToHash(uri.search, true);
	var pairs = [];
	paramHash.each(function (pair) {
		if (pair.value) pairs.push(pair.key + '/' + pair.value);
	});
	var pos = uri.pathname.lastIndexOf('/');
	var paramStr = '/p/' + (pairs.length ? pairs.sort().join('/') + '/' : '')
	uri.pathname = uri.pathname.substr(0, pos) + paramStr + uri.pathname.substr(pos + 1).replace(/\.asp$/, '.html');
	uri.search = '';
}
function CmsLinkReplacement_makeUnFriendly(uri) {
	var delim = '/p/';
	var posD = uri.pathname.indexOf(delim);
	if (posD >= 0) {
		var posF = uri.pathname.lastIndexOf('/');
		var paramStr = uri.pathname.substring(posD + delim.length, posF);
		var parts = paramStr.split('/');
		var pairs = [];
		for (var i = 0; i < parts.length; i+=2) {
			if (i < parts.length - 1) pairs.push(parts[i] + '=' + parts[i + 1]);
		}
		uri.pathname = uri.pathname.substr(0, posD) + uri.pathname.substr(posF).replace(/\.html$/, '.asp');
		uri.search = (uri.search ? uri.search + '&' : '') + pairs.join('&');
	}
}
CmsLinkReplacement.prototype.replace = CmsLinkReplacement_replace;
CmsLinkReplacement.paramStringToHash = CmsLinkReplacement_paramStringToHash;
CmsLinkReplacement.makeFriendly = CmsLinkReplacement_makeFriendly;
CmsLinkReplacement.makeUnFriendly = CmsLinkReplacement_makeUnFriendly;
CmsLinkReplacement.friendly = true;

/**************************************************************
 * Class XslLoader
 *
 * Asynchronous xsl loader, capable of resolving imports.
 *
 * TODO: implement overwriting of templates
 **************************************************************/

function XslLoader(href, options) {
	this._options = {};
	Object.extend(this._options, options); //clone options to this._options, prevents the given object from being modified
	this._onComplete = options.onComplete;
	this._options.onComplete = this._onXslRequestComplete.bind(this);
	new Ajax.Request(href, this._options);
}
function XslLoader_onXslRequestComplete(request) {
	// TODO: imports have to be in reverse order so that overwriting works correctly
	// -> responses have to be stored until importCount is 0
	if (request.responseXML) {
		this._request = request;
		var imports = [];
		with (this._request.responseXML.documentElement) {
			for (var i = 0; i < childNodes.length; i++) {
				if (childNodes[i].tagName == 'xsl:import') imports.push(childNodes[i]);
			}
		}
		if (imports.length == 0 && this._onComplete) this._onComplete(this._request);
		else {
			this._importCount = imports.length;
			var options;
			for (var i = 0; i < imports.length; i++) {
				options = {};
				Object.extend(options, this._options); //clone this.options to local options
				options.onComplete = this._onImportRequestComplete.bind(this, imports[i]);
				new XslLoader(imports[i].getAttribute('href'), options);
			}
		}
	}
}
function XslLoader_onImportRequestComplete(node, request) {
	// TODO: import only templates, and only those that do not exist yet
	if (request.responseXML && !AjaxModule.getXmlParseError(request.responseXML)) {
		var docEl = this._request.responseXML.documentElement;
		with (request.responseXML.documentElement) {
			for (var i = 0; i < childNodes.length; i++) {
				docEl.appendChild(cloneDomNode(this._request.responseXML, childNodes[i]));
			}
		}
	}
	this._request.responseXML.documentElement.removeChild(node);
	if (--this._importCount == 0 && this._onComplete) this._onComplete(this._request);
}
XslLoader.prototype._onXslRequestComplete = XslLoader_onXslRequestComplete;
XslLoader.prototype._onImportRequestComplete = XslLoader_onImportRequestComplete;


/**************************************************************
 * Class Hash
 *
 * Enhance the class from the Prototype framework
 **************************************************************/
function Hash_toNormalizedQueryString(obj) {
	var parts = [];
	this.prototype._each.call(obj, function(pair) {
		if (!pair.key) return;
		pair[0] = pair[0].toLowerCase();
		if (pair.value || '' != '') parts.push(pair.map(encodeURIComponent).join('='));
	});
	return parts.sort().join('&');
}
Object.extend(Hash, {
	toNormalizedQueryString: Hash_toNormalizedQueryString
});
Object.extend(Hash.prototype, {
	toNormalizedQueryString: function() { return Hash.toNormalizedQueryString(this) }
});


/**************************************************************
 * Class AjaxModuleStatic
 *
 * Modified AjaxModule for pre-rendered content.
 * Can also be used to cast custom AjaxModules with Object.extend.
 **************************************************************/
function AjaxModuleStatic(uid, htmlContEl, href) {
	AjaxModule.all[uid] = this;
	this.uid = uid;
	this.htmlContEl = htmlContEl || $(uid);
	this.href = href;
	this.params = $H();
	if (!this.xmlParams) this.xmlParams = $H(); // compatibility
	if (!this.xslParams) this.xslParams = $H(); // compatibility
	if (!this.xsltParams) this.xsltParams = $H(); // compatibility
	this.notEmpty = true;
	this.debug = AjaxModule.debug;
	if (!this.links) this.links = $H();
	if (!this.emitters) this.emitters = $H();
	this.emitters.htmlLoaded = new CallbackList(); // emits the html text
	if (!this.emitters.rendered) this.emitters.rendered = new CallbackList(); // emits nothing but the module
	if (!this.emitters.itemIds) this.emitters.itemIds = new CallbackList(); // emits all available items
	if (!this.emitters.displayedItemIds) this.emitters.displayedItemIds = new CallbackList(); // emits all processed items
	if (!this.loadConditions) this.loadConditions = $H();
	if (!this.loadConditionsXml) this.loadConditionsXml = $H(); // compatibility
	if (!this.loadConditionsXsl) this.loadConditionsXsl = $H(); // compatibility
	if (!this.renderConditions) this.renderConditions = $H(); // compatibility

	/* PRIVATE */
	this._loading = false; // prevents from concurrent reloads
	this._firstLoad = true;
	if (!this._nestedContent) this._nestedContent = [];
	if (!this._skelContents) this._skelContents = $H();
}
function AjaxModuleStatic_load() {
	var mergedConditions = this.renderConditions.merge(this.loadConditionsXsl.merge(this.loadConditionsXml.merge(this.loadConditions)));
	if (this._loading || !mergedConditions.all(function(pair){return pair[1]})) return false;
	this.preserveSkeletons();
	this.addLoadingMessage();
	this._loading = true;
	new Ajax.Request(
		this.href,
		{
			method: 'get',
			parameters: this.xsltParams.merge(this.xslParams.merge(this.xmlParams.merge(this.params))).toNormalizedQueryString(),
			onComplete: this.doOnRequestsComplete.bind(this),
			onFailure: this.doOnFailure.bind(this),
			onException: this.doOnException.bind(this)
		});
	return true;
}
function AjaxModuleStatic_doOnRequestsComplete(request) {
	this._loading = false;
	this.emitters.htmlLoaded.invoke(request.responseText, this);
	this.preserveNestedContent();
	this.htmlContEl.innerHTML = request.responseText.replace(/Q7vYx/g, this.uid);
	evalJsNodes(this.htmlContEl);
	this.replaceLinks();
	this.replaceSkeletons();
	this.emitters.rendered.invoke(this);
	if (!this.notEmpty) {
		if (this.htmlContEl.childElements()[0].childElements()[0].childElements()[0]) {
			if (this.htmlContEl.childElements()[0].childElements()[0].childElements()[0].id.toLowerCase() == this.uid.toLowerCase()+'_static') {
				var re = new RegExp('\S', 'i'); // find any char other than withespace
				if (!(re.test(this.htmlContEl.childElements()[0].childElements()[0].childElements()[0].innerHTML))) {
					$(this.uid).style.display='none'; // set display of (outer) module DIV to none; IE empty modules leave (reserve) space [#3780]
				}
			}
		}
	}
	this._firstLoad = false;
}
function AjaxModuleStatic_processStaticContent(el) {
	this.preserveNestedContent();
	el.setAttribute('id', ''); //prevent from being a skeleton
	this.preserveSkeletons();
	this.replaceLinks();
	this.replaceSkeletons(el);
	this.emitters.rendered.invoke(this);
	this._firstLoad = false;
}
Object.extend(AjaxModuleStatic.prototype, AjaxModule.prototype);
AjaxModuleStatic.prototype.load = AjaxModuleStatic_load;
AjaxModuleStatic.prototype.doOnRequestsComplete = AjaxModuleStatic_doOnRequestsComplete;
AjaxModuleStatic.prototype.processStaticContent = AjaxModuleStatic_processStaticContent;

/**************************************************************
 * Class AjaxListStatic inherits AjaxModuleStatic
 *
 * Modified AjaxList for pre-rendered content.
 **************************************************************/
function AjaxListStatic(uid, htmlContEl, href) {
	AjaxModuleStatic.call(this, uid, htmlContEl, href);
	if (!this.pager) this.pager = new Pager();
	this.pager.onPageChange = this.doOnPageChange.bind(this);
	if (!this.emitters.currentPage) this.emitters.currentPage = new CallbackList();
}
function AjaxListStatic_doOnPageChange() {
	this.emitters.currentPage.invoke(this.pager.getCurrentPage(), this);
	this.load();
}
function AjaxListStatic_load() {
	if (this.pager) this.params['cp'] = this.pager.getCurrentPage();
	AjaxModuleStatic.prototype.load.apply(this, arguments);
}
Object.extend(AjaxListStatic.prototype, AjaxModuleStatic.prototype);
AjaxListStatic.prototype.doOnPageChange = AjaxListStatic_doOnPageChange;
AjaxListStatic.prototype.load = AjaxListStatic_load;
