/* vim: set syntax=javascript tabstop=4: */
/* le but est d'avoir une routine facilement adaptable
	à différents formulaires, nom de champs, etc..
	et facile à définir depuis le code php du formulaire
	(noms des champs corrects, traduction facile des messages d'erreur, etc...)



	synopsis:
		for each field validation, add a line like:

		addRule(formName,fieldName,checkName,error message,param,label);

		where:
			checkName is one of (not_empty,selected,min_length,email_reg,phone_reg,regexp,not_regexp)
			error message is the string to use to alert user
			param is optionnal, except for min_length/regexp/not_regexp, and, if exist,
								contain either the min length or the regexp to test
			label is optionnal, and default to 'name-label'

	example:
		<script type="text/javascript" language="javascript" src="/js/formvalidation.js"></script>
		...

		<form method="post" name="test" onSubmit="checkForm(this);">
			<label for="Firstname" id="First-label">First Name:</label>
			<input name="Firstname">
			...
		</form>
		<script type="text/javascript" language="javascript">
			addRule('test','Firstname', 'not_empty',  'Please, provide your First name');
			addRule('test','Lastname',  'min_length', 'Please, provide your Last name',2);
			addRule('test','MF_Tel_CTRY_CODE','phone_reg','Please, verify your country code phone number,null,'Tel1-label');
			addRule('test','Email','email_reg','Please, verify your Email (malformed or invalid)';
			addRule('test','RegKey','regexp', 'Please, verify your key (invalid)','^[\w]{5}-[\w]{5}-[w]{5}$');
		</script>

*/


// CSS class for labels
var focus_class = 'focus';
var error_class = 'invalid'; // also set by Sitellite


// select spaces at begining or end of string
var trim_reg  = /(^\s*|\s*$)/;

// syntactically valid email address
var email_reg = /^[-\w]+(\.[-\w]+)*@[-\w]+(\.[-\w]+)*(\.\w+)+$/i;

// phone number: number and separators (.- )
var phone_reg = /^[-. 0-9]+$/;


/* 	rules: an Object with one property per form (the form name) which then
	hold one property per field

	rules['form_one'] is an array of fields, where fields are objects with
	a property for each rule they must pass

*/

var rules = new Object;

// allowed checks (for now) : they all can be prefixed with 'not'
var checks = new Array(
	'empty',	// input: field must (not) have a value
	'selected',		// select: must (not) be set to non-default (>0)
	'length',	// input: minimal length
    'equal',        // input: must (not) match given arg field
	'email',	// input: must (not) match email regexp
	'phone',	// input: must (not) match phone regexp
    'intl_phone',   // combined input: Ctry_Code and Phone_Number
	'regexp'		// input: must (not) match given regexp
	);

// fieldChecker.checkSelected
/**
 * the logic is different for select and radiobuttons
 */
 
function _isChecked(form) {
	if (!this) return false;
	
	if (!this.obj) {
		this.obj = form[this.field];
	}
	var o = this.obj;
	if (!o) return true;
	
	if(o.length) {
		var x;
		for(x=0; x < o.length; x++) {
			if(o[x].checked) return true;
		}
		return false;
	} else {
		return o.checked;
	}
}
// checkIsEmpty
function _isEmpty(form) {
	if (!this) return true;
	
	if (!this.obj) {
		this.obj = form[this.field];
	}
	var o = this.obj;
	if (!o) return false;
	

	if (o.type) {
		switch(o.type) {
			case 'textarea':
			case 'text':
				return o.value.length == 0;
				break;
			case 'select':
			default:
				if (obj.childNodes && obj.childNodes.length) {
					return  !this.isChecked( form );
				}
				return o.value.length == 0;
		}
	} else {
		return o.value.length == 0;
	}
}
// check the Intl_Phone widget
/**
 * the widget is made of 2 input:
 *      MF_<field_name>_CTRY_CODE
 *      MF_<field_name>_PHONE_NUMBER
 * they must either be all filled or all empty
 */
function _isIntlPhone(form) {
	if (!this) return false;
	
	if (!this.obj_ctry) {
		this.obj_ctry = form['MF_' + this.field + '_CTRY_CODE'];
	}
	if (!this.obj_num) {
		this.obj_num = form['MF_' + this.field + '_NUMBER'];
	}
    if ( !this.obj_ctry || !this.obj_num ) {
        alert ('*** programmer error: bad check specification!\nField: '+this.field);
        return false;
    }
    var ctry = this.obj_ctry, num = this.obj_num;
   
    if ( ctry.value ) {
        if ( !phone_reg.test ( ctry.value ) ) {
            this.obj = ctry;
            return false;
        }
        if (!num.value || !phone_reg.test( num.value ) ) {
            this.obj = num;
            return false;
        }
    } else if ( num.value ) {
        if ( !phone_reg.test (num.value) ) {
            this.obj = num;
            return false;
        }
        if ( !ctry.value || !phone_reg ( ctry.value ) ) {
            this.obj = ctry;
            return false;
        }
    }
    return true;
}

