var phorm = {
	
	id_count: 0,
	// return types: message, alert, div, return
	return_type: "message",	
	// set which keys to ignore on field checks
	ignore_keys: [13],
	// sets the element which innerHTML should be changed when type = message
	message_element_id: "",
	// save events for form submission
	checks: [],
	// override default message
	messages: [],
	// set rules for checks
	rules: [],
	// set whether to bail on first error when checking all errors
	bail_on_first_error: false,
	// set whether to update on loss of focus
	check_onblur: true,
	// created funcs
	func_id: 0,
	func_objs: {},

	/***************************************************************************************************/


	// adds a check to a single form element
	add_check: function(element, type, event_type, params, div_id)
	{
		if (type != "all") {
			var x = new Object;
			x.form = element.form;
			x.name = element.name;
			x.element = element;
			x.type = type;
			x.event_type = event_type;
			x.params = params;
			x.div_id = div_id;
			x.multi = false;
			// radio needs options to be set up separately
			if (type == "radio") {
				for (var q = 0; q < element.length; q ++) {
					eval("element[q]." + event_type + " = function(e) { if (phorm.enforce('" + event_type + "', e)) { if (! phorm.check_" + type + "(this, params)) { phorm.error(this, '" + type + "', '" + event_type + "', [" + params + "], '" + div_id + "'); } else { return phorm.ok(this, '" + div_id + "'); } } };");
				}
			}
			// match needs all elements that need to match to handle this event
			else if (type == "match" || type == "password_bonhams" || type == "password_bonhams2" || x.type == "email_confirm") {
				params.unshift(element);
				for (var b = 0; b < params.length; b ++) {
					// overwrite element
					element = params[b];
					if (element) {				

						if (event_type.length) {
							if (phorm.check_onblur) {
								eval("element.onblur = function(e) { if (! phorm.check_" + type + "(this, params)) { phorm.error(this, '" + type + "', '" + event_type + "', [], '" + div_id + "'); } else { return phorm.ok(this, '" + div_id + "'); } };");
							}
							eval("element." + event_type + " = function(e) { if (phorm.enforce('" + event_type + "', e)) { if (! phorm.check_" + type + "(this, params)) { phorm.error(this, '" + type + "', '" + event_type + "', [], '" + div_id + "'); } else { return phorm.ok(this, '" + div_id + "'); } } };");
						}
					}
				}
				// re-assign params
				x.params = params;
			}
			// all other elements
			else {
				if (x.element.id == "undefined" || ! x.element.id.length) {
					x.element.id = "phorm_" + (phorm.id_count++);
				}
				if (event_type.length) {
					if (phorm.check_onblur) {
						eval("element.onblur = function(e) { if (! phorm.check_" + type + "(this, params)) { phorm.error(this, '" + type + "', '" + event_type + "', [" + params + "], '" + div_id + "'); } else { return phorm.ok(this, '" + div_id + "'); } };");
					}
					eval("element." + event_type + " = function(e) { if (phorm.enforce('" + event_type + "', e)) { if (! phorm.check_" + type + "(this, params)) { phorm.error(this, '" + type + "', '" + event_type + "', [" + params + "], '" + div_id + "'); } else { return phorm.ok(this, '" + div_id + "'); } } };");
				}
			}
			phorm.checks.push(x);
		}
		else {
			eval("element." + event_type + " = function(e) { return phorm.check_all(this); };");
		}		
	},

	// checks by combining elements (eg birthdates, credit cards)
	add_multi_check: function(els, type, event_type, params, div_id)
	{
		var id = phorm.func_id++;
		for (var w in els) {
			els[w].setAttribute("phorm_func_id", id);
			if (typeof(phorm.func_objs[id]) == "undefined") {
				phorm.func_objs[id] = [];
			}
			phorm.func_objs[id].push(els[w]);
			eval("els[w]." + event_type + " = function(e) { if (phorm.enforce('" + event_type + "', e)) { if (! phorm.check_" + type + "(phorm.get_multi_value(this.getAttribute('phorm_func_id')), params)) { phorm.error(this, '" + type + "', '" + event_type + "', [" + params + "], '" + div_id + "'); } else { return phorm.ok(this, '" + div_id + "'); } } };");
			if (phorm.check_onblur) {
				eval("els[w].onblur = function(e) { if (! phorm.check_" + type + "(phorm.get_multi_value(this.getAttribute('phorm_func_id')), params)) { phorm.error(this, '" + type + "', '" + event_type + "', " + params + ", '" + div_id + "'); } else { return phorm.ok(this, '" + div_id + "'); } };");
			}
			

		}
		var x = new Object;
		x.element = els[0];
		if (x.element.id == "undefined" || ! x.element.id.length) {
			x.element.id = "phorm_" + (phorm.id_count++);
		}
		x.type = type;
		x.event_type = event_type;
		x.params = params;
		x.div_id = div_id;
		x.multi = true;
		phorm.checks.push(x);
	},

	// overrides a default message
	add_message: function(element, msg, type)
	{
		var m = new Object;
		m.element = element;
		m.message = msg;
		m.event = type;
		phorm.messages.push(m);
	},
	
	// sets multiple elements messages
	add_multi_message: function(els, msg, type)
	{
		for (var b = 0; b < els.length; b ++) {
			phorm.add_message(els[b], msg, type);
		}
	},

	// sets a rule for when a check should be performed - not in use yet
	/*add_rule: function(element, rule, type)
	{
		var v = new Object;
		v.element = element;
		v.rule = rule;
		v.event = type;
		phorm.rules.push(v);
	},*/

	/***************************************************************************************************/
	
	// checks to see whether the check should take place - some cases are ignored eg normally return
	enforce: function(type, e)
	{
		var key = phorm.key(e);
		switch (true) {
			// check for onkey(press|up|down) and ignore certain chars
			case (type.search(/key/) != -1) :
				for (var x = 0; x < phorm.ignore_keys; x ++) {
					if (phorm.ignore_keys[x] == key) {
						return false;
					}
				}
			break;
		}
		return true;
	},

	/***************************************************************************************************/

	check_all: function(f)
	{
		var ok = true;
		var focused = false;
		for (var q in phorm.checks) {
			var m = phorm.checks[q];
			//Skip elements that are of type hidden.
			try {
				if (m.element.type == 'hidden') 
					continue;
			} catch (error) {
				
			}
			if (m.multi) {
				var val = phorm.get_multi_value(m.element.getAttribute("phorm_func_id"));
				eval("if (! phorm.check_" + m.type + "('" + val + "', [" + m.params + "])) { phorm.error(phorm.get('" + m.element.id + "'), '" + m.type + "', '" + m.event_type + "', [" + m.params + "], '" + m.div_id + "'); ok = false; } else { phorm.ok(phorm.get('" + m.element.id + "'), '" + m.div_id + "'); }");
			}
			else if (m.type == "radio") {
				eval("var me = phorm.get(m.element[0].name, m.element[0].form);");
				eval("if (! phorm.check_radio(me)) { phorm.error(me, '" + m.type + "', '" + m.event_type + "', [" + m.params + "], '" + m.div_id + "'); ok = false; } else { phorm.ok(me, '" + m.div_id + "'); }");
			}
			/*else if (m.type == "email_confirm") {
				alert('Email adresses do not match');
				eval("if (! phorm.check_" + m.type + "(phorm.get('" + m.element.id + "'), [" + m.params + "])) { phorm.error(phorm.get('" + m.element.id + "'), '" + m.type + "', '" + m.event_type + "', [" + m.params + "], '" + m.div_id + "'); ok = false; } else { phorm.ok(phorm.get('" + m.element.id + "'), '" + m.div_id + "'); }");
				ok = false;
			}*/
			else {
				try {
					// run check
					eval("if (! phorm.check_" + m.type + "(phorm.get('" + m.element.id + "'), [" + m.params + "])) { phorm.error(phorm.get('" + m.element.id + "'), '" + m.type + "', '" + m.event_type + "', [" + m.params + "], '" + m.div_id + "'); ok = false; } else { phorm.ok(phorm.get('" + m.element.id + "'), '" + m.div_id + "'); }");
				}
				catch (e) { 
					switch (m.type) {
						case "match" : case "password_bonhams" : case "password_bonhams2" :
							eval("if (! phorm.check_" + m.type + "(m.element, m.params)) { phorm.error(m.element, '" + m.type + "', '" + m.event_type + "', [], '" + m.div_id + "'); ok = false; } else { phorm.ok(m.element, '" + m.div_id + "'); }");
						break;
					}
				};
			}
			// focus if first element error
			if (! ok) {
				if (! focused) {
					try {
						m.element.focus();
						focused = true;
					}
					catch (e) { 
						// element cannot receive focus - i.e. radio button
					};
				}
				if (phorm.bail_on_first_error) {
					break;
				}
			}	
		}
		return ok;
	},
	
	// checks the length of the given field
	check_length: function(obj, params)
	{
		var re = phorm.length_regexp("*", params);
		return (! (obj.value.length < params[0]) || obj.value.search(re) != -1);
	}, 

	/*
	checks a string to make sure the given param length is met with alpha chars
	parameter order:

	1: length to meet
	2: whether to be strict on length met
	2: whether to be strict or alpha only

	*/
	check_alpha_length: function(obj, params)
	{
		var re = phorm.length_regexp("alpha", params);
		var val = (typeof(obj) == "object") ? obj.value : obj;
		return (val.search(re) != -1);
	},

	/*
	checks a string to make sure the given param length is met with numeric chars

	1: length to meet
	2: whether to be strict on length met
	2: whether to be strict or numeric only

	*/
	check_numeric_length: function(obj, params)
	{
		var re = phorm.length_regexp("numeric", params);
		var val = (typeof(obj) == "object") ? obj.value : obj;
		//alert("RegExp: " + re + "\nValue: " + val + "\nResult: " + val.search(re));
		return (val.search(re) != -1);
	},
	
	// checks to make sure the value entered is indeed a decimal
	check_decimal: function(obj, params)
	{
		// set number of decimal places to go to, default to 2
		/*
		var dp = (! params) ? 2 : params[0];
		var re = new RegExp('/^[0-9]+\.?[0-9]{0,' + dp + '}$/');
		return (obj.value.search(re) != -1);
		*/
		return (obj.value.search(/^[0-9]+\.?[0-9]{0,2}$/) != -1);
	},
	
	// checks an email
	check_email: function(obj)
	{
		return (obj.value.search(/^([a-zA-Z0-9_\.\-\+])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/) != -1);
	},

	// checks a checkbox has been selected
	check_checkbox: function(obj)
	{
		return obj.checked;
	},

	// checks to see whether two or more fields are the same
	check_match: function(obj, params)
	{
		// create check
		for (var x = 0; x < params.length; x ++) {
			var val = params[x].value;			
			// set comparison 
			if (x == 0) {
				var str = val;
				continue;
			}
			// do check
			if (str != val) {
				return false;
			}
		}
		return true;
	},

	check_password_bonhams: function(obj, params)
	{
		if (obj.name.search(/_2$/) != -1) {
			var result = phorm.check_length(obj, [5]);
			// check the fields match
			if (result) {
				result = phorm.check_match(obj, params);
			}
			// check for numbers and characters
			if (result) {
				result = (obj.value.search(/[^0-9a-z]*/i) != -1);
			}	
			return result;
		}
		else {
			return true;
		}
	},

	check_password_bonhams2: function(obj, params)
	{
		if (obj.name.search(/_2$/) != -1) {
			var result = true;
			if (obj.value.length) {
				// check the fields match
				if (result) {
					result = phorm.check_match(obj, params);
				}
				// check for numbers and characters
				if (result) {
					result = (obj.value.search(/[^0-9a-z]*/i) != -1);
				}
			}
			return result;
		}
		else {
			return true;
		}
	},

	check_email_confirm: function(obj, params)
	{
		if (obj.name == "Email_confirm") {
			var result = phorm.check_email(obj);
			if (result) {
				result = phorm.check_match(obj, params);
			}
			return result;
		}
		return true;
	},

	// checks a radio button fro a value
	check_radio: function(obj)
	{		
		// get radio buttons, two options can be passed, a direct reference to a radio button or the entire radio button group
		var radios = (obj.length) ? obj[0].form.elements[obj[0].name] : obj.form.elements[obj.name];
				
		var x = 0;
		while (radios[x]) {
			if (radios[x].checked) {
				return true;
			}
			x++;
		}
		return false;
	},

	/***************************************************************************************************/

	// returns the regular expression required
	length_regexp: function(type, params)
	{
		var strictlen = (typeof(params[1]) != "undefined") ? params[1] : false;
		var strict = (typeof(params[2]) != "undefined") ? params[2] : false;
		var exp = "";
		if (strict) {
			exp += "^";
		}
		switch (type) {
			case "*" : exp += "."; break;
			case "alpha" : exp += "[a-z]"; break;
			case "numeric" : exp += "[0-9]"; break;
		}
		exp += "{" + params[0] + ((strictlen) ? "}" : ",}");
		if (strict) {
			exp += "$";
		}
		return new RegExp(exp, "ig");
	},
	
	// returns the key pressed by the user
	key: function(e)
	{
		if (! e) {
			e = window.event;
		}
		if (e) {
			if (e.keyCode) {
				return e.keyCode;
			}
			else if (e.charCode) {
				return e.charCode;
			}
		}
		else {
			return 0;
		}
	},
		
	// returns an element, stops an error occuring when trying to do the eval on check_all
	get: function(id, form)
	{
		var obj = document.getElementById(id);
		if (! obj) {
			var f;
			if (form.hasAttribute("id")) {
				f = document.getElementById(form.id);
			}
			if (! f) {
				var f = document.forms[form.name];
			}
			if (! f) {
				alert("Form Element ID / Name: " + id + "\nForm ID / Name: " + form + "\nThe form element could not be sourced");
			}
			obj = f.elements[id];
		}
		return obj;
	},
	
	// returns a combined value
	get_multi_value: function(id)
	{
		var val = "";
		var x = phorm.func_objs[id];
		for (var b = 0; b < x.length; b ++) {
			val += x[b].value;
		}
		return val;
	},

	/***************************************************************************************************/

	// handles the return / output of an error
	error: function(obj, type, event_type, params, div_id)
	{
		var err = phorm.error_message(obj, type);
		if (err.length == 0) {
			switch (type) {
				case "decimal" : 
					err = "The value you have entered is not a valid number";
				break;
				case "alpha_length" : case "numeric_length" : case "length" :
					err = "Please complete this field";
				break;
				case "match" : 
					err = "Your fields do not match";
				break;
				case "radio" :
					err = "Please select a value for " + obj.name.replace(/_/g, " ");
				break;
				case "password_bonhams" : case "password_bonhams2" :
					err = "Your passwords either do yet not match, or are under the minimum length"; 
					//  or do not contain at least one letter and at least one character
				break;
				case "email_confirm" :
					err  = "Please confirm your email address";
				break;

				default: err = "Unspecified\nObj: " + obj + "\nType: " + type;
			}
		}

		switch (phorm.return_type) {
			case "message" : 
				phorm.set_content(phorm.message_element_id, err);
			break;
			case "alert" : 
				alert(err);
			break;
			case "div" : 
				phorm.div_message(obj, err, div_id);
			break;
		}
	},
	
	// returns true etc
	ok: function(obj, div_id)
	{
		switch (phorm.return_type) {
			case "message" : 
				phorm.set_content(phorm.message_element_id, "");
			break;
			case "div" :
				var id = (div_id != null && div_id != "undefined") ? div_id : obj.name + "_phorm_error";
				var d = document.getElementById(id);
				if (d) {
					switch (d.getAttribute("displayState")) {
						case "under" : d.style.display = "none"; break;
						//default : d.style.visibility = "hidden";
					}
				}
			break;
		}
		return true;
	},

	// gets a set error message
	error_message: function(obj, type)
	{
		for (var x in phorm.messages) {
			var c = phorm.messages[x];
			if (c.element == obj) {
				if (c.type == null || c.type == "undefined" || c.type == type) {
					return c.message;
				}
			}
		}
		return "";
	},

	/***************************************************************************************************/
	
	// creates if needed a div and places next to text
	div_message: function(obj, str, div_id)
	{
		// radio button
		if (obj.length) {
			var id = (div_id != null && div_id != "undefined") ? div_id : obj[0].name + "_phorm_error";
		}
		else {
			var id = (div_id != null && div_id != "undefined") ? div_id : obj.name + "_phorm_error";
		}
		var d = document.getElementById(id);
		if (! d) {
			var d = document.createElement("div");
			d.id = id;
			d.className = "phorm_error";
			// radio buttons need a block level element
			if ((document.all && obj.tagName.toLowerCase() == "input" && obj.type == "radio" ) || (obj.length && obj[0].tagName.toLowerCase() == "input" && obj[0].type == "radio")) {
				d.style.display = "none";
			}
			// add error div
			try {
				obj.parentNode.appendChild(d);
			}
			// radio buttons
			catch (e) {
				obj[0].parentNode.appendChild(d);
			}
		}
		d.style.display = "block";
		d.style.margin = "5px 0 0 0";
		d.setAttribute("displayState", "under");
		d.innerHTML = str;
	},

	// sets the content of a given obj
	set_content: function(id, str)
	{
		var x = document.getElementById(id);
		if (x) {
			x.innerHTML = str;
		}
		else {
			alert("document element with id: " + id + " does not exist");
		}
	}
}