/**
* This class provides methods for validating forms on the client side. It uses the class ValidField
* @author Ian Yates
* @version 0.1a
* @param String formID The id of the Form to validate
* @access public
*
*/
function ValidForm(formID)
{
   /**
   * The error message to display when required field values are not included
   * @constant String REQUIRED_ERROR
   * @access public
   * @since 0.1a
   *
   */
   this.REQUIRED_ERROR = 'REQUIRED_ERROR';
   
   /**
   * The error message to display when fields that must be checked are not checked
   * @constant String NOT_CHECKED_ERROR
   * @access public
   * @since 0.1a
   *
   */
   this.CHECKED_ERROR = 'CHECKED_ERROR';
   
   /**
   * The error message to display when fields that must not be checked are checked
   * @constant String CHECKED_ERROR
   * @access public
   * @since 0.1a
   *
   */
   this.NOT_CHECKED_ERROR = 'CHECKED_ERROR';
   
   /**
   * The error message to display when fields include prohibited substrings
   * @constant String CHECKED_ERROR
   * @access public
   * @since 0.1a
   *
   */
   this.PROHIBITED_STRING_ERROR = 'PROHIBITED_STRING_ERROR';
   
   /**
   * The error message to display when fields do not include required substrings
   * @constant String CHECKED_ERROR
   * @access public
   * @since 0.1a
   *
   */
   this.REQUIRED_STRING_ERROR = 'REQUIRED_STRING_ERROR';
   
   /**
   * The error message to display when a field does not fall into the given numeric range
   * @constant String RANGE_ERROR
   * @access public
   * @since 0.1a
   *
   */
   this.RANGE_ERROR = 'RANGE_ERROR';
   
   /**
   * The error message to display when a field length is not in the specified range
   * @constant String LENGTH_ERROR
   * @access public
   * @since 0.1a
   *
   */
   this.LENGTH_ERROR = 'LENGTH_ERROR';
   
   /**
   * The error message to display when a field length is not the specified data type
   * @constant String DATA_TYPE_ERROR
   * @access public
   * @since 0.1a
   *
   */
   this.DATA_TYPE_ERROR = 'DATA_TYPE_ERROR';
   
   /**
   * The error message to display when a field does not contain the specified number of a given substring
   * @constant String SUBSTRING_COUNT_ERROR
   * @access public
   * @since 0.1a
   *
   */
   this.SUBSTRING_COUNT_ERROR = 'SUBSTRING_COUNT_ERROR';
   
   /**
   * The error message to display when a field does not match a specified regular expression
   * @constant String REG_EXP_ERROR
   * @access public
   * @since 0.1a
   *
   */
   this.REG_EXP_ERROR = 'REG_EXP_ERROR';
   
   /**
   * An array containing all the fields of the form . This is equivalent to form.elements
   * but contains some additional values relating to validation.
   * @var Mixed[] fields The fields of the form
   * @see getValidatedFields()
   * @see getFields();
   * @see validate()
   * @access public
   * @since 0.1a
   *
   */
   this.fields = new Array();
   
   /**
   * Whether or not the form is valid
   * @var Boolean fields The validity of the form
   * @see getValidatedFields()
   * @see isValid()
   * @see validate()
   * @access public
   * @since 0.1a
   *
   */
   this.valid = false;
   
   /**
   * Sets a form field as compulsory
   * @param String fieldID The id of the field to make compulsory
   * @access public
   * @since 0.1a
   *
   */
   this.setFieldAsCompulsory = ValidForm_setFieldAsCompulsory;
   
   /**
   * Adds a string that the field MUST contain
   * @param String fieldID The name of the field
   * @param String str The compulsory String
   * @access public
   * @since 0.1a
   *
   */
   this.addFieldCompulsoryString = ValidForm_addFieldCompulsoryString;
   
   /**
   * Adds a string that the field MUST NOT contain
   * @param String fieldID The name of the field
   * @param String str The prohibited String
   * @access public
   * @since 0.1a
   *
   */
   this.addFieldProhibitedString = ValidForm_addFieldProhibitedString;
   
   /**
   * Makes it compulsory to check the field (if it is a radio or checkbox)
   * @param String fieldID The name of the field
   * @see makeFieldCheckProhibited()
   * @access public
   * @since 0.1a
   *
   */
   this.makeFieldCheckCompulsory = ValidForm_makeFieldCheckCompulsory;
   
   /**
   * Makes it prohibited to check the field (if it is a radio or checkbox)
   * @param String fieldID The name of the field
   * @see makeFieldCheckCompulsory()
   * @access public
   * @since 0.1a
   *
   */
   this.makeFieldCheckProhibited = ValidForm_makeFieldCheckProhibited;
   
   /**
   * Sets the minimum string length of the field
   * @param String fieldID The name of the field
   * @param Int minLength The minimum length of the field
   * @see setFieldMaxStringLength()
   * @access public
   * @since 0.1a
   *
   */
   this.setFieldMinStringLength = ValidForm_setFieldMinStringLength;
   
   /**
   * Sets the maximun string length of the field
   * @param String fieldID The name of the field
   * @param Int minLength The maximum length of the field
   * @see setFieldMinStringLength()
   * @access public
   * @since 0.1a
   *
   */
   this.setFieldMaxStringLength = ValidForm_setFieldMaxStringLength;
   
   /**
   * Sets the range of numeric values allowable for the field
   * @param String fieldID The name of the field
   * @param Number low The lowest allowable value
   * @param Number high The highest allowable avlue
   * @access public
   * @since 0.1a
   *
   */
   this.setFieldNumRange = ValidForm_setFieldNumRange;
   
   /**
   * Sets the compulsory data type of the field
   * @param String fieldID The name of the field
   * @param String dataType the data type. (Currently supports Number and String)
   * @access public
   * @since 0.1a
   *
   */
   this.setFieldDataType = ValidForm_setFieldDataType;
   
   /**
   * Sets the number of times a given substring can occur in astring
   * @param String fieldID The name of the field
   * @param String subStr The substring to check against
   * @param Int num The number of times the substring must occur
   * @access public
   * @since 0.1a
   *
   */
   this.setFieldSubstringCount = ValidForm_setFieldSubstringCount;
   
   /**
   * Sets a regular expression that the field must match
   * @param String fieldID The name of the field
   * @param String exp The regular expression to match
   * @access public
   * @since 0.1a
   *
   */
   this.setFieldToMatchRegExp = ValidForm_setFieldToMatchRegExp;
   
   /**
   * Validates the form
   * @see getValidatedFields()
   * @access public
   * @since 0.1a
   *
   */
   this.validate = ValidForm_validate;
   
   /**
   * Returns the fields
   * @see getValidatedFields()
   * @access public
   * @since 0.1a
   *
   */
   this.getFields = ValidForm_getFields;
   
   /**
   * Returns the fields
   * @see getFields()
   * @see validate();
   * @access public
   * @since 0.1a
   *
   */
   this.getValidatedFields = ValidForm_getValidatedFields;
   
   /**
   * Determines whether a form element is an input field
   * @access private
   * @since 0.1a
   *
   */
   this.elementIsInput = ValidForm_elementIsInput;
   
   /**
   * Returns whether or not the form is valid
   * @access public
   * @since 0.1a
   *
   */
   this.isValid = ValidForm_isValid;
   
   /**
   * Initialises the class
   * @access public
   * @since 0.1a
   *
   */
   this.init = ValidForm_init;
   this.init(formID);
}