function _isEqual ( form, arg ) {

	if (!this) return false;
	
	if (!this.obj) {
		this.obj = form[this.field];
	}
	var o = this.obj;
	if (!o) return true;
    
    var e = form[arg];
    if (!e) return true;
    
    return e.value == o.value;
}

// fieldChecker.validate
var lengthReg = /^([0-9]+)([+-]?)([0-9]*)$/;

function _validate(form, obj, rule) {
	switch(rule.check) {
		case 'empty':
			/*
			if (obj.childNodes && obj.childNodes.length) {
				result = ! this.isChecked( form );
			} else {
				result = !obj.value || obj.value == '';
			}*/
			result = this.isEmpty( form );
			break;
		case 'length':
			var m = lengthReg.exec(rule.arg);
			if(m && m[3]) {
				// min-max (ex: 2-15)
				result = (obj.value && m[1] <= obj.value.length && obj.value.length <= m[3]);
				break
			}
			if(m && m[2]) {
				if(m[2]=='+') {
					result = (obj.value && m[1]<= obj.value.length);
					//alert('length:'+obj.value.length+', min: ' + m[1]);
				} else {
					result = (obj.value && obj.value.length <= m[1]);
				}
				break;
			}
			if(m && m[1]) {
				result = (obj.value && obj.value.length == m[1]);
				break;
			}
			alert('*** Programmer error: bad length specs !!');
			break;
		case 'equals':
			result = this.isEqual ( form, rule.arg );
			break;
		case 'selected':
			result = obj.selectedIndex > 0;
			break;
		case 'checked':
			result = this.isChecked(form);
			break;
		case 'email':
			result = email_reg.test(obj.value);
			break;
		case 'phone':
			result = phone_reg.test(obj.value);
			break;
		case 'intl_phone':
			result = this.isIntlPhone(form);
			break;
		case 'regex':
			s=rule.arg;
			if(s.charAt(0)=='/' && s.charAt(s.length-1)=='/') {
				s = s.substr(1,s.length-2);
			}
			re=new RegExp(s);
			//alert(re.toString());
			result = re.test(obj.value);
			break;
		default:
			result = true;
	}
	return result;
}

/*
function _validateNegated(form, obj, rule) {
	switch(rule.check) {
		case 'empty':
			result = (obj.value && obj.value.length > 0);
			break;
		case 'length':
			var m = lengthReg.exec(rule.arg);
			if(m && m[3]) {
				// min-max (ex: 2-15)
				result = !(obj.value && m[1] <= obj.value.length && obj.value.length <= m[3]);
				break
			}
			if(m && m[2]) {
				if(m[2]=='+') {
					result = !(obj.value && m[2]<= obj.value.length);
				} else {
					result = !(obj.value && obj.value.length <= m[2]);
				}
				break;
			}
			if(m && m[1]) {
				result = !(obj.value && obj.value.length == m[1]);
				break;
			}
			alert('*** Programmer error: bad length specs !!');
			break;
		case 'equals':
			result = !this.isEqual ( form, rule.arg );
			break;
		case 'selected':
			result = !(obj.selectedIndex > 0);
			break;
		case 'checked':
			result = !this.isChecked(form);
			break;
		case 'email':
			result = !email_reg.test(obj.value);
			break;
		case 'phone':
			result = !phone_reg.test(obj.value);
			break;
		case 'intl_phone':
			result = !this.isIntlPhone(form);
			break;
		case 'regexp':
			result = !rule.arg.test(obj.value);
			break;
		default:
			result = true;
	}
	return result;
}*/

// fieldChecker.verify
function _verify(form) {
	
	if(!this) {
		//alert('no this object!');
		return false;
	}

	if(! this.rules || !this.field || !this.label) {
		alert('*** Programmer error: bad fieldCheck object!!!');
		return false;
	}

	this.obj = form[this.field]; 
	var obj = this.obj;
	if(!obj) return true;

	var key, rule, result=true;

	// reset error_msg
	this.error_msg = '';

	for(key in this.rules) {
		rule = this.rules[key];
		if(rule.negated) {
			//alert('checking rule : not ' + rule.check);
			result = !this.validate(form, obj, rule);
		} else {
			//alert('checking rule : ' + rule.check);
			result = this.validate(form, obj, rule);
		}
		if(!result) {
			this.error_msg = rule.msg;
			//alert('pas glop,pas glop!:' + rule.msg + ' (' + rule.check + ')');
			break;
		}
	}
	return result;
}

