// class definition AdzFormValidator and AdzFormElement
// version 1.0 updated 2005-11-13
// Author: Adam Royle (AdzFormValidator at ifunk.net)
// class maintained at http://ifunk.net/AdzFormValidator/
// you must keep the above lines intact if you wish to use this class
// see other conditions at the website above
// you can remove any comments below

function AdzFormValidator(form){
	this.useForm(form);
	this._elements = [];
	this.elements = [];
	this.select = true;
	this.focus = true;
	this.func = null;
	this.func_alert = null;
}

function AdzFormElement(name,AdzFormValidatorObject){
	this.name = name;
	this.desc = name;
	this.required = false;
	this.trim = false; // only used with text fields
	this.stripWhite = false; // only used with text fields
	this.alert = "The field '$desc' is required";
	this.select = true;
	this.focus = true;
	this.__restrict = [];
	this.__AdzFormValidatorObject = AdzFormValidatorObject;
}

AdzFormElement.prototype.getText = function(returnArray){
	var i, j, objForm;
	var element = this.__AdzFormValidatorObject.form.elements[this.name];
	this.type = element.type;
	if (!this.type) this.type = element[0].type;
	objForm = element.form;
	switch (this.type){
		case "text":
		case "textarea":
		case "hidden":
		case "file":
		case "password":
		case "checkbox":
		case "radio":
			return this.getValue(returnArray);

		case "select-one":
			for (i=0;i<element.length;i++){
				if (element[i].selected){
					return (returnArray?[element[i].text]:element[i].text);
				}
			}
			return (returnArray?[]:'');

		case "select-multiple":
			if (returnArray){
				strValue = [];
				for (i=0;i<element.length;i++) {
					if (element[i].selected){
						strValue[strValue.length] = element[i].text;
					}
				}
			} else {
				strValue = '';
				for (i=0;i<element.length;i++) {
					if (element[i].selected){
						if (strValue == ''){
							strValue = element[i].text;
						} else {
							strValue += ', '+(element[i].text);
						}
					}
				}
			}
			return strValue;

		default:
			// can't detect a valid / invalid response
			return;
	}
}

AdzFormElement.prototype.getValue = function(returnArray){
	var i, j, objForm, strValue;
	var element = this.__AdzFormValidatorObject.form.elements[this.name];
	this.type = element.type;
	if (!this.type) this.type = element[0].type;
	objForm = element.form;
	switch (this.type){
		case "text":
		case "textarea":
		case "hidden":
		case "file":
		case "password":
			return (returnArray?[element.value]:element.value);

		case "checkbox":
			if (isUndefined(element.length)){
				if (element.checked) return (returnArray?[element.value]:element.value);
				return (returnArray?[]:'');
			} else {
				if (returnArray){
					strValue = [];
					for (i=0;i<element.length;i++) {
						if (element[i].checked){
							strValue[strValue.length] = element[i].value;
						}
					}
				} else {
					strValue = '';
					for (i=0;i<element.length;i++) {
						if (element[i].checked){
							if (strValue == ''){
								strValue = element[i].value;
							} else {
								strValue += ', '+element[i].value;
							}
						}
					}
				}
				return strValue;
			}


		case "radio":
			if (isUndefined(element.length)){
				if (element.checked) return (returnArray?[element.value]:element.value);
			} else {
				for (i=0;i<element.length;i++){
					if (element[i].checked) return (returnArray?[element[i].value]:element[i].value);
				}
			}
			return (returnArray?[]:'');

		case "select-one":
			for (i=0;i<element.length;i++){
				if (element[i].selected){
					if (element[i].value == ''){
						// check if all options are blank (then use face value)
						for (j=0;j<element.options.length;j++){
							if (element.options[j].value != '') return (returnArray?[]:'');
						}
						return (returnArray?[element[i].text]:element[i].text);
					}
					return (returnArray?[element[i].value]:element[i].value);
				}
			}
			return (returnArray?[]:'');

		case "select-multiple":
			// should we use .value or .text?
			var useValue = false;
			for (j=0;j<element.options.length;j++){
				if (element.options[j].value != ''){
					useValue = true;
					break;
				}
			}

			if (!useValue) return this.getText(returnArray);

			if (returnArray){
				strValue = [];
				for (i=0;i<element.length;i++) {
					if (element[i].selected){
						strValue[strValue.length] = element[i].value;
					}
				}
			} else {
				strValue = '';
				for (i=0;i<element.length;i++) {
					if (element[i].selected){
						if (strValue == ''){
							strValue = element[i].value;
						} else {
							strValue += ', '+(element[i].value);
						}
					}
				}
			}
			return strValue;

		default:
			// can't detect a valid / invalid response
			return;
	}
}