function ValidForm_setFieldAsCompulsory(fieldID)
{
   this.fields[fieldID].compulsory = true;
}

function ValidForm_addFieldCompulsoryString(fieldID, str)
{
   this.fields[fieldID].compulsoryStrings[this.fields[fieldID].compulsoryStrings.length] = str;
}

function ValidForm_addFieldProhibitedString(fieldID, str)
{
   this.fields[fieldID].prohibitedStrings[this.fields[fieldID].prohibitedStrings.length] = str;
}

function ValidForm_makeFieldCheckCompulsory(fieldID)
{
   if((this.fields[fieldID].type == 'radio') || (this.fields[fieldID].type == 'checkbox'))
   {
      this.fields[fieldID].compulsoryOn = true;
   }
}

function ValidForm_makeFieldCheckProhibited(fieldID)
{
   if((this.fields[fieldID].type == 'radio') || (this.fields[fieldID].type == 'checkbox'))
   {
      this.fields[fieldID].compulsoryOff = false;
   }
}

function ValidForm_setFieldMinStringLength(fieldID, num)
{
   this.fields[fieldID].minStringLength = num;
}

function ValidForm_setFieldMaxStringLength(fieldID, num)
{
   this.fields[fieldID].maxStringLength = num;
}

function ValidForm_setFieldNumRange(fieldID, low, high)
{
   this.fields[fieldID].lowNumLimit = low;
   this.fields[fieldID].highNumLimit = high;
}

function ValidForm_setFieldDataType(fieldID, theType)
{
   var dType = new String(theType);
   this.fields[fieldID].dataType = dType.toUpperCase();  
}

