/*
| COPYRIGHT (C) BRYAN ENGLISH 2004
|
| Script: foul
| Version 1.5
| 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.5
 - allowed format function to accept multiple parameters
 - added padded formatting keyword

1.4
 - added format() function that formats fields on validation. (~field~ as datatype)
 - added integer format function that strips non digit chars
 - added float format function
 
1.3
 - added length, if no field is found it checks the local hash for a value.
 - made length command a compound statement. 

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
as - (starts a formatter function)
is - (just for readability, does nothing)
not - (inverts keyword test following it)
[empty|blank|null] - (checks for a blank value)
email - (checks for a wellformed email address)
[range|between] - (checks for a number range)
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)
length - (checks to see if the field is an exact number of chars long)
length [between|range] - (checks for a number range of chars)
length [greater-than|>] - (checks to make sure char length is greater than number)
length [less-than|<] - (checks to make sure char length is less than number)

Format keywords

float, decimal
integer, number
pad [length] [char]


* 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.formats = 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;

	this.format = function(v){
      var checksum = v.split("~"); //check for typos//
      if((checksum.length+1) % 2 == 1)alert("Mis-matched ~ :" + v);
		var n = v.match(/\~([^~]+)\~/)[1];
		this.formats[this.formats.length] = new Array(v,n);
      };

	//--------------------------------------------//
	// 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 = '';
		this.formatter(form);
		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);
			result = (result==-1?false:result)
         }

      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));
         }
      }

	//--------------------------------------------//
	// FORMATTER formats the form data            //
	//--------------------------------------------//
   this.formatter = function(form){
      this.form = form;

      for(var cnt=0;cnt<this.formats.length;cnt++)
         this.tokenize(this.formats[cnt][0]);			

      };	
		
	//--------------------------------------------//
	// 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){

			//FORMATTER//         
         case 'as': 
            if(value == null || value == '')return(true);				
				param = param.split(' ');
				switch(param[0]){
					case 'number':
					case 'integer': // remove all non-number chars//
						this.form[field].value = (isNaN(parseInt(value.replace(/[^0-9\.]/g,''))))?'':parseInt(value.replace(/[^0-9\.]/g,''));
						break;
						
					case 'float':
					case 'decimal': // remove all non-number chars//
						this.form[field].value = (isNaN(parseFloat(value.replace(/[^0-9\.]/g,''))))?'':parseFloat(value.replace(/[^0-9\.]/g,''));
						break;

					case 'padded':
						var s = new String(value);
						while(s.length < param[1])
							s = param[2] + s;
						this.form[field].value = s;
						break;
					}

				return(true);
				break;
			//END FORMATTER//         

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

         case '!':
         case 'not':
				var result = this.evaluate('~'+field+'~ '+ param);
				if(result!=-1)
	            return(!this.evaluate('~'+field+'~ '+ param));
				else
					return(-1);
            break;

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

         case 'range':
         case 'between':
            if(value == null || value == '')return(-1);
            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(-1);
				if(value > param)return(true);
            break;

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

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

         case 'length':
            if(value == null || value == '')return(-1);
            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 'number':
         case 'float':
         case 'decimal':
         case 'numeric':
            if(value == null || value == '')return(-1);
            if(!isNaN(value))return(true);
            break;

			case 'valid_credit_card':
			case 'valid_cc':
			case 'vcc':
				if(value == null || value == '')return(false);
				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;

         case 'date-us':
            if(value == null || value == '')return(-1);
            if(/\d\d?\/\d\d?\/\d{2,4}/.test(value))return(true)
            break;

         case 'date-us-y2k':
            if(value == null || value == '')return(-1);
            if(/\d\d?\/\d\d?\/\d{4}/.test(value))return(true)
            break;
			
			}
      
      return false;
      };

   }


var foul = new Foul();
