/**
* @author Alex Oroshchuk aka lexor
* @version 0.6
* Changelog: 
* date: 04/05/06, a nice day :^) created main features
* date: 29/05/06, moved to prototype declarations, added error highlighting feature
* date: 05/06/06, new localization mechanism plus important bug fixes
*
* A useful class for client-side form fields validation. 
* Supports so-called validation rules. 
* There are some predefined rules, but you are welcome to extend them.
*/

// validation status for a field
JSV_FIELD_VALID = 1;
JSV_FIELD_EMPTY = 2;
JSV_FIELD_INVALID = 3;
//extended status for a field, replaces very abstract JSV_FIELD_INVALID
JSV_FIELD_INV_LEN = 4;		// not a valid length
JSV_FIELD_INV_RANGE = 5;	// not in the range
JSV_FIELD_INV_NUMBER = 6;	// RESERVED number is not numeric (e.g 'asd12.3' instead of a number)
JSV_FIELD_INV_NINSET = 7;	// RESERVED value is not in set
JSV_FIELD_INV_EMAIL = 8;	// not a valid email
JSV_FIELD_INV_NEQ = 9;		// doesn't equal string
JSV_FIELD_NSELECTED = 10;	// list item not selected
JSV_FIELD_INFILLED = 11;    // one or more of the input fields not filled
// whole process return status, returned by Validate method
JSV_CHECKED = 100;
JSV_BAD_RULE = 101;
// comparison max values
JSV_MAX_LENGTH = 0xffffffff;
JSV_MAX_VALUE = 0xffffffff;
// error message settings, may be overridden for maximum convinience
// specify them in a separate file following the pattern below
// and replacing 'def' with desired locale name (en, de, it, fr etc)
JSV_ERRMSG_HDR = 0;
JSV_ERRMSG_LFDELIM = 1;
var jsv_errmsgs_def = [];
jsv_errmsgs_def[JSV_ERRMSG_HDR] = "These fields are not valid:\n ---";
jsv_errmsgs_def[JSV_ERRMSG_LFDELIM] = " : ";
jsv_errmsgs_def[JSV_FIELD_INVALID] = "Invalid field value.";
jsv_errmsgs_def[JSV_FIELD_EMPTY] = "The field is empty. Enter correct data, please.";
jsv_errmsgs_def[JSV_FIELD_INV_LEN] = "The content of the field is too short or too long.";
jsv_errmsgs_def[JSV_FIELD_INV_EMAIL] = "Invalid email address.";
jsv_errmsgs_def[JSV_FIELD_NSELECTED] = "No items are selected.";
jsv_errmsgs_def[JSV_FIELD_INV_NEQ] = "The passwords don't match.";
jsv_errmsgs_def[JSV_FIELD_INV_RANGE] = "The number is out of allowable range.";
jsv_errmsgs_def[JSV_FIELD_INFILLED] = "None of the fields is filled.";

JSV_DEF_FIELD_NAME = "<< Field >>";
JSV_DEF_MSG_HEADER = "<< Errors >>";
JSV_DEF_LABEL_PREFIX = "dlp_";

// Just a shortcut to getElementById
// Returns the 'value' property if present, innerHTML if 'value' attribute is missing and null otherwise
function vbi(id){
	var obj;
	if((obj=document.getElementById(id)) != null){
		if(obj.type=="checkbox")
			return (obj.checked ? "true" : "");
		if(obj.value!=null)
			return obj.value;	
		if(obj.innerHTML != null)
			return obj.innerHTML;
	}
	return null;
}
// Returns the form value of the specified by name form field
// Note that no form name is needed but field's name must be unique!
// Only the first found value will be returned.
function vbn(fname){
	var i=0, obj, j;

	for(; i<document.forms.length; i++){
		eval("obj = document.forms.item(i)."+fname);
		if(obj != "undefined" && obj != null){
			if(obj.value)
				return obj.value;
			if(obj.length){
				for(j=0; j<obj.length; j++){
					if(obj.item(j).checked == true)
						return obj.item(j).value;
				}
			}
		}
	}
	return null;
}
// Returns form control either by its name or by id.
function gfc(id_or_name){
	var i=0, obj;
	if((obj=document.getElementById(id_or_name)) != null)
		return obj;	
	for(; i<document.forms.length; i++){
		eval("obj = document.forms.item(i)."+id_or_name);
		if(obj != "undefined" && obj != null)
			return obj;
	}
	return null;
}