function ValidForm_setFieldSubstringCount(fieldID, subStr, strCount)
{
   this.fields[fieldID].countString = subStr;
   this.fields[fieldID].countStringCount = strCount; 
}

function ValidForm_setFieldToMatchRegExp(fieldID, reg)
{
   this.fields[fieldID].matchExp = reg;
}

function ValidForm_validate()
{
   var ok = true;
   for(var i = 0; i < this.fields.length; i++)
   {
      if(this.fields[i].id && this.elementIsInput(this.fields[i].id))
	  {
	     var checkVal = null;
	     switch(this.fields[i].type)
		 {
		    case 'file':
		    case 'text':
		    case 'password':
		    case 'hidden':
		    case 'textarea':
		    case 'checkbox':
		    case 'radio':
			  checkVal = this.fields[i].value;
		    break;
		   
		    case 'select-one':
		    case 'select-multiple':
		      checkVal = this.fields[i].options[this.fields[i].selectedIndex].text;
		    break;
		   
		    default:
		      break;
		 }
		 var str = new String(checkVal);
		 var regExp = new RegExp();
         //compulsory field validation
		 if(this.fields[i].compulsory != null)
		 {
		    if(checkVal.length == 0 || checkVal == "")
		    {
		        this.fields[i].valid = false;
			    this.fields[i].errors[this.fields[i].errors.length] = this.REQUIRED_ERROR;
			    ok = false;
		    }
		 } 
		 //compulsory string validation 
		 for(var j = 0; j < this.fields[i].compulsoryStrings.length; j++)
		 {
		    if(str != '' && str.indexOf(this.fields[i].compulsoryStrings[j]) == -1)
		    {
		       this.fields[i].valid = false;
		       this.fields[i].errors[this.fields[i].errors.length] = this.REQUIRED_STRING_ERROR;
			   ok = false; 
			   break;
		    }
		 }
		 //prohibited string validation 
		 for(var j = 0; j < this.fields[i].prohibitedStrings.length; j++)
		 {
		    if(str != '' && str.indexOf(this.fields[i].prohibitedStrings[j]) != -1)
		    {
		       this.fields[i].valid = false;
		       this.fields[i].errors[this.fields[i].errors.length] = this.PROHIBITED_STRING_ERROR;
			   ok = false;
			   break;
		    }
		 }
		 //compulsory checking validation
		 if((this.fields[i].type == 'radio' || this.fields[i].type == 'checkbox') && this.fields[i].compulsoryOn == true)
		 {
		    if(!this.fields[i].checked)
		    {
		       this.fields[i].valid = false;
		       this.fields[i].errors[this.fields[i].errors.length] = this.CHECKED_ERROR;
			   ok = false; 
		    }
		 }
		 //prohibited checking validation
		 if((this.fields[i].type == 'radio' || this.fields[i].type == 'checkbox') && this.fields[i].compulsoryOff == true)
		 {
		    if(this.fields[i].checked)
		    {
		       this.fields[i].valid = false;
		       this.fields[i].errors[this.fields[i].errors.length] = this.NOT_CHECKED_ERROR;
			   ok = false; 
		    }
		 }
		 //validate high range
		 if(checkVal != '' && this.fields[i].highNumLimit != null)
		 {
		    var parsedNum = null;
		    if(str.indexOf('.') > -1)
		    {
		       parsedNum = parseFloat(str);
		    }
		    else
		    {
		       parsedNum = parseInt(str);
		    }
		   
		    if(parsedNum != 'NaN')
		    {
		       if(parsedNum > this.fields[i].highNumLimit)
			   {
			      this.fields[i].valid = false;
		          this.fields[i].errors[this.fields[i].errors.length] = this.RANGE_ERROR;
			      ok = false;
		       }
		    }
		    else
		    {
		       this.fields[i].valid = false;
		       this.fields[i].errors[this.fields[i].errors.length] = this.DATA_TYPE_ERROR;
			   ok = false;
		    }
		 }
		 //validate low range
		 if(checkVal != '' && this.fields[i].lowNumLimit != null)
		 {
		    var parsedNum = null;
		    if(str.indexOf('.') > -1)
		    {
		       parsedNum = parseFloat(str);
		    }
		    else
		    {
		       parsedNum = parseInt(str);
		    }
		    if(parsedNum != 'NaN')
		    {
		       if(parsedNum < this.fields[i].lowNumLimit)
			   {
			      this.fields[i].valid = false;
		          this.fields[i].errors[this.fields[i].errors.length] = this.RANGE_ERROR;
			      ok = false;
		       }
		    }
		    else
		    {
		       this.fields[i].valid = false;
		       this.fields[i].errors[this.fields[i].errors.length] = this.DATA_TYPE_ERROR;
			   ok = false;
		    }
		 }              
		 //validate minimum length
		 if(this.fields[i].minStringLength != null)
		 {
		    if(str.length < this.fields[i].minStringLength)
		    {
		       this.fields[i].valid = false;
		       this.fields[i].errors[this.fields[i].errors.length] = this.LENGTH_ERROR;
			   ok = false; 
		    }
		 }
		 //validate maximum length
		 if(this.fields[i].maxStringLength != null)
		 {
		    if(str.length > this.fields[i].maxStringLength)
		    {
		       this.fields[i].valid = false;
		       this.fields[i].errors[this.fields[i].errors.length] = this.LENGTH_ERROR;
			   ok = false; 
		    }
		 }
		 //validate data type
		 if(checkVal != '' && this.fields[i].dataType != null)
		 {
		    regExp.compile('[a-z]|[A-Z]');
	        var matched = regExp.test(str);
	        var dType = null;
	        if(matched)
	        {
	           //it is a string
	           dType = 'STRING';
		    }
	        else
	        {
	           //check to see if it is a valid number
		       var num = null;
		       if(str.indexOf('.') != -1)
	           {
	               num = parseFloat(str);
	           }
	           else
	           {
	               num = parseInt(str);
	           }
	           if(num != 'NaN')
	           {
	              dType = 'NUMBER';
		       }
	        }
		    if(dType != this.fields[i].dataType)
		    {
		       this.fields[i].valid = false;
		       this.fields[i].errors[this.fields[i].errors.length] = this.DATA_TYPE_ERROR;
			   ok = false;
		    }
		 }
		 //validate substring count
		 if(checkVal != '' && this.fields[i].countString != null && this.fields[i].countStringCount != null)
		 {
		    regExp.compile(this.fields[i].countString, 'g');
			
			var matched = regExp.test(str);
			if(matched)
			{
			   var matches = str.match(regExp);
		       if(matches.length != this.fields[i].countStringCount)
		       {
		          this.fields[i].valid = false;
		          this.fields[i].errors[this.fields[i].errors.length] = this.SUBSTRING_COUNT_ERROR;
			      ok = false;
		       }
			}
			else
			{
			   this.fields[i].valid = false;
		       this.fields[i].errors[this.fields[i].errors.length] = this.SUBSTRING_COUNT_ERROR;
			   ok = false;
			}
	     }
		 //validate regular expression
		 if(checkVal != '' && this.fields[i].matchExp != null)
		 {
		    regExp.compile(this.fields[i].matchExp, 'g');
			var matched = regExp.test(matchExp);
			if(!matched)
			{
			   this.fields[i].valid = false;
		       this.fields[i].errors[this.fields[i].errors.length] = this.REG_EXP_ERROR;
			   ok = false;
			}
	     }
		 //set whether or not the form is valid	         
      } 
   }
   this.valid = ok;
}

