var AlertNotifier = {
	notify: function(errors) {
		var msg;
		if(typeof errors == "object") {
			msg = "";
			if (errors instanceof Array) {
				for(var i=0; i<errors.length; i++) msg += "\n" + errors[i];
			} else {
				for(var err in errors) msg += "\n" + errors[err];
			}
			if(msg) msg = msg.substring(1);
		} else {
			msg = errors;
		}
		if(msg != null && msg != "") {
			alert(msg);
		}
	}
};

var UnorderedListNotifier = ClassUtils.create();
UnorderedListNotifier.prototype = {
	initialize: function(container) {
		this.container = container;
	},
	notify: function(errors) {
		this.clear();
		var container = $(this.container);
		var size = HashUtils.size(errors);
		if(size == 1) {
			for(var err in errors) {
				container.append(this._createHeader(errors[err]));
			}
		} else if(size > 1) {
			var group = this._createGroup(errors);
			if(group) {
				container.append(this._createHeader());
				container.append(group);
			}
		}
	},
	begin: function() {
		// TODO Show some loading notification
		this.clear();
	},
	complete: function() {
		// TODO Hide loading notification
	},
	clear: function() {
		$(this.container).html("");
	},
	_createHeader: function(msg) {
		if(arguments.length < 1) msg = "The following errors have occured:";
		var header = document.createElement("h3");
		header.innerHTML = msg;
		return header;
	},
	_createGroup: function(errors) {
		var group;
		if(errors == null || HashUtils.isEmpty(errors)) {
			group = null;
		} else {
			group = document.createElement("ul");
			for(var err in errors) {
				group.appendChild(this._createElement(errors[err]));
			}
		}
		return group;
	},
	_createElement: function(msg) {
		var elem = document.createElement("li");
		elem.innerHTML = msg;
		return elem;
	}
};

var ModalNotifier = ClassUtils.create(UnorderedListNotifier);
ClassUtils.extend(ModalNotifier.prototype, {
	initialize: function(dialog, container) {
		this._super("initialize", container);
		this.dialog = dialog;
	},
	notify: function(errors) {
		this._super("notify", errors);
		this.errored = !HashUtils.isEmpty(errors);
		if(this.errored) this.showModal();
	},
	clear: function() {
		this._super("clear");
		this.errored = false;
	},
	complete: function() {
		this._super("complete");
		if(this.errored) this.showModal();
	},
	showModal: function() {
		$(this.dialog).jqmShow();
	},
	hideModal: function() {
		$(this.dialog).jqmHide();
	}
});

var Validator = ClassUtils.create();
Validator.prototype = {
	initialize: function(formSelector, validationNotifier, submitOptions) {
		this.validators = [];
		this.notifier = validationNotifier;
		var _this = this;
		if(!submitOptions) submitOptions = {};
		$(document).ready(function() {
			_this.jform = $(formSelector);
			_this.jform.attr("action",_this.jform.attr("action"));
			_this.jform.ajaxForm(ClassUtils.extend({
				dataType: "json",
				resetForm: false,
				beforeSubmit: function(formArray, jqForm) {
					var pass = _this.validate(jqForm[0]);
					if (!pass) jqForm.removeClass("submitting");
					if (!pass && submitOptions.onError) submitOptions.onError.apply(window, []);
					return  pass;
				},
				success: function(data) {
					_this.jform.removeClass("submitting");
					if(data.success) {
						if(data.redirect) location.href = data.redirect;
						else if (data.message) {
							var succ = {};
							succ[getElementId(_this.jform[0])] = data.message;
							_this.notifier.notify(succ);
						}
						if (submitOptions.onSuccess) submitOptions.onSuccess.apply(window, []);
					} else {
						var err = {};
						err[getElementId(_this.jform[0])] = data.message;
						_this.notifier.notify(err);
						if (submitOptions.onError) submitOptions.onError.apply(window, []);
					}
				}
			}, submitOptions));
			_this.jform.defaultAjaxForm = _this.jform.ajaxForm;
		});
	},
	completeValidation: function(invalidElems, noshow) {
		var valid = HashUtils.isEmpty(invalidElems);
		if(this.notifier && !noshow) {
			this.notifier.notify(invalidElems);
			if(this.notifier.complete) this.notifier.complete();
		}
		return valid;
	},
	validate: function(form, noshow) {
		if(this.notifier && this.notifier.begin && !noshow) this.notifier.begin();
		return this.completeValidation(this.getInvalidElemIds(form), noshow);
	},
	validateAsync: function(form, callback) {
		if(this.notifier && this.notifier.begin) this.notifier.begin();

		this.asyncs = 0;
		this.invalidAsyncs = {};
		var _this = this;
		var asyncsCallback = function(invalidElems) {
			HashUtils.addAll(_this.invalidAsyncs, invalidElems);
			if(--_this.asyncs == 0) {
				var valid = _this.completeValidation(_this.invalidAsyncs);
				callback.apply(window, [valid]);
			}
		}
		
		for (var i=0; i<this.validators.length; i++) {
			if(!this.validators[i].async) continue;
			if(!this.validators[i].validate(form, asyncsCallback)) this.asyncs++;
		}
		return (this.asyncs == 0);
	},
	validateElem: function(elem, noshow) {
		if(this.notifier && this.notifier.begin && !noshow) this.notifier.begin();
		
		var jelem = $(elem);
		var form = jelem.parents("form")[0];
		var invalidElems = this.getInvalidElemIds(form);
		var valid;
		if(invalidElems && invalidElems[jelem[0].id]) {
			if(this.notifier && !noshow) {
				var id = jelem[0].id;
				var invalidElem = {};
				invalidElem[id] = invalidElems[id];
				this.notifier.notify(invalidElem);
				if(this.notifier.complete) this.notifier.complete();
			}
			valid = false;
		} else {
			valid = true;
		}
		return valid;
	},
	getInvalidElemIds: function(form) {
		var invalidElems = {};
		for (var i=0; i<this.validators.length; i++) {
			if(this.validators[i].async) continue;
			var result = this.validators[i].validate(form);
			if(result != null) {
				for(var elemId in result) {
					invalidElems[elemId] = result[elemId];
				}
			}
		}
		return invalidElems;
	},
	register: function(validator) {
		this.validators[this.validators.length] = validator;
	}
};