jsValidator = function(stop_on_error, pref){
	this.stop_on_error = (stop_on_error==null ? false : stop_on_error);
	this.label_prefix = (pref==null ? JSV_DEF_LABEL_PREFIX : pref);
	// initialize
	this.total_errors = 0;
	this.check_status = [];
	this.vfields = null;
	// set default values for error show
	this.rule_delimiter = "|";
	this.msgs = jsv_errmsgs_def;
}

jsValidator.prototype.SetDelimiter = function(d){
	if(d=='' || d==null)
		return;
	this.rule_delimiter = d;	
}

jsValidator.prototype.SetErrDesc = function(jsv_msgs){
	this.msgs = jsv_msgs;
}

jsValidator.prototype.GetErrorFields = function(){
	var i, ef = [];
	if(this.total_errors == 0) 
		return null;
	for(i=0; i<this.check_status.length; i++){
		if(this.check_status[i] > JSV_FIELD_VALID)
			ef[ef.length]=this.vfields[i];
	}
	return ef;
}

jsValidator.prototype.GetErrorStrings = function(locale){
	var i, tmp, strings=[], cf;
	if(locale == null)
		locale = "def";
	try{
		// check if locale resources are loaded
		eval("this.msgs = jsv_errmsgs_" + locale);
	}
	catch(__exception){
			return strings;
	}

	for (i=0; i<this.check_status.length; i++){
		if(this.check_status[i]== JSV_FIELD_VALID)
			continue;
		cf = [];
		tmp = vbi(this.label_prefix + this.vfields[i]);
		if(tmp == null)
			tmp = JSV_DEF_FIELD_NAME;
		cf.label = tmp;
		cf.msg = this.msgs[this.check_status[i]];
		strings.push(cf);
	}
	return strings;
}

jsValidator.prototype.GetErrorMsg = function(locale, strsep){
	var i, errstr = this.GetErrorStrings(locale), msg;
	try{
		eval("msg = jsv_errmsgs_" + locale + "[0] + \"\\n\"");
	}
	catch(__exception){
		msg= this.msgs[0] + "\n"; 		
	}
	strsep = (strsep == null ? "\n" : strsep);
	for(i=0; i < errstr.length; i++)
		msg = msg + errstr[i].label + this.msgs[1] + errstr[i].msg + strsep; 
	
	try{
		eval("gfc(this.GetErrorFields()[0]).focus()");
	}
	catch(__exception){}
	
	return msg;
}

jsValidator.prototype.highlightErrors = function(errColor, mode){
	var ef = this.GetErrorFields(), i, cel, cel2, trigger;
	mode = mode==null ? true : mode;
	if(ef==null)
		return;

	if(mode==true){
		for(i=0; i<ef.length; i++){
			if(cel = gfc(ef[i])){
				try{
					cel._orig_color = (cel._orig_color == null ? cel.style.backgroundColor : cel._orig_color);
					cel.style.backgroundColor = errColor;
					if(cel.type=="text" || cel.type=="password" || cel.nodeName=="TEXTAREA"){
						if(cel.readOnly)
							trigger = "onclick";
						else
							trigger = "onkeypress";
					}
					else if(cel.type=="select-one")
						trigger = "onchange";
					else
						trigger = "onclick";
					eval("cel." + trigger + " = function (){ this.style.backgroundColor = '" + cel._orig_color + "';}");
				}
				catch(e){}
			}
		}
	}
	else{
		for(i=0; i<ef.length; i++){
			if(cel = gfc(this.label_prefix + ef[i])){
				cel._orig_color = (cel._orig_color == null ? cel.style.color : cel._orig_color);
				cel.style.color = errColor;
				if(!(cel2 = gfc(ef[i])))
					continue;
				if(cel2.type=="text" || cel2.type=="password" || cel2.nodeName=="TEXTAREA"){
					if(cel.readOnly)
						trigger = "onclick";
					else
						trigger = "onkeypress";
				}
				else if(cel2.type=="select-one")
					trigger = "onchange";
				else
					trigger = "onclick";
				eval("cel2." + trigger + 
				" = function (){ document.getElementById('" + this.label_prefix + ef[i]+ "').style.color = '" + cel._orig_color + "';}");
			}
		}
	}
	
	try{
		gfc(ef[0]).focus();
	}
	catch(__exception){}
	
}

