var LW = LW || {};
LW.validation = {
	_hasScrolled: false,
	doScroll:     true,
	_validateByValue: {
		required: function (input) {
			/* handle select boxes */
			if (input.val() == '') {
				LW.validation.highlight(input, 'Required field');
				return false;
			}
			return true;
		},
		minlength: function (input, count) {
			var value = $(input).val();
			if (value != '' && value.length < count) {
				LW.validation.highlight(input, 'Must be at least ' + count + ' characters');
				return false;
			}
			return true;
		},
		maxlength: function (input, count) {
			var value = $(input).val();
			if (value != '' && value.length > count) {
				LW.validation.highlight(input, 'Must be at most ' + count + ' characters');
				return false;
			}
			return true;
		},
		minimum: function (input, min) {
			var value = $(input).val();
			if (value != '' && parseInt(value, 10) < min) {
				LW.validation.highlight(input, 'Must be at least '  + min);
				return false;
			}
			return true;
		},
		maximum: function (input, max) {
			var value = $(input).val();
			if (parseInt(value, 10) > max) {
				LW.validation.highlight(input, 'Must be no more than '  + max);
				return false;
			}
			return true;
		},
		equals: function (input, other) {
			var value = $(input).val();
			/* big caveat - this won't work with multiple input pairs with the same names */
			var form = $(input).get(0).form;
			var otherInput = $(form).find('*[name=' + other + ']');
			var otherValue = otherInput.val();
			if (value != '' && value != otherValue) {
				LW.validation.highlight(input, 'Must match', true);
				LW.validation.highlight(otherInput, 'Must match', true);
				return false;
			}
			return true;
		}
	},
	_validateByType: {
		integer: function (input) {
			var value = $(input).val();
			if (value != '' && value.match(/\D/)) {
				LW.validation.highlight(input, 'Must be an integer value');
				return false;
			}
			return true;
		},
		unsigned: function(input) {
			if( LW.validation._validateByType['integer'](input) == true &&
			    parseInt(input.val(), 10) >= 0 ){
				return true;
			}
			LW.validation.removeHighlight(input);
			LW.validation.highlight(input, 'Must be integer value greater than or equal to 0');
			return false;
		},
		text: function(input) {
			if( input.val() != null &&
			    input.val() != "" ){
				return true;
			}

			LW.validation.highlight(input, 'Must be a non-empty string');
			return false;
		},
		word: function(input) {
			if( input.val() != null &&
			    input.val().match(/^\w+$/) ){
				return true;
			}
			LW.validation.highlight(input, 'Must contain only word characters');
			return false;
		},
		string: function(input) {
			if( LW.validation._validateByType['text'](input) == true &&
			    !input.val().match(/\n/) ){
				return true;
			}
			LW.validation.removeHighlight(input);
			LW.validation.highlight(input, 'Must be a non-empty, single-line string');
			return false;
		},
		dns_rr_name: function(input) {
			var value = $(input).val();

			if( value != null &&
			    ( value.match(/^\*(\.[\w\-]+)*$/)
				  || value == '@'
			      || value.match(/^[\w\-]+(\.[\w\-]+)*$/) ) ) {
				return true;
			}
			LW.validation.highlight(input, 'Must be a relative domain name, a "*", or "@"');
			return false;
		},
		dns_rr_cname_name: function(input) {
			if( input.val() != '@' &&
				LW.validation._validateByType['dns_rr_name'](input) == true
			    ){
				return true;
			}
			LW.validation.removeHighlight(input);
			LW.validation.highlight(input, 'Must be a relative domain name or "*"');
			return false;
		},
		dns_rr_type: function(input) {
			if( LW.dns.RR_TYPE_FIELDS.hasOwnProperty( input.val().toUpperCase() ) ){
				return true;
			}
			LW.validation.highlight(input, 'Must be a supported RR type');
			return false;
		},
		dns_rr_domain: function (input) {
			var value = $(input).val();
			if( value == '@' ) {
				return true;
			}

			var error = new Array();
			if( value.length >= 65 ){
				error.push('fewer than 65 characters and');
			}

			if( LW.validation._validateByType['dns_rr_name'](input) == true ){
				if( error.length == 0 ) {
					return true;
				}
			}
			else {
				LW.validation.removeHighlight(input);
			}

			if( LW.validation._validateByType['domain'](input) == true ){
				if( error.length == 0 ) {
					return true;
				}
			}
			else {
				LW.validation.removeHighlight(input);
				error.push('either a FQDN or relative domain name');
			}

			errstr = 'Must be fewer than 65 characters and either a FQDN, relative domain name or "@"';

			LW.validation.highlight(input, errstr);

			return false;

		},
		domain: function (input) {
			var value = $(input).val();
			if (value == '') {
				return true;
			}
			if (! value.match(/^[a-zA-Z0-9][a-zA-Z0-9\-\.]*\.[a-zA-Z0-9][a-zA-Z0-9\-\.]*$/) ) {
				LW.validation.highlight(input, 'Must be a valid domain name');
				return false;
			}
			if (value.match(/--/) || value.match(/\.\./)) {
				LW.validation.highlight(input, 'Must be a valid domain name');
				return false;
			}
			var parts = value.split('.');
			for (var i=0; i<parts.length; i++) {
				if (parts[i].match(/-$/)) {
					LW.validation.highlight(input, 'Must be a valid domain name');
					return false;
				}
			}
			return LW.validation._validateByValue.maxlength(input, 255);
		},
		email: function (input) {
			var value = $(input).val();
			if (value == '') {
				return true;
			}
			var parts = value.split('@');
			if (parts.length != 2) {
				LW.validation.highlight(input, 'Must be an email address');
				return false;
			}
			parts = parts[1].split('.');
			if (parts.length < 2) {
				LW.validation.highlight(input, 'Must be an email address');
				return false;
			}
			return true;
		},
		phone: function (input) {
			var value = $(input).val();
			if (value != '' && ! value.match(/^[0-9extnsio.,()+ \-]{2,25}$/i)) {
				LW.validation.highlight(input, 'Must be a phone number');
				return false;
			}
			return true;
		},
		port: function (input) {
			var value = $(input).val();
			if (value == '') {
				return true;
			}

			if (value.match(/\D/)) {
				LW.validation.highlight(input, 'Must be a valid port number.');
				return false;
			}

			var port = parseInt(value, 10);
			if (port > 65535 || port < 0) {
				LW.validation.highlight(input, 'Must be a valid port number.');
				return false;
			}
			return true;
		},
		portrange: function (input) {
			var value = $(input).val();
			if (value == '' || value == '*') {
				return true;
			}

			if (value.match(/^\d+$/)) {
				var port = parseInt(value, 10);
				if (port > 65535 || port < 0) {
					LW.validation.highlight(input, 'Must be a port range.');
					return false;
				}
			}
			else {
				var match = value.match(/^(\d+)[:\-](\d+)$/);
				if (match) {
					var start = parseInt(match[1], 10);
					var end   = parseInt(match[2], 10);

					if (start > 65535 || start < 0 || end > 65535 || end < 0 || end <= start) {
						LW.validation.highlight(input, 'Must be a port range');
						return false;
					}
				}
				else {
					// didn't match either regex, no good.
					LW.validation.highlight(input, 'Must be a port range');
					return false;
				}

			}

			return true;
		},
		password: function (input) {
			var value = $(input).val();
			if (value == '') {
				return true;
			}

			if (!value.match(/[$!@#%\^&*()_=+|\\\[\]{}:;,.<>\/?~`'"\-]/)) {
				LW.validation.highlight(input, 'Password must contain at least one special character');
				return false;
			}

			if (value.match(/[\s]/)) {
				LW.validation.highlight(input, 'Password must not contain spaces');
				return false;
			}

			return true;
		},
		ip: function (input) {
			var value = $(input).val();
			if (value == '') {
				return true;
			}
			if (! value.match(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/i)) {
				LW.validation.highlight(input, 'Must be a valid ip address');
				return false;
			}
			var parts = value.split('.');
			if (parts.length != 4) {
				LW.validation.highlight(input, 'Must be a valid ip address');
				return false;
			}
			for (var i=0; i<parts.length; i++) {
				var chunk = parseInt(parts[i], 10);
				if (chunk < 0 || chunk > 255) {
					LW.validation.highlight(input, 'Must be a valid ip address');
					return false;
				}
			}
			return true;
		},
		ip_mask: function (input) {
			var value = $(input).val();
			if (value == '') {
				return true;
			}
			if (! value.match(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?$/i)) {
				LW.validation.highlight(input, 'Must be a valid ip address');
				return false;
			}

			var temp = value;
			var parts = value.split('/');
			/* so the ip check will check only the ip portion */
			input.attr('value', parts[0]);

			var pass = true;
			if (LW.validation._validateByType.ip(input)) {
				if (parts.length == 2) {
					var mask = parseInt(parts[1], 10);
					if (mask > 32 || mask < 1) {
						LW.validation.highlight(input, 'Must be a valid ip mask');
						pass = false;
					}
				}
			}
			else {
				pass = false;
			}
			input.attr('value', temp);

			return pass;
		},
		date_future: function (input) {
			var value = $(input).val();
			if (value == '') {
				return true;
			}

			if (! value.match(/^\d{4}-\d{2}-\d{2}$/i)) {
				LW.validation.highlight(input, 'Must be in YYYY-MM-DD format');
				return false;
			}

			var parts = value.split('-');
			var year  = parseInt(parts[0], 10);
			var month = parseInt(parts[1], 10);
			var day   = parseInt(parts[2], 10);

			var today = new Date();
			var date  = new Date();
			date.setFullYear(year);
			date.setMonth(month - 1);
			date.setDate(day);
			if (date > today) {
				LW.validation.highlight(input, 'Must be a future date');
				return false;
			}

			return true;
		},
		month_year: function (input) {
			var value = $(input).val();
			if (value == '') {
				return true;
			}
			if (! value.match(/^\d{2}\/\d{4}$/i)) {
				LW.validation.highlight(input, 'Must be in MM/YYYY format');
				return false;
			}
			var parts = value.split('/');
			var month = parseInt(parts[0], 10);
			if (month < 1 || month > 12) {
				LW.validation.highlight(input, 'Must be a valid month (1-12)');
				return false;
			}
			return true;
		},
		cc: function (input) {
			var value = $(input).val();
			if (value == '' && value != '*') {
				return true;
			}

			var type = value.substr(0, 1);
			if (! LW.validation._validateCC[type]) {
				LW.validation.highlight(input, 'We only accept Visa, Mastercard, American Express, and Discover');
				return false;
			}
			return LW.validation._validateCC[type](input);
		},
		ccmon: function (input) {
			var value = $(input).val();
			if (value.match(/^[0-9]+$/) && value >= 1 && value <= 12) {
				return true;
			}

			LW.validation.highlight(input, 'Please enter a month from 1 to 12.');
			return false;
		},
		ccyear: function (input) {
			var value = $(input).val();
			if (value.match(/^[0-9]+$/)) {
				value = value.replace(/^0*/, '');
				var year = parseInt(value, 10) + 2000;
				var date = new Date();

				if (year >= date.getFullYear()) {
					return true;
				}
			}

			LW.validation.highlight(input, 'Please enter a valid year (20xx).');
			return false;
		},
		catchall: function (input) {
			var value = $(input).val();
			if (value != '' && value != '*') {
				LW.validation.highlight(input, 'Enter * for all');
				return false;
			}
			return true;
		},
		username: function (input) {
			var value = $(input).val();
			if (!value.match(/^[a-zA-Z0-9_]{2,20}$/)) {
				LW.validation.highlight(input, "Must contain only letters, numbers, and underscores.");
				return false;
			}
			return true;
		},
		mailing_field: function (input) {
			/* doesn't check length, but we have maxlength and minlength for that */
			var value = $(input).val();
			if (!value.match(/^[a-z0-9.,\'\/()<>_ \-]*$/i)) {
				LW.validation.highlight(input, 'Contains invalid characters.');
				return false;
			}
			return true;
		},
		tosagree: function (input) {
			if (!$(input).attr('checked')) {
				LW.validation.highlight(input, "Please indicate you agree with the terms of service.");
				return false;
			}
			return true;
		}
	},
	_validateCC: {
		'X': function (input) {
			/* obfuscated card should just validate length, loosely */
			return LW.validation._validateByValue.minlength(input, 13)
			    && LW.validation._validateByValue.maxlength(input, 16);
		},
		'3': function (input) {
			/* AmEx is 15 digits */
			return LW.validation._validateByValue.minlength(input, 15)
			    && LW.validation._validateByValue.maxlength(input, 15)
			    && LW.validation._validateCC._mod10(input);
		},
		'4': function (input) {
			/* Visa is either 13 or 16 digits */
			return LW.validation._validateByValue.minlength(input, 13)
			    && LW.validation._validateByValue.maxlength(input, 16)
			    && LW.validation._validateCC._mod10(input);
		},
		'5': function (input) {
			/* Mastercard is 16 digits */
			return LW.validation._validateByValue.minlength(input, 16)
			    && LW.validation._validateByValue.maxlength(input, 16)
			    && LW.validation._validateCC._mod10(input);
		},
		'6': function (input) {
			/* Discover is 16 digits */
			return LW.validation._validateByValue.minlength(input, 16)
			    && LW.validation._validateByValue.maxlength(input, 16)
			    && LW.validation._validateCC._mod10(input);
		},
		_mod10: function (input) {
			var value = $(input).val();
			var digits = value.split('').reverse();
			var sum = 0;
			var l = digits.length;
			for (var i=1; i<=l; i++) {
				var digit = parseInt(digits[(i-1)], 10);
				if (i % 2 == 0) {
					var product = 2 * digit;
					var moreDigits = product.toString().split('');
					for (var j=0; j<moreDigits.length; j++) {
						sum += parseInt(moreDigits[j], 10);
					}
				}
				else {
					sum += digit;
				}
			}
			if (sum % 10 != 0) {
				LW.validation.highlight(input, 'Must be a valid card number');
				return false;
			}
			return true;
		}
	},
	_scrollTo: function (input) {
		if (!LW.validation._hasScrolled && LW.validation.doScroll) {
			window.scroll(0, $(input).offset().top - 50);
			LW.validation._hasScrolled = true;
			setTimeout(function () { LW.validation._hasScrolled = false; }, 1000);
		}
	},
	_getOrBuildNote: function (id, el) {
		var note = $('#note_' + id);
		if (! note || note.length == 0) {
			var div = document.createElement('DIV');
			div.className = 'invalid';
			div.id = 'note_' + id;
			note = $(div);
			note.data('_is_auto_generated', true);
			note.insertAfter(el);
		}
		return note;
	},
	removeHighlight: function (input) {
		var el = $(input);
		el.removeClass('invalid');
		el.data('failed', false);

		var note = $('#note_' + el.attr('id'));

		if (note.data('_is_auto_generated')) {
			note.remove();
		} else {
			note.html('');
		}

		$('label[for=' + el.attr('id') + ']').removeClass('invalid');
	},
	removeAllHighlights: function (form) {
		$(form).find('input, select, textarea').each(function () {
			if ($(this).hasClass('invalid')) {
				LW.validation.removeHighlight(this);
			}
		});
	},
	highlight: function (input, message, skipFlag) {
		var el = $(input);
		if (! skipFlag) {
			el.data('failed', true);
			el.data('lastFailure', el.formValue());
		}
		var label = $('label[for=' + el.attr('id') + ']');
		var note;
		if (el.attr('type') == 'checkbox' || el.attr('type') == 'radio') {
			note = LW.validation._getOrBuildNote(label.attr('for'), label);
		}
		else {
			note = LW.validation._getOrBuildNote(el.attr('id'), el);
		}
		note.html((note.html()) ? note.html() + '<br>' + message : message);
		el.addClass('invalid');
		label.addClass('invalid');
	},
	validateInput: function (input, options) {
		var pass = true;

		if ($(input).attr('disabled')) {
			return pass;
		}

		if ($(input).data('failed') && $(input).data('lastFailure') == $(input).formValue()) {
			return false;
		}

		LW.validation.removeHighlight($(input));

		if (options.type) {
			var typePass = false;
			var types = (typeof options.type == 'string') ? [options.type] : options.type;
			for (var i=0; i<types.length; i++) {
				if (LW.validation._validateByType[types[i]]) {
					typePass = typePass || LW.validation._validateByType[types[i]]($(input));
				}
			}
			if (typePass) {
				/* only one validation needs to pass, so de-highlight other failures */
				LW.validation.removeHighlight($(input));
			}
			pass = pass && typePass;
		}

		for (var check in LW.validation._validateByValue) {
			if (options[check] || (typeof options[check] == 'number' && options[check] == 0)) {
				pass = pass && LW.validation._validateByValue[check]($(input), options[check]);
			}
		}

		if (! pass) {
			LW.validation._scrollTo(input);
		}
		else {
			$(input).data('failed', false);
		}
		return pass;
	},
	validateForm: function (selector, fields) {
		var allPass = true;
		var form = $(selector);
		var l = fields.length;
		for (var i=0; i<l; i++) {
			var thisPass = true;
			var field = fields[i];
			if (typeof field == 'string') {
				form.find('*[name=' + field + ']').each( function () {
					var fieldPass = LW.validation.validateInput(this, { required: true });
					thisPass = thisPass && fieldPass;
				});
			}
			else {
				/*
				  passing in field => ['a', 'b', 'c'] generates an OR condition where only one of the fields must pass
				  for cases where those fields are repeated multiple times (think rows of the same inputs)
				  we do the OR condition per 'row' by grabbing all of the 'a' fields and comparing them to all the
				  'b' and 'c' fields at the same index (j)
				*/
				var names = (typeof field.name == 'string') ? [field.name] : field.name;
				form.find('*[name=' + names[0] + ']').each( function (j) {
					var groupPass = false;
					for (var k=0; k<names.length; k++) {
						var fieldPass = LW.validation.validateInput($(form).find('*[name=' + names[k] + ']').get(j), field);
						groupPass = groupPass || fieldPass;
					}
					thisPass = thisPass && groupPass;
				});
			}
			allPass = allPass && thisPass;
		}

		return allPass;
	}
};

jQuery.fn.formValue = jQuery.fn.formValue || function () {
	var elm = $(this).eq(0);

	if (elm.is('input')) {
		switch (elm.attr('type').toLowerCase()) {
			case 'checkbox':
			case 'radio':
				return elm.is(':checked') ? elm.val() : null;
			default:
				return elm.val();
		}
	}

	if (elm.is('select')) {
		var selected = elm.find('option:selected');
		if (selected.size() == 0) {
			return null;
		}
		else {
			return selected.eq(0).val();
		}
	}
	return null;
};

