/**
 * Validation of forms
 * 
 * @constructor
 * @param {Integer} validation_delay
 * @author Wade.Womersley
 */
function Validation(validation_delay){}

/**
 * Test
 * @param {Boolean} cancel
 */
Validation.cancelSubmit = function(cancel){};

/**
 * Sends the form via AJAX
 * @param {String} url
 * @param {String} sendmethod
 * @param {Function} return_function
 * @param {Function} failure_function
 */
Validation.submitAJAX = function(url, sendmethod, return_function, failure_function){};

/**
 * Function to execute if the form passes validation. Should only be used
 * if cancelSubmit = true, otherwise this function will be executed as the 
 * browser starts to submit.
 * 
 * @param {Function} func
 */
Validation.setOnSubmit = function(func){};

/**
 * Checks if the key pressed is a keyboard command
 * 
 * @param {Integer} key
 * @return true/false
 * @type {Boolean}
 */
Validation.isCommandKey = function(key){};

/**

 * Performs validation across every element in the form
 * calling the specified functions on each and returning
 * true if the form passed validation, false if not.
 * Will return null if there is no bound form.
 * 
 * @return true/false
 * @type {Boolean}
 */
Validation.validateForm = function(){};

/**
 * Either disables or enables all the form fields
 * (input, select, textarea) in the form this Validation
 * is bound to
 * 
 * @param {Boolean} lock
 */
Validation.lockForm = function(lock){};

/**
 * Resets the form
 */
Validation.resetForm = function(){};

/**
 * Internal function to validate a field and call the relevant functions
 * when the field is invalid/valid/empty
 * 
 * @param {DOMElement} element
 * @ignore
 */
Validation.validateField = function(element){};

/**
 * Binds this validation object to a specific form.
 * Ensure all fields are added with addFields prior to
 * calling this!
 * 
 * @param {DOMElement} form
 */
Validation.bindToForm = function(form){};

/**
 * Internal. Sets up a validation timer for a form field
 * @param {DOMElement} el
 * @ignore
 */
Validation.setValidationTimer = function(el){};

/**
 * Gets all the elements in the form this 
 * Validation class is bound to of type
 * input, select, textarea
 * 
 * @return Array of elements
 * @type {Array}
 */
Validation.getElements = function(){};

/**
 * Fills form fields from an object where the object
 * property names match a field name
 * 
 * @param {Object/String} t
 */
Validation.fillForm = function(t){};

/**
 * addField(fieldname, type, minlength, maxlength, required, onvalid, oninvalid, onempty, custom, regex)
 * 
 *  definitions of type fields:
 * email - validates an email
 * integer - must be an integer (no decimal)
 * float - must be a valid number (possibly decimal)
 * alpha - can contain letters and spaces/CR's only
 * alphaextended - alpha + most standard punctuation
 * alphanumeric - can contain letters/spaces/CR's and numbers
 * alphanumericextended - alphanumeric + most standard punctuation
 * regex - use custom regular expression
 * custom - use custom function
 * 
 * regex - requires regex variable be set
 * custom - requires custom variable be a function returning true/false
 * minlength - null or an int for minimum length
 * maxlength - null or an int for maximum length
 * required - used during submit, is this field required for submit?
 * onvalid - function to call when field is good
 * oninvalid - function to call when field is bad
 * onempty - function to call when field is empty
 * custom - function to call when type=custom. must return true or false.
 * regex - function to call when type=regex. must be a valid regex.
 * @param {String} fieldname - the NAME of the input/select/textarea
 * @param {String} type - one of the following validation types:
 * email, integer, float, alpha, alphaextended, alphanumeric, alphanumericextended
 * @param {Integer} minlength - null or minimum field length
 * @param {Integer} maxlength - null or maximum field length
 * @param {Boolean} required - field must be entered to submit form
 * @param {function} onvalid - function to call if the field is valid
 * @param {function} oninvalid - function to call if the field is invalid
 * @param {function} onempty - function to call if the field is empty
 * @param {function} custom - function to call under type=custom
 * @param {RegExp} regex - regular expression to test against under type=regex
 */
