/* ************************************************************************************* *\
 * The MIT License
 * Copyright (c) 2007 Fabio Zendhi Nagao - http://zend.lojcomm.com.br
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this
 * software and associated documentation files (the "Software"), to deal in the Software
 * without restriction, including without limitation the rights to use, copy, modify,
 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies
 * or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
\* ************************************************************************************* */

var fValidator = new Class({
	options: {
		//msgContainerTag: "div",
		msgClass: "curo-validation",

		styleNeutral: {
			"background-color": "#FFFFCC",
			"border-width": "1px",
			"border-style": "solid",
			"border-color": "#CCCC00"
		},
		styleInvalid: {
			"background-color": "#FFCCCC",
			"border-width": "1px",
			"border-style": "solid",
			"border-color": "#CC0000"
		},
		styleValid: {
			"background-color": "#CCFFCC",
			"border-width": "1px",
			"border-style": "solid",
			"border-color": "#00CC00"
		},

		required: {type: "required", re: /[^.*]/, msg: "Required :: This field is required."},
		alpha: {type: "alpha", re: /^[a-z ._-]*$/i, msg: "Letters only :: This field accepts alphabetic characters only."},
		alphanum: {type: "alphanum", re: /^[a-z0-9 ._-]*$/i, msg: "Letters & numbers only :: This field accepts alphanumeric characters only."},
		integer: {type: "integer", re: /^[-+]?\d*$/, msg: "Please enter a valid integer."},
		real: {type: "real", re: /^[-+]?\d*\.?\d*$/, msg: "Please enter a valid number."},
		//date: {type: "date", instant: false, re: /^((((0[13578])|([13578])|(1[02]))[\/](([1-9])|([0-2][0-9])|(3[01])))|(((0[469])|([469])|(11))[\/](([1-9])|([0-2][0-9])|(30)))|((2|02)[\/](([1-9])|([0-2][0-9]))))[\/]\d{4}$|^\d{4}$/, msg: "Please enter a valid date (mm/dd/yyyy)."},
		USdate: {type: "USdate", instant: false, re: /^((((0[13578])|([13578])|(1[02]))[\/](([1-9])|([0-2][0-9])|(3[01])))|(((0[469])|([469])|(11))[\/](([1-9])|([0-2][0-9])|(30)))|((2|02)[\/](([1-9])|([0-2][0-9]))))[\/]\d{4}$|^\d{4}$/, msg: "Please enter a valid date (mm/dd/yyyy)."},
		UKdate: {type: "UKdate", instant: false, re: /^$|(?:(?:0?[1-9]|1\d|2[0-8])(\/|-)(?:0?[1-9]|1[0-2]))(\/|-)(?:[1-9]\d\d\d|\d[1-9]\d\d|\d\d[1-9]\d|\d\d\d[1-9])$|^(?:(?:31(\/|-)(?:0?[13578]|1[02]))|(?:(?:29|30)(\/|-)(?:0?[1,3-9]|1[0-2])))(\/|-)(?:[1-9]\d\d\d|\d[1-9]\d\d|\d\d[1-9]\d|\d\d\d[1-9])$|^(29(\/|-)0?2)(\/|-)(?:(?:0[48]00|[13579][26]00|[2468][048]00)|(?:\d\d)?(?:0[48]|[2468][048]|[13579][26]))$/, msg: "Please enter a valid date (dd/mm/yyyy)."},
		email: {type: "email", re: /^[a-z0-9._%-]+@[a-z0-9.-]+\.[a-z]{2,4}$/i, msg: "Email address :: Please enter a valid email."},
		phone: {type: "phone", re: /^[\d\s ().-]+$/, msg: "Please enter a valid phone."},
		url: {type: "url", re: /^(http|https|ftp)\:\/\/[a-z0-9\-\.]+\.[a-z]{2,3}(:[a-z0-9]*)?\/?([a-z0-9\-\._\?\,\'\/\\\+&amp;%\$#\=~])*$/i, msg: "Please enter a valid url."},
		confirm: {type: "confirm", msg: "Confirm Password does not match original Password."},

		onValid: Class.empty,
		onInvalid: Class.empty
	},

	initialize: function(form, options) {
		this.form = $(form);
		this.setOptions(options);

		this.fields = $A(this.form.getElementsByTagName('input'));
		this.fields.extend(this.form.getElementsByTagName('select'));//("*[class^=fValidate]");
		this.validations = [];
		this.alert = null;

		$A(this.fields).each(function(element){
			element = $(element);

			element.store('cbErr', new Array());
			var classes = element.getProperty("class").split(' ');

			var setNeutral = false;
			classes.each(function(klass, num){
				if (klass.indexOf('validate-') !== 0) return;

				klass = klass.substring('validate-'.length);

				//if(klass.match(/^fValidate(\[.+\])$/)) {
					//var aFilters = eval(klass.match(/^fValidate(\[.+\])$/)[1]);
					//for(var i = 0; i < aFilters.length; i++) {
						//if(this.options[aFilters[i]]) this.register(element, this.options[aFilters[i]]);
						if(this.options[klass]) this.register(element, this.options[klass]);
						setNeutral = true;
						//if(aFilters[i].charAt(0) == '=') this.register(element, $extend(this.options.confirm, {idField: aFilters[i].substr(1)}));
					//}
				//}
			}.bind(this));

			//if (setNeutral){
			//	this._injectImg(element);
			//}

			//if(setNeutral && !this._isChildType(element)) element.setStyles(this.options.styleNeutral);

			// Validate it
		}.bind(this));

		this.form.addEvents({
			"submit": this._onSubmit.bindWithEvent(this),
			"reset": this._onReset.bindWithEvent(this)
		});

		this._onSubmit();

		return this;
	},

	register: function(field, validations){
		field = $(field);

		this._injectImg(field);

		if(!this._isChildType(field)) field.setStyles(this.options.styleNeutral);

		$splat(validations).each(function(options){
			this.validations.push([field, options]);
			func = function() {
				this._validate(field, options);
			}.bind(this);

			var obj = {"blur": func};

			// Validate instantly?
			if (typeof(options.instant) == 'undefined' || options.instant){
				obj.keyup = func;
			}

			field.addEvents(obj);

			if (typeof(options.idField) != 'undefined'){
				$(options.idField).addEvents(obj);
			}

			if (typeof(options.instant) == 'undefined' || options.instant){
				this._validate(field, options);
			}
		}.bind(this));

		return this;
	},

	_isChildType: function(el) {
		var elType = el.type.toLowerCase();
		if((elType == "radio") || (elType == "checkbox")) return true;
		return false;
	},

	_validate: function(field, options) {
		switch(options.type) {
			case "confirm":
				if($(options.idField).get('value') == field.get('value')) this._msgRemove(field, options);
				else this._msgInject(field, options);
				break;
			case 'request':

				// Only validate if there's no other errors
				if ($A(cbErr).erase(options.type).length == 0){
					var req = new Request.JSON({
						url: options.url,
						onComplete: function(success){
							if (success) this._msgRemove(field, options);
							else this._msgInject(field, options);
						}.bind(this)
					}).get({'value': field.get('value')});
				}

				break;
			default:
				if(options.re.test(field.get('value'))) this._msgRemove(field, options);
				else this._msgInject(field, options);
		}

		return this;
	},

	_validateChild: function(child, options) {
		var nlButtonGroup = this.form[child.getProperty("name")];
		var cbCheckeds = 0;
		var isValid = true;
 		for(var i = 0; i < nlButtonGroup.length; i++) {
			if(nlButtonGroup[i].checked) {
				cbCheckeds++;
				if(!options.re.test(nlButtonGroup[i].get('value'))) {
					isValid = false;
					break;
				}
			}
		}
		if(cbCheckeds == 0 && options.type == "required") isValid = false;
		if(isValid) this._msgRemove(child, options);
		else this._msgInject(child, options);

		return this;
	},

	_msgInject: function(owner, options) {
		cbErr = owner.retrieve('cbErr');

		// Add it to the stack
		if (!cbErr.contains(options.type)){
			cbErr.push(options.type);
		}
		else {
			return;
		}

		owner.store('cbErr', cbErr);

		// Update the image
		var el = owner.retrieve('validImg');
		el.src = 'images/warning-small.gif';

		// Update the tooltip
		var dual = options.msg.split('::');

		if (dual.length > 1){
			el.store('tip:title', dual[0].trim());
			el.store('tip:text', dual[1].trim());
		}
		else {
			el.store('tip:title', 'Validation');
			el.store('tip:text', dual[0].trim());
		}

		// Change the colour?
		this._chkStatus(owner, options);

		return this;
	},

	_msgRemove: function(owner, options, isReset) {
		isReset = isReset || false;

		// Remove it from the stack
		cbErr = owner.retrieve('cbErr');
		cbErr = $A(cbErr).erase(options.type);
		owner.store('cbErr', cbErr);

		// Don't set it to valid if there are invalid ones
		if (cbErr.length > 0){
			return;
		}

		// Update the image
		var el = owner.retrieve('validImg');
		el.src = 'images/tick-small.gif';

		// Update the tooltip
		//var dual = options.msg.split('::');

		//if (dual.length > 1){
		//	el.store('tip:title', dual[0].trim());
		//}
		//else {
			el.store('tip:title', 'Success');
		//}

		el.store('tip:text', 'Validation passed!');

		// Change the colour?
		this._chkStatus(owner, options);

		return this;
	},

	_injectImg: function(owner){

		var el = owner.retrieve('validImg');

		// Do we already have an image?
		if (el){
			return el;
		}

		msgContainer = new Element('img', {
			"id": owner.getProperty("id") + "_msg",
			"class": this.options.msgClass,
			'src': 'images/tick-small.gif',
			'alt': ''
		});

		var ownParent = owner.getParent();

		if (ownParent && ownParent.hasClass('validation-wrapper')){
			msgContainer.injectAfter(ownParent);
		}
		else {
			msgContainer.injectAfter(owner);
		}

		new Tips(msgContainer, {
			className: 'curo-tool',
			fixed: true
		});

		// Store it
		owner.store('validImg', msgContainer);

		return msgContainer;
	},

	_chkStatus: function(field, options, original) {
		var elms = field.retrieve('cbEffects');

		if (elms){
			elms.cancel();
		}

		if (field.retrieve('cbErr').length == 0){
			field.store('cbEffects', new Fx.Morph(field, {duration: 500, transition: Fx.Transitions.linear}).start(this.options.styleValid));
			this.fireEvent("onValid", [field, options], 50);
		} else {
			field.store('cbEffects', new Fx.Morph(field, {duration: 500, transition: Fx.Transitions.linear}).start(this.options.styleInvalid));
			this.fireEvent("onInvalid", [field, options], 50);
		}

		return this;
	},

	_onSubmit: function(event) {
		var isValid = true;

		this.validations.each(function(array) {
			if(this._isChildType(array[0])) this._validateChild(array[0], array[1]);
			else this._validate(array[0], array[1]);

			if(array[0].retrieve('cbErr').length > 0) isValid = false;
		}.bind(this));

		if (!isValid && event){
			event.stop();

			if (typeof(GUI) != 'undefined' && typeof(GUI.dialog) != 'undefined'){
				if (!this.alert){
					this.alert = new GUI.dialog('validation-alert', 'Please fix all fields marked with <img src="images/warning-small.gif" class="inlineimg" alt="(!)" /> before continuing.', 'warning');
				}
				else {
					this.alert.show();
				}
			}
			else {
				alert('Please fix all fields marked with (!) before continuing.');
			}

		}

		return isValid;
	},

	_onReset: function() {
		this.validations.each(function(array) {
			if(!this._isChildType(array[0])) array[0].setStyles(this.options.styleNeutral);
			array[0].retrieve('cbErr') = 0;
			this._msgRemove(array[0], array[1], true);
		}.bind(this));

		return this;
	}
});
fValidator.implement(new Events); // Implements addEvent(type, fn), fireEvent(type, [args], delay) and removeEvent(type, fn)
fValidator.implement(new Options);// Implements setOptions(defaults, options)