var JSONRESP = {
	processCache: {},
	sourcesLoaded: {},
	currentHash: '',
	startLocation: '',

	loaderSrc: '',
	loader: null,
	processHeadCount: 0,
	maxProcessHeadCount: 3,

	md5Actions: {},
	md5Stack: [],
	md5LastCall: null,
	md5LastCheckPoint: null,

	lastCheckPointElement: null,
	convertLastCheckPointElement: false,
	
	activeCount: 0,

	dataStorage: {},
	lastTrigger: null,

	init: function() {
		Event.stopObserving(window, 'load', JSONRESP.init, false);
		if (JSONRESP.loaderSrc) {
			JSONRESP.loader = new Image();
			JSONRESP.loader.src = JSONRESP.loaderSrc;
		}
		if (JSONRESP.isAJAXavailable) {
			JSONRESP.updateLinks(true);
			JSONRESP.findLastCheckPoint();
			// JSONRESP.updateHash();
			JSONRESP.startLocation = location.href;
		} else {
			JSONRESP.updateLinks(false);
		}
	},
	
	unload: function(e) {
		Event.stopObserving(window, 'unload', JSONRESP.unload, false);
		if (JSONRESP.currentLocation==location.href) {
			//alert('unload: '+JSONRESP.currentLocation+'//'+location.href);
		}
	},
	
	updateHash: function(lastHash) {
		var hash = JSONRESP.getHash();
		JSONRESP.currentHash = hash;
		if (typeof lastHash!='undefined' && lastHash!=hash) {
			if (!JSONRESP.md5LastCall || JSONRESP.md5LastCall!=hash) {
				JSONRESP.handleChange(hash);
			}
		}
		JSONRESP.md5LastCall = null;
		window.setTimeout(function() { JSONRESP.updateHash(JSONRESP.currentHash); }, 200);
	},
	
	handleChange: function(hash) {
		var index, i, action;
		if (hash && hash.match(/^jsonresp_/)) {
			index = $A(JSONRESP.md5Stack).indexOf(hash);
				// We have JSON stack elements:
			if (index!=-1 && index<JSONRESP.md5Stack.length) {
				for (i=JSONRESP.md5Stack.length-1; index<=i; i--) {
					JSONRESP.md5Stack.pop();
				}
				action = JSONRESP.getMD5Action(hash);
				if (action) {
					JSONRESP.call(action.element, action.url, action.script, hash);
				}
				// We have no stack elements:
			}
		} else if (hash=='') {
			if (JSONRESP.startLocation) {
				location.href = JSONRESP.startLocation;
			}
		}
	},
	
	getHash: function() {
		var result = '';
		var hash = location.hash;
		if (hash.match(/^#jsonresp_/)) {
				// Remove leading '#':
			result = hash.substr(1);
		}
		return result;
	},

	updateLinks: function(useAJAX) {
		$$('A.JSONRESP').each(function(element) {
			var url = Element.readAttribute(element, 'onclick');
			if (url) {
				if (useAJAX) {
					element.setAttribute('href', '#');
				}
			}
		});
	},
	
	findLastCheckPoint: function() {
		var anchors = $$('A.JSONRESP_LCP');
		if (anchors.length) {
			JSONRESP.lastCheckPointElement = anchors[0];
		}
	},

	startCall: function() {
		if (!JSONRESP.activeCount++) {
			var className = (Prototype.Browser.IE ? 'jsonrespWaitIE' : 'jsonrespWait');
			$(document.body).addClassName(className);
		}
	},

	finishCall: function() {
		if (!--JSONRESP.activeCount) {
			var className = (Prototype.Browser.IE ? 'jsonrespWaitIE' : 'jsonrespWait');
			document.body.removeClassName(className);
		}			
	},

	/**
	 * @unused
	 */
	addLoader: function(element) {
		var loader = null
		if (JSONRESP.loader) {
			loader = document.createElement('div');
			loader.style.position = 'absolute';
			loader.style.zIndex = '500';
			Element.addClassName(loader, 'jsonrespLoader');
			loader.appendChild(JSONRESP.loader);
			document.body.appendChild(loader);
	
			element = JSONRESP.getChildIfAnchor(element);
			Element.clonePosition(loader, element, { setWidth: false, setHeight: false });
			loader.style.left = (parseInt(loader.style.left)-loader.style.width+element.style.width)+'px';
		}
		return loader;
	},
	
	/**
	 * @unused
	 */
	removeLoader: function(loader) {
		if (loader && loader.parentNode) {
			loader.parentNode.removeChild(loader);
			loader = null;
		}
	},
	
	getChildIfAnchor: function(element) {
		if (element && element.nodeName.toLowerCase()=='a' && element.firstChild && element.firstChild.nodeType==1) {
			return element.firstChild;
		}
		return element;
	},

	call: function(element, url, script, md5, isCheckPoint) {
		var callOptions = { noCache: false, usePost: false, parameters: null, exit: false };
		url = url.replace(/%26/g, '&');

		if (JSONRESP.convertLastCheckPointElement) {
			element = JSONRESP.lastCheckPointElement;
			JSONRESP.convertLastCheckPointElement = false;
		}
			// Execute pre-working script if any:
		if (script) {
			try {
				eval(script);
			} catch (error) { }
		}
		if (callOptions.exit) {
			return false;
		}
		if (isCheckPoint) {
			JSONRESP.md5LastCheckPoint = md5;
		}
		if (!callOptions.noCache && JSONRESP.processCache[url]) {
			JSONRESP.md5Stack.push(md5);
			JSONRESP.processJSON(JSONRESP.processCache[url]);
		} else {
			if (callOptions.noCache) {
				JSONRESP.processCache[url] = false;
			}

			// var loader = JSONRESP.addLoader(element);
			JSONRESP.startCall();
			JSONRESP.md5Actions[md5] = { 'element': element, 'url':url, 'script': script, 'isCheckPoint': isCheckPoint };
			JSONRESP.md5Stack.push(md5);
			var options = {
				method:		(callOptions.usePost && callOptions.parameters ? 'post' : 'get'),
				parameters:	(callOptions.usePost && callOptions.parameters ? callOptions.parameters : null),
				onSuccess:	function(xhr) { /*JSONRESP.removeLoader(loader);*/ JSONRESP.getSuccess(xhr, url); },
				onFailure:	function(xhr) { /*JSONRESP.removeLoader(loader);*/ JSONRESP.getFailure(xhr); }
			};
			new Ajax.Request(url, options);
		}
		JSONRESP.md5LastCall = md5;
		// location.hash = '#'+md5;
	},

	getSuccess: function(xhr, url) {
		JSONRESP.finishCall();
		var json = eval('('+xhr.responseText+')');
		if (json) {
			if (url && typeof JSONRESP.processCache[url]=='undefined') {
				JSONRESP.processCache[url] = json;
			}
			JSONRESP.processHeadJSON(json);
		}
	},

	getFailure: function(xhr) {
		JSONRESP.finishCall();
	},

	processJSON: function(json) {
		var ext, i, max, cmd, targets;
		for (ext in json) {
				// Call scripts on finished JSON action:
			if (ext=='finish') {
				if (json.finish && json.finish.length) {
					$A(json.finish).each(function(script) {
						eval(script);
					});
				}
				// Handle regular data parts:
			} else {
				for (i=0, max=json[ext].length; i<max; i++) {
					cmd = json[ext][i];
					if (cmd.data === null) { cmd.data = ''; }
						// Replace the inner nodes of the target object:
					if (cmd.type == 'replace' && cmd.target) {
						JSONRESP.removeAllChildNodes($(cmd.target));
						new Insertion.Top(cmd.target, cmd.data);
						// Exchange the whole target object with the new data:
					} else if (cmd.type == 'replace' && cmd.className) {
						targets = $$('.'+cmd.className);
						if (targets.length) {
							$A(targets).each(function(target) {
								JSONRESP.removeAllChildNodes(target);
								new Insertion.Top(target, cmd.data);
							});
						}
					} else if (cmd.type == 'exchange' && cmd.target && cmd.data) {
						Element.replace(cmd.target, cmd.data);
					} else if (cmd.type == 'exchange' && cmd.className && cmd.data) {
						targets = $$('.'+cmd.className);
						if (targets.length) {
							$A(targets).each(function(target) {
								Element.replace(target, cmd.data);	
							});
						}
					} else if (cmd.type == 'storage' && cmd.target && cmd.data) {
						JSONRESP.dataStorage[cmd.target] = cmd.data;
					} else if (cmd.type == 'script' && cmd.script) {
						eval(cmd.script);
					}
				}
			}
		}
	},
	
	processHeadJSON: function(json, addTags) {
		var cmd=null, addTag=null, restart=false, processedCount=0, scriptCount=0, restartWaitTime=50, element=null, errorCatch=[], sourcesWaiting=[];
		var head = JSONRESP.getDomHeadTag();
		if (typeof addTags=='undefined') {
			addTags = JSONRESP.getAddTags(json);
		}

		if (head!==false && addTags) {
			var headTags = JSONRESP.getDomHeadChildren(head);
			$A(addTags).each(function(addTag) {
				if (addTag && !JSONRESP.searchInDomTags(headTags, addTag) || addTag.innerHTML) {
					// Check whether to restart the processing:
					if (addTag.name=='script' && (scriptCount || addTag.innerHTML && processedCount)) {
						restart = true;
						return false;
					} else {
						if (addTag.name=='script' && addTag.innerHTML) {
							try {
								eval(addTag.innerHTML);
							} catch(e) {
								errorCatch.push(e);
							}
						} else {
							element = JSONRESP.getNewDomElement(addTag);
								// Set onload handler for external JS scripts:
							if (addTag.name=='script' && element.src) {
								// Checking whether to explicitely check for loaded files:
								// (IE has some issues on dynamically loading script.aculo.us "in time")
								// (Attention: Object.isUndefined(Effect) does not work here, using typeof instead)
								if (element.src.match(/scriptaculous\/dragdrop\.js$/) && typeof Effect == 'undefined') {
									if (JSONRESP.processHeadCount++ < JSONRESP.maxProcessHeadCount) {
										restart = true;
										restartWaitTime = 100;
										return false;
									}
								}
								element.onload = JSONRESP.sourceLoadedHandler(element.src);
								sourcesWaiting.push(element.src);
								scriptCount++;
							}
							head.appendChild(element);
							addTags.shift();
							processedCount++;
						}
					}
				}
			});
			if (restart) {
				window.setTimeout(function() { JSONRESP.restartHeadJSON(sourcesWaiting, json, addTags); }, restartWaitTime);
			} else {
				JSONRESP.processJSON(json);
			}
		}
	},
	
	getAddTags: function(json) {
		var addTags = [];
		if (json) {
			$H(json).each(function(ext) {
				$A(ext.value).each(function(cmd) {
					if (cmd && cmd.type && cmd.type=='head') {
						$A(cmd.tags).each(function(addTag) {
							addTags.push(addTag);
						});
					}
				});
			});
		}
		return addTags;
	},
	
	restartHeadJSON: function(sourcesWaiting, json, addTags) {
		var loaded = true;
		if (sourcesWaiting && sourcesWaiting.length) {
			$A(sourcesWaiting).each(function(source) {
				if (!JSONRESP.sourcesLoaded[source]) {
					loaded = false;
					return false;
				}
			});
		}
		if (loaded) {
			JSONRESP.processHeadJSON(json, addTags);
		} else {
			window.setTimeout(function() { JSONRESP.restartHeadJSON(sourcesWaiting, json, addTags) }, 50);
		}
	},
	
	sourceLoadedHandler: function(source) {
		if (source) {
			JSONRESP.sourcesLoaded[source] = true;
		}
	},

	getNewDomElement: function(addTag) {
		var element = document.createElement(addTag.name);
		if (addTag.attributes) {
			$H(addTag.attributes).each(function(attribute) {
				element[attribute.key] = attribute.value;
			});
		}
		return element;
	},

	getDomHeadTag: function() {
		var head = document.getElementsByTagName('head');
		if (head.length) {
			return head[0];
		}
		return false;
	},
	
	getDomHeadChildren: function(head) {
		var tagName, headTags=[];
		var tags = head.getElementsByTagName('*');
		if (tags.length) {
			$A(tags).each(function(tag) {
				tagName = tag.nodeName.toLowerCase();
				if (tagName=='script' || tagName=='link') {
					headTags.push(tag);
				}
			});
		}
		return headTags;
	},
	
	searchInDomTags: function(haystack, needle) {
		var result = false;
		$A(haystack).each(function(element) {
			//alert('name: '+element.nodeName.toLowerCase()+'->'+needle.name);
			if (element.nodeName.toLowerCase()==needle.name) {
				var attributesCount = $H(needle.attributes).keys().length;
				var attributesFound = 0;
				$H(needle.attributes).each(function(attribute) {
					if (element.getAttribute && element.getAttribute(attribute.key)==attribute.value) {
						attributesFound++;
					}
				});
				if (attributesFound==attributesCount) {
					result = true;
					return true;
				}
				/*
					if (needle.attributes && needle.attributes.src || needle.attributes.rel) {
						if (needle.attributes.src && needle.attributes.src==element.getAttribute('src')) {
							result = true;
							return true;
						}
						if (needle.attributes.rel && needle.attributes.src==element.getAttribute('rel')) {
							result = true;
							return true;
						}
					}
				*/
			}
		});
		return result;
	},

	removeAllChildNodes: function(element) {
		while (element.hasChildNodes()) {
			element.removeChild(element.lastChild);
		}
	},

	updateCssClass: function(parentElement, className, newElement) {
		var i, max, items;
		items = Element.select(parentElement, '.'+className);
		for (i=0, max=items.length; i<max; i++) {
			Element.removeClassName(items[i], className);
		}
		Element.addClassName(newElement, className);
	},

	isAJAXavailable: function() {
		return Ajax.getTransport() === false ? false : true;
	},

	revertLastCache: function() {
		var md5, action;
		if (JSONRESP.md5Stack && JSONRESP.md5Stack.length) {
				// Get last element:
			md5 = $A(JSONRESP.md5Stack).last();
			action = JSONRESP.getMD5Action(md5);
			if (action) {
				delete(JSONRESP.processCache[action.url]);
			}
		}
	},

	back: function() {
		var md5, action;
		if (JSONRESP.md5Stack && JSONRESP.md5Stack.length > 1) {
				// Remove current element:
			JSONRESP.md5Stack.pop();
				// Get previous element:
			md5 = $A(JSONRESP.md5Stack).last();
			action = JSONRESP.getMD5Action(md5);
			if (action) {
				JSONRESP.call(action.element, action.url, action.script, md5);
			}
		} else {
			history.back();
		}
	},

	backToLastCheckPoint: function() {
		var action, md5;
		if (JSONRESP.isAJAXavailable) {
			if (JSONRESP.md5LastCheckPoint) {
				md5 = JSONRESP.md5LastCheckPoint;
				action = JSONRESP.getMD5Action(md5);
				if (action) {
					JSONRESP.call(action.element, action.url, action.script, md5, action.isCheckPoint);
				}
			} else if (JSONRESP.lastCheckPointElement) {
				JSONRESP.convertLastCheckPointElement = true;
				JSONRESP.lastCheckPointElement.onclick();
				JSONRESP.convertLastCheckPointElement = false;
			}
		} else {
			history.back();
		}
	},

	getMD5Action: function(md5) {
		var action = false;
		if (md5 && JSONRESP.md5Actions && JSONRESP.md5Actions[md5]) {
			action = JSONRESP.md5Actions[md5];
		}
		return action;
	}
};
Event.observe(window, 'load', JSONRESP.init, false);