jsValidator.prototype.Validate = function(fields){
	var error = JSV_FIELD_VALID, i=0, k=0, fv, checkMethods;
	this.total_errors = 0;

	this.vfields = [];
	for(; i<fields.length; i+=2){
		this.check_status[i/2] = JSV_FIELD_VALID;
		// emptiness check
		if(fields[i].indexOf(":list") != -1){
			fv = fields[i].replace(":list","");
			this.vfields[i/2] = fv;
		}
		else if(fields[i].indexOf(":group") != -1){
			fv = fields[i].replace(":group","");
			this.vfields[i/2] = fv;				
		}
		else{
			this.vfields[i/2] = fields[i];
			fv = vbi(fields[i]);
			if(fv == null)
				fv = vbn(fields[i]);
			if(fv == null || fv == ''){
				// register error
				//this.wrong_fields[i/2] = 1;
				this.check_status[i/2] = JSV_FIELD_EMPTY;
				error = JSV_FIELD_EMPTY;
				this.total_errors ++ ;
				if(this.stop_on_error == true)
					return JSV_CHECKED;
				continue;
			}
		}
		// no rule is defined, mission executed
		if(fields[i+1] == "")
			continue;
		// more thorough check
		checkMethods = fields[i+1].split(this.rule_delimiter);
		var patt = /(\w+?)\s*?(\((.*?)\))?/;
		for(; k<checkMethods.length; k++){
			checkMethods[k] = (checkMethods[k]).replace(/^\s+/,"");
			checkMethods[k] = (checkMethods[k]).replace(/\s+$/,"");
			if(checkMethods[k].match(patt) == null)
			//an error occured, some rules are malformed
				return JSV_BAD_RULE;
		}			
		// call the necessary handlers
		for (k=0; k<checkMethods.length; k++){
			error = JSV_FIELD_VALID;
			if(checkMethods[k].indexOf('length') != -1)
				error = this._jsv_length(checkMethods[k], fv);
			else if(checkMethods[k].indexOf('email') != -1)
				error = this._jsv_email(checkMethods[k], fv);
			else if(checkMethods[k].indexOf('range') != -1)
				error = this._jsv_range(checkMethods[k], fv);
			else if(checkMethods[k].indexOf('udefunc') != -1)
				error = this._jsv_udefunc(checkMethods[k], fv);
			else if(checkMethods[k].indexOf('equals') != -1)
				error = this._jsv_equals(checkMethods[k], fv);
			else if(checkMethods[k].indexOf('selected') != -1)
				error = this._jsv_selected(checkMethods[k], fv);
			else if(checkMethods[k].indexOf('filled') != -1)
				error = this._jsv_filled(checkMethods[k], fv);

			if(error == JSV_BAD_RULE)
				return JSV_BAD_RULE;
			// stop the check if at least one of the rules is violated
			if(error >= JSV_FIELD_INVALID){
				// register error
				//this.wrong_fields[i/2] = 1;
				this.check_status[i/2] = error;
				error = JSV_FIELD_INVALID;
				this.total_errors ++ ;
				break;
			}
		}
		if(error == JSV_FIELD_INVALID && this.stop_on_error == true)
			return JSV_CHECKED;
	}
	return JSV_CHECKED;
}
// private method, checks the length	
jsValidator.prototype._jsv_length = function (rule, data){
	var lp = /\((\d*?),(\d*?)\)/;
	var m = new Array();
	if(!(m = rule.match(lp)))
		return JSV_BAD_RULE;
	if(m[1]=='' && m[2]== '')
		return JSV_BAD_RULE;
	var min_length = (m[1]==''? 0 : parseInt(m[1],10));
	var max_length = (m[2]==''? JSV_MAX_LENGTH : parseInt(m[2],10));
	var datalen = data.length; 
	if(datalen < min_length || datalen > max_length)
		return JSV_FIELD_INV_LEN;
	return JSV_FIELD_VALID;
}
// private method, checks email validity
jsValidator.prototype._jsv_email = function (rule,data){
	email_patt = /^[\w\-\._]+@([\w\-_]+\.)+(\w){2,}$/;
	if(!data.match(email_patt))
		return JSV_FIELD_INV_EMAIL;
	return JSV_FIELD_VALID;
}