AdzFormElement.prototype.validate = function(){

	var i, objForm, type, cond;

	var element = this.__AdzFormValidatorObject.form.elements[this.name];

	this.type = element.type;

	if (!this.type) this.type = element[0].type;

	var valid = true;

	objForm = element.form;

	// -- check if field is empty

	switch (this.type){

		/************/
		case "text":
		case "textarea":
		case "hidden":
		case "password":
			// only trim or strip the fields we are allow to edit
			if (this.stripWhite) element.value = element.value.replace(/\s/g,'');
			if (this.trim) element.value = element.value.trim();
		case "file":
			// no "break" above so we do this to all above fields
			this.data = !isBlank(element.value);
			this.empty = (element.value == '');
			break;
		/************/

		case "checkbox":
		case "radio":
			this.empty = !(this.data = false);
			if (element.checked){
				this.empty = !(this.data = true);
				break;
			}
			for (i=0;i<element.length;i++){
				if (element[i].checked){
					this.empty = !(this.data = true);
					break;
				}
			}
			break;

		case "select-multiple":
			this.empty = !(this.data = false);
			for (i=0;i<element.length;i++) {
				if (element[i].selected){
					this.empty = !(this.data = true);
					break;
				}
			}
			break;

		default:
			// can't detect a valid / invalid response
			this.empty = !(this.data = true);
	}

	if (this.required && !this.data){
		//alert("required, no data");
		valid = false;
	} else if (!this.required && this.empty){
		//alert("not required, is empty");
		valid = true;
	} else if (this.data){
		//alert("has data, may or may not be required");
		if (!this.__restrict.length){
			//alert("has data, may or may not be required - no restrict");
			valid = true;
		} else {
			//alert("has data, may or may not be required - has restrict");
			// -- validate the restriction

			for (i=0;i<this.__restrict.length;i++){
				
				if (!valid) break;
				
				type = this.__restrict[i]['type'].toLowerCase();
				cond = this.__restrict[i]['cond'];

				switch (type){

					case "numeric":
					case "number":
					case "num":
						valid = isNumeric(element.value);
						if (valid && isString(cond)) valid = eval(cond.replace(/n/g,element.value));
						break;

					case "int":
					case "integer":
						valid = isInteger(element.value);
						if (valid && isString(cond)) valid = eval(cond.replace(/n/g,element.value));
						break;

					case "length":
						valid = eval(cond.replace(/n/g,element.value.length));
						break;

					case "selected":
						valid = eval(cond.replace(/n/g,element.selectedIndex));
						break;

					case "email":
						valid = isEmail(element.value);
						break;

					case "regex":
						this.restrictCond = isString(cond) ? new RegExp(cond,"i") : this.restrictCond;
						valid = cond.test(element.value);
						break;

					case "in_array":
						valid = in_array(cond, element.value);
						break;

					case "!in_array":
						valid = !in_array(cond, element.value);
						break;

					case "currency":
						valid = isCurrency(element.value);
						if (valid && isString(cond)) valid = eval(cond.replace(/n/g,element.value.replace(/[\$,]/g,'')));
						break;

					case "date":
						valid = validateDateString(element.value,cond);
						break;

					case "value":
						valid = (element.value == cond);
						break;

					case "!value":
						valid = (element.value != cond);
						break;

					case "abn":
						valid = isABN(element.value);
						break;

					case "acn":
						valid = isACN(element.value);
						break;

				}
			}
		}
	} else {
		//alert("else");
		valid = true;
	}

	this.valid = valid;
	return valid;
}

AdzFormElement.prototype.restrict = function(type,cond,alert){
	this.__restrict[this.__restrict.length] = {type:type,cond:cond,alert:alert};
}

AdzFormElement.prototype.showAlert = function(){
	var a = this.alert;
	if (a !== false){
		a = a.replace(/\$desc/g, this.desc);
		a = a.replace(/\$name/g, this.name);
		a = a.replace(/\$type/g, this.type);
		a = a.replace(/\$value/g, this.getValue());
		a = a.replace(/\$text/g, this.getText());
		alert(a);
	}
}