var Validation = ClassUtils.create();
Validation.prototype = {
	async: false,
	initialize: function() {},
	validate: function(form) { return null; }
}

var ProfanityValidation = ClassUtils.create(Validation);
ClassUtils.extend(ProfanityValidation.prototype, {
	async: true,
	initialize: function(className) { this.className = className; },
	/*
	 * @return Initially returns false if there are phrases to check (not valid
	 * 		pending the result), and true if there are not any phrases to check
	 * 		(valid).
	 */ 
	validate: function(form, callback) {
		var phrases = [];
		var elems = $(form).find("." + this.className);
		elems.each(function(pos) {
			if(this.value) phrases[phrases.length] = this.value;
		});
		if (phrases.length > 0) {
			var _this = this;
			$.post(APP_CONTEXT + "/textAction/check.do", {phrases:phrases}, function(checkData) {
				var invalidElems = {};
				if(!checkData.success) {
					elems.each(function(pos, elem) {
							for(var i=0; i<checkData.phrases; i++) {
								if (this.value == checkData.phrases[i]) {
									invalidElems[getElementId(this)] = _this.getMessage(this);
								}
							}
					});
					if(HashUtils.isEmpty(invalidElems)) invalidElems[form] = _this.getMessage(form);
				}
				callback.apply(window, [invalidElems]);
			}, "json");
		} else return true;
	},
	getMessage: function(elem) {
		return "You may not use innappropriate language";
	}
});

var ClassValidation = ClassUtils.create(Validation);
ClassUtils.extend(ClassValidation.prototype, {
	initialize: function(className) { this.className = className; },
	validate: function(form) {
		var invalidElems = {};
		var elems = $(form).find("." + this.className);
		var _this = this;
		elems.each(function(elem) {
			if(!_this.validateElem(this)) {
				invalidElems[getElementId(this)] = _this.getMessage(this);
			}
		});
		return invalidElems;
	},
	validateElem: function(elem) { return true; }
});

var RequiredValidation = ClassUtils.create(ClassValidation);
ClassUtils.extend(RequiredValidation.prototype, {
	validateElem: function(elem) { return (elem && elem.value); },
	getMessage: function(elem) { return "'" + elem.title + "' is a required field"; }
});

var DateValidation = ClassUtils.create(ClassValidation);
ClassUtils.extend(DateValidation.prototype, {
	validateElem: function(elem) {
		if(elem && elem.value) return DateUtils.isValid(elem.value);
		else return true;
	},
	getMessage: function(elem) { return "You have entered an invalid date for '" + elem.title + "'"; }
});

/*
 * Determines if the length is within a given range. Elements with the class for
 * the length validation check to see if there is a min and/or max attribute and
 * ensures the value is within that range.
 * Example: <input class="validate_length min10 max20" />
 */
