// main constructor
var Application = function(params) {
	var application = this;

	application.options = {};
	var defaults = {
		tooltipTarget: $('body'),
		form: $('form'),
		onSubmit: function() { application.form.submit(); }
	};
	_.extend(application.options, defaults, params);

	application.validateentries = $('.validate');
	application.validatecollections = $('.validate_collection');
	application.submitbutton = $('#js_submit');
	application.submitmessage = $('.submitmessage');
	application.form = application.options.form;

	application.pairs = {
		password: $('.pair_password'),
		email: $('.pair_email')
	};

	application.rulecollection = [];

	application.validationrules = {
		empty: {
			displaysuccess: true,
			displaydesc: false,
			verfunc: function(entry) { return (entry.val().length > 0); },
			desc: 'This field cannot be blank.'
		},
		empty_startdisabled: {
			defaultEnabled: false,
			verifyOnPageLoad: false,
			displaysuccess: true,
			displaydesc: false,
			verfunc: function(entry) { return (entry.val().length > 0); },
			desc: 'This field cannot be blank.'
		},
		emptymulti: {
			displaysuccess: true,
			displaydesc: false,
			verfunc: function(entry) { return (entry.val() !== null ? true : false); },
			desc: 'You must select at least one.'
		},
		emptyfile: {
			displaysuccess: true,
			displaydesc: false,
			verfunc: function(entry) { return (entry.val().length > 0); },
			desc: 'You must choose a file.',
			change: function(event) {
				if (event.data.rule.validationrule.verfunc(event.data.rule.element)) {
					event.data.rule.tooltip.changemode('success');
					event.data.rule.validatestatus = true;
				} else {
					event.data.rule.tooltip.changemode('failure');
					event.data.rule.validatestatus = false;
				};
			}
		},
		emptyfile_startdisabled: {
			defaultEnabled: false,
			verifyOnPageLoad: false,
			displaysuccess: true,
			displaydesc: false,
			verfunc: function(entry) { return (entry.val().length > 0); },
			desc: 'You must choose a file.',
			change: function(event) {
				if (event.data.rule.validationrule.verfunc(event.data.rule.element)) {
					event.data.rule.tooltip.changemode('success');
					event.data.rule.validatestatus = true;
				} else {
					event.data.rule.tooltip.changemode('failure');
					event.data.rule.validatestatus = false;
				};
			}
		},
		silentempty: {
			displaysuccess: false,
			displaydesc: false,
			verfunc: function(entry) { return (entry.val().length > 0); },
			desc: 'This field cannot be blank.'
		},
		silentempty_startdisabled: {
			defaultEnabled: false,
			verifyOnPageLoad: false,
			displaysuccess: false,
			displaydesc: false,
			verfunc: function(entry) { return (entry.val().length > 0); },
			desc: 'This field cannot be blank.'
		},
		phone: {
			displaysuccess: true,
			displaydesc: true,
			verfunc: function(entry) { return /^([\(]{1}[0-9]{3}[\)]{1}[ |\-]{0,1}|^[0-9]{3}[\-| ])?[0-9]{3}(\-| ){1}[0-9]{4}$/.test(entry.val()); },
			desc: 'Please type a valid phone number. ex: 123-456-7890'
		},
		emailfirst: {
			displaysuccess: true,
			displaydesc: true,
			verfunc: function(entry) { return /^\s*[\w\-\+_]+(\.[\w\-\+_]+)*\@[\w\-\+_]+\.[\w\-\+_]+(\.[\w\-\+_]+)*\s*$/.test(entry.val()); },
			desc: 'Please type a valid email address.',
			onKeyup: function() { application.pairs.email.last().keyup(); },
			onFocusout: function() { application.pairs.email.last().focusout(); }
		},
		email: {
			displaysuccess: true,
			displaydesc: true,
			verfunc: function(entry) {
				var values = [];
				application.pairs.email.each(function() { values.push($(this).val()); });
				return _.all(values, function(value) { return (value == _(values).first() && _(values).first().length > 0); });
			},
			desc: 'Email fields must match.'
		},
		passwordfirst: {
			displaysuccess: true,
			displaydesc: true,
			verfunc: function(entry) { return (entry.val().length >= 6); },
			desc: 'Password must be at least 6 characters.',
			onKeyup: function() { application.pairs.password.last().keyup(); },
			onFocusout: function() { application.pairs.password.last().focusout(); }
		},
		password: {
			displaysuccess: true,
			displaydesc: true,
			verfunc: function(entry) {
				var values = [];
				application.pairs.password.each(function() { values.push($(this).val()); });
				return _.all(values, function(value) { return (value == _(values).first() && _(values).first().length > 0); });
			},
			desc: 'Password fields must match.'
		},
		scrollthrough: {
			displaysuccess: true,
			displaydesc: true,
			verfunc: function(entry) { return (entry.scrollTop() >= (entry.effectiveheight - 25)); },
			desc: 'A complete scroll through is required.',
			scroll: function(event) {
				if (event.data.rule.validationrule.verfunc(event.data.rule.element)) {
					event.data.rule.tooltip.changemode('success');
					event.data.rule.validatestatus = true;
				} else {
					event.data.rule.tooltip.changemode(null);
					event.data.rule.validatestatus = false;
				};
			}
		},
		checked: {
			displaysuccess: true,
			displaydesc: false,
			verfunc: function(entry) { return entry.prop('checked'); },
			desc: 'You must read, understand and agree to these items to continue.',
			click: function(event) {
				if (event.data.rule.validationrule.verfunc(event.data.rule.element)) {
					event.data.rule.tooltip.changemode('success');
					event.data.rule.validatestatus = true;
				} else {
					event.data.rule.tooltip.changemode(null);
					event.data.rule.validatestatus = false;
				};
			}
		},
		authorized: {
			displaysuccess: true,
			displaydesc: false,
			verfunc: function(entry) { return (entry.val().length > 0); },
			notes: [
				{
					// Canadian Filmakers
					verfunc: function(entry) {
						var country = entry.val();

						if (country === 'CA' && application.checkRoles('FilmMaker')) {
							return true;
						} else {
							return false;
						};
					},
					note: 'We are only accepting applications for writers and copy editors in your country.'
				},

				{
					// Great Britian Copy Editors and Filmmakers
					verfunc: function(entry) {
						var country = entry.val();

						if (country === 'GB' && (application.checkRoles('Editor') || application.checkRoles('FilmMaker'))) {
							return true;
						} else {
							return false;
						};
					},
					note: 'We are only accepting applications for writers in your country.'
				}
			],
			desc: 'This field cannot be blank.',
			change: function(event) { event.data.rule.element.trigger('softverify'); }

		},
		expertauthorized: {
			displaysuccess: true,
			displaydesc: false,
			verfunc: function(entry) { return (entry.val().length > 0); },
			desc: 'This field cannot be blank.'
		},
		blogauthorized: {
			displaysuccess: true,
			displaydesc: false,
			verfunc: function(entry) { return (entry.val().length > 0); },
			desc: 'This field cannot be blank.'
		},
		validurl: {
			displaysuccess: true,
			displaydesc: true,
			nonemptyfunc: function(entry){ return (entry.val().length > 0); },
			verfunc: function(entry) { return /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/.test(entry.val()); },
			desc: 'Please enter a valid URL such as http://www.myworkonline.com/samplework'
		}
	};

	application.collectionrules = {
		atleastone: {
			displaydesc: false,
			displaysuccess: false,
			verfunc: function(collection) { application.workauth.trigger('softverify'); return _.any(collection.subelements, function(subel) { return $(subel).prop('checked'); }); },
			desc: 'You must select at least one.'
		},
		atleastthree: {
			displaydesc: false,
			displaysuccess: true,
			verfunc: function(collection) {
				var count = _.select(collection.subelements, function(subel) { return $(subel).val().length > 0; }).length;
				if (count >= 3) {
					return true;
				} else {
					return false;
				};
			},
			desc: 'You must specify at least three.'
		},
		atleastone_b: {
			displaydesc: true,
			displaysuccess: true,
			verfunc: function(collection) {
				return _.any(collection.subelements, function(subel) { return $(subel).val().length > 0; });
			},
			desc: 'You must specify at least one.'
		}
	};

	application.checkRoles = function checkRoles(checkRole) {
		return _.include(application.roleswitches.filter(':checked').map(function() { return $(this).val(); }).get(), checkRole);
	};


	var init = function init() {
		_.each(application.validateentries, function(entry) {
			application.rulecollection.push(new SingleRule(entry));
		});

		_.each(application.validatecollections, function(collection) {
			application.rulecollection.push(new CollectionRule(collection));
		});

		application.submitbutton.click(function(e) {

			e.preventDefault();
			application.submitmessage.addClass('invisible');
			application.repositiontooltips();
			$('.Error').hide();

			var errors = _.select(application.rulecollection, function(rule) {
				if (!rule.enabled) { return false; };
				if (!rule.validatestatus) { return true; };
			});

			var numberoferrors = errors.length;

			if (numberoferrors > 0) {
				application.submitmessage.removeClass('invisible');
				application.repositiontooltips();
				$('body').scrollTo();
				_.each(errors, function(rule) { rule.tooltip.changemode('alert'); });
			} else {
				application.options.onSubmit();
			};

		});
	};

	// ____________________________________________________________________________________________
	// nested constructors

	var ToolTip = function(rule) {

		var tooltip = this;

		this.target = rule.element;

		if (rule.validationrule.notes) {
			this.comptemplate = __.template('<div class="tooltip blank"><p class="desc">[!= dt.desc !]</p><p class="note">empty</p></div><!-- /.tooltip -->');
		} else {
			this.comptemplate = __.template('<div class="tooltip blank"><p class="desc">[!= dt.desc !]</p></div><!-- /.tooltip -->');
		};

		this.element = $(tooltip.comptemplate(rule.validationrule)).appendTo(application.options.tooltipTarget);
		this.note = this.element.find('p.note');


		// methods

		this.showtooltip = function() {
			tooltip.element.css('display', 'block');
		};

		this.hidetooltip = function() {
			tooltip.element.css('display', 'none');
		};

		this.changemode = function(targetmode) {
			tooltip.element.removeClass('success failure blank alert note').addClass(targetmode);
		};

		this.reposition = function() {
			tooltip.top = tooltip.target.position().top;
			tooltip.element.css('top', tooltip.top);
		};

		tooltip.reposition();

	};

	var SingleRule = function(entry) {

		var rule = this;
		this.validatestatus = false;

		this.elementscrollheight = entry.scrollHeight;
		this.entry = entry;
		this.element = $(entry);
		this.element.effectiveheight = this.elementscrollheight - this.element.innerHeight();
		this.element.hadfocus = false;

		this.element.classes = application.classestoarray(this.element);

		var ruleDefaults = {
			defaultEnabled: true,
			verifyOnPageLoad: true
		};
		this.validationrule = _.extend(ruleDefaults, application.findrule(this.element));

		this.enabled = this.validationrule.defaultEnabled;

		this.tooltip = new ToolTip(rule);

		// generic bindings for all inputs

		this.genericfunctions = {
			focusin: function() {
				rule.hadfocus = true;

				if (!rule.validationrule.nonemptyfunc || rule.validationrule.nonemptyfunc(rule.element)) {
					if (rule.validationrule.verfunc(rule.element)) {
						if (rule.validationrule.displaysuccess) {
							if (rule.validationrule.notes) {
								var successnote = _.detect(rule.validationrule.notes, function(note, index, list) { return note.verfunc(rule.element); }, rule.element);

								if (successnote) {
									rule.tooltip.changemode('note');
									rule.tooltip.note.html(successnote.note);
								} else {
									rule.tooltip.changemode('success');
								};
							} else {
								rule.tooltip.changemode('success');
							};
						} else {
							rule.tooltip.changemode('blank');
						};

						rule.validatestatus = true;
					} else {
						if (rule.validationrule.displaydesc) {
							rule.tooltip.changemode(null);
						} else {
							rule.tooltip.changemode('blank');
						};
					};

					if (rule.validationrule.onFocusin) { rule.validationrule.onFocusin(); };
				} else {
					rule.enabled = false;
					rule.tooltip.changemode('blank');
				};
			},
			softverify: function() {
				if (!rule.validationrule.nonemptyfunc || rule.validationrule.nonemptyfunc(rule.element)) {
					if (rule.validationrule.verfunc(rule.element)) {
						if (rule.validationrule.displaysuccess) {
							if (rule.validationrule.notes) {
								var successnote = _.detect(rule.validationrule.notes, function(note, index, list) { return note.verfunc(rule.element); }, rule.element);

								if (successnote) {
									rule.tooltip.changemode('note');
									rule.tooltip.note.html(successnote.note);
								} else {
									rule.tooltip.changemode('success');
								};
							} else {
								rule.tooltip.changemode('success');
							};
						} else {
							rule.tooltip.changemode('blank');
						};

						rule.validatestatus = true;
					} else {
						// soft failure so users don't get the error message when first init
						rule.tooltip.changemode('blank');
						rule.validatestatus = false;
					};

					if (rule.validationrule.onVerify) { rule.validationrule.onVerify(); };
				} else {
					rule.enabled = false;
					rule.tooltip.changemode('blank');
				};
			},
			focusout: function() {
				if (!rule.validationrule.nonemptyfunc || rule.validationrule.nonemptyfunc(rule.element)) {
					if (rule.validationrule.verfunc(rule.element)) {
						if (rule.validationrule.displaysuccess) {
							if (rule.validationrule.notes) {
								var successnote = _.detect(rule.validationrule.notes, function(note, index, list) { return note.verfunc(rule.element); }, rule.element);

								if (successnote) {
									rule.tooltip.changemode('note');
									rule.tooltip.note.html(successnote.note);
								} else {
									rule.tooltip.changemode('success');
								};
							} else {
								rule.tooltip.changemode('success');
							};
						} else {
							rule.tooltip.changemode('blank');
						};

						rule.validatestatus = true;
					} else {
						// hard failure when users leave the form field
						rule.tooltip.changemode('failure');
						rule.validatestatus = false;
					};

					if (rule.validationrule.onFocusout) { rule.validationrule.onFocusout(); };
				} else {
					rule.enabled = false;
					rule.tooltip.changemode('blank');
				};
			},
			hardverify: function() {
				if (!rule.validationrule.nonemptyfunc || rule.validationrule.nonemptyfunc(rule.element)) {
					if (rule.validationrule.verfunc(rule.element)) {
						if (rule.validationrule.displaysuccess) {
							if (rule.validationrule.notes) {
								var successnote = _.detect(rule.validationrule.notes, function(note, index, list) { return note.verfunc(rule.element); }, rule.element);

								if (successnote) {
									rule.tooltip.changemode('note');
									rule.tooltip.note.html(successnote.note);
								} else {
									rule.tooltip.changemode('success');
								};
							} else {
								rule.tooltip.changemode('success');
							};
						} else {
							rule.tooltip.changemode('blank');
						};

						rule.validatestatus = true;
					} else {
						// hard failure when users leave the form field
						rule.tooltip.changemode('alert');
						rule.validatestatus = false;
					};

					if (rule.validationrule.onHardVerify) { rule.validationrule.onHardVerify(); }
				} else {
					rule.enabled = false;
					rule.tooltip.changemode('blank');
				};
			}
		};

		this.element.bind('softverify', (rule.validationrule.softverify || rule.genericfunctions.softverify));
		this.element.bind('hardverify', (rule.validationrule.hardverify || rule.genericfunctions.hardverify));

		if (rule.validationrule.focusin) { this.element.bind('focusin', { rule: rule }, rule.validationrule.focusin); } else { this.element.focusin(rule.genericfunctions.focusin); };
		if (rule.validationrule.focusout) { this.element.bind('focusout', { rule: rule }, rule.validationrule.focusout); } else { this.element.focusout(rule.genericfunctions.focusout); };
		if (rule.validationrule.scroll) { this.element.bind('scroll', { rule: rule }, rule.validationrule.scroll); };
		if (rule.validationrule.click) { this.element.bind('click', { rule: rule }, rule.validationrule.click); };
		if (rule.validationrule.change) { this.element.bind('change', { rule: rule }, rule.validationrule.change); };

		// methods

		this.entry.changevalidatestatus = function(targetrequirement) {
			rule.validatestatus = targetrequirement;
		};

		this.entry.hidetooltip = function() {
			rule.tooltip.hidetooltip();
		};

		this.entry.showtooltip = function() {
			rule.tooltip.showtooltip();
		};

		this.entry.repositiontooltip = function() {
			rule.tooltip.reposition();
		};

		this.verify = function() { rule.element.trigger('softverify'); };
		rule.entry.verifyrule = rule.verify;
		rule.entry.disablerule = function() { rule.enabled = false; };
		rule.entry.enablerule = function() { rule.enabled = true; };

	};

	var CollectionRule = function(collection) {

		var rule = this;
		this.validatestatus = false;
		this.enabled = true;

		this.entry = collection;
		this.element = $(collection);
		this.subelements = this.element.find('.collectionentry');

		this.element.classes = application.classestoarray(this.element);

		var ruleDefaults = {
			defaultEnabled: true,
			verifyOnPageLoad: true
		};
		this.validationrule = _.extend(ruleDefaults, application.findcollectionrule(this.element));

		this.tooltip = new ToolTip(rule);

		// generic bindings for all inputs

		var genericfunctions = {
			standard: function() {
				if (rule.enabled && rule.validationrule.verfunc(rule)) {
					if (rule.validationrule.displaysuccess) {
						rule.tooltip.changemode('success');
					} else {
						rule.tooltip.changemode('blank');
					};

					rule.validatestatus = true;
				} else if (rule.enabled) {
					// hard failure
					rule.tooltip.changemode('failure');
					rule.validatestatus = false;
				} else {
					rule.tooltip.changemode('blank');
					rule.validatestatus = false;
				};
			}
		};

		this.subelements.bind('click', (rule.validationrule.onClick || genericfunctions.standard));
		this.subelements.bind('blur', (rule.validationrule.onBlur || genericfunctions.standard));

		this.verify = function() {
			if (rule.enabled && rule.validationrule.verfunc(rule)) {
				if (rule.validationrule.displaysuccess) {
					rule.tooltip.changemode('success');
				} else {
					rule.tooltip.changemode('blank');
				};

				rule.validatestatus = true;
			} else if (rule.enabled) {
				// soft failure so users don't get the error message as they type
				if (rule.validationrule.displaydesc) {
					rule.tooltip.changemode(null);
				} else {
					rule.tooltip.changemode('blank');
				};

				rule.validatestatus = false;
			} else {
				rule.tooltip.changemode('blank');
			};
		};

		rule.entry.verifyrule = rule.verify;

		rule.entry.disablerule = function() {
			rule.enabled = false;
			rule.tooltip.changemode('blank');
			rule.validatestatus = false;
		};

		rule.entry.enablerule = function() {
			rule.enabled = true;
			rule.validatestatus = true;
		};
	};

	// ____________________________________________________________________________________________
	// global methods


	application.findrule = function(item) {

		var ruleclass = _.detect(item.classes, function(clss) { return /rule_/.test(clss) }).replace(/rule_/, '');
		return application.validationrules[ruleclass];

	};

	application.findcollectionrule = function(item) {

		var ruleclass = _.detect(item.classes, function(clss) { return /rule_/.test(clss) }).replace(/rule_/, '');
		return application.collectionrules[ruleclass];

	};

	application.classestoarray = function(item) {

		return item.attr('class').split(' ');

	};

	application.repositiontooltips = function() {

		_.each(application.rulecollection, function(entry) { entry.tooltip.reposition() });

	};

	// ____________________________________________________________________________________________

	init();
};