AdzFormValidator.prototype.useForm = function(f){
	if (!isUndefined(f)){
		if (isObject(f)){
			this.form = f;
		} else if (isString(f) || isNumber(f)){
			this.form = document.forms[f];
			if (isUndefined(this.form)) this.form = f;
		} else {
			alert("AdzFormValidator :: DEVELOPER ALERT :: useForm()\n\nInvalid form definition");
		}
	}
}

AdzFormValidator.prototype.add = function(){
	var i, args, name;

	args = AdzFormValidator.prototype.add.arguments;

	// add all the fields specified in the arguments of the function
	for (i=0;i<args.length;i++){
		name = args[i];
		if (!isBlank(name) && !this.elements[name]){
			this.elements[name] = new AdzFormElement(name,this);
			this._elements[this._elements.length] = this.elements[name];
		}
	}
}

AdzFormValidator.prototype.addAll = function(){
	var i, j, field, args;
	if (!isObject(this.form) && !isUndefined(document.forms[this.form])){
		this.form = document.forms[this.form];
	}
	if (!isObject(this.form)){
		alert("AdzFormValidator :: DEVELOPER ALERT :: addAll()\n\nNo form selected!\n\nuse: obj.useForm('formName');");
	} else {

		args = AdzFormValidator.prototype.addAll.arguments;

		if (args.length == 0){
			// run through all of the form elements and add each one that has a name and is not a button
			for (i=0;i<this.form.elements.length;i++){
				field = this.form.elements[i];
				if (!isUndefined(field.name) && !isBlank(field.name)){
					if (field.type != "button" && field.type != "submit" && field.type != "reset"){
						this.add(field.name);
					}
				}
			}
		} else {
			for (i=0;i<args.length;i++){
				for (j=0;j<this.form.elements.length;j++){
					field = this.form.elements[j];
					if (field.type == args[i] || (args[i] == 'select' && (field.type == 'select-one' || field.type == 'select-multiple'))){
						if (!isUndefined(field.name) && !isBlank(field.name)){
							this.add(field.name);
						}
					}
				}
			}
		}
	}
}

AdzFormValidator.prototype.remove = function(){
	var i, j, temp_array, args, name;

	args = AdzFormValidator.prototype.remove.arguments;

	temp_array = [];

	// remove all the fields specified in the arguments of the function
	for (j=0;j<args.length;j++){
		name = args[j];
	 	for (i=0;i<this._elements.length;i++){
	 		if (this._elements[i].name != name){
				temp_array[temp_array.length] = this._elements[i];
				delete this.elements[name];
	   	}
	 	}
	}
	this._elements = temp_array;
}

AdzFormValidator.prototype.removeAll = function(){
	var i, j, field, args;

	args = AdzFormValidator.prototype.removeAll.arguments;

	if (args.length == 0){
		// reset elements array
		this._elements = [];
		this.elements = [];
	} else {
		for (i=0;i<args.length;i++){
			for (j=0;j<this.form.elements.length;j++){
				field = this.form.elements[j];
				if (field.type == args[i]  || (args[i] == 'select' && (field.type == 'select-one' || field.type == 'select-multiple'))){
					this.remove(field.name);
				}
			}
		}
	}
}

AdzFormValidator.prototype.describe = function(){
	var element, i;
	var strMsg = "";

	if (this._elements.length == 0){
		strMsg += "No form elements!";
	} else {
		strMsg += "List of form elements:\n";
		for (i=0;i<this._elements.length;i++){
			element = this._elements[i];
			strMsg += " - "+element.name+" : "+element.restrictType+"\n";
		}
	}
	alert(strMsg);
}

AdzFormValidator.prototype.setGlobal = function(){

	var args, setType, setValue, element, i;

	args = AdzFormValidator.prototype.setGlobal.arguments;

	if (args.length <= 1){
		alert("AdzFormValidator :: DEVELOPER ALERT :: setGlobal()\n\nIncorrect number of arguments supplied.");
		return false;
	}

	setType = args[0];
	setValue = args[1];

	// run through all of the form elements and add each one that has a name and is not a button
	for (i=0;i<this._elements.length;i++){
		element = this._elements[i];

		switch (setType.toLowerCase()){

			// put custom ones in here

			// generic

			default:
				element[setType] = setValue;
				break;

		}

	}

}