function ValidForm_isValid()
{
   return this.valid;
}

function ValidForm_getFields()
{
   return this.fields;
}

function ValidForm_getValidatedFields()
{
   this.validate();
   return this.fields;
}

function ValidForm_init(formID)
{
   var newForm = document.forms[formID];
   this.fields = newForm.elements;
   for(var i = 0; i < this.fields.length; i++)
   {
      if(this.fields[i].id && this.elementIsInput(this.fields[i].id))
      {
	     this.fields[i].prohibitedStrings = new Array();
	     this.fields[i].compulsoryStrings = new Array();
	     this.fields[i].errors = new Array();
	     this.fields[i].minStringLength = null;
	     this.fields[i].maxStringLength = null;
	     this.fields[i].highNumLimit = null;
	     this.fields[i].lowNumLimit = null;
	     this.fields[i].countString = null;
	     this.fields[i].countStringNum = null;
	     this.fields[i].compulsory = null;
	     this.fields[i].compulsoryOn = null;
	     this.fields[i].compulsoryOff = null;
	     this.fields[i].dataType = null;
		 this.fields[i].matchExp = null;
	     this.fields[i].valid = true;
      }
   }
}

function ValidForm_elementIsInput(fieldID)
{
   var obj = document.getElementById(fieldID);
   var ok = false;
   switch(obj.type)
   {
      case 'checkbox':
	  case 'file':
	  case 'hidden':
	  case 'image':
	  case 'password':
	  case 'radio':
	  case 'select-multiple':
	  case 'select-one':
	  case 'text':
	  case 'textarea':
	     
		  ok = true;
		  break;
		  
	  default:
	     break;     
   }
   return ok;
}     