// $Id: validate.js,v 1.37 2008-09-05 06:08:31 ozgur Exp $
//
// Default rules are at top
//

function ucfirst(s) {
	s = s.replace(/^\s+/, '').replace(/\s+$/, '');
	if (!s.match(/[A-Z]/) || !s.match(/[a-z]/)) {
		s = s.toLowerCase().replace(/\b\w+\b/g,
			function(word) {
				return word.substring(0,1).toUpperCase() + word.substring(1);
			}
		);
	}
	return s;
}

// 
// Validate an email address... very basic. Doesn't check things like control
// characters or validate the domain, or anything like that. That stuff should
// be handled in the back end.
// 
function validate_email(str) {
	/*
	 * Empty string is valid... Other checks should be done for mandatory fields
	 */
	if (str == null || str == "") {
		return true;
	}

	var at = "@";
	var dot = ".";
	var lat = str.indexOf(at);
	var lstr = str.length;
	var ldot = str.indexOf(dot);

	if (
		lat == -1 ||
		lat == 0 ||
		lat == lstr || /* An @ symbol that is not the first or last character */
		ldot == -1 ||
		ldot == 0 ||
		ldot == lstr || /* A period that is not the first or last character */
		str.indexOf(at, (lat + 1)) != -1 || /* No more than 1 @ symbol */
		str.substring(lat - 1, lat) == dot || /* No period just before the @ */
		str.substring(lat + 1, lat + 2) == dot || /* No period just after the @ */
		str.indexOf(dot, (lat + 2)) == -1 || /* At least 1 dot after the @ */
		str.indexOf(" ") != -1 /* No spaces */
	) {
		return false;
	}
	return true;
}

//
// Untested : http://www.bigbold.com/snippets/posts/show/452
//
function validate_url(s) {
	var regexp = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/
	return !s || regexp.test(s);
}

//
// Validate a telephone number:
//   spaces and hyphens are allowed anywhere
// then, in order:
//   optional international prefix:  plus sign, 1 to 3 digits
//   optional area code in brackets: open round bracket, digits, close round bracket
//   at least 8 digits, or '13' followed by at least 4 digits, or '000'
//   optional arbitrary "memo" text _after_ the phone number

//
function validate_phone(s) {
	var p = s.replace(/[-\s]/g,'');        // remove formatting
	return (/^(\+\d{1,3})?(\(\d+\))?(\d{8,}|13\d{4,}|000)/).test(p);
}

//
// Returns 0 if anything (non-hidden) is filled
//
function validate_emptyform(form) {
	for (var i=0; i<form.elements.length; i++) {
		element = form.elements[i];
		if (element.type && element.type.match(/submit|hidden|password/i)) {
			continue;
		} else if (element.type && element.type.match(/text/i)) {
			if (element.value) {
				return 0;
			}
		} else if (element.type && element.type.match(/checkbox|radio/i)) {
			if (element.checked && element.value) {
				return 0;
			}
		} else if (element.tagName.match(/select/i)) {
			if (element.selectedIndex > 0 && element.options[element.selectedIndex].value) {
				return 0;
			}
		}
	}
	return 1;
}

var FormGroup = function (group_name, elem) {
	FormGroup[group_name] = FormGroup[group_name] || [];
	FormGroup[group_name].push(elem);
	FormGroup[group_name].firstelem = FormGroup[group_name].firstelem || elem;
}
FormGroup.first = function (group_name, elem) {
	return FormGroup[group_name].firstelem == elem;
} 
FormGroup.empty = function (group_name) {
	return validate_emptyform({elements : FormGroup[group_name]});
}

//
// Makes sure only one alert box appears
//
function block_alert(elem, message)
{
	if (block_alert.block) {
		return false;
	}
	block_alert.block = true;
 	alert(decodeURIComponent(message.replace(/_/g,' ')));
 	elem.select && setTimeout(function(){elem.focus();elem.select();}, 50);
	return block_alert.block = false;
}

function block_confirm(elem, message)
{
	if (!confirm(decodeURIComponent(message.replace(/_/g,' ')))) {
		elem.select && setTimeout(function(){elem.focus();elem.select();}, 50);
		return false;
	}
	return true;
}

