var live_default = 'polite';

// Settings for the library
var Configuration = {
	defaults : {
		live : 'polite',
		response_type : 'xml'
	}
};


// Controller
function Controller() { }
Controller.prototype = {
	views : new Array()
}

/**
 * The Model provides an interface to the transport
 * of the actual data to and from the server.
 */
function Model() { }
Model.prototype = {
	data : { }
}

// View
function View() { }
View.prototype = {
	fields : { },
	
	get : function(field) {
		if (this.fields[field]) {
			return this.getFieldValue(field);
		} else {
			return false;
		}
	},
	
	getFieldValue : function(field) {
		
	}
}

// Field
function Field() { }
Field.prototype = {
	id : null,
	name : null,
	type : null,
	value : null,
	readonly : false,
	regex : /^.*$/,
	
	valid : function() {
		return this.regex.test(this.value);
	}
}

// Throbber manager
function Throbber() { }
Throbber.prototype = {
	image : null,
	requests : 0,
	
	requestOpened : function(event) {
		if (this.requests == 0) {
			this.image.src = '../images/throbber.gif';
		}
		this.requests++;
	},
	
	requestLoaded : function(event) {
		this.requests--;
		if (this.requests == 0) {
			this.image.src = '../images/throbber_stopped.gif';
		}
	},

	clicked : function() {
		request_manager.abortAll();
	},
	
	// Called on window load
	attach : function() {
		this.image = document.getElementById('throbber');
		if (this.image && request_manager) {
			request_manager.addEventListener('open', new Array(this, this.requestOpened));
			request_manager.addEventListener('load', new Array(this, this.requestLoaded));
			request_manager.addEventListener('abort', new Array(this, this.requestLoaded));
			this.image.onclick = new Function("Throbber.prototype.clicked.apply(throbber, arguments);");
		}
	}
}
var throbber = new Throbber();
window.addEventListener('load', new Function("Throbber.prototype.attach.apply(throbber, arguments);"), false);

function Messenger() { }
Messenger.prototype = {
	error_node : null,
	message_node : null,
	output : null,

	attach : function() {
		// Setup a reference to the output container
		this.output = document.getElementById('messages');
		// Setup the template for error/message nodes
		this.error_node = document.createElement('span');
		this.error_node.setAttribute('class', 'error');
		this.message_node = document.createElement('span');
		this.message_node.setAttribute('class', 'message');
	},

	displayError : function(msg) {
		if (this.output) {
			var new_error = this.error_node.cloneNode(true);
			new_error.textContent = msg;
			new_error.style.color = '#666';
			this.output.appendChild(new_error);
			var fader = new ForegroundFade();
			fader.start = '666';
			fader.end = 'f33';
			fader.run(new_error);
			return new_error;
		} else {
			return false;
		}
	},

	displayMessage : function(msg) {
		if (this.output) {
			var new_message = this.message_node.cloneNode(true);
			new_message.textContent = msg;
			new_message.style.color = '#666';
			this.output.appendChild(new_message);
			var fader = new ForegroundFade();
			fader.start = '666';
			fader.end = '000';
			fader.run(new_message);
			return new_message;
		} else {
			return false;
		}
	},
	
	removeError : function(error) {
		var fader = new ForegroundFade();
		fader.start = 'f33';
		fader.end = '666';
		fader.addEventListener('end', new Array(this, Messenger.prototype.removeElement));
		fader.run(error);
	},

	removeElement : function(e) {
		try {
			this.output.removeChild(e.element);
		// Not a child, so nothing to remove
		} catch (exception) { }
	},
	
	removeMessage : function(message) {
		var fader = new ForgroundFade();
		fader.start = '000';
		fader.end = '666';
		fader.run(message);
		try {
			this.output.removeNode(message);
		// Not a child, so nothing to remove
		} catch (e) { }
	}
}
var messenger = new Messenger();
window.addEventListener('load', new Function("Messenger.prototype.attach.apply(messenger, arguments);"), false);

/**
 * A class with static methods of oft-used bits of logic
 */
function Utilities() { }
/**
 * Factory to spit out AjaxRequest objects prepared for this application
 */
Utilities.createAjaxRequest = function(type) {
	if (!type) {
		type = Configuration.defaults.response_type;
	}
	var req = request_manager.createAjaxRequest();
	req.headers['X-ResponseType'] = type;
}
/**
 * A list of available classes, as keys to their
 * corresponding source files. A script should
 * pre-generate this list rather than having it
 * hard-coded, so that it could always have the
 * latest classes and files.
 */