var LengthValidation = ClassUtils.create(RequiredValidation);
ClassUtils.extend(LengthValidation.prototype, {
	validateElem: function(elem) {
		return !(this.isLessThan(elem) || this.isGreaterThan(elem));
	},
	getMessage: function(elem) {
		if(this.isLessThan(elem)) {
			var min = this._getCount(elem, "min");
			if(min == 1) {
				return this._super("getMessage", elem);
			} else {
				return "'" + elem.title + "' must be at least " + min + " characters";
			}
		} else {
			var max = this._getCount(elem, "max");
			return "'" + elem.title + "' must not exceed " + max + " characters";
		}
	},
	isLessThan: function(elem) {
		var min = this._getCount(elem, "min");
		return (elem.value.length < min);
	},
	isGreaterThan: function(elem) {
		var max = this._getCount(elem, "max");
		return (max > -1 && elem.value.length > max);
	},
	_getCount: function(elem, prefix) {
		var regex = new RegExp("(^|\\s)" + prefix + "(\\d+)(\\s|$)");
		if(regex.test(elem.className)) {
			return eval(regex.exec(elem.className)[2]);
		} else {
			return -1;
		}
	}
});

var MatchValidation = ClassUtils.create(ClassValidation);
ClassUtils.extend(MatchValidation.prototype, {
	validateElem: function(elem) {
		return this.isEqual(elem);
	},
	getMessage: function(elem) {
		var matchElem = this._getElem(elem, "eq");
		if(matchElem == null) {
			return "Could not find the element that '" + elem.title + "' should be matched against.";
		} else {
			return "'" + elem.title + "' does not match '" + matchElem.title + "'";
		}
	},
	isEqual: function(elem) {
		var matchElem = this._getElem(elem, "eq");
		return (matchElem == null) ? false : (elem.value == matchElem.value);
	},
	_getElem: function(elem, prefix) {
		var regex = new RegExp("(^|\\s)" + prefix + "_([^\\s]+)");
		if(regex.test(elem.className)) {
			var id = regex.exec(elem.className)[2];
			return $("#" + id)[0];
		} else {
			return null;
		}
	}
});

var ZipCodeValidation = ClassUtils.create(ClassValidation);
ClassUtils.extend(ZipCodeValidation.prototype, {
	validateElem: function(elem) {
		if(elem && elem.value) return /^\d{5}$/.test(elem.value);
		else return true;
	},
	getMessage: function(elem) { return "'" + elem.title + "' must be five digits long"; }
});

var EmailValidation = ClassUtils.create(ClassValidation);
ClassUtils.extend(EmailValidation.prototype, {
	validateElem: function(elem) {
		if(elem && elem.value) return this.validateEmail(elem.value);
	    else return true;
	},
	validateEmail: function(email) {
		return /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(email);
	},
	getMessage: function(elem) { return "You have entered an invalid email address for '" + elem.title + "'"; }
});
var MultiEmailValidation = ClassUtils.create(EmailValidation);
ClassUtils.extend(MultiEmailValidation.prototype, {
	validateElem: function(elem) {
		if(elem && elem.value) {
			var emails = elem.value.replace(/(\s*,\s*)|[\s;]+/g,",").split(",");
			for(var i=0; i<emails.length; i++) {
				if(!this.validateEmail(emails[i])) return false;
			}
		}
		return true;
		
	}
});

var PhoneValidation = ClassUtils.create(ClassValidation);
ClassUtils.extend(PhoneValidation.prototype, {
	validateElem: function(elem) {
		if(elem && elem.value) return this.validatePhone(elem.value);
	    else return true;
	},
	validatePhone: function(phone) {
		if(!phone) return true;
		phone += "";
		var length = phone.replace(/[^\d]/g, "").length;
		if(length == 11) return /^1/.test(phone);
		else return (length == 10);
	},
	getMessage: function(elem) { return "You have entered an invalid phone number for '" + elem.title + "'"; }
});

var GroupValidation = ClassUtils.create(Validation);
ClassUtils.extend(GroupValidation.prototype, {
	initialize: function(container, groupName, min, max) {
		this.container = container;
		this.groupName = groupName;
		this.min = min;
		this.max = max;
	},
	validate: function(form) {
		var checked = this.countChecked(form);
		var msg;
		var name = $(this.container)[0].title;
		if (this.min && checked < this.min) {
			if (this.min == 1) msg = "You have not selected any " + name;
			else msg = "You must select at least " + this.min + " " + name;
		} else if (this.max && checked > this.max) {
			msg = "You may only select up to " + this.max + " " + name;
		} else {
			msg = null;
		}
		if (msg) {
			var obj = {};
			obj[getElementId($(this.container)[0])] = msg;
			return obj;
		} else {
			return null;
		}
	},
	countChecked: function(form) {
		// Does not work in IE6
		//var items = form[this.groupName];
		var items = $(form).find("input[name='" + this.groupName + "']");
		var cnt = 0;
		if (items != null) {
			for(var i=0; i<items.length; i++) {
				if (items[i].checked) cnt++;
			}
		}
		return cnt;
	}
});

/** Retrieves an element ID if one exists; otherwise, it creates a new one */
function getElementId(elem) {
	if(!elem.id) {
		elem.id = "guid_" + Math.round(Math.random((new Date()).getTime()) * 10000000);
	}
	return elem.id;
}
