/*
| Script: foul
| Version 1.3
| Author: Bryan English
| Description: Form Validation Language - Easy way to check forms.
| Date: April 21, 2004
| Usage:

foul.when("~Field Name~ test tokens","ERROR MESSAGE");
foul.when("~Field Name~ test tokens and ~Another Field~ test tokens","ERROR MESSAGE");

Version log

1.2
 - added greater-than, less-than ability

1.1 
 - added when() function as alias for add()
 - added credit card validation but needs work

Current Keywords
is - (just for readability, does nothing)
not - (inverts keyword test following it)
empty - (checks for a blank value)
blank - ('')
null - ('')
email - (checks for a wellformed email address)
range - (checks for a number range)
between - ('')
numeric - (checks to make sure value is a number)
greater-than - (checks to make sure value is greater than number)
> - ('')
less-than - (checks to make sure value is less than number)
< - ('')

* Needs Work *
valid_credit_card - (checks for a wellformed credit card number)
valid_cc - (checks for a wellformed credit card number)
vcc  - (checks for a wellformed credit card number)

* Need To Do *
ssn
phone
zip
money
date
url
filetype
unchanged
visa
mastercard
discovery
amex


*/
function Foul(){

   //----------------------------------------------------------------------------//	

   this.form = null;
   this.breakpoints = false;
   this.interactive = false;
   this.tests = new Array();
	this.local = new Array();
	this.defmsg = {
		"is (null|empty|blank)": " is a required field.",
		"email": " is not a valid e-mail address.",
		"numeric": " is a number."
		};	

	//----------------------------------------------------------------------------//	

	//--------------------------------------------//
	// ADD adds a test
	//--------------------------------------------//
	this.add = function(v,m,i){
      var checksum = v.split("~"); //check for typos//
      if((checksum.length+1) % 2 == 1)alert("Mis-matched ~ :" + v);
		if(!m)
			for(reg in this.defmsg)
				if(v.search(new RegExp(reg))!=-1){
					m = this.defmsg[m];
					break;
					}
		this.tests[this.tests.length] = new Array(v,m,i);
      };

	this.when = this.add;

	//--------------------------------------------//
	// GET VALUE get a value from any form control
	//--------------------------------------------//
	this.get_value = function(e){
		if(e.type!=null)
			switch(e.type){
				case "text": case "hidden": case "password": case "textarea":return(e.value);break;
				case "checkbox":return(((e.checked)?e.value:''));break;
				case "select-one":var o = e.options[e.selectedIndex];
					return(((o.value==null)?o.text:o.value));break;
				}
		else
			for(var cnt=0;cnt<e.length;cnt++)
				if(e[cnt].checked)return(e[cnt].value);

		return(false);
		};

	//--------------------------------------------//
	// VALIDATE optional function to do the dirty work
	//--------------------------------------------//
	this.validate = function(form){
		var errors = '';
		errors = this.test(form);
		if(errors!=''){
			alert("There is a problem with your submission:\n"+errors);
			return false;
			}
		return true;
		}	

	//--------------------------------------------//
	// CHOMP perl ripoff
	//--------------------------------------------//
   this.chomp = function(str){
      str = str.match(/\s*(.*\S)\s*/);
      return str[1];
      }

	//--------------------------------------------//
	// ONION peel layers strings via paranthesis
	//--------------------------------------------//
   this.onion = function(str,start,end){
      
      var cnt,tally = 1;

      for(cnt=1;cnt<str.length && tally!=0;cnt++){
         if(str.charAt(cnt) == start)tally++;
         if(str.charAt(cnt) == end)tally--;
         }
      return(str.substring(1,cnt-1));
      }

	//--------------------------------------------//
	// PARSE foul parser 
	//--------------------------------------------//
   this.tokenize = function(str){

      var left,right,bool = null;
      var result = false;
      str = this.chomp(str);

      //check for paranthesis//
      if(str.charAt(0) == '('){
         left = this.onion(str,"(",")");
         right = str.substring(str.indexOf(left)+left.length,str.length);
         left = str.substring(1,str.length-1);
         result = this.tokenize(left);
         }
      //else just split and eval the left part//
      else{         
         left = str.match(/\~[^\~]+\~.*?(?=and|or|$)/)[0];
         right = str.substring(str.indexOf(left)+left.length,str.length);
         result = this.evaluate(left);
         }

      bool = right.match(/or|and|\s*$/)[0];
      right = right.substring(right.indexOf(bool)+bool.length,right.length);

      //get recursive!//
      switch(bool){
         case "":
            return result;
         case "and":
            return(result && this.tokenize(right));
         case "or":
            return(result || this.tokenize(right));
         }
      }

	//--------------------------------------------//
	// TEST parseing the tests and create error  //
	//--------------------------------------------//
   this.test = function(form){
		var errors = '';
      this.form = form;

      for(var cnt=0;cnt<this.tests.length;cnt++)
         if(this.tokenize(this.tests[cnt][0]))
			   errors += '\n- ' + this.tests[cnt][1];

		return errors;
      };		 

   //--------------------------------------------//
	// EVALUATE where the field testing is done
	//--------------------------------------------//
   this.evaluate = function(str){
      var list = str.match(/\~([^~]+)\~(?:\s(\S+)(?:\s(.*))?)?/);
      var field = list[1];		
      var value = (this.form[field])?this.get_value(this.form[field]):this.local[field];
      var test = (list[2]==null||list[2]=="")?"null":list[2];
      var param = list[3];      

      switch(test){
         
         case 'is':
         case '=':
            return(this.evaluate('~'+field+'~ '+ param));
            break;

         case '!':
         case 'not':
            return(!this.evaluate('~'+field+'~ '+ param));
            break;

         case 'empty':
         case 'blank':
         case 'null':
            if(value == null || value == '')return(true);
            break;

         case 'range':
         case 'between':
            if(value == null || value == '')return(true);
            param = param.split(/\s/);
				if(value > param[0] && value < param[1])return(true);
            break;

         case 'greater-than':
         case '>':
            if(value == null || value == '' || isNaN(value))return(true);
				if(value > param)return(true);
            break;

         case 'less-than':
         case '<':
            if(value == null || value == '' || isNaN(value))return(true);
				if(value < param)return(true);
            break;

         case 'email':
            if(value == null || value == '')return(true);
            if(/^.+\@..+\..+/.test(value))return(true);
            break;

         case 'length':
            if(value == null || value == '')return(true);
            param = param.split(/\s/);
				if(param.length > 1){
					this.local["_LOCAL_" + field] = parseInt(value.length);
					return(this.evaluate('~_LOCAL_'+field+'~ '+ param.join(' ')));
					}
				else{
					return(value.length == parseInt(param[0]));
					}
            break;

         case 'numeric':
            if(value == null || value == '')return(true);
            if(!isNaN(value))return(true);
            break;

			case 'valid_credit_card':
			case 'valid_cc':
			case 'vcc':
				if(value == null || value == '')return(true);
				if (value.length > 19)
					return (false);

				var sum = 0; mul = 1; l = value.length;
				for (i = 0; i < l; i++) {
					var digit = value.substring(l-i-1,l-i);
					var tproduct = parseInt(digit ,10)*mul;
					if (tproduct >= 10)
						sum += (tproduct % 10) + 1;
					else
						sum += tproduct;
					if (mul == 1)
						mul++;
					else
						mul--;
					}
				if ((sum % 10) == 0)return (true);
				break;
         }
      
      return false;
      };

   }


var foul = new Foul();