/*
===============================================================================

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

===============================================================================

MCM Validate 0.1.1
Copyright (c) 2008, 2009 McMillan & Associates Inc.
Written by Mathieu Bouchard

===============================================================================

How to use this validation script:

1)	Include jQuery along with this script in the head tag of your document:
		http://code.mcmillanlabs.com/inc/scripts/jquery-1.3.2.min.js
		http://code.mcmillanlabs.com/inc/scripts/validate-x.x.x.js

2)	Make sure to wrap your entire form using a traditional <form> tag. Note
	that the validation hook occurs automatically, however it is hooked to the
	form submit event.
	
3)	Add the "validate" class to your <form>.  You can also add the following
	options:
		a)	Blink (enabled by default)
				1)	option-blink-enable: Enables blinking effect to draw
					attention to required fields.
				2)	option-blink-disable: Disables blinking effect.
		b)	Remember (enabled by default)
				1)	option-remember-enable: Enables automatic saving field
					values to local cookie. Saved values populated on page 
					refresh. Saved values wiped when form successfully
					submitted.
				2)	option-remember-disable: Disables automatic saving field
					values to local cookie.

4)	Add various condition words as classes wherever appropriate to indicate
	what fields are required.  By default, all fields are optional:
		a)	required: Marks all descendant+self fields as being required.
		b)	zeroOrMore: Marks all descendant+self fields as having zero or more
			fields as being required.
		c)	oneOrMore: Marks all descendant+self fields as having one or more
			fields as being required.
		d)	optional: Marks all descendant+self fields as being optional.
			Useful to negate the effects of one of the above words for a
			specific sub-section of fields.

5)	Optionally add specific validation words as classes directly on
	input/select/textarea fields:
		a)	validate-as-email: Validates field against a regular expression 
			designed for e-mail addresses
		b)	validate-as-regexp: Validates against regular expression stored in
			a hidden input field.  See below for example usage

6)	Optionally add auto-formatting words as classes directly on 
	input/select/textarea fields:
		a)	format-as-postal: Actively formats postal/zip code (3 space 3
			canadian format or U.S. numbers only)
		b)	format-as-email: Actively formats e-mail address
		c)	format-as-phone: Actively formats phone number to achieve an 11 
			digit format 1 (234) 567-8901
		d)	format-as-ucwords: Actively formats by Uppercasing The First Letter
			Of Each Word
		e)	format-as-regexp: Replaces all matches in the specified regular
			expression to empty. Used similarly to validate-as-regexp.  See
			below for example usage

7)	Optionally create a standalone function titled "OnSubmit" with the first
	parameter being the form object and the second parameter being a boolean
	indicating if the validation succeeded.  If the function is present, its
	return value will be used to determine whether or not to allow the submit
	event to occur.  This is useful to override the submit event and
	potentially invoke an AJAX technique to submit form data.

8)	Setup styles for required fields. (Note that it is up to you to style the
	"required star" next to each field, if so desired)

	input.required-on,
	textarea.required-on,
	select.required-on
	{
		(NOT RECOMMENDED, try sticking to styling labels only. Remember that
		it is valid XHTML to nest your input within the label element.)
		background-color: [choose a color]; 
	}

	label.required-on
	{
		color: [choose a color];
	}

9)	Example of usage:

	<head>
		...
		<script type="text/javascript" src="http://code.mcmillanlabs.com/inc/scripts/validate-x.x.x.js"></script>
		<script type="text/javascript">
			function OnSubmit( form, success )
			{
				if( success )
				{
					// Send form data via AJAX, for example
				}

				return( false );
			}
		</script>
		...
	</head>

	<body>
		...
		<form action="./" method="post" class="validate option-remember-disable">
			<input type="hidden" name="integers" value="^[0-9]+$" />
			<input type="hidden" name="except-integers" value="[^0-9]" />

			<div class="oneOrMore">
				<input type="checkbox" name="box[]" value="1" />
				<input type="checkbox" name="box[]" value="2" />
				<input type="checkbox" name="box[]" value="3" />
				<input type="checkbox" name="box[]" value="4" />
				<input type="text" name="something" value="" class="optional" />
			</div>
			<input type="text" name="first-name" value="" class="required" />
			<input type="text" name="last-name" value="" class="required" />
			<input type="text" name="email" value="" class="required validate-as-email format-as-email" />
			<input type="text" name="phone" value="" class="format-as-phone" />
			<input type="text" name="num-things" value="0" class="validate-as-regexp=integers" />
			<input type="text" name="only-numbers-here" value="0" class="format-as-regexp=except-integers" />
			
			<input type="submit" />
		</form>
		...
	</body>

===============================================================================

*/

