var MvpWebTemplate_Class = Class.create({
	initialize: function() {
		this.templates = Array();
		this.shownElements = new Hash();
		this.templatesLoaded = false;
		this.extraDebug = false;
	},
	
	loadTemplate: function(name, template) {
		this.templates[name] = template;
	},
	
	clearErrorLog: function() {
		$('MvpWebTemplateErrorLog').innerHTML = '';
	},
	
	toggleExtraDebug: function() {
		this.extraDebug = ! this.extraDebug;
		$('MvpWebTemplateErrorLog-extraDebug').innerHTML = this.extraDebug ? 'on' : 'off';
	},
	
	render: function(templateName, values, container, successFunction) {
		var logContainer = $('MvpWebTemplateErrorLog');
		if (this.templatesLoaded) {
			if (this.templates[templateName] != null) {
				var renderer = new MvpWebTemplateRenderer_Class();
				renderer.templates = this.templates;
				try {
					renderer.render(templateName, values, container, successFunction);
				} catch (e) {
					this.logException(e);
				}
			} else {
				var renderer = new MvpWebTemplateRenderer_Class();
				renderer.logValueError('Template &quot;' + templateName +'&quot; not found.', null);
			}
		} else {
			path = MvpWebTemplate.getSelfPath() + '/jsTemplates';
			new Ajax.Request(path, {
				method: 'get',
				onSuccess: function(transport) {
					templates = transport.responseText.evalJSON(true);
					for (var i = 0; i < templates.length; i++) {
						MvpWebTemplate.templates[templates[i].name] = templates[i];
					}
					MvpWebTemplate.templatesLoaded = true;
					MvpWebTemplate.render(templateName, values, container, successFunction);
				}	
			});
		}
	},

	getSelfPath: function() {
		var path = location.pathname;
		if (path.endsWith('/')) {
			path += 'index.xml';
		}
		return path;
	},
	
	doShowHide: function(show, hide) {
		if (show != undefined) {
			this.shownElements.set(show, 'true');
			this.showElements(show, false);
		}
		if (hide != undefined) {
			this.shownElements.unset(hide);
			this.showElements(hide, true);
		}
	},
	
	showElements: function(name, hide) {
		var elements = $$('.CAF-show-' + name);
		for (var i = 0; i < elements.length; i++) {
			if (hide) {
				elements[i].removeClassName('show');
				elements[i].addClassName('hide');
			} else {
				elements[i].removeClassName('hide');
				elements[i].addClassName('show');
				this.focusInput(elements[i]);
			}
		}
		var elements = $$('.CAF-hide-' + name);
		for (var i = 0; i < elements.length; i++) {
			if (hide) {
				elements[i].removeClassName('hide');
				elements[i].addClassName('show');
				this.focusInput(elements[i]);
			} else {
				elements[i].removeClassName('show');
				elements[i].addClassName('hide');
			}
		}
		elements = $$('.CAF-switchShow-' + name);
		for (var i = i; i < elements.length; i++) {
			if (hide) {
				elements[i].removeClassName('active');
				elements[i].addClassName('inactive');
			} else {
				elements[i].removeClassName('inactive');
				elements[i].addClassName('active');
			}
		}
	},
	
	focusInput: function(element) {
		var inputs = element.select("input[type=text]", "input[type=password]", "textarea");
		if (inputs.length > 0) {
			inputs[0].focus();
		}
	},
	
	log: function(message) {
		var logContainer = $('MvpWebTemplateErrorLog');
		if (logContainer != null) {
			Element.insert(logContainer, message + '<br/>');
		}
	},
	
	logException: function(e) {
		var renderer = new MvpWebTemplateRenderer_Class();
		renderer.logValueError(e, null);
	},

	logError: function(message) {
		this.log('<strong style="color: #ff0000">' + message + '</strong>');
		if (this.customErrorReporter != undefined) {
			this.customErrorReporter(message);
		}
	},
	
	clearLog: function() {
		$('MvpWebTemplateErrorLog').innerHTML = '';
	},
	
	escapeValue: function(value) {
		if (value == null) {
			return null;
		}
		if (value.isSafe == true) {
			return value.safeValue;
		}
		return String(value).escapeHTML();
	}
});