Utilities.classFiles = {
	"AjaxEvent" : "includes/ajax.lib.js",
	"AjaxRequest" : "includes/ajax.lib.js",
	"AjaxRequestManager" : "includes/ajax.lib.js",
	"BackgroundFade" : "includes/effects.lib.js",
	"ColorFade" : "includes/effects.lib.js",
	"Controller" : "includes/main.lib.js",
	"CustomEvent" : "includes/ajax.lib.js",
	"Effect" : "includes/effects.lib.js",
	"ElementEffectEvent" : "includes/effects.lib.js",
	"EventDispatcher" : "includes/ajax.lib.js",
	"FadeEvent" : "includes/effects.lib.js",
	"Field" : "includes/main.lib.js",
	"ForegroundFade" : "includes/effects.lib.js",
	"Messenger" : "includes/main.lib.js",
	"Model" : "includes/main.lib.js",
	"Throbber" : "includes/main.lib.js",
	"Utilities" : "includes/main.lib.js",
	"View" : "includes/main.lib.js"
}
/**
 * Late-loading of JavaScript files based on object to
 * file lookups. Once the file loads (or times out), it
 * triggers the callback (if specified), passing a boolean
 * as to whether it successfully loaded.
 */
Utilities.include = function(classname, callback) {
	// First, if already loaded, just call the callback
	if (window[classname]) {
		if (callback) {
			setTimeout(callback, 10, true);
		}
		return true;
	} else if (Utilities.classFiles[classname]) {
		return Utilities.loadJavaScript(
			Utilities.classFiles[classname],
			callback
		);
	} else {
		// Class not found, just return false
		return false;
	}
}
/**
 * Keep track of files already loaded
 */
Utilities.loadedJavaScript = { };
/**
 * Load the specified JavaScript file, optionally
 * calling a callback function, passing a boolean
 * as to whether the file loaded
 */
Utilities.loadJavaScript = function(file, callback) {
	if (Utilities.loadedJavaScript[file]) {
		if (callback) {
			setTimeout(callback, 10, true);
		}
		return true;
	} else {
		var head = document.getElementsByTagName("head")[0];
		var script = head.appendChild(
			document.createElement("script")
		);
		// Set timeout of a very liberal 30 seconds
		var timeout = setTimeout(
			function() {
				callback(false);
			},
			6000
		);
		script.addEventListener(
			"load",
			function() {
				clearTimeout(timeout);
				Utilities.loadedJavaScript[file] = true;
				callback(true);
			},
			false
		);
		script.type = "text/javascript";
		script.src = file;
		return true;
	}
}

function viewJSSource() {
	var code_elements = document.getElementsByTagName('script');
	if (code_elements) {
		for (var i = 0; i < code_elements.length; i++) {
			if (!code_elements.item(i).attributes.src) {
				var code_block = code_elements.item(i).firstChild;
				if (code_block && code_block.data) {
					var pre = document.createElement('pre');
					pre.setAttribute('class', 'demo javascript');
					pre.appendChild(code_block.cloneNode(true));
					document.body.appendChild(pre);
				}
			}
		}
	}
	return false;
}

/**
 * Abstract out the replacement of text to add screen reader support.
 */
function setElementText(container, text) {
	var live = (arguments[2]) ? arguments[2] : live_default;
	container.setAttribute('aaa:live', live);
	if (container.firstChild) {
		if (container.firstChild.nodeType == 3) {
			container.firstChild.nodeValue = text;
		} else {
			var new_text_node = document.createTextNode();
			new_text_node.nodeValue = text;
			container.replaceChild(new_text_node, container.firstChild);
		}
	} else {
		var new_text_node = document.createTextNode();
		new_text_node.nodeValue = text;
		container.appendChild(new_text_node);
	}
}

/**
 * Abstract out the replacement of an element to add screen reader support.
 */
function replaceAndFocusElement(new_element, old_element) {
	var live = (arguments[2]) ? arguments[2] : live_default;
	new_element.setAttribute('aaa:live', live);
	new_element.setAttribute('aaa:atomic', true);
	parent_element = old_element.parentNode;
	parent_element.replaceChild(new_element, old_element);
}

/**
 * Escape output with XHTML entities
 * This copies the string, and would need to get modified
 * in order to handle larger values.
 */
function escapeHTML(output) {
	var escaped_output = '';
	var temp_char = null;
	for (var i = 0; i < output.length; i++) {
		temp_char = output.charCodeAt(i).toString(16).toUpperCase();
		if (temp_char.length == 2) {
			escaped_output += '&#x' + temp_char + ';';
		} else {
			escaped_output += '&#x0' + temp_char + ';';
		}
	}
	return escaped_output;
}

/**
 * Abstract out the standards vs. IE implementation of
 * adding event listeners
 */
function addElementListener(element, action, listener) {
	if (!element) {
		return false;
	} else if (element.addEventListener) {
		if (typeof listener == 'function') {
			element.addEventListener(action, listener, false);
			// Accepts an array of the contextual object and the function to call
		} else if (typeof listener == 'object') {
			element.addEventListener(
				action,
				function() {
					return listener[1].apply(listener[0], arguments);
				},
				false
			);
		}
	} else if (element.attachEvent) {
		if (typeof listener == 'function') {
			element.attachEvent(action, listener);
			// Accepts an array of the contextual object and the function to call
		} else if (typeof listener == 'object') {
			element.attachEvent(
				action,
				function() {
					return listener[1].apply(listener[0], arguments);
				}
			);
		}
	}
	return true;
}