var validators = new Array();

$( document ).ready( function()
{
	$( "form.validate" ).each( function( i )
	{
		var validator = new MCM_Validation();
		
		validators[ i ] = validator;
		
		validator.Init( $( this ) );

		validator.form.submit( function( i )
		{
			if( validator.form.data( "submit-clicked" ) != "1" )
			{
				validator.InitForm();
				validator.form.data( "submit-clicked", "1" );
			}

			if( validator.ValidateForm( true ) )
			{
				validator.RememberClear();

				if( window.OnSubmit )
				{
					return( OnSubmit( $( this ), true ) );
				}
				else
				{
					return( true );
				}
			}
			else
			{
				if( window.OnSubmit )
				{
					return( OnSubmit( $( this ), false ) );
				}
				else
				{
					return( false );
				}
			}
		});
	});
});

function MCM_Validation()
{
	this.form					= null;
	this.optionBlink			= true;
	this.optionRemember			= true;
	this.eraseCookies			= true;
	this.animatedObj			= new Array();

	this.Init					= Init;
	this.ValidateForm			= ValidateForm;
	this.IsValid				= IsValid;
	this.Validate				= Validate;
	this.MakeRequired			= MakeRequired;
	this.ClearRequired			= ClearRequired;
	this.IsValidEmail			= IsValidEmail;
	this.IsValidPhone			= IsValidPhone;
	this.IsValidPostal			= IsValidPostal;
	this.IsValidAgainstRegExp	= IsValidAgainstRegExp;
	this.InitForm				= InitForm;
	this.InitAutoAdjusts		= InitAutoAdjusts;
	this.GetType				= GetType;
	this.GetSelector			= GetSelector;
	this.Blink					= Blink;
	this.RememberClear			= RememberClear;
	this.CreateCookie			= CreateCookie;
	this.ReadCookie				= ReadCookie;
	this.EraseCookie			= EraseCookie;

	function Init( formObject )
	{
		this.form = formObject;

		this.optionBlink = this.form.hasClass( "option-blink-enable" ) ? true : this.optionBlink;
		this.optionBlink = this.form.hasClass( "option-blink-disable" ) ? false : this.optionBlink;

		this.optionRemember = this.form.hasClass( "option-remember-enable" ) ? true : this.optionRemember;
		this.optionRemember = this.form.hasClass( "option-remember-disable" ) ? false : this.optionRemember;

		this.InitAutoAdjusts();
	}

	function ValidateForm( isOnSubmit )
	{
		var validator = this;
		var successCount = 0;
		var successTotal = 0;

		validator.form.find( ".optional, .zeroOrMore, .oneOrMore, .required" ).each( function()
		{
			var validCount 	= 0;
			var totalCount	= 0;
			var fields 		= [];
			var type		= validator.GetType( this );
			var selector	= validator.GetSelector( type );
			
			$( this ).find( selector ).andSelf().filter( "input, select, textarea" ).not( "input[type='hidden']" ).each( function( i )
			{
				fields[ i ] = [ this, validator.IsValid( this ) ];
				validCount += ( fields[ i ][ 1 ] ? 1 : 0 );
				totalCount++;
			});
			
			switch( type )
			{
				case "optional":
					condition = true;
				break;
				
				case "zeroOrMore":
					condition = ( validCount >= 0 );
				break;
				
				case "oneOrMore":
					condition = ( validCount >= 1 );
				break;
				
				case "required":
					condition = ( validCount == totalCount );
				break;
			}
			
			for( k = 0; k < fields.length; k++ )
			{
				if( condition )
				{
					validator.ClearRequired( fields[ k ][ 0 ] );
				}
				else
				{
					validator.MakeRequired( fields[ k ][ 0 ], isOnSubmit );
				}
			}
			
			successCount += ( condition ? 1 : 0 );
			successTotal++;
		});
		
		return( successCount == successTotal );
	}

	function IsValid( obj )
	{
		var validator = this;
		var valid = true;

		if( $( obj ).hasClass( "validate-as-email" ) )
		{
			valid = validator.IsValidEmail( $( obj ).val() );
		}
		else if( $( obj ).hasClass( "validate-as-phone" ) )
		{
			valid = validator.IsValidPhone( $( obj ).val() );
		}
		else if( $( obj ).hasClass( "validate-as-postal" ) )
		{
			valid = validator.IsValidPostal( $( obj ).val() );
		}
		else if( ( pos = $( obj ).attr( "class" ).indexOf( "validate-as-regexp=" ) ) != -1 )
		{
			var hiddenField = $( obj ).attr( "class" ).substr( pos + 19 ).split( " " )[ 0 ];

			if( hiddenField != "" )
			{
				var regExp = validator.form.find( "input[name='" + hiddenField + "']" ).val();

				if( regExp != "" )
				{
					valid = validator.IsValidAgainstRegExp( $( obj ).val(), regExp );
				}
			}
		}
		else
		{
			if( $( obj ).is( "[type='radio']" ) )
			{
				valid = ( $( "[name='" + $( obj ).attr( "name" ) + "']:checked" ).length > 0 );
			}
			else if( $( obj ).is( "[type='checkbox']" ) )
			{
				valid = ( $( obj ).is( ":checked" ) );
			}
			else
			{
				if( $( obj ).val() == "" || obj.selectedIndex == 0 )
				{
					valid = false;
				}
			}
		}
		
		return( valid );
	}

	function Validate( obj, valid )
	{
		var validator = this;

		if( valid )
		{
			validator.ClearRequired( obj );
		}
		else
		{
			validator.MakeRequired( obj, false );
		}
	}

	function MakeRequired( obj, isOnSubmit )
	{
		var validator = this;

		if( isOnSubmit )
		{
			if( validator.optionBlink )
			{
				validator.Blink( $( obj ), 50, 5 );
				validator.Blink( $( "label[for='" + $( obj ).attr( "id" ) + "']" ), 50, 5 );
			}
			else
			{
				$( obj ).addClass( "required-on" );
				$( "label[for='" + $( obj ).attr( "id" ) + "']" ).addClass( "required-on" );
			}
		}
		else
		{
			$( obj ).addClass( "required-on" );
			$( "label[for='" + $( obj ).attr( "id" ) + "']" ).addClass( "required-on" );
		}
	}

	function ClearRequired( obj )
	{
		if( $( obj ).hasClass( "required-on" ) )
		{
			var tt = $( "#entryform input[type=text]" ).tooltip();
			tt.getTip().hide();
		}
		
		$( obj ).removeClass( "required-on" );
		$( "label[for='" + $( obj ).attr( "id" ) + "']" ).removeClass( "required-on" ).css({ background: "none" });
	}

	function IsValidEmail( value )
	{
		var ereg = new RegExp( "^[\\w-_\.]*[\\w-_\.]\@[\\w]\.+[\\w]+[\\w]$" );

		return( ereg.test( value ) );
	}
	
	function IsValidPhone( value )
	{
		var ereg = new RegExp( "[^A-Za-z]" );
		var stripped = value.replace( /[^0-9]/g, "" );
		
		if( stripped.length >= 10 && ereg.test( value ) )
		{
			return true;
		}
		else
		{
			return false;	
		}
	}
	
	function IsValidPostal( value )
	{
		var ereg = new RegExp( "[A-Za-z0-9]" );
		var stripped = value.replace( " ", "" );
		
		if( stripped.length >= 6 && ereg.test( value ) )
		{
			return true;
		}
		else
		{
			return false;	
		}
	}

	function IsValidAgainstRegExp( value, regExp )
	{
		var ereg = new RegExp( regExp );

		return( ereg.test( value ) );
	}

	function InitForm()
	{
		var validator = this;

		validator.form.find( "input[type='text'], textarea" ).each( function( i )
		{
			$( this ).keyup( function()
			{
				validator.ValidateForm( false );
			});
		});

		validator.form.find( "input[type='checkbox']" ).each( function( i )
		{
			$( this ).click( function()
			{
				validator.ValidateForm( false );
			});
		});

		validator.form.find( "input[type='radio']" ).each( function( i )
		{
			$( this ).click( function()
			{
				/*
				$( "input[name='" + $( this ).attr( "name" ) + "']" ).each( function()
				{
					validator.Validate( this, validator.IsValid( this ) );
				});
				*/

				validator.ValidateForm( false );
			});
		});

		validator.form.find( "select" ).each( function( i )
		{
			$( this ).change( function()
			{
				validator.ValidateForm( false );
			});
		});
	}

	function InitAutoAdjusts()
	{
		var validator = this;

		validator.form.find( ".format-as-postal" ).change( function()
		{
			var currentPostalCode = $( this ).val();
			var pc = currentPostalCode.toUpperCase();
			var ereg = new RegExp( /[A-Z]+/gi );

			pc = pc.replace( / /g, "" );

			if( ereg.test( pc ) )
			{
				//postal code (K2K 3C4)
				var formattedPostalCode = pc.substr( 0, 3 ) + " " + pc.substr( 3, 3 );
			}
			else
			{
				//zip code (90210)
				var formattedPostalCode = pc;
			}

			$( this ).val( formattedPostalCode );
		});

		validator.form.find( ".format-as-email" ).change( function()
		{
			$( this ).val( $( this ).val().toLowerCase() );
		});

		validator.form.find( ".format-as-phone" ).change( function()
		{
			var currentPhone	= $( this ).val();

			var digitsOnly		= currentPhone.replace( /[^0-9]+/g, "" );
			var digitsLen		= digitsOnly.length;
			var p				= digitsOnly;

			if( digitsLen >= 11 )
			{
				var formattedPhone = p.substr( 0, 1 ) + " (" + p.substr( 1, 3 ) + ") " + p.substr( 4, 3 ) + "-" +  p.substr( 7, 4 );
			}
			else if( digitsLen > 7 )
			{
				var formattedPhone = "(" + p.substr( 0, 3 ) + ") " + p.substr( 3, 3 ) + "-" +  p.substr( 6, 4 );
			}
			else if( digitsLen > 3 )
			{
				var formattedPhone = p.substr( 0, 3 ) + "-" + p.substr( 3, 4 );
			}
			else if( digitsLen > 0 )
			{
				var formattedPhone = p;
			}
			else if( digitsLen == 0 )
			{
				var formattedPhone = "";
			}

			$( this ).val( formattedPhone );
		});

		validator.form.find( ".format-as-ucwords" ).change( function()
		{
			$( this ).val( $( this ).val().replace( /\w+/g, function( word )
			{
				return( word.charAt( 0 ).toUpperCase() + word.substr( 1 ).toLowerCase() );
			}));
		});

		validator.form.find( "input, textarea, select" ).each( function( i )
		{
			if( ( pos = $( this ).attr( "class" ).indexOf( "format-as-regexp=" ) ) != -1 )
			{
				var hiddenField = $( this ).attr( "class" ).substr( pos + 17 ).split( " " )[ 0 ];

				if( hiddenField != "" )
				{
					var regExp = validator.form.find( "input[name='" + hiddenField + "']" ).val();

					if( regExp != "" )
					{
						$( this ).change( function()
						{
							$( this ).val( $( this ).val().replace( new RegExp( regExp, "g" ), "" ) );
						});
					}
				}
			}

			if( validator.optionRemember )
			{
				var isCheckbox = $( this ).is( "[type='checkbox']" );

				$( this ).change( function()
				{
					if( isCheckbox )
					{
						var valueToStore = $( this ).is( ":checked" ) ? "1" : "0";
					}
					else
					{
						var valueToStore = $( this ).val();
					}

					validator.CreateCookie( document, $( this ).attr( "id" ), valueToStore, "" );
				});

				var storedValue 	= null;
				var getVariables 	= window.location.href.slice( window.location.href.indexOf( "?" ) + 1 ).split( "&" );

				for( var j = 0; j < getVariables.length; j++ )
				{
					var keyValuePair = getVariables[ j ].split( "=" );

					if( keyValuePair[ 0 ].toLowerCase() == $( this ).attr( "id" ).toLowerCase() )
					{
						storedValue = urldecode( keyValuePair[ 1 ] );
					}
				}

				if( storedValue == null )
				{
					storedValue = validator.ReadCookie( document, $( this ).attr( "id" ) );
				}

				if( storedValue != null )
				{
					if( isCheckbox )
					{
						if( storedValue == "1" )
						{
							this.checked = true;
						}
					}
					else
					{
						$( this ).val( storedValue );
					}
				}
			}
		});
	}

	function GetType( obj )
	{
		var type = "";
		
		if( $( obj ).hasClass( "zeroOrMore" ) )
		{
			type = "zeroOrMore";
		}
		else if( $( obj ).hasClass( "optional" ) )
		{
			type = "optional";
		}
		else if( $( obj ).hasClass( "oneOrMore" ) )
		{
			type = "oneOrMore";
		}
		else if( $( obj ).hasClass( "required" ) )
		{
			type = "required";
		}
		
		return( type );
	}

	function GetSelector( type )
	{
		var selector = "";
		
		switch( type )
		{
			case "optional":
				selector = ":not( .optional .optional *, .zeroOrMore *, .oneOrMore *, .required * )";
			break;

			case "zeroOrMore":
				selector = ":not( .optional *, .zeroOrMore .zeroOrMore *, .oneOrMore *, .required * )";
			break;
			
			case "oneOrMore":
				selector = ":not( .optional *, .zeroOrMore *, .oneOrMore .oneOrMore *, .required * )";
			break;
			
			case "required":
				selector = ":not( .optional *, .zeroOrMore *, .oneOrMore *, .required .required * )";
			break;
		}
		
		return( selector );
	}

	function Blink( obj, speed, frequency )
	{
		var validator = this;
		var index = validator.animatedObj.length;

		if( obj != undefined )
		{
			validator.animatedObj[ index ] = obj;
			validator.animatedObj[ index ].addClass( "required-on" );

			for( var t = 1; t <= ( frequency * 2 ); t += 2 )
			{
				setTimeout( function() { validator.animatedObj[ index ].removeClass( "required-on" ); }, t * speed );
				setTimeout( function() { validator.animatedObj[ index ].addClass( "required-on" ); }, ( t + 1 ) * speed );
			}

			setTimeout( function() { validator.animatedObj = new Array(); }, ( t + 2 ) * speed );
		}
	}

	function RememberClear()
	{
		var validator = this;

		if( validator.optionRemember )
		{
			if( validator.form.data( "submitted" ) != "1" )
			{
				if( validator.eraseCookies )
				{
					$( "input, textarea, select" ).each( function( i )
					{
						validator.EraseCookie( document, $( this ).attr( "id" ) );
					});
				}

				validator.form.data( "submitted", "1" );
			}
		}
	}

	function CreateCookie( doc, name, value, days )
	{
		if( days )
		{
			var date = new Date();
			date.setTime( date.getTime() + ( days * 24 * 60 * 60 * 1000 ) );
			var expires = "; expires=" + date.toGMTString();
		}
		else
		{
			var expires = "";
		}

		doc.cookie = name + "=" + value + expires + "; path=/";
	}

	function ReadCookie( doc, name )
	{
		var nameEQ = name + "=";
		var ca = doc.cookie.split( ';' );

		for( var i = 0; i < ca.length; i++ )
		{
			var c = ca[ i ];

			while( c.charAt(0) == ' ' )
			{
				c = c.substring( 1, c.length );
			}

			if( c.indexOf( nameEQ ) == 0 )
			{
				return c.substring( nameEQ.length, c.length );
			}
		}

		return( null );
	}

	function EraseCookie( doc, name )
	{
		var validator = this;

		validator.CreateCookie( doc, name, "", -1 );
	}
}