// add a rule for a fieldChecker
var chreg = /^(not )?(empty|length|contains|regex|equals|email|phone|intl_phone|is|gt|lt|ge|le|numeric)( ("|'|`)(.*)\4)?$/;

var emailReg = /^\w+[-\w]*(\.\w+[-\w]+)*@\w+[-\w]*(\.\w+[-\w]*)*(\.\w+)+$/;
var phoneReg = /^[- .,0-9]*$/;
var intl_phoneReg = /^(\(.{2,5}\))*([- .,0-9]*)$/;

function _addCheckRule(check,msg) {
	//alert('addCheckRule(' + check + ',...)');
	var mcheck = chreg.exec(check);
	if(mcheck) {
		//s=''; for(x=0;x<mcheck.length;x++) s += (''+x+': '+mcheck[x]+'\n'); alert(s);
		var negated = (mcheck[1] && mcheck[1]=='not ');
		var rule = mcheck[2];
		var arg = mcheck[5];
		if(rule == 'length' || rule == 'regexp' || rule == 'equals') {
			if(typeof arg == 'undefined') {
				alert('*** Programmer error: missing param for fieldChecker\n' +
					'field = ' + this.field + '\n' +
					'rule  = ' + rule + '\n ** this rule need an additional parameter, please!');
				return;
			}
		}
		this.rules.push({'check':rule,'msg':msg,'arg':arg,'negated':negated});
		//alert('ajout de la regle: ' + rule + ',' + msg + ','+arg+','+negated);
	}
	else {
		alert('*** Programmer error: bad check rule: ' + check);
	}
}

// object fieldChecker
function fieldChecker(form,field,label,tab) {
	this.form = form;
	this.field = field;
	if(label) {
		this.label = label;
	}
	else {
		this.label = field + '-label';
	}
//	if(tab != false && tab != null) {
		this.tab = tab;
/*	} else {
		this.tab = false;
	}*/
	this.rules = new Array();
	this.isChecked = _isChecked;
    this.isEmpty = _isEmpty;
    this.isEqual = _isEqual;
    this.isIntlPhone = _isIntlPhone;
	this.validate = _validate;
	this.verify = _verify;
	this.addCheckRule = _addCheckRule;
}


function addRule(formName, fieldName, check, msg, tab, label) {
	//alert('addRule('+formName+','+fieldName+','+check+','+msg+'...)');
	if(!rules[formName]) {
        //alert('creating new ruleset for ' + formName);
		rules[formName] = new Object;
	}
	formFields = rules[formName];
	if(!formFields[fieldName]) {
        //alert('creating new checker for field ' + fieldName);
		formFields[fieldName] = new fieldChecker(formName,fieldName,label,tab);
	}
	checker = formFields[fieldName];
	checker.addCheckRule(check, msg);
}


// set/remove the invalid_class for the the field label
// WARNING: need dom.js

function setFieldLabel(field,css,onOff) {
	if(!document.getElementById) return;
	var obj = document.getElementById(field);
	if (!obj) return;
	var cl = new ClassName( obj.className );
	if (onOff) {
		cl.addClass ( css );
	} else {
		cl.removeClass ( css );
	}
	obj.className = cl.get();
}


// check the form
var error_msg='';
var error_obj=null;

function checkForm(form) {

    if (typeof form == 'undefined') {
        form = this;
    }

	// no rules ?
	// do not put a field named 'name' in the form !!!
	if(!rules[form.name]) {
        //alert('checkForm(): no rules for form ' +  form.name + '\nrules.length = ' + rules.length);
        //return false;
        return true;
    }

	var checker;
	var result=true;

	error_msg = '';
	error_obj=null;
	error_tab=null;

	for(key in rules[form.name]) {
		checker = rules[form.name][key]
		//alert('checker for ' + key);
		if(!checker.verify(form)) {
			if(!error_obj) {
				error_msg = checker.error_msg;
				error_obj = checker.obj;
				error_tab = checker.tab;
			}
			result=false;
			setFieldLabel(checker.label,error_class,true);
		}
		else {
			setFieldLabel(checker.label,error_class,false);
		}
	}
	if(error_msg) {
		alert(error_msg);
		if (error_tab != null && error_tab != '') {
			tab = document.getElementById(error_tab);
			if(tab && tab.onclick) {
				tab.onclick();
			} else {
				//alert(tab ? 'tab without onclick, ' : 'no tab, ' + error_tab );
			}
			//showPane ( 'pane'+error_tab, tab ); //, error_obj );
		}
		if(error_obj && error_obj.focus) {
			error_obj.focus();
		}
	} else {
		if (window.customCheckForm) {
			result = customCheckForm(form);
		}
	}
	return result;
}

// check one field (used by onBlur event to highlight label in case of error)
function checkField(form,field) {
	if(form.name && field.name && rules[form.name] && rules[form.name][field.name]) {
		var checker = rules[form.name][field.name];
		return checker.verify(form);
	}
	return true;
}

// highlight the focused field
   
function fieldFocus(){
	if(this) {
		setFieldLabel(this.name + '-label', focus_class, true);
		if(this.select) {
			this.select();
		}
	}
}

function fieldBlur(){
	if(this) {
		setFieldLabel(this.name + '-label', focus_class, false);
	    // if checked ok, reset the label color...
		var passed = checkField(this.form,this);
		setFieldLabel(this.name + '-label', error_class, !passed);
	}
}

// attach onblur & onfocus event to form fields
function initFormFields(f) {
	if(!f || !f.elements) return;
	// DEBUG
	//var s='elements type for ' + f.name + '\n';
	var x, el, tag, y=0;
	for(x=0; x < f.elements.length; x++) {
		el=f.elements[x];
		if (el._passed) continue;
		el._passed = true;
		tag=el.tagName.toLowerCase();
		typ=el.type ? el.type.toLowerCase() : '' ;
		
		if( ! ( tag=='input' || tag=='select' ||tag=='textarea') ) {
			continue;
		}
		if (tag =='input' ) { 
			if( ! (typ == 'text' || typ == 'radio' ||typ == 'checkbox' ) ) {
				continue;
			}
		}
		el.onfocus = fieldFocus;
		el.onblur = fieldBlur;
        /*
		s += el.name + ': ' + tag + ', (type=' + typ + ') :: ';
		if(y++ > 5) {
			y=0;
			s +='\n';
		}
        */
	}
	//alert(s);
}

// used by next function
function bissextile(annee) {
	if (annee%4==0 && annee %100!=0 || annee%400==0) return true; else return false
}
/**
 * used by the Sitellite mydate widget (inc/app/dataself/lib/Widget/MyDate.php)
 * to set the correct number of day per month
 */
function checkDate(obj) {
	if (!obj) return;
	var match = /MF_(.*)_(MONTH|DAY|YEAR)/.exec(obj.name);
	if (match.length != 3)  return;
	var form = obj.form;
	var name = match[1];
	// Ok, we have enough to works
	var _month = form['MF_' + name + '_MONTH'];
	var _day   = form['MF_' + name + '_DAY'];
	var _year  = form['MF_' + name + '_YEAR'];
	if(!_month || !_day || !_year) {
		return;
	}
	var nmonth = _month.selectedIndex > 0 ? _month.options[_month.selectedIndex].value : -1;
	var nday   = _day.selectedIndex > 0 ? _day.options[_day.selectedIndex].value : -1;
	var nyear  = _year.selectedIndex > 0 ? _year.options[_year.selectedIndex].value : -1;

	// skip incomplete date
	if (nmonth == -1 || nyear == -1) return;
	
	switch(nmonth-1) {
		case 0:
		case 2:
		case 4:
		case 6:
		case 7:
		case 9:
		case 11:
			maxday = 31;
			break;
		case 1:
			maxday =  bissextile(nyear) ? 29 : 28;
			break;
		default:
			maxday = 30;
	}
	// used for options, so add one for '--Select..' entry
	maxday++;
	var num;
	// ajuster le nombre de jours 
	//alert('maxday = ' + maxday + '\n length = ' + _day.options.length );
	while ( _day.options.length < maxday ) {
		num = _day.options.length;
		_day.options[num] = new Option(num,num);
	}
	while ( _day.options.length > maxday ) {
		_day.options[_day.options.length-1] = null;
	}
	if (nday >= maxday ) {
		changing = true;
		_day.selectedIndex = maxday -1;
		changing = false;
	}
}
// helper
/*
function dumpRules() {
	document.write('<h3>Rules</h3><ul>');
	for(item in rules) {
		document.write('<li>[' + item + ']: ' + rules[item] + '\n<ul>');
		for(chk in rules[item]) {
			document.write('<li>[' + chk + ']: ' + rules[item][chk] + '<ul>');
			for(o in rules[item][chk]) {
				if (o == 'verify' || o == 'addCheckRule') continue
				document.write('<li>[' + o + ']: ' + rules[item][chk][o]);
				if(o == 'rules') {
					document.write('<ul>');
					for(r in rules[item][chk][o]) { document.write('<li>[' + r + ']:'+rules[item][chk][o][r]+'</li>');}
					document.write('</ul>');
				}
				document.write('</li>');
			}
			document.write('</ul></li>');
		}
		document.write('</ul></li>');
	}
	document.write('</ul>');
}
function dumpField(form,field) {
	if(!document.getElementById) return;
	document.write("<h3>Field's properties (" + field + ')</h3><ul>');
	frm = document.getElementById(form);
	if(frm && frm[field]) {
		obj = frm[field];
		for(prop in obj) {
			document.write('<li>[' + prop + ']: ' + obj[prop] + '</li>\n');
		}
	}
	document.write( "</ul>");
}
*/

