// form parsing
var FormValidation = Class.create();
FormValidation.prototype = {
	// constructor supports up to 4 arguments
	// id of form (mandatory)
	// message to alert on validation failure (not mandatory)
	// liveUpdate - weather to change the status of each field while typing / onchange (not mandatory, default to true)
	// custom error field mark up
  	initialize: function(name) {
		this.form = $(name);
		this.rules = { };
		this.msg = null;
		if (arguments[1] != null)
			this.msg = arguments[1];
		this.liveUpdate = true;
		if (arguments[2] != null)
			this.liveUpdate = arguments[2];
		this.markMode = null;
		if (arguments[3] != null)
			this.markMode = arguments[3];
	},
	
	// define or remove filters for a field
	setRule: function(elem) {
		// no rules sent exit
		if (arguments.length != 2) return;
		
		// if field does not exist don't create rule
		if (this.form.elements[elem] == null) return;
		
		if (this.rules[elem] == null) this.rules[elem] = { };
		for (filter in arguments[1]) this.rules[elem][filter] = arguments[1][filter];
		
		// add event handlers for live update
		if (this.liveUpdate) {
			if (this.form.elements[elem].type == 'select-one' || this.form.elements[elem].type == 'checkbox')
				Event.observe(this.form.elements[elem], 'change', this.updateElement.bindAsEventListener(this));
			if (this.form.elements[elem].type == 'checkbox')
				Event.observe(this.form.elements[elem], 'click', this.updateElement.bindAsEventListener(this));
			Event.observe(this.form.elements[elem], 'keyup', this.updateElement.bindAsEventListener(this));			
		}
	},
	
	// remove filters: all of a form, all of an element or one of an element
	removeRule: function() {
		if (arguments.length == 0) {
			// no parameter - remove all form filters
			this.unmarkFields();
			this.rules = { };
		}
		else {
			if (arguments.length == 1) {
				// remover all filters of an element
				elem = arguments[0];
				if (this.rules[elem] != null) { 
					delete this.rules[elem];
					this.unmarkField(elem);
				}
			}
			else {
				// remover a filter of an element
				elem = arguments[0];
				for (filter in arguments[1]) 
					if (this.rules[elem][filter] != null) { 
						delete this.rules[elem][filter];
						this.unmarkField(elem);
					}
				if (this.countProperties(this.rules[elem]) == 0) delete this.rules[elem];
			}
		}
	},
	
	// check form based on defined filters
	testForm: function(doSubmit) {
		testForm = true;
		
		testElements = { };
		// check each element that has a rule
		for (elem in this.rules) {
			testElem = true;
			// check each filter of current element
			for (filter in this.rules[elem]) {
				// aply rule
				testFilter = this.testRule(this.form.elements[elem], filter, this.rules[elem][filter]);
				
				if (!testFilter && testForm) testForm = false;
				if (!testFilter && testElem) testElem = false;

				// if one filter fails don't check the others
				if (!testElem) break;
			}
			// store each elements validation status
			testElements[elem] = testElem;
		}
		
		// if at least one check has failed mark all elements that have rules according to validation status
		if (!testForm)
			for (elem in this.rules)
				this.markField(elem, testElements[elem]);
				
		// if all fields passed validation and in submit mode send form
		if (doSubmit && testForm) this.form.submit();
		else {
			if (!testForm && this.msg && (arguments[1] == null || arguments[1]))
				alert(this.msg);
			return testForm;
		}
	},
	
	// check a value against a filter and its parameters
	testRule: function(elem, filter, params) {
		var test = new DataValidation(elem);
		try { eval('val = test.is' + filter + '(params)'); }
		catch(e) { val = true; }
		return val;
	},
	
	markField: function(elem, validated) {
		// create DOM element if not created
		if (this.markMode != null) {
			eval('this.markField' + this.markMode + '(elem, validated)');
			return;
		}
		
		id = elem + '_status';
		
		if ($(id) == null) {
			$(document.body).insert('<div id="' + id + '"></div>');

			// get coordinates of status image based on element's position
			pos = Position.cumulativeOffset(this.form.elements[elem]);
			width = Element.getWidth(this.form.elements[elem]);
			pos_top = pos[1] + 2 + 'px';
			pos_left = pos[0] + width + 5 + 'px';
			$(id).setStyle({ position: 'absolute', top: pos_top, left: pos_left });
		}
		
		$(id).update('<img src="/images/valid_' + (validated ? 'ok' : 'ko') + '.gif">');
	},
	
	markField1: function(elem, validated) {
		// just change background
		if (validated) Element.setStyle(this.form.elements[elem], { backgroundColor: '#ffffff' });
		else Element.setStyle(this.form.elements[elem], { backgroundColor: '#e9e8c7' });
	},
		
	// unmark an element (when filter is removed)
	unmarkField: function(elem) {
		this.markField(elem, true);
	},
	
	// unmark all elements (when all filters are removed)
	unmarkFields: function() {
		for (elem in this.rules)
			this.unmarkField(elem);
	},
	
	// count properties of an object
	countProperties: function(obj) {
		i = 0;
		for (prop in obj) i++;
		return(i);
	},
	
	// check element on keyup / change
	updateElement: function(e) {
		elem = Event.element(e).name;
		
		// check if element has rules
		if (this.rules[elem] == null)
			return;
			
		testElem = true;
		// check each filter of element
		for (filter in this.rules[elem]) {
			if (filter == 'AjaxValid') continue;
			// aply rule
			testFilter = this.testRule(this.form.elements[elem], filter, this.rules[elem][filter]);

			if (!testFilter && testElem) testElem = false;

			// if one filter fails don't check the others
			if (!testElem) break;
		}
		
		// special live check for ajax filter
		if (testElem && this.rules[elem]['AjaxValid'] != null) {
			var test = new DataValidation(this.form.elements[elem]);
			this.rules[elem]['AjaxValid'].form = this;
			this.rules[elem]['AjaxValid'].elem = elem;
			test.isAjaxValidAsync(this.rules[elem]['AjaxValid']);
		}
		else
			this.markField(elem, testElem);
	},
	
	// debug
	debug: function() {
		ret = '';
		for (elem in this.rules) {
			ret += elem + '\n';
			for (filter in this.rules[elem]) {
				ret += '  - ' + filter + ': ' + this.rules[elem][filter];
				ret += '\n';
			}
		}
		
		ret += '\n';
		ret += 'Msg: ' + this.msg + '\n';
		ret += 'Live update: ' + this.liveUpdate + '\n';
		ret += 'Mark mode: ' + this.markMode + '\n';

		alert(ret);
	},
	
	// returns amount of vertical scroll
	getScrollY: function() {
		var sy = 0;
		if (document.documentElement && document.documentElement.scrollTop)
			sy = document.documentElement.scrollTop;
		else if (document.body && document.body.scrollTop) 
			sy = document.body.scrollTop; 
		else if (window.pageYOffset)
			sy = window.pageYOffset;
		else if (window.scrollY)
			sy = window.scrollY;
		return sy;
	},
	
	// returns amount of horizontal scroll
	getScrollX: function() {
		var sy = 0;
		if (document.documentElement && document.documentElement.scrollLeft)
			sy = document.documentElement.scrollLeft;
		else if (document.body && document.body.scrollLeft) 
			sy = document.body.scrollLeft; 
		else if (window.pageXOffset)
			sy = window.pageXOffset;
		else if (window.scrollX)
			sy = window.scrollX;
		return sy;
	}
}