(function($) {
 
   $.fn.validate = function(settings) {
     	var config = {
						//on my todo is a multidimentional array of rules
						'rules' : {},
						//highlight error fields?
						'highlight' : true,
						//style the element
						'highlightStyle' : { 'border':'1px solid red' },
						//reset the highlight to the original colour
						'highlightReset' : { 'border':'1px solid #333333' },
						//as a standard we have single errors under, by the side or as a pop up, 
						//the other option would be to have a ul or ol with li errors grouped together 
						'singleErrors': true,
						//one message allows you to set a message that covers all errors, 
						//ie 'This ##field## is required and needs to be a valid number', please set this message in the custom error array
						'singleMessage': false,
						//these will be inserted into the error messages text, use the ID for the key and then the text for the ##field## replace
						'fieldNames' : {},
						//fieldErrors, id for the key and specify the message like this 'Your message for ##field##'
						'fieldErrors' : {},
						//if you want to turn first letter caps off on your own messages then put false
						'fieldErrorsCap' : true,
						// this gets the next element and gives
						'errorElement' : 'li',
						//if we are letting the system and call backs set the message then we need to have a seperator for each section it fails
						'appendErrorSpacer' : '<br />',
						//set errors to zero, this allows the user to tell validation that other custom validation failed.
						'errors' : 0,
						//Ok, so if you want to do ajax, let the user input there own function, we will allow just a true false on ajax,
						//they will supply us with a url and we will send the form, more advanced options will need to called by their own function
						'ajax' : true,
						//ajax url
						'url' : '',
						//ajax callback function
						'ajax_callback': '',
						//if we have errors and we want to add another call back to may be alert the user, or unhide a master message then this
						//is the place to do it.
						'errorCallback': '',
						//validation variables
						'numb' : '0123456789',
						'lwr' : 'abcdefghijklmnopqrstuvwxyz',
						'upr' : 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
						'digits' : '0123456789',
						'phoneNumberDelimiters' : '()- ',
						'validWorldPhoneChars' : '()- +',
						'minDigitsInIPhoneNumber' : 10
					};
		//ok, load our settings and let the user overide
     	if (settings) $.extend(config, settings);
		//use this section to do any calls that might be needed to get the plugin ready 
		$(document).ready(function()
		{	
			//first we need to call initialise which will setup the error output
			init(config);			
		});		
		this.each(function(){
		    //add the validation functions to the form submit action			
			$(this).submit(function(){			
				//reset validation needs to occur every time they submit to make sure old messages are cleared
				resetValidation(this, config);
				//return false;
				//this is the actual function that loops the fields and validation calls
				if(validateForm(this, config))
				{
					return true;
				}  
				else
				{
					//just because it returns false it does not mean it failed! if its using ajax then its exactly what we want it to do
					return false;
				}
			});
		});	
	};
	//This is where all the validation happens, oh and all the other fun stuff
	function validateForm(form, config)
	{
		
		//ok so lets loop our elements
		$("form#" + $(form).attr('id')).find("input, textarea, select").each(function()
		{
			var classes = $(this).attr('class');
			var class_loop = classes.split(' ');
			
			var parent = this;
			
			var id = $(this).attr('id');
			var name = $(this).attr('name');
			
			jQuery.each(class_loop, function(key, val) 
			{
				//check that the class is actually a validation request and not styling
				if(val != "" && val != " " && eval("typeof " + val + " == 'function'"))
				{
					
					var resultMessage = eval(val + "('" + $(parent).val() + "', config)");
					if(resultMessage == "")
					{
						// all clear, carry on.
					}
					else
					{
						if(config['singleErrors'])
						{
							var capsName = true;
							
							if(config['fieldErrors'][id] != undefined)
							{
									//ok, this is where we set the error to reveal to the user from the custom messages supplied
									resultMessage = config['fieldErrors'][id];
									if(!config['fieldErrorsCap'])
									{
										capsName = false;
									}
							}
															
							if(config['fieldNames'][id] != undefined)
							{
								//ok, use the user specified name to be used in this case
								var finalName = config['fieldNames'][id];
							}
							else
							{
								//else use the name attr to proccess and use: eg first_name =  first name
								var finalName = name.toLowerCase();
								finalName = name.replace('_',' ');
							}
							
							if(capsName)
							{
								//prep the field name so that it has a caps for the first letter
								finalName = finalName.substr(0, 1).toUpperCase() + finalName.substr(1);
							}	
							
							//replace the field holder with the field
							var finalMessage = resultMessage.replace('##field##',finalName);
							//what we do here is test to see if we are using one message or adding them together in one field
							if(config['fieldErrors'][id] != undefined)
							{
								if(config['errorElement'] == "li")
								{
									$(parent).parent().next(config['errorElement'] + ".errors").html(finalMessage);
								}
								else
								{
									$(parent).next(config['errorElement'] + ".errors").html(finalMessage);
								}	
							}
							else
							{
								if(config['errorElement'] == "li")
								{
									$(parent).parent().next(config['errorElement'] + ".errors").append(finalMessage + config['appendErrorSpacer']);	
								}
								else
								{
									$(parent).next(config['errorElement'] + ".errors").append(finalMessage + config['appendErrorSpacer']);	
								}
								
							}
							
							if(config['errorElement'] == "li")
							{
								//show the error message
								$(parent).parent().next(config['errorElement'] + ".errors").show();
							}
							else
							{
								//show the error message
								$(parent).next(config['errorElement'] + ".errors").show();	
							}
							
						}
						else
						{
							//this one you will have to add later alex, here you will add li's to either the ol or ul
						}
						
						if(config['highlight'])
						{
							$(parent).css(config['highlightStyle']);
						}
						config['errors']++;
					}
				}
			});
		});
		
		if(config['errors'] == 0)
		{
			var param=$("form#" + $(form).attr('id')).find("input, textarea, select").serialize();
			if(config['ajax'] == true && config['url'] != '' && config['ajax_callback'] != '')
			{
				//do the ajax call
				$.post(config['url'], param, function(data)
				{
					if(eval("typeof " + config['ajax_callback'] + " == 'function'"))
					{
						//run custom callback function
						eval(config['ajax_callback'] + "(data)");
					}
				});
				
			}
			else
			{
				if(eval("typeof " + config['ajax'] + " == 'function'"))
				{
					//run custom callback function
					return eval(config['ajax'] + "(param)");
					return false;
				}
				else
				{
					//else let the form submit the good old fashioned way
					return true;
				}	
			}
			return false;
		}
		else
		{
			//hey we have errors, lets tell em and not submit the form
			if(config['errorCallback'] != "" && eval("typeof " + config['errorCallback'] + " == 'function'"))
			{
				eval(config['errorCallback'] + "()")
			}
			return false;
		}		
	}
	//this where we make sure that we reset the form errors elements for another sweep
	function resetValidation(form, config)
	{
		config['errors'] = 0;
		if(config['singleErrors'])
		{
			$(config['errorElement'] + ".errors").hide().html('');
		}
		else
		{
			//this one you will have to add later alex, here you will reset the error ul or ol
		}

		if(config['highlight'])
		{
			$("form#" + $(form).attr('id')).find("input, textarea, select").css(config['highlightReset']);
		}
		
	}
	//this where we make sure that any stuff related to the errors is hidden
	function init(config)
	{
		//this is where we make sure all our errors are hidden for the start of the page
		$(config['errorElement'] + ".errors").hide();
	}
	/*
	 *
	 *			This is the place to put any and all validation functions
	 *
	 */
	//checks the field is not blank
	function required(val, config)
	{
		val = trim(val);
		if(val == "")
		{
			return "'##field##' is a required field.";
		}
		else
		{
			return "";
		}
	}
	//validates email addresses
	function validateEmail(email, config)
	{
	   var reg = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;
	   if(reg.test(email) == false)
	   {
			return "'##field##' is not a valid email.";
	   }
		return "";
	}
	//validate the post code
	function validatePostCode(pCode, config)
	{
		if (isValidPostcode(pCode)==false)
		{
			return "'##field##' is not a valid post code.";
		}
		return "";
	}
	//check that the field is alpha
	function isAlpha(val, config)
	{
		val = trim(val);
		if(isValid(val,config['lwr'] + config['upr'] + " ") != true)
		{
				return "'##field##' does not contain just alpha characters.";
		}
		else
		{
				return "";
		}
	}
	//validate the provided phone number
	function validatePhone(val, config)
	{
		if (checkInternationalPhone(val, config)==false)
		{
			return "'##field##' is not a valid phone number.";
		}
		return "";
	}
	//validate selectbox, this is an old function that is most likely not needed and will be removed
	function isSelect(val, config)
	{
		val = trim(val);
		if(val == "")
		{
			return "'##field##' is a required field.";
		}
		else
		{
			return "";
		}
	}	
	/*
	 *
	 *			Validation helpers. This will prepare, format and may be evaluate values but are not core functions
	 *
	 */
	//trim all white space, could revisit to strip other characters
	function trim(stringToTrim) 
	{
		return stringToTrim.replace(/^\s+|\s+$/g,"");
	}
	//left trim white space, could revisit to strip other characters
	function ltrim(stringToTrim) 
	{
		return stringToTrim.replace(/^\s+/,"");
	}
	//right trim white space, could revisit to strip other characters
	function rtrim(stringToTrim) 
	{
		return stringToTrim.replace(/\s+$/,"");
	}
	//this function trims the white space from your string
	function old_trim(s)
	{   var i;
	    var returnString = "";
	    // Search through string's characters one by one.
	    // If character is not a whitespace, append to returnString.
	    for (i = 0; i < s.length; i++)
	    {   
	        // Check that current character isn't whitespace.
	        var c = s.charAt(i);
	        if (c != " ") returnString += c;
	    }
	    return returnString;
	}
	//check that the postcode is valid
	function isValidPostcode(p) 
	{
		p = p.toUpperCase();
		var postcodeRegEx = /^([A-PR-UWYZ0-9][A-HK-Y0-9][AEHMNPRTVXY0-9]?[ABEHMNPRVWXY0-9]? {1,2}[0-9][ABD-HJLN-UW-Z]{2}|GIR 0AA)$/i;
		return postcodeRegEx.test(p);
	}
	//this runction formats the post code if valid and returns result
	function formatPostcode(p) 
	{
		if (isValidPostcode(p)) 
		{
			var postcodeRegEx = /(^[A-Z]{1,2}[0-9]{1,2})([0-9][A-Z]{2}$)/i;
			return p.replace(postcodeRegEx,"$1 $2");
		} 
		else 
		{
			return p;
		}
	}
	//is valid use the config setting to test against: the config settings are:
	function isValid(parm,val)
	{
		if (parm == "") return true;
		for (i=0; i<parm.length; i++)
		{
			if (val.indexOf(parm.charAt(i),0) == -1) return false;
		}
		return true;
	}
	//check that the string has characters 0-9
	function isInteger(s)
	{   var i;
	    for (i = 0; i < s.length; i++)
	    {   
	        // Check that current character is number.
	        var c = s.charAt(i);
	        if (((c < "0") || (c > "9"))) return false;
	    }
	    // All characters are numbers.
	    return true;
	}
	//only returns valid characters from bag
	function stripCharsInBag(s, bag)
	{   var i;
	    var returnString = "";
	    // Search through string's characters one by one.
	    // If character is not in bag, append to returnString.
	    for (i = 0; i < s.length; i++)
	    {   
	        // Check that current character isn't whitespace.
	        var c = s.charAt(i);
	        if (bag.indexOf(c) == -1) returnString += c;
	    }
	    return returnString;
	}
	//checks the phone number supplied against uk and international
	function checkInternationalPhone(strPhone, config)
	{
		var bracket=3
		strPhone=trim(strPhone)
		if(strPhone.indexOf("+")>1) return false
		if(strPhone.indexOf("-")!=-1)bracket=bracket+1
		if(strPhone.indexOf("(")!=-1 && strPhone.indexOf("(")>bracket)return false
		var brchr=strPhone.indexOf("(")
		if(strPhone.indexOf("(")!=-1 && strPhone.charAt(brchr+2)!=")")return false
		if(strPhone.indexOf("(")==-1 && strPhone.indexOf(")")!=-1)return false
		s=stripCharsInBag(strPhone,config['validWorldPhoneChars']);
		return (isInteger(s) && s.length >= config['minDigitsInIPhoneNumber']);
	}
	 
 })(jQuery);