//
// find the label for a particular field
// With cache for direct access
//
function form_labelfor(fieldName)
{
	if (form_labelfor.cache) {
		return form_labelfor.cache[fieldName];
	}
	form_labelfor.cache = {};
	var labels = document.getElementsByTagName('label');
	for (var i = 0; i < labels.length; ++i) {
		form_labelfor.cache[labels[i].htmlFor]
			= labels[i].textContent || labels[i].innerHTML;
	}
	return form_labelfor.cache[fieldName];
}


FormsClassParser.addRules({
//
// Use class="validate_date" on input field to force dd/mm/yyyy format
// Currently accepts dd/mm/yyyy, dd-mm-yyyy, ddmmyyyy, yyyy-mm-dd
//
	validate_date : {
		'onblur,onformsubmit' : function() {
			this.value = this.value.replace(/^\s+/, '').replace(/\s+$/, '');
			if (this.value.length && !date_format(this)) {
				return block_alert(this, "Please enter date fields as dd/mm/yyyy");
			}
			return true;
		}
	},
//
// Use class="validate_time" on input field to force hh:mm:ss format
// Currently accepts hh:mm:ss, hh-mm-ss, hhmmss
//
	validate_time : {
		'onblur,onformsubmit' : function() {
			this.value = this.value.replace(/^\s+/, '').replace(/\s+$/, '');
			if (this.value.length && !time_format(this)) {
				return block_alert(this, "Please enter time fields as hh:mm:ss");
			}
			return true;
		}
	},
//
// Use class="validate_datetime" on input field to force correct date and
// time format.
//
	validate_datetime : {
		'onblur,onformsubmit' : function() {
			this.value = this.value.replace(/^\s+/, '').replace(/\s+$/, '');
			if (this.value.length && !date_format_substr(this)) {
				return block_alert(this, "Please enter date as dd/mm/yyyy");
			}
			if (this.value.length && !time_format_substr(this)) {
				return block_alert(this, "Please enter time as hh:mm:ss");
			}
			return true;
		}
	},
//
// Use class="validate_confirm::form_field::Field%20Name" on a confirmation 
// field to ensure the value is identical to that of the specified field.
// When the values do not match, alert with
//   The confirmation does not match the Field Name you provided.
//   Please try again.
//
	validate_confirm : {
		onformsubmit : function() {
			if (this.className.match(/validate_confirm::([^: ]+)/)) {
				var field = this.form.elements[RegExp.$1];
				var name = 
					this.className.match(/validate_confirm::([^: ]+)::([^: ]+)/)
					? RegExp.$2
					: (form_labelfor(this.name) || this.name);
				if (field.value != this.value) {
					return block_alert(this, "The confirmation does not match the " + name + " you provided. Please try again.");
				}
			}
			return true;
		}
	},
//
// Use class="validate_mandatory" on input field to make mandatory.
// Use class="validate_mandatory::Field%20Name" to alert with
//   Field Name is mandatory, please fill it in
//
	validate_mandatory : {
		onformsubmit : function() {
			window.placeholder = window.placeholder || {};
			if (!this.value || this.value == window.placeholder[this.name]) {
				var name =
					this.className.match(/validate_mandatory::([^: ]+)/)
					? RegExp.$1
					: (form_labelfor(this.name) || this.name);
				return block_alert(this, name + " is mandatory, please fill in");
			}
			return true;
		}
	},
//
// Use class="validate_ucfirst" on input fields for name to change
// john smith to John Smith
//
	validate_ucfirst : {
		'onblur,onformsubmit' : function() {
			if (this.value) {
				this.value = ucfirst(this.value);
			}
			return 1;
		}
	},

//
// Use class="placeholder::Please%20%Fill20In%20Message" To have "Please 
//   Fill In Message" as the initial text of the blank field
//
	placeholder : {
		onload : function() {
			if (this.className.match(/placeholder::([^: ]+)/)) {
				window.placeholder = window.placeholder || {};
				window.placeholder[this.name] = this.value = decodeURIComponent(RegExp.$1.replace(/_/g,' '));
			}
		},
		onfocus : function() {
			if (this.value == window.placeholder[this.name]) {
				this.value = '';
			}
		},
		postblur : function() {
			if (this.value == '') {
				this.value = window.placeholder[this.name];
			}
		},
		postformsubmit : function() {
			if (this.value == window.placeholder[this.name]) {
				this.value = '';
			}
		}
	},

//
// Use class="validate_past_date" to make sure date is in the past, good for birthday etc
//
	validate_past_date : {
		'onblur,onformsubmit' : function() {
			this.value = this.value.replace(/^\s+/, '').replace(/\s+$/, '');
			if (!this.value.length) {
				return true;
			}
			var date = date_format(this);
			if (!date) {
				return block_alert(this, "Please enter date fields as dd/mm/yyyy");
			}
			if ((new Date(date[2], date[1] - 1, date[0])) > (new Date())) {
				return block_alert(this, "Please enter a date that is in the past for " + form_labelfor(this.name));
			}
			return true;
		}
	},

//
// Use class="validate_postcode" to validate a 4 digit postcode
// 
	validate_postcode : {
		'onblur,onformsubmit' : function() {
			this.value = this.value.replace(/^\s+/, '').replace(/\s+$/, '');
			if (this.value.length && !this.value.match(/^\d{4}$/)) {
				return block_alert(this, "Please enter postcode fields as 4 digits");
			}
			return true;
		}
	},

//
// Use class="validate_integer" to check that its all digits
//
	validate_integer : {
		'onblur,onformsubmit' : function(event) {
			this.value = this.value.replace(/^\s+/, '').replace(/\s+$/, '');
			if (this.value.length && !this.value.match(/^\d+$/)) {
				return block_alert(this, "That does not look like a whole number, please try again.\nPlease enter all digits");
			}
			return true;
		}
	},

//
// Use class="validate_length" to check length
//
	validate_length : {
		'onblur,onformsubmit' : function(event) {
			this.value = this.value.replace(/^\s+/, '').replace(/\s+$/, '');
			var length = this.className.match(/validate_length::(\d+)/)[1];
			if (this.value.length && this.value.length < length) {
				return block_alert(this, "Please enter " + length + " or more charaters");
			}
			return true;
		}
	},

//
// Use class="validate_email" to check that its a valid email address. It 
// allows comma seperated email addresses, up to 10 addresses
//
	validate_email : {
		'onblur,onformsubmit' : function() {
			this.value = this.value.replace(/^\s+/, '').replace(/\s+$/, '');
			var email = this.value.split(/,\s+/);
			if (email.length > 10) {
				return block_alert(this, "You cannot add more than 10 email address, please try again.");
			}
			for (var i=0; i<email.length; i++) {
				if (!validate_email(email[i])) {
					return block_alert(this, "That does not look like a valid email address, please try again.");
				}
			}
			return true;
		}
	},

//
// Use class="validate_phone" to check that its a valid phone number
//
	validate_phone : {
		'onblur,onformsubmit' : function() {
			this.value = this.value.replace(/^\s+/, '').replace(/\s+$/, '');
			if (this.value.length && !validate_phone(this.value)) {
				return block_alert(this, "That does not look like a phone number, please try again.");
			}
			return true;
		}
	},

//
// Use class="validate_url" to check that its a valid web address
//
	validate_url : {
		onload : function() {
			window.placeholder = window.placeholder || {};
			window.placeholder[this.name] = 'http://';
			if (!this.value) {
				this.value = 'http://';
			}
		},
		onblur : function() {
			if (this.value == window.placeholder[this.name]) {
				this.value = '';
			}
			var str = this.value.replace(/^\s+/, '').replace(/\s+$/, '');
			if (!validate_url(str)) {
				return block_alert(this, "That does not look like a valid web address, please try again.");
			}
			if (this.value == '') {
				this.value = window.placeholder[this.name];
			}
		},
		postformsubmit : function() {
			if (this.value == window.placeholder[this.name]) {
				this.value = '';
			}
		}
	},

//
// Use class="validate_emptyform::Message%20Here" on a form to check
// that the form is not
// empty
//
	validate_emptyform : {
		onsubmit : function () {
			if (validate_emptyform(this)) {
				return block_alert(
					this,
					this.className.match(/validate_emptyform::([^: ]+)/)
					? RegExp.$1
					: "Please fill in the form"
				);
			}
		}
	},

//
// Use class="validate_empty_group::Group%20Name" on a group of elements.
// If any are empty, a message saying Please Fill in Group Name will appear
// Useful for radio buttons, or search forms where at least one search item is
// chosen, and not all form elements are search items
//
	validate_empty_group : {
		onload : function () {
			this.className.match(/validate_empty_group::([^: ]+)/) && FormGroup(RegExp.$1, this);
		},
		onformsubmit : function () {
			if (this.className.match(/validate_empty_group::([^: ]+)/)) {
				var group_name = RegExp.$1;
				if (FormGroup.first(group_name, this) && FormGroup.empty(group_name)) {
					return block_alert(this, "Please fill in " + group_name);
				}
			}
			return 1;
		}
	},

	validate_submitOnEnter : {
		onkeypress : function (event) {
			event = event || window.event;
			var key = event.which || event.keyCode;
			if (event && key !=13) {
				return true;
			}
			var ret = true;
			/*
			* Ok, ready to wrap your brain around this one...
			*
			* If the field has on onBlur, then by submitting from here, we bypass the
			* onBlur... we don't want that, so we need to trigger it manually.
			*
			* BUT... if that onBlur does something like "alert", then the onblur is
			* really triggered, but we've already triggered it, so it happens twice.
			* Damnit.
			*
			* Sooo, we need set the onblur to null, so we can call the onblur, then
			* we can set the onblur back.
			*/
			if (ret && this.onblur) {
				var onblur = this.onblur;
				this.onblur = null;
				ret = onblur.call(this, event);
				this.onblur = onblur;
			}
			if (ret && this.form.onsubmit) {
				ret = this.form.onsubmit();
			}
			if (ret) {
				this.form.submit();
			}
			return false;
		}
	},

//
// For Developers
//

//
// Deprecated, use validate_clear instead
//
// Use class="validate_checkbox" to add checkboxname_delete=1 on empty checkboxes for
// form_to_hash
//
	validate_checkbox : {
		postformsubmit : function () {
			if (!this.form) return;
			function isArray (elem) {
				if (typeof(elem.length) != "undefined") {
					return true;
				}
			}
			var obj = this.form.elements[this.name] || this;
			if (!isArray(obj) && obj.checked)
				return 1;
			//
			// If the element is part of an array, the '_delete' field is only
			// needed if none of the items in the list is checked.
			//
			if (isArray(obj)) {
				for (var i = 0; i < obj.length; i++) {
					if (obj[i].checked ||
					($(obj[i].name + '_delete') && $(obj[i].name + '_delete').value))
						return 1;
				}
			}
			var del = document.createElement('input');
			del.type = 'hidden';
			del.name = this.name + '_delete';
			del.id = this.name + '_delete';
			del.value = 1;
			this.form.appendChild(del);
		}
	},

//
// Use class="validate_clear" to add elemname_delete=1 on empty checkboxes
// or multi select boxes for form_to_hash
//
	validate_clear : {
		postformsubmit : function () {
			if (!this.form) return;
			function isArray (elem) {
				if (typeof(elem.length) != "undefined") {
					return true;
				}
			}
			function create_delete(elem) {
				var del = document.createElement('input');
				del.type = 'hidden';
				del.name = elem.name + '_delete';
				del.id = elem.name + '_delete';
				del.value = 1;
				elem.form.appendChild(del);
			}
			
			var obj = this.form.elements[this.name] || this;
			if (obj.type == 'select-multiple') {
				for (var i = 0; i < obj.options.length; i++) {
					if (obj.options[i].selected) {
						return;
					}
				}
				return create_delete(this);
			} else if (isArray(obj)) {
				//
				// If the element is part of an array, the '_delete' field is only
				// needed if none of the items in the list is checked.
				//
				var i = 0;
				for (i = 0; i < obj.length; i++) {
					if (obj[i].checked ||
					($(obj[i].name + '_delete') && $(obj[i].name + '_delete').value)) {
						return;
					}
				}
				return create_delete(this);
			} else if (obj.type.match(/checkbox|radio/)) {
				if (!obj.checked) {
					return create_delete(this);
				}
			}
		}
	},

//
// Globally change yyyy-dd-mm to dd/mm/yyyy
//
	fix_iso_date : {
		onload : function () {
			if (this.tagName == "INPUT") {
				this.value = this.value.replace(/\b(\d{4})-(\d\d)-(\d\d)\b/g, '$3/$2/$1');
			} else if (this.innerHTML) {
				this.innerHTML = this.innerHTML.replace(/\b(\d{4})-(\d\d)-(\d\d)\b/g, '$3/$2/$1');
			}
		},
		'postformsubmit' : function () {
			if (this.tagName == 'INPUT') {
				this.value = this.value.replace(/(\d\d)[-/](\d\d)[-/](\d{4})/g, '$3-$2-$1');
			}
		}
	},
	
	strip_blanks : {
		onsubmit : function () {
			var elems = this.getElementsByTagName('input');
			for (var i = 0; i < elems.length; ++i) {
				if (typeof(elems[i].value) == 'string') {
					if (elems[i].value.match(/^\s+/)) {
						elems[i].value = elems[i].value.replace(/^\s+/, '');
					}
					if (elems[i].value.match(/\s+$/)) {
						elems[i].value = elems[i].value.replace(/\s+$/, '');
					}
				}
			}
		}
	},
	//
	// Use class="set_selecbox_other" to allow the user to add a new
	// option to a selectbox using a textfield.
	//
	// requires:
	// - A selectbox with class="set_selectbox_other".
	// - An (hidden) input field named "[selectboxname]_value" 
	//   which holds the value saved in the database.
	// - A textbox named "[selectboxname]_other", with
	//   class="set_selectbox_other", wrapped in an HTML element
	//   (<span>,<div>,..) with id="[selectboxname]_other".
	//
	// See Twiki for example.
	//
	set_selectbox_other : {
		onload : function () {
			if (this.tagName == 'SELECT') {
				if ($(this.name + '_other') && $(this.name + '_other').style.display != 'none')
					$(this.name + '_other').style.display = 'none';
				//
				// Get the element holding the value saved in the database
				// and select the corresponding option in the selectbox if possible.
				//
				var elem = this.form.elements['_nosave_' + this.name + '_value'] ||
				this.form.elements[this.name + '_value'];
				
				if (!elem && this.className.match(/set_selectbox_other::([^:\s]+)/))
					elem = this.form.elements[RegExp.$1];

				if (elem) {
					elem.name = '_nosave_' + elem.name.replace(/^_nosave/,'');
					for (var i = 0; i < this.options.length; ++i) {
						if (this.options[i].value == elem.value) {
							this.options[i].selected = true;
						}
					}
					//
					// If the saved value could not be found in the selectbox,
					// create a new option and select it.
					//
					if (!this.options[this.selectedIndex] ||
					this.options[this.selectedIndex].value == '' && elem.value != '') {
						var other = this.options[this.options.length -1].value;
						this.options[this.options.length] = new Option(other,other);
						this.options[this.options.length - 2] = new Option(elem.value,elem.value);
						this.options[this.options.length - 2].selected = true;
					}
				} else return;
			}
		},
		onchange : function () {
			if (this.tagName == 'SELECT' && this.options[this.options.length-1].selected == true) {
				//
				// If the 'last' option in the selectbox is selected,
				// display the '_other' field and focus on it.
				//
				$(this.name + '_other').style.display = '';
				(this.form.elements['_nosave_' + this.name + '_other'] ||
					this.form.elements[this.name + '_other']).focus();
			}
		},
		onblur : function () {
			if (this.tagName == 'INPUT' &&
			this.name.match(/^(_nosave_)?([^\s:]+)_other$/) && this.value.length) {
				function selectOptionValue (box,val) {
					for (var i = 0; i < box.options.length; ++i) {
						if (box.options[i].value == val) {
							box.options[i].selected = true;
							return true;
						}
					}
					return false;
				}

				var selectboxname = RegExp.$2;
				var selectbox = this.form.elements[selectboxname];
				//
				// Add the user input as a new option to the selectbox, unless it's
				// already there.
				//
				if (!selectOptionValue(selectbox,this.value)) {
					var other = selectbox.options[selectbox.options.length - 1].value;
					selectbox.options[selectbox.options.length] = new Option(other,other);
					selectbox.options[selectbox.options.length - 2] = new Option(this.value,this.value);
					selectbox.options[selectbox.options.length - 2].selected = true;
				}
				//
				// Hide the '_other' field and make sure it's not getting saved.
				//
				this.name = '_nosave_' + this.name.replace(/^_nosave_/,'');
				$(selectboxname + '_other').style.display = 'none';
			}
		}
	}
});