jsValidator.prototype._jsv_range = function (rule, data){
	var lp = /\((\d*?),(\d*?)\)/;
	var m;
	if(!(m = rule.match(lp)))
		return JSV_BAD_RULE;
	if(m[1]=='' && m[2]== '')
		return JSV_BAD_RULE;
	var vdata = parseFloat(data,10); 
	if(isNaN(vdata))
		return JSV_FIELD_INV_RANGE;
	var min_val = (m[1]==''? 0 : parseFloat(m[1],10));
	var max_val = (m[2]==''? JSV_MAX_VALUE : parseFloat(m[2],10));
	if(vdata < min_val || vdata > max_val)
		return JSV_FIELD_INV_RANGE;
	return JSV_FIELD_VALID;
}
// private method, checks for equality
jsValidator.prototype._jsv_equals = function (rule, data){
	var patt = /\((.*?)\)/;
	var m, fval;
	if((m=rule.match(patt))==null)
		return JSV_BAD_RULE;
	if(data != m[1])
		return JSV_FIELD_INV_NEQ;
	return JSV_FIELD_VALID;
}
// private method, checks if N values are selected in a list-like form control
// (used for checkboxes and multiple-selects)
jsValidator.prototype._jsv_selected = function (rule, data){
	var lp = /\((\d*),(\d*),(\d*)\)/;
	var m = new Array();
	var i, from, to, limit, cnt=0, obj;
	if(!(m = rule.match(lp)))
		return JSV_BAD_RULE;
	if(m[1]=='' || m[2]== '' || m[3]== '')
		return JSV_BAD_RULE;
	from = parseInt(m[1],10);
	to = parseInt(m[2],10);
	limit = parseInt(m[3],10);
	for(i=from; i<=to; i++){
		obj = document.getElementById(data + i);
		// select option element?
		if(obj.selected != "undefined")
			if(obj.selected == true)
				cnt++;
		// checkbox ?
		if(obj.checked != "undefined")
			if(obj.checked == true)
				cnt++;			
	}	
	if(cnt < limit)
		return JSV_FIELD_NSELECTED;
	return JSV_FIELD_VALID;
}

jsValidator.prototype._jsv_filled = function (rule, fieldName){
	var lp = /\((\d*),(\d*),(\d*)\)/;
	var m = new Array();
	var i, from, to, limit, cnt=0, obj;
	if(!(m = rule.match(lp)))
		return JSV_BAD_RULE;
	if(m[1]=='' || m[2]== '' || m[3]== '')
		return JSV_BAD_RULE;
	from = parseInt(m[1],10);
	to = parseInt(m[2],10);
	limit = parseInt(m[3],10);
	for(i=from; i<=to; i++){
		obj = document.getElementById(fieldName + i);
		if(obj.value != "undefined")
			if(obj.value != "")
				cnt++;
	}	
	if(cnt < limit)
		return JSV_FIELD_INFILLED;
	return JSV_FIELD_VALID;
}

jsValidator.prototype._jsv_udefunc = function (rule, data){
	var patt = /\((.*?)(,.*?)?\)/;
	var m, func;
	if((m=rule.match(patt))==null)
		return JSV_BAD_RULE;
	if(isNaN(parseFloat(data)))
		data = "'"+data+"'";
	if(m[2]==null){
		func = m[1] + "(" + data + ")";
	}
	else{
		func = m[1] + "(" + data + m[2] + ")";
	}
	return eval(func);
}