Validation.addField = function(fieldname, type, minlength, maxlength, required, onvalid, oninvalid, onempty, custom, regex){};

/**
 * Uses the information in the database to set up form validation
 * @see http://wiki.cleverclover.local/index.php/Validation#addFieldsFromDb.28fields.29
 * @param {Object} fields
 */
Validation.addFieldsFromDb = function(fields){};

/**
 * Class Validation
 * First: Add each field you want to validate with "addField"
 * Finally: bindtoForm(formname/formref) to add validation
 * @author Wade.Womersley
 * @constructor
 * @class
 * @exec
 * @requires Class
 * @requires Events
 * @param {Integer} validation_delay Default 1000 (ms)
 */
var Validation = new Class({
	
	/**
	 * Initilization of Validation class
	 * @method
	 * @alias initialize
	 * @alias Validation.initialize
	 * @memberOf Validation
	 * @member Validation
	 */
	initialize: function(validation_delay) {
		Validation.implement(new Events);
		this.validation_timers = new Object;
		this.commandkeys = [3,8,9,13,16,17,18,19,20,27,33,34,35,36,37,38,39,40,44,45,46,112,113,114,115,116,117,118,119,120,121,122,123,144,145];
		this.form = null;
		this.elements = new Object;
		this.failedvalidation = false;
		this.cancelsubmit = false;
		this.validationdelay = validation_delay;
		if(this.validationdelay == undefined || parseInt(this.validationdelay) != this.validationdelay - 0)
			this.validationdelay = 1000;
	},
	
	/**
	 * cancelSubmit(cancel)
	 * 
	 * Should the Validation object cancel submittal of the form
	 * 
	 * @param {Boolean} cancel
	 * @member Validation
	 */
	cancelSubmit: function(cancel){
		this.cancelsubmit = cancel;
	},
	
	/**
	 * Sends the form via AJAX
	 * @param {String} url
	 * @param {String} sendmethod
	 * @param {Function} return_function
	 * @param {Function} failure_function
	 */
	submitAJAX: function(url, sendmethod, return_function, failure_function) {
		if(!this.form) return;
		if(typeof failure_function != 'function') failure_function = function(){};
		var qs = this.form.toQueryString();
		new Ajax(url, {
			method: sendmethod,
			data: qs,
			onRequest: function(){this.lockForm(true)}.bind(this),
			onComplete: function(t){
				this.lockForm(false);
				v = t;
				t = Json.evaluate(t);
				return_function(t, v);
			}.bind(this),
			onFailure: failure_function
		}).request();
	},
	
	/**
	 * setOnSubmit(function)
	 * 
	 * Function to execute if the form passes validation. Should only be used
	 * if cancelSubmit = true, otherwise this function will be executed as the 
	 * browser starts to submit.
	 * 
	 * @param {function} func
	 */
	setOnSubmit: function(func) {
		this.onsubmit = func;
	},
	
	/**
	 * isCommandKey(key)
	 * 
	 * Checks if the key pressed is a keyboard command
	 * 
	 * @param {Integer} key
	 * @return {Boolean}
	 */
	isCommandKey: function(key) {
		if(this.commandkeys.indexOf(key) == -1)
			return false;
		else
			return true;
	},
	
	/**
	 * validateForm()
	 * 
	 * Performs validation across every element in the form
	 * calling the specified functions on each and returning
	 * true if the form passed validation, false if not.
	 * Will return null if there is no bound form.
	 * 
	 * @return {Boolean}
	 */
	validateForm: function() {
		if(this.form) {
			var elements = this.getElements();
			this.failedvalidation = false;
			elements.each(function(el, i) {
				if(this.elements[el.name] !=undefined)
					this.validateField(el, true)
			}.bind(this));
			return !this.failedvalidation;
		}
		return null;
	},
	
	/**
	 * lockForm()
	 * 
	 * Either disables or enables all the form fields
	 * (input, select, textarea) in the form this Validation
	 * is bound to
	 * 
	 * @param {Boolean} lock
	 */
	lockForm: function(lock) {
		if (this.form) {
			var elements = this.getElements();
			elements.each(function(el, i){
				el.disabled = lock
			}.bind(this));
		}
		return;
	},
	
	/**
	 * Resets the form
	 */
	resetForm: function() {
		if(this.form)
			this.form.reset();
	},
	
	/**
	 * validateField(element)
	 * 
	 * Internal function to validate a field and call the relevant functions
	 * when the field is invalid/valid/empty
	 * 
	 * @param {Object} element
	 */
	validateField: function(element) {
		var validation_info = this.elements[element.name];

		if(validation_info.required != true && element.value.length == 0 && validation_info.onempty != undefined) {
			validation_info.onempty(element);
		} else {	
			if(validation_info.type.constructor.toString().indexOf('function Array') == -1)
				validation_info.type = new Array(validation_info.type);
			validation_info.type.each(function(t, i) {
				switch (t) {
					case 'email':
						var reg = /^([A-Za-z0-9_\-\.\+])+\@([A-Za-z0-9_\-\.]){2,100}\.([A-Za-z]{2,4})$/;
						if(reg.test(element.value) == false) {
							this.failedvalidation = true;
							validation_info.oninvalid(element, 'email'); return false;
						} else {
							validation_info.onvalid(element);
						}
						break;
					case 'integer':
						if(parseInt(element.value) == element.value - 0) {
							validation_info.onvalid(element);
						} else {
							this.failedvalidation = true;
							validation_info.oninvalid(element, 'integer'); return false;
						}
						break;
					case 'float':
						if(parseFloat(element.value) == element.value - 0) {
							validation_info.onvalid(element);
						} else {
							this.failedvalidation = true;
							validation_info.oninvalid(element, 'float'); return false;
						}
						break;
					case 'alpha':
						var reg = /^([A-Za-z\s]+)$/;
						if(reg.test(element.value) == false) {
							this.failedvalidation = true;
							validation_info.oninvalid(element, 'alpha'); return false;
						} else {
							validation_info.onvalid(element);
						}
						break;
					case 'alphaextended':
						var reg = /^([A-Za-z\.\,\?\!\"\(\)\'\;\:\-\`\s]+)$/;
						if (reg.test(element.value) == false) {
							this.failedvalidation = true;
							validation_info.oninvalid(element, 'alphaextended'); return false;
						} else {
							validation_info.onvalid(element);
						}
						break;
					case 'alphanumeric':
						var reg = /^([A-Za-z0-9\s]+)$/;
						if(reg.test(element.value) == false) {
							this.failedvalidation = true;
							validation_info.oninvalid(element, 'alphanumeric'); return false;
						} else {
							validation_info.onvalid(element);
						}
						break;
					case 'alphanumericextended':
						var reg = /^([A-Za-z0-9\.\,\?\!\"\(\)\'\;\:\-\`\s]+)$/;
						if (reg.test(element.value) == false) {
							this.failedvalidation = true;
							validation_info.oninvalid(element, 'alphanumericexpanded');
							return false;
						} else {
							validation_info.onvalid(element);
						}
						break;
					case 'regex':
						if(validation_info.regex != undefined) {
							if (validation_info.regex.test(element.value) == false) {
								this.failedvalidation = true;
								validation_info.oninvalid(element, 'regex');
								return false;
							} else {
								validation_info.onvalid(element);
							}
						}
						break;
					case 'custom':
						if (validation_info.custom != undefined) {
							if (validation_info.custom(element.value) == false) {
								this.failedvalidation = true;
								validation_info.oninvalid(element, 'custom');
								return false;
							}
							else {
								validation_info.onvalid(element);
							}
						}
						break;
				}
				if(validation_info.minlength != undefined && validation_info.minlength != null && parseInt(validation_info.minlength) == validation_info.minlength - 0) {
					if (element.value.length < validation_info.minlength) {
						this.failedvalidation = true;
						validation_info.oninvalid(element, 'minlength');
						return false;
					} else {
						validation_info.onvalid(element);
					}
				}
				
				if(validation_info.maxlength != undefined && validation_info.maxlength != null && parseInt(validation_info.maxlength) == validation_info.maxlength - 0) {
					if (element.value.length > validation_info.maxlength) {
						this.failedvalidation = true;
						validation_info.oninvalid(element, 'maxlength');
						return false;
					} else {
						validation_info.onvalid(element);
					}
				}
			}.bind(this));
		}
	},
	
	/**
	 * bindToForm(form)
	 * 
	 * Binds this validation object to a specific form.
	 * Ensure all fields are added with addFields prior to
	 * calling this!
	 * 
	 * @param {Object} form
	 */
	bindToForm: function(form) {
		form = $(form);
		if (form) {
			form.addEvent('submit', function(event){
				if(this.cancelsubmit == true) {
					if (!event.preventDefault) {
						this.returnValue = false;
					} else {
						event.preventDefault()
					}
				}
						this.onsubmit();
			}.bind(this));
			this.form = form;
			var elements = this.getElements();
			elements.each(function(el, i) {
				if(el.name.length > 0 && this.elements[el.name] != undefined) {
					if(this.elements[el.name].maxlength) el.setAttribute('maxlength', this.elements[el.name].maxlength);
					el.addEvent('keypress', function(event){
						this.setValidationTimer(el);
					}.bind(this));
					el.addEvent('blur', function(event){
						this.setValidationTimer(el);
					}.bind(this));
					el.addEvent('focus', function(event){
						this.setValidationTimer(el);
					}.bind(this));
				}
			}.bind(this));
		}
	},
	
	/**
	 * Internal. Sets up a validation timer for a form field
	 * @param {DOMElement} el
	 */
	setValidationTimer: function(el) {
		if(this.validation_timers[el.name] == undefined)
			this.validation_timers[el.name] = 0;
		if(this.validation_timers[el.name] > 0)
			$clear(this.validation_timers[el.name]);
		this.validation_timers[el.name] = this.validateField.pass([el], this).delay(this.validationdelay);
	},
	
	/**
	 * getElements()
	 * 
	 * Gets all the elements in the form this 
	 * Validation class is bound to of type
	 * input, select, textarea
	 * 
	 * @return {Array}
	 */
	getElements: function() {
		return this.form.getElements('input').extend(this.form.getElements('select')).extend(this.form.getElements('textarea'));
	},
	
	/**
	 * fillForm()
	 * 
	 * Fills form fields from an object where the object
	 * property names match a field name
	 * 
	 * @param {Object/String} t
	 */
	fillForm: function(t) {
		if(typeof t == 'string')
			t = Json.evaluate(t);
		var elements = this.getElements();
		elements.each(function(el, i){
			if (t[el.name] != undefined)
				el.value = t[el.name];
		}.bind(this));
	},
	
	/**
	 * addField(fieldname, type, minlength, maxlength, required, onvalid, oninvalid, onempty, custom, regex)
	 * 
	 *  definitions of type fields:
	 * 		email - validates an email
	 * 		integer - must be an integer (no decimal)
	 * 		float - must be a valid number (possibly decimal)
	 * 		alpha - can contain letters and spaces/CR's only
	 * 		alphaextended - alpha + most standard punctuation
	 * 		alphanumeric - can contain letters/spaces/CR's and numbers
	 * 		alphanumericextended - alphanumeric + most standard punctuation
	 * 		regex - use custom regular expression
	 * 		custom - use custom function
	 * 
	 * 	regex - requires regex variable be set
	 * 	custom - requires custom variable be a function returning true/false
	 * minlength - null or an int for minimum length
	 * maxlength - null or an int for maximum length
	 * required - used during submit, is this field required for submit?
	 * onvalid - function to call when field is good
	 * oninvalid - function to call when field is bad
	 * onempty - function to call when field is empty
	 * custom - function to call when type=custom. must return true or false.
	 * regex - function to call when type=regex. must be a valid regex.
	 * @param {String} fieldname - the NAME of the input/select/textarea
	 * @param {String} type - one of the following validation types:
	 * 		email, integer, float, alpha, alphaextended, alphanumeric, alphanumericextended
	 * @param {Integer} minlength - null or minimum field length
	 * @param {Integer} maxlength - null or maximum field length
	 * @param {Boolean} required - field must be entered to submit form
	 * @param {function} onvalid - function to call if the field is valid
	 * @param {function} oninvalid - function to call if the field is invalid
	 * @param {function} onempty - function to call if the field is empty
	 * @param {function} custom - function to call under type=custom
	 * @param {RegExp} regex - regular expression to test against under type=regex
	 */
	addField: function(fieldname, type, minlength, maxlength, required, onvalid, oninvalid, onempty, custom, regex) {
		element = new Object;
		element.type = type;
		element.minlength = minlength;
		element.maxlength = maxlength;
		element.required = required;
		element.onvalid = onvalid;
		element.oninvalid = oninvalid;
		element.onempty = onempty;
		element.custom = custom;
		element.regex = regex;
		this.elements[fieldname] = element;
	},
	
	/**
	 * Uses the information in the database to set up form validation
	 * @see http://wiki.cleverclover.local/index.php/Validation#addFieldsFromDb.28fields.29
	 * @param {Object} fields
	 */
	addFieldsFromDb: function(fields) {
		if(typeof fields == 'string') fields = Json.evaluate(fields);
		for (var i in fields) {
			var curField = fields[i];
			//if(curField.Extra == 'auto_increment' && curField.Format != undefined && curField.Format.show != true)
			switch(curField.DataType) {
				case 'tinyint': case 'utinyint': case 'smallint': case 'usmallint': case 'mediumint': case 'umediumint': case 'int': case 'uint': case 'bigint': case 'ubigint':
					var validation = 'integer';
					break;
				case 'varchar': case 'text': case 'char':
					var validation = 'alphanumericextended';
					break;
				case 'float': case 'double': case 'decimal':
					var validation = 'float';
					break;
				default:
					var validation = null;
			}
			var minlength = null;
			var maxlength = curField.Length != undefined ? curField.Length : null;
			var required = false;
			var onvalid = function(){};
			var oninvalid = function(){};
			var onempty = function(){};
			var custom = null;
			var regex = null;
			if(curField.Format != undefined) {
				validation = curField.Format.validation != undefined ? curField.Format.validation : validation;
				minlength = curField.Format.minlength != undefined ? curField.Format.minlength : null;
				maxlength = curField.Format.maxlength != undefined ? curfield.Format.maxlength : maxlength;
				required = curField.Format.required == true ? true : false;
				onvalid = curField.Format.onvalid != undefined ? eval(curField.Format.onvalid) : function(){};
				oninvalid = curField.Format.oninvalid != undefined ? eval(curField.Format.oninvalid) : function(){};
				onempty = curField.Format.onempty != undefined ? eval(curField.Format.onempty) : function(){};
				custom = curField.Format.custom != undefined ? eval(curField.Format.custom) : null;
				regex = curField.Format.regex != undefined ? curField.Format.regex : null;
				if(regex != null){
					var regexflags = regex.substring(regex.lastIndexOf(regex.substring(0, 1)) + 1)
					regex = regex.substring(1, regex.lastIndexOf(regex.substring(0, 1)))
					regex = regexflags.length > 0 ? new RegExp(regex, regexflags) : new RegExp(regex);
				}
			}
			if(validation != null)
				this.addField(i, validation, minlength, maxlength, required, onvalid, oninvalid, onempty, custom, regex)
		}
	}
});