$(function(){
    
    $.fn.extend({
	
	ttValidate: function(options){
	    
	    var form = $(this)[0];
	    var settings = $.extend( true, {}, options );
	    
	    $.each(settings, function(el, opts){
		
		var elem = $('#'+el);
				
		$(elem).focus(function(){
		    
		    var tip = ($(this).hasClass('invalid')) ? (typeof opts.resp_tip != 'undefined')?opts.resp_tip:opts.error_tip : opts.info_tip;
		    
		    var isError = false;
		    var tip = opts.info_tip;
		    if($(this).hasClass('invalid')){
			isError = true;
			tip = (typeof opts.resp_tip != 'undefined') ? opts.resp_tip : opts.error_tip;
		    }
		    
		    $.fn.ttShow(elem, tip, isError);
		    
		}).blur(function(){
		    $('#tt-show').remove();
		    
		    // if has resp_tip - kill it
		    if(typeof opts.resp_tip != 'undefined'){
			delete opts.resp_tip;
		    }
		    
		    // if any other element has equalTo this one, and it has a value - trigger blur on it
		    $.each(settings, function(e, o){
			if(o.equalTo != 'undefined' && o.equalTo == el){
			    if($('#'+e).val().length){
				$('#'+e).trigger('blur');
			    }
			}
		    });
		    
		    var valid;
		    
		    // if opts.equalTo - check if $(opts.equalTo).hasClass('invalid') - if so - valid = false
		    if(opts.equalTo != 'undefined' && $('#'+opts.equalTo).hasClass('invalid')){
			valid = false;
		    }else{
			valid = $.fn.ttValidator(elem.val(), opts);
		    }
		    
		    if(valid === false){
			$(this).addClass('invalid'); // should add red glow
			$(this).removeClass('normal');
		    }else{
			$(this).removeClass('invalid');
			$(this).addClass('normal');
		    }
		    
		});
		
	    });
	    
	    $('.invalid').live('keyup',function(){
		var elemId = $(this).attr('id');
		var opts = settings[elemId];
		
		if(typeof opts == 'undefined'){
		    return false;
		}
		
		// if any other element has equalTo this one, and it has a value - trigger blur on it
		$.each(settings, function(e, o){
		    if(o.equalTo != 'undefined' && o.equalTo == elemId){
			if($('#'+e).val().length){
			    $('#'+e).trigger('blur');
			}
		    }
		});
		
		var valid;
		
		// if opts.equalTo - check if $(opts.equalTo).hasClass('invalid') - if so - valid = false
		if(opts.equalTo != 'undefined' && $('#'+opts.equalTo).hasClass('invalid')){
		    valid = false;
		}else{
		    valid = $.fn.ttValidator($(this).val(), opts);
		}
		
		if(valid === true){
		    if($(this).val().length){
			$(this).removeClass('invalid');
			$(this).addClass('normal');
			
			$('#tt-show').remove();
		    }
		    
		    // toggle to show info_tip (if applicable)
		    if(typeof opts.info_tip != 'undefined'){
			$('#tt-show').remove();
			
			$.fn.ttShow($(this), opts.info_tip, false);
		    }
		}
		
	    });
	    
	    // catch/handle submit
	    $(form).bind("submit", function(e){
		
		var callback;
		
		$('#tt-show').remove();
		
		$.each(settings, function(el, opts){
		    if(typeof opts.success == 'function' && typeof callback == 'undefined'){
			callback = opts.success;
		    }
		    
		    var elem = $('#'+el);
		    
		    // validate element
		    var valid = $.fn.ttValidator(elem.val(), opts);
		    
		    if(valid === false){
			elem.addClass('invalid');
			elem.removeClass('normal');
		    }
		    
		});
		
		if($(this).find('.invalid').length){
		    $(this).find('.invalid:first').focus();
		    e.preventDefault();
		    return false;
		}
		
		if(typeof callback == 'function'){
		    callback.apply(this);
		    return false;
		}else{
		    return true;
		}
		
	    });
	    
	},
	
	ttMethods: {
	    email: function(emailAddress){
		var pattern = new RegExp(/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i);
		return pattern.test(emailAddress);
	    },
	    password: function(passValue){
		var options = {
		    length: [8, Infinity], // must be min 8 characters
		    alpha: 1, // must contain min 1 alpha character
		    custom: /[\W(0-9)_]/gi, // must contain min 1 non-alpha character
		    badWords: []
		}
		
		if($('#email').length){
		    if($('#email').val().length){
			var email = $('#email').val();
			emailArray = email.split('@');
			options.badWords = [emailArray[0]]; // cannot be username portion of email
		    }
		}
		
		return $.fn.validatePassword(passValue, options);
	    },
	    equalTo: function(value, equalVal){
		return $.trim(value) == $.trim(equalVal);
	    }
	},
	
	ttShow: function(elem, tip, isError){
	    
	    if(typeof tip != 'undefined'){
		var error = '';
		
		if(isError === true){
		    error = "-error";
		}
		
		var lines = Math.ceil((parseFloat(tip.length) / 40));
		var height = lines * 12;
		var pos = $(elem).offset();
		//console.log($(elem).height());
		
		//var top = pos.top - $(elem).height() - (height + 42);
		var top = pos.top - 14 - (height + 42);
		var div = '<div class="tooltip-container" id="tt-show" style="display:block;position:absolute;top:'+top+'px;left:'+pos.left+'px;"><div class="tooltip'+error+'" style="display:block;height:'+height+'px;">'+tip+'</div><div class="tooltip-bottom'+error+'" style="display:block;"><span style="display: none;">&nbsp;</span></div></div>';
		$('body').append(div);
		
	    }
	    
	},
	
	ttValidator: function(value, opts){
	    var valid = true;
	    
	    if(opts.required === true && typeof value != 'undefined' && value.length > 0){
		
		if(typeof $.fn.ttMethods[opts.method] == 'function'){
		    var args = [value];
		    if(typeof opts.equalTo != 'undefined'){
			args.push($('#'+opts.equalTo).val());
		    }
		    if(!($.fn.ttMethods[opts.method].apply(this,args))){
			valid = false;
		    }
		}
		
		if(valid === true && typeof opts.remote == 'object'){
		    var params = {};
		    if(typeof opts.remote.data === 'object'){
			for(var d in opts.remote.data){
			    params[d] = opts.remote.data[d];
			}
		    }
		    
		    $.ajax({
			url: (typeof opts.remote.url == 'undefined') ? '' : opts.remote.url,
			type: (typeof opts.remote.type == 'undefined') ? 'POST' : opts.remote.type,
			data: params,
			async: false,
			success: function(response){
			    response = $.trim(response);
			    if(response != 'true'){
				opts.resp_tip = response;
			    }
			    
			    valid = (response === 'true');
			}
		    });
		}
		
	    }else if(opts.required === true && (typeof value == 'undefined' || value.length == 0)){
		valid = false;
	    }
	    
	    return valid;
	},
	
	validatePassword: function(pw, options) {
		// default options (allows any password)
		var o = {
			lower:    0,
			upper:    0,
			alpha:    0, /* lower + upper */
			numeric:  0,
			special:  0,
			length:   [0, Infinity],
			custom:   [ /* regexes and/or functions */ ],
			badWords: [],
			badSequenceLength: 0,
			noQwertySequences: false,
			noSequential:      false
		};
	
		for (var property in options){
			o[property] = options[property];
		}
	
		var	re = {
				lower:   /[a-z]/g,
				upper:   /[A-Z]/g,
				alpha:   /[A-Z]/gi,
				numeric: /[0-9]/g,
				special: /[\W_]/g
			},
			rule, i;
	
		// enforce min/max length
		if (pw.length < o.length[0] || pw.length > o.length[1]){
		    //console.log('min/max length');
		    return false;
		}
	
		// enforce lower/upper/alpha/numeric/special rules
		for (rule in re) {
			if ((pw.match(re[rule]) || []).length < o[rule]){
			    //console.log('lower/upper/alpha/numeric/special rules');
			    return false;
			}
		}
	
		// enforce word ban (case insensitive)
		for (i = 0; i < o.badWords.length; i++) {
			if (pw.toLowerCase().indexOf(o.badWords[i].toLowerCase()) > -1){
			    //console.log('word ban');
			    return false;
			}
		}
	
		// enforce the no sequential, identical characters rule
		if (o.noSequential && /([\S\s])\1/.test(pw)){
		    //console.log('no sequential');
		    return false;
		}
	
		// enforce alphanumeric/qwerty sequence ban rules
		if (o.badSequenceLength) {
			var	lower   = "abcdefghijklmnopqrstuvwxyz",
				upper   = lower.toUpperCase(),
				numbers = "0123456789",
				qwerty  = "qwertyuiopasdfghjklzxcvbnm",
				start   = o.badSequenceLength - 1,
				seq     = "_" + pw.slice(0, start);
			for (i = start; i < pw.length; i++) {
				seq = seq.slice(1) + pw.charAt(i);
				if (
					lower.indexOf(seq)   > -1 ||
					upper.indexOf(seq)   > -1 ||
					numbers.indexOf(seq) > -1 ||
					(o.noQwertySequences && qwerty.indexOf(seq) > -1)
				) {
				    //console.log('alphanumeric/qwerty seq');
				    return false;
				}
			}
		}
	
		// enforce custom regex/function rules
		for (i = 0; i < o.custom.length; i++) {
			rule = o.custom[i];
			if (rule instanceof RegExp) {
				if (!rule.test(pw)){
				    //console.log('custom regex');
				    return false;
				}
			} else if (rule instanceof Function) {
				if (!rule(pw)){
				    //console.log('custom regex function');
				    return false;
				}
			}
		}
		
		return true;
	}
	
    });
    
});