AdzFormValidator.prototype.setUserFunction = function(func,func_alert){
	this.func = func;
	this.func_alert = func_alert;
}


AdzFormValidator.prototype.validate = function(){
	var element, elementName, fe, i;
	var isValid = true;

	if (!this.form){
		alert("AdzFormValidator :: DEVELOPER ALERT :: validate()\n\nNo form selected!\n\nuse: obj.useForm('formName');");
		return false;
	} else {
		// -- start the validation
		for (i=0;i<this._elements.length;i++){

			element = this._elements[i];
			elementName = element.name;
			fe = this.form.elements[elementName]; // form element

			// -- return false if invalid element
			if (fe.type == "button" || fe.type == "submit" || fe.type == "reset"){
				this.debug(this.DEBUG_DEV,"validate()","Form element '"+fe.name+"' is of an unacceptable type.\n\nAdzFormValidator will not validate buttons.");
				isValid = false;
				return false;
			}

			// -- validate the form element
			if ( !element.validate() ) {

				isValid = false;
				element.showAlert();
				if (this.focus && element.focus && fe.focus && !fe.disabled && fe.type != "hidden") fe.focus();
				if (this.focus && element.focus && fe.length > 0 && fe[0].focus && !fe.disabled && fe.type != "hidden") fe[0].focus();
				if (this.select && element.select && fe.select && !fe.disabled && fe.type != "hidden") fe.select();
				return false;
			}
		}

		// -- call the user defined function to validate more advanced stuff
		if (this.func){
			if (!this.func(this.form)){
				if (this.func_alert) alert(this.func_alert);
				isValid = false;
			}
		}

		return isValid;
	}
}

AdzFormValidator.prototype.DEBUG_DEV = 1;

AdzFormValidator.prototype.debug = function(type,method,message){
	switch (type){
		case this.DEBUG_DEV:
			alert("!! DEVELOPER ALERT !!\n\n"+message);
			break;
	}
}

AdzFormElement.prototype.debug = AdzFormValidator.prototype.debug;

// validation checks

function isCurrency(s){
	var re = /^\$?\d{1,3}(,?\d{3})*(\.\d{1,2})?$/;
	return re.test(s);
}

function isEmail(s){
	var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
	return re.test(s);
}

function isNumeric(s){
	var re = /^[\-]?[0-9]+[.]?[0-9]*$/;
	return re.test(s);
}

function isInteger(s){
	var re = /^[\-]?[0-9]+$/;
	return re.test(s);
}

function isBlank(s){
	var re = /^\s*$/;
	return re.test(s);
}


/*******
 These functions found at http://www.crockford.com/javascript/remedial.html
*******/
function isAlien(a) {
   return isObject(a) && typeof a.constructor != 'function';
}
function isArray(a) {
    return isObject(a) && a.constructor == Array;
}
function isBoolean(a) {
    return typeof a == 'boolean';
}
function isEmpty(o) {
    var i, v;
    if (isObject(o)) {
        for (i in o) {
            v = o[i];
            if (isUndefined(v) && isFunction(v)) {
                return false;
            }
        }
    }
    return true;
}
function isFunction(a) {
    return typeof a == 'function';
}
function isNull(a) {
    return typeof a == 'object' && !a;
}
function isNumber(a) {
    return typeof a == 'number' && isFinite(a);
}
function isObject(a) {
    return (a && typeof a == 'object') || isFunction(a);
}
function isString(a) {
    return typeof a == 'string';
}
function isUndefined(a) {
    return typeof a == 'undefined';
}
/*******/

function isABN(s){
	var m, as, i, sum = 0;
	m = [10,1,3,5,7,9,11,13,15,17,19];
	s = String(s).replace(/\s/g,'');
	if (s.length != 11) return false;
	as = s.split('');
	as[0] = String(parseInt(as[0]) - 1);
	for (i=0;i<as.length;i++) sum += parseInt(as[i]) * m[i];
	return (sum % 89 == 0);
}

function isACN(s){
	var m, as, i, sum = 0, r;
	m = [8,7,6,5,4,3,2,1];
	s = String(s).replace(/\s/g,'');
	if (s.length != 9) return false;
	as = s.split('');
	for (i=0;i<8;i++) sum += parseInt(as[i]) * m[i];
	r = 10 - (sum % 10);
	if (r == 10) r = 0;
	return (r == parseInt(as[8]));
}