var MvpWebTemplateRenderer_Class = Class.create({

	render: function(templateName, data, container, successFunction) {
		MvpWebTemplate.log('<h5>' + templateName + ':</h5>');
		this.parser = new ValueKeyParser_Class();
		this.parser.data = data.data;
		this.addShowHideProviders();
		content = this.renderTemplate(templateName);
		if (MvpWebTemplate.extraDebug) {
			MvpWebTemplate.log('<h5>Data:</h5>');
			MvpWebTemplate.log(Object.toJSON(data));
			MvpWebTemplate.log('');
			MvpWebTemplate.log('<h5>Content:</h5>');
			MvpWebTemplate.log(content.escapeHTML());
		}
		container.innerHTML = content;
		successFunction();
	},
	
	renderTemplate: function(templateName) {
		var template = this.templates[templateName];
		this.parser.pushParentKey(template.parentKey);
		if (template == undefined) {
			MvpWebTemplate.logError('Template &quot;' + templateName + '&quot; not found');
			return '';
		}
		var result = this.renderNode(template.contents);
		this.parser.popParentKey();
		return result;
	},

	renderNode: function(node) {
		if (Object.isArray(node)) {
			return this.renderArray(node);
		}
		switch (node.type) {
			case 'regular':
				return this.renderRegularNode(node, false);
			case 'text':
				return node.contents;
			case 'pluginValue':
				return this.renderPluginValueNode(node);
			case 'pluginList':
				return this.renderPluginListNode(node);
			case 'pluginSubstitution':
				return this.renderRegularNode(node, true);
			case 'pluginConditional':
				return this.renderPluginConditionalNode(node);
			case 'pluginSwitch':
				return this.renderPluginSwitchNode(node);
			case 'pluginFormInputText':
				return this.renderPluginFormInputTextNode(node);
			case 'pluginFormInputHidden':
				return this.renderPluginFormInputHiddenNode(node);
			case 'pluginFormTextArea':
				return this.renderPluginFormTextAreaNode(node);
			case 'pluginFormSelect':
				return this.renderPluginFormSelectNode(node);
			case 'pluginFormOption':
				return this.renderPluginFormOptionNode(node);
			case 'pluginFormChoice':
				return this.renderPluginFormChoiceNode(node);
			case 'element':
				return this.renderElement(node);
		}
		MvpWebTemplate.logError('Unknown node type: ' + node.type);
		return '';
	},
	
	renderArray: function(node) {
		var str = '';
		for (var i = 0; i < node.length; i++) {
			str += this.renderNode(node[i]);
		}
		return str;
	},
	
	renderRegularNode: function(node, doSubstitution) {
		var str = '<' + node.name;
		str += this.buildNodeAttributes(node, doSubstitution);
		if (node.isExpanded) {
			str += '>';
			str += this.renderArray(node.children);
			str += '</' + node.name + '>';
		} else {
			str += '/>';
		}
		return str;
	},
	
	renderPluginValueNode: function (node) {
		try {
			var key = this.doSubstitution(node.key, node);
			var value = this.getValue(key);
		} catch (e) {
			this.logValueError(e, node);
		}
		value = MvpWebTemplate.escapeValue(value);
		if (value == null) {
			return '';
		}
		return value;
	},
	
	renderPluginListNode: function(node) {
		try {
			var key = this.doSubstitution(node.key, node);
			var list = this.getValue(key);
		} catch (e) {
			this.logValueError(e, node);
		}
		if (list == null) {
			return '';
		}
		if (! Object.isArray(list)) {
			MvpWebTemplate.logError('Non-list: ' + node.key);
			return '';
		}
		var listKey = this.parser.getListKey(node.key);
		if (node.setParentKey) {
			this.parser.pushParentKey(listKey);
		}
		var str = '';
		if (node.condition != null) {
			var condition = this.doSubstitution(node.condition);
			var conditionParser = new ValueKeyParser_Class();
		}
		for (var i = 0; i < list.length; i++) {
			var value = list[i];
			if (condition != undefined) {
				conditionParser.data = value;
				var result = conditionParser.getValue(condition, value);
				if (result == null || (new Boolean(result)) == false) {
					continue;
				}
			}
			value.hasNext = (i + 1 < list.length);
			value.hasPrev = (i > 0);
			this.parser.currentLists[listKey] = value;
			str += this.renderArray(node.children);
		}
		this.parser.currentLists[listKey] = undefined;
		if (node.setParentKey) {
			this.parser.popParentKey();
		}
		return str;
	},
	
	renderPluginConditionalNode: function(node) {
		var result = true;
		try {
			var key = this.doSubstitution(node.key, node);
			var value = this.getValue(key);
		} catch (e) {
			this.logValueError(e, node);
		}
		if (value == null) {
			result = false;
		} else {
			result = new Boolean(value);
		}
		if (result != node.desiredResult) {
			return '';
		}
		return this.renderArray(node.children);
	},
	
	renderPluginSwitchNode: function(node) {
		try {
			var value = this.getValue(node.key, false);
		} catch (e) {
			this.logValueError(e, node);
		}
		var caseNode = node.cases[value];
		if (caseNode == null) {
			caseNode = node.defaultCase;
		}
		if (caseNode != null) {
			return this.renderNode(caseNode);
		}
		return '';
	},
	
	renderPluginFormInputTextNode: function(node) {
		var newNode = Object.toJSON(node).evalJSON();
		var key = node.attributes['name'];
		try {
			var value = this.getValue(key);
		} catch (e) {
			this.logValueError(e, node);
		}
		value = MvpWebTemplate.escapeValue(value);
		if (value != null && (String(value).length > 0)) {
			newNode.attributes['value'] = value;
		} else if (newNode.attributes['value'] == null){
			newNode.attributes['value'] = '';
		}
		return this.renderRegularNode(newNode);
	},
	
	renderPluginFormInputHiddenNode: function(node) {
		var newNode = Object.toJSON(node).evalJSON();
		var key = node.attributes['name'];
		try {
			var value = this.getValue(key);
		} catch (e) {
			this.logValueError(e, node);
		}
		value = MvpWebTemplate.escapeValue(value);
		newNode.attributes['value'] = value;
		return this.renderRegularNode(newNode);
	},
	
	renderPluginFormTextAreaNode: function(node) {
		var newNode = Object.toJSON(node).evalJSON();
		var key = node.attributes['name'];
		key = this.doSubstitution(key, node);
		try {
			var value = this.getValue(key);
		} catch (e) {
			this.logValueError(e, node);
		}
		value = MvpWebTemplate.escapeValue(value);
		if ((value != null) && (value.toString().length > 0)) {
			newNode.children = new Array();
			var text = new Object();
			text.type = 'text';
			text.contents = value.toString();
			newNode.children.push(text);
		}
		return this.renderRegularNode(newNode, true);
	},
	
	renderPluginFormSelectNode: function(node) {
		var newNode = Object.toJSON(node).evalJSON();
		var key = node.attributes.name;
		key = this.doSubstitution(key, node);
		try {
			var value = this.getValue(key);
		} catch (e) {
			this.logValueError(e, node);
		}
		this.currentSelectValue = value;
		newNode.type = 'regular';
		newNode.name = 'select';
		var str = this.renderRegularNode(newNode, true);
		this.currentSelectValue = undefined;
		return str;
	},
	
	renderPluginFormOptionNode: function(node) {
		var newNode = Object.toJSON(node).evalJSON();
		var value = this.doSubstitution(node.attributes.value, node);
		if (this.currentSelectValue != undefined) {
			if (value == this.currentSelectValue) {
				newNode.attributes.selected='selected';
			} else {
				newNode.attributes.selected = undefined;
			}
		}
		newNode.type = 'regular';
		newNode.name = 'option';
		newNode.attributes.value = value;
		return this.renderRegularNode(newNode);
	},
	
	renderPluginFormChoiceNode: function(node) {
		var newNode = Object.toJSON(node).evalJSON();
		var key = node.attributes.name;
		key = this.doSubstitution(key, node);
		try {
			var value = this.getValue(key);
		} catch (e) {
			this.logValueError(e, node);
		}
		if (node.radio) {
			MvpWebTemplate.logError('Radio button inputs have not been implemented');
		} else {
			var result;
			if (value == null) {
				result = node.defaultChecked;
			} else {
				result = new Boolean(value);
			}
			if (result == true) {
				newNode.attributes.checked = 'checked';
			} else {
				newNode.attributes.checked = undefined;
			}
			
		}
		newNode.name = 'input';
		return this.renderRegularNode(newNode, true);
	},
	
	renderElement: function(node) {
		this.parser.pushElementAttributes(node);
		var name = this.doSubstitution(node.attributes.name, node);
		var result = this.renderTemplate(name);
		this.parser.popElementAttributes();
		return result;
	},
	
	buildNodeAttributes: function(node, doSubstitution) {
		var str = '';
		var attrs = $H(node.attributes);
		var keys = attrs.keys();
		for (var i = 0; i < keys.length; i++) {
			var key = keys[i];
			var attrName = key;
			if (key.startsWith(":if")) {
				var not;
				var valueKey;
				if (key.startsWith(":ifnot:")) {
					not = true;
					valueKey = key.substring(7);
				} else {
					not = false;
					valueKey = key.substring(4);
				}
				attrName = valueKey.substring(valueKey.lastIndexOf(':') + 1);
				var valueKey = valueKey.substring(0, valueKey.lastIndexOf(':'));
				valueKey = this.doSubstitution(valueKey, node);
				var value = this.getValue(valueKey, node);
				if (value == null) {
					result = false;
				} else {
					result = new Boolean(value);
				}
				result = result ^ not;
				if (! result) {
					continue;
				}
			}
			var value = attrs.get(key);
			if (doSubstitution) {
				value = this.doSubstitution(value, node);
			}
			if (value != undefined) {
				str += ' ' + attrName + '="' + value + '"';
			}
		}
		return str;
	},
	
	getValue: function(key) {
		if (key.startsWith('show-') || key.startsWith('hide-')) {
			return this.getShownElementClass(key);
		}
		var value = this.parser.getValue(key, null);
		if (value == null) {
			return undefined;
		}
		return value;
	},
	
	doSubstitution: function(value, node) {
		try {
			return this.parser.doSubstitution(value, null);
		} catch (e) {
			this.logValueError(e, node);
		}
	},
	
	getShownElementClass: function(key) {
		var opposite = false;
		if (key.startsWith('hide-')) {
			opposite = true;
		}
		var show;
		if (MvpWebTemplate.shownElements.get(key.substr(5)) != undefined) {
			show = true;
		} else {
			show = false;
		}
		if (opposite) {
			show = ! show;
		}
		var result = Object();
		result.type = 'String';
		if (show) {
			result.value = 'show ' + 'CAF-' + key;
		} else {
			result.value = 'hide ' + 'CAF-' + key;
		}
		return result;
	},
	
	addShowHideProviders: function() {
		this.parser.data['show'] = this.createShowHideProvider(false, false);
		this.parser.data['hide'] = this.createShowHideProvider(true, false);
		this.parser.data['showClass'] = this.createShowHideProvider(false, true);
		this.parser.data['hideClass'] = this.createShowHideProvider(true, true);
		this.parser.data['switchShowLink'] = this.createShowHideProvider(false, true, true);
	},
	
	logValueError: function(e, node) {
		var message = this.buildErrorMessage(e, node);
		MvpWebTemplate.logError(message);
	},
	
	buildErrorMessage: function(e, node) {
		var message = '<code>';
		if (e.message != null) {
			message += e.message;
		}
		if (e.key != null) {
			message += ': &quot;' + e.key + '&quot;';
			if (e.fullKey != null && e.fullKey != e.key) {
				message += ' [full key: &quot;' + e.fullKey + '&quot;]';
			}
			if ((node != null) && (node.location != null)) {
				message += ' (' + node.location + ')';
			}
		}
		message += '</code>';
		message += this.getStackTrace(e);
		if (e.cause != null) {
			message += '<div class="cause">';
			message += this.buildErrorMessage(e.cause, null);
			message += '</div>';
		}
		return message;
	},
	
	getStackTrace: function(e) {
		var message = '';
		message += Object.toJSON(e);
		debuggedcaf = true;
		if (e.stack != null) {
			message += '<ul>';
			var stack = e.stack.split('\n');
			for (var i = 0; i < stack.length; i++) {
				message += '<li><code>' + stack[i] + '</code></li>';
			}
			message += '</ul>';
		}
		return message;
	},
	
	createShowHideProvider: function(isHide, isClass, isSwitchShow) {
		var provider = new Object();
		provider.getValue = function(key) {
			var show = MvpWebTemplate.shownElements.get(key) != undefined;
			show = show ^ this.isHide;
			if (this.isClass) {
				if (this.isSwitchShow) {
					return (show ? 'active' : 'inactive') + ' CAF-switchShow-' + key;
				} else {
					return (show ? 'show' : 'hide') + ' CAF-' + (this.isHide ? 'hide-' : 'show-') + key;
				}
			}
			return show;
		};
		provider.isHide = isHide;
		provider.isClass = isClass;
		provider.isSwitchShow = isSwitchShow;
		return provider;
	}
});

var MvpWebTemplate = new MvpWebTemplate_Class();