String.prototype.trim = function(){
	return this.replace(/^\s+/,'').replace(/\s+$/,'');
}

function in_array(array,value){
	for (var i=0;i<array.length;++i)
		if (array[i].toString().toLowerCase() == value.toString().toLowerCase()) return true;
	return false;
}

function defangRegexChars(strInput){
	var find = [ "\\", ".", "^", "$", "+", "*", "?", "(", ")", "[", "]", "{", "}" ];
	for (i=0;i<find.length;i++)
		strInput = strInput.replace( new RegExp("\\"+find[i],"g") , "\\"+find[i] );
	return strInput;
}

function getFullYear(y){
	var iy = parseInt(y,10);
	if ( isNaN(iy) || iy < 0 || iy > 99 ) return y; // no hard feelings
	return iy + (iy < 50 ? 2000 : 1900);
}

function isValidDate(y,m,d){
	if (!y || !m) return false;
	if (isUndefined(d)) d = 1;
	m--; // month is zero-indexed
	var oDate = new Date(y,m,d);
	if (oDate.getDate() == d &&
		oDate.getMonth() == m &&
		oDate.getFullYear() == y) return true;
	return false;
}

function validateDateString(strDate,strPattern){

	var d, m, y, yy, yyyy;
	var re, h, i, j;
	var aDate, aPositions, aMatches;
	var aMatchList, aMatchListValues, strPatternPos, tmpArr;

	// quick check

	if (!strPattern || !strDate) return false;

	// set our replacement regex

	d = "([0-3]?[0-9])";
	m = "([0-1]?[0-9])";
	y = "([0-9]{2}|[0-9]{4})";
	yy = "([0-9]{2})";
	yyyy = "([0-9]{4})";

	// preliminary replacements (defang regex chars);

	strPattern = defangRegexChars(strPattern);

	// we may need this later (to work out positioning)

	strPatternPos = strPattern;

	// create our pattern

	strPattern = strPattern.replace(/d+/gi,'d');
	strPattern = strPattern.replace(/d/gi,d);
	strPattern = strPattern.replace(/m+/gi,'m');
	strPattern = strPattern.replace(/m/gi,m);
	strPattern = strPattern.replace(/y{4,}/gi,'yyyy');
	strPattern = strPattern.replace(/y{4}/gi,yyyy);
	strPattern = strPattern.replace(/y{2,}/gi,'yy');
	strPattern = strPattern.replace(/y{2}/gi,yy);
	strPattern = strPattern.replace(/y/gi,y);

	// test our pattern against the string

	re = new RegExp('^'+strPattern+'$');
	if (!re.test(strDate)) return false;

	// extract date portions from our string

	aMatches = re.exec(strDate);

	// structure replacements

	strPatternPos = "#"+strPatternPos+"#";
	strPatternPos = strPatternPos.replace(/d+/gi,'#d#');
	strPatternPos = strPatternPos.replace(/m+/gi,'#m#');
	strPatternPos = strPatternPos.replace(/([^y])y([^y])/gi,'$1#y#$2');
	strPatternPos = strPatternPos.replace(/([^y])y{2,3}([^y])/gi,'$1#yy#$2');
	strPatternPos = strPatternPos.replace(/y{4,}/gi,'#yyyy#');

	aMatchList = ['#d#','#m#','#y#','#yy#','#yyyy#'];
	aMatchListValues = ['d','m','y','y','y'];

	tmpArr = [];
	for (h=0;h<aMatchList.length;h++){
		for (i=0;i<strPatternPos.length;i++){
			j = strPatternPos.indexOf(aMatchList[h],i);
			if (j == -1) break; // none of this character - go to next character
			// record this info - a match has been found
			tmpArr[j] = aMatchListValues[h];
			i = j;
		}
	}

	aPositions = [null]; // need this value so the index reference against aMatch is right
	for (i=0;i<tmpArr.length;i++) if (tmpArr[i]) aPositions[aPositions.length] = (tmpArr[i]);

	aDate = [];
	for (i=0;i<aPositions.length;i++) aDate[aPositions[i]] = aMatches[i];

	return isValidDate(getFullYear(aDate.y), aDate.m, aDate.d);

}