var default_vtform_configuration = {
	 error_display_section: false
	,error_display_inline: true
	,error_display_inline_position: 'prefix'
	,error_display_alert: false
	,error_class: 'vtError'
	,focus: 'first_error_field' // May be 'first_error_field', 'error_section', or false for no focus
	,date_format: 'DD/MM/YYYY'
};

function vtForm(name, configuration) {
    // If it's being called as a function - call the constructor
    // (allow for shorthand)
    if (this.window) return new vtForm(name, configuration);
    
    
    // Member variables
	this.sets = new Array();
    this.fields = new Array();
	this.errors = new Array();
    // end member variables
    
    // Constructor
    this.name = name;
	this.form_name = name;
    this.configuration = array_merge(default_vtform_configuration, (configuration || new Array())) ;
    // end constructor
    
    // Functions
    this.add = function(field) {
		if (typeof(field.required) == 'function') // Assume Required will never be removed or renamed - it is pretty basic
			return this.addField(field);
		else // Assume set
			return this.addSet(field);
    };

	this.addField = function(field) {
		this.fields.push(field);
		return this;
	}
	
	this.addSet = function(set) {
		this.sets.push(set);
		set.form_name = this.name;
		return this;
	}
	
	this.validateSets = function() {
		var errors = new Object();
		
		if (this.sets) {
			for (set in this.sets) {
				this.sets[set].validate();
				errors = array_merge_recursive(errors, this.sets[set].errors);
			}
		}
		return errors;
	}

    this.validate = function() {
        var errors = new Object();
        

		// Remove error class from block
		if (this.configuration.error_display_section) {
			remove_class(document.getElementById(this.name + '-errors'), 'vtErrors');
			remove_class(document.getElementById(this.name + '-errors'), 'vtNoErrors');
		}

        for(field in this.fields) {
            // Clear the errors
            remove_class(document.getElementsByName(this.form_name + '[' + this.fields[field].name +']')[0], this.configuration.error_class);
            
            if (document.getElementById('vt_' + this.form_name + '_' + this.fields[field].name + '-error')) { 
                document.getElementById('vt_' + this.form_name + '_' + this.fields[field].name + '-error').innerHTML = '';                                
                remove_class(document.getElementById('vt_' + this.form_name + '_' + this.fields[field].name + '-error'), 'vtErrors');
                add_class(document.getElementById('vt_' + this.form_name + '_' + this.fields[field].name + '-error'), 'vtNoErrors');
            }
            
            // Load the new errors into the array
            errors = array_merge_recursive(errors, this.fields[field].validate(document.getElementById('vt_' + this.form_name), this.configuration));
        }
        this.errors = errors;
		var set_errors = this.validateSets();
        if (is_empty(errors) && is_empty(set_errors)) {
			if (this.configuration.error_display_section) add_class(document.getElementById(this.name + '-errors'), 'vtNoErrors');
        	return true;
        }
        
        // Output the errors
        
        if (this.configuration.error_display_section) var error_display_section = '<ul>';
        
        if (this.configuration.error_display_alert) var error_display_alert = '';
        
            for(field in errors) {
                add_class(document.getElementsByName(this.form_name + '[' + field + ']')[0], this.configuration.error_class);
                
                if (is_array(errors[field])) {
                    
                    for(error in errors[field]) {
                        
                        if (this.configuration.error_display_section) error_display_section += '<li>' + errors[field][error] + '</li>';
                        if (this.configuration.error_display_alert) error_display_alert += errors[field][error] + '\n';
                        if (document.getElementById('vt_' + this.form_name + '_' + field + '-error')) { 
                            document.getElementById('vt_' + this.form_name + '_' + field + '-error').innerHTML = errors[field][error];
                            remove_class(document.getElementById('vt_' + this.form_name + '_' + field + '-error'), 'vtNoErrors');
                            add_class(document.getElementById('vt_' + this.form_name + '_' + field + '-error'), 'vtErrors');
                    }
                        
                        
                    }
                } else {
                        if (this.configuration.error_display_section) error_display_section += '<li>' + errors[field] + '</li>';
                        if (this.configuration.error_display_alert) error_display_alert += errors[field] + '\n';
                        if (document.getElementById('vt_' + this.name + '_' + field + '-error')) {
                            document.getElementById('vt_' + this.name + '_' + field + '-error').innerHTML = errors[field];
                            remove_class(document.getElementById('vt_' + this.name + '_' + field + '-error'), 'vtNoErrors');
                            add_class(document.getElementById('vt_' + this.name + '_' + field + '-error'), 'vtErrors');
                    }
                }
            }
            
            
            if (this.configuration.error_display_section) {
				add_class(document.getElementById(this.name + '-errors'), 'vtErrors');
                document.getElementById(this.name + '-errors').innerHTML = error_display_section + '</ul>';
			}
            if (this.configuration.error_display_alert)
                alert(error_display_alert);

            if (this.configuration.focus == 'first_error_field')
                // Get the first key - TODO there must be a better way than this
                for (var field in errors) {
                    document.getElementsByName(this.form_name + '[' + field + ']')[0].focus();
                    break;
                }
            else if (this.configuration.focus == 'error_section')
                window.location = '#' + this.form_name + '-errors';

        return false;
    }
    // end functions
    
    return this;
}

vtSet = vtForm;

function vtField(name) {
    // If it's being called as a function - call the constructor
    // (allow for shorthand)
    if (this.window) return new vtField(name);
    
    
    // Member variables
    this.validations = new Array();
    // end member variables
    
    // Constructor
    this.name = name;
    // end constructor
    
    // Functions
    this.add = function(validation) {
       this.validations.push(validation);
       return this;
    };
    
    this.validate = function(form, configuration) {
        var errors = new Object();
        for(validation in this.validations) {
            errors = array_merge_recursive(errors, this.validations[validation].validate(form, this.name, configuration));
        }
        return errors;
    }
    
    
        // Shorthand
        
        this.required = function(options, error) {
			return this.add(vtRequired(options, error));
		}
		
        this.matches= function(regex, options, error) {
			return this.add(vtMatches(regex, options, error));
		}
		
		this.lengthBetween = function(min, max, options, error) {
		    return this.add(vtLengthBetween(min, max, options, error));
		}
		
		this.minimumLength = function(min, options, error) {
		    return this.add(vtMinimumLength(min, options, error));
		}
		
		this.maximumLength = function(max, options, error) {
		    return this.add(vtMaximumLength(max, options, error));
		}
		
		this.number = function(options, error) {
		    return this.add(vtNumber(options, error));
		}
		
		this.between = function(min, max, options, error) {
		    return this.add(vtBetween(min, max, options, error));
		}
		
		this.minimum = function(min, options, error) {
		    return this.add(vtMinimum(min, options, error));
		}
		
		this.maximum = function(max, options, error) {
		    return this.add(vtMaximum(max, options, error));
		}
		
		this.options = function(options, _options, error) {
		    return this.add(vtOptions(options, _options, error));
		}
		
		this.notOptions = function(options, _options, error) {
		    return this.add(vtNotOptions(options, _options, error));
		}
		
		this.notEqual = function(to, options, error) {
		    return this.add(vtNotEqual(to, options, error))
		}
		
		this.email = function(options, error) {
		    return this.add(vtEmail(options, error));
		}
		
		this.date = function(options, error) {
		    return this.add(vtDate(options, error));
		}
		
		this.dateBetween = function(earliest, latest, options, error) {
		    return this.add(vtDateBetween(earliest, latest, options, error));
		}
		
		this.dateEarliest = function(earliest, options, error) {
		    return this.add(vtDateEarliest(earliest, options, error));
		}
		
		this.dateLatest = function(latest, options, error) {
		    return this.add(vtDateLatest(latest, options, error));
		}
		
		this.custom = function(javascript_function, javascript_array) {
		    return this.add(vtCustom(javascript_function, javascript_array));
		}
		
		this.sameAs = function(other_function, options, error) {
		    return this.add(vtSameAs(other_function, options, error));
		}
		
		// end shorthand
    // end functions
    
    return this;
}

// Validations

    function vtRequiredValidation(options, error) {
        // If it's being called as a function - call the constructor
        // (allow for shorthand)
        if (this.window) return new vtRequiredValidation(options, error);
    
    
        // Member variables
        // end member variables
    
        // Constructor
        if (is_string(options)) {
            error = options
            options = new Object()
        }
        this.options = options
        this.error = error || '%n is required';
        // end constructor
    
        // Functions
        this.validate = function(form, key, configuration) {
            if (document.getElementsByName(form.name + '[' + key + ']')[0].value.length == 0) {
                var ret = new Object();
                ret[key] = [this.error.replace(/%n/, labelize(key))];
                return ret;  
            }
            return {};
        };
        // end functions
    
        return this;
    }

    var vtRequired = vtRequiredValidation;

    function vtRegularExpressionValidation(regex, options, error) {
        // If it's being called as a function - call the constructor
        // (allow for shorthand)
        if (this.window) return new vtRegularExpressionValidation(regex, options, error);
    
    
        // Member variables
        // end member variables
    
        // Constructor
        if (is_string(options)) {
            error = options
            options = new Object()
        }
        this.options = options
        this.error = error || '%n must match ' + regex;
        this.regex = new RegExp(regex);
        // end constructor
    
        // Functions
        this.validate = function(form, key, configuration) {
            if (document.getElementsByName(form.name + '[' + key + ']')[0].value.length == 0) return {};
            
            if (document.getElementsByName(form.name + '[' + key + ']')[0].value.match(this.regex)) return {};
            else {
                var ret = new Object();
                ret[key] = [this.error.replace(/%n/, labelize(key))];
                return ret; 
            }
        };
        // end functions
    
        return this;
    }

    var vtRegexValidation = vtRegularExpressionValidation;
    var vtMatches = vtRegexValidation;
    
    function vtEmailAddressValidation(options, error) {
        // If it's being called as a function - call the constructor
        // (allow for shorthand)
        if (this.window) return new vtEmailAddressValidation(options, error);
    
    
        // Member variables
        // end member variables
    
        // Constructor
        if (is_string(options)) {
            error = options
            options = new Object()
        }
        this.options = options
        this.error = error || '%n must be a valid email address'
        // end constructor
    
        // Functions
        this.validate = function(form, key, configuration) {
            if (document.getElementsByName(form.name + '[' + key + ']')[0].value.length == 0) return {};
            
            if (document.getElementsByName(form.name + '[' + key + ']')[0].value.match(/^.*@.*\..*$/)) return {};
            else {
                var ret = new Object();
                ret[key] = [this.error.replace(/%n/, labelize(key))];
                return ret; 
            }
        };
        // end functions
    
        return this;
    }

    var vtEmailValidation = vtEmailAddressValidation;
    var vtEmail = vtEmailValidation;
    
    function vtNumberValidation(options, error) {
        // If it's being called as a function - call the constructor
        // (allow for shorthand)
        if (this.window) return new vtNumberValidation(options, error);
    
        // Member variables
        // end member variables
    
        // Constructor
        if (is_string(options)) {
            error = options
            options = new Object()
        }
        this.options = options
        this.error = error || '%n must be a number';
		var number_characters = '\\d'
		
		if (this.options.separator != undefined) {
			if (this.options.separator === true) {
				// Default to comma separator
				number_characters = '[\\d,]';
			} else {
				number_characters = '[\\d' + RegExp.escape(this.options.separator) + ']';
			}
		}
		
		var regex_start = '^';
		if (this.options.negative != undefined) {
			if (this.options.negative === true) {
				// Default to - negative
				regex_start = '^-?';
			} else {
				regex_start = '^' + RegExp.escape(this.options.negative) + '?';
			}
		}
		
		var regex_middle = number_characters + '+';
		if (this.options.decimal) {
			if (this.options.decimal === true) {
				// Default to . decimal
				regex_middle = number_characters + '+(\\.' + number_characters + '+)?';
			} else {
				regex_middle = number_characters + '+(' + RegExp.escape(this.options.decimal) + number_characters + '+)?';
			}
		}
        this.regex = new RegExp(regex_start + regex_middle + '$');
        // end constructor
    
        // Functions
        this.validate = function(form, key, configuration) {
            if (document.getElementsByName(form.name + '[' + key + ']')[0].value.length == 0) return {};
            
            if (document.getElementsByName(form.name + '[' + key + ']')[0].value.match(this.regex)) return {};
            else {
                var ret = new Object();
                ret[key] = [this.error.replace(/%n/, labelize(key))];
                return ret; 
            }
        };
        // end functions
    
        return this;
    }

    var vtNumber = vtNumberValidation;

    function vtLengthValidation(min, max, options, error) {
        // If it's being called as a function - call the constructor
        // (allow for shorthand)
        if (this.window) return new vtLengthValidation(min, max, options, error);
    
    
        // Member variables
        // end member variables
    
        // Constructor
        if (is_string(options)) {
            error = options
            options = new Object()
        }
        this.options = options
        if (min && max) var default_error = '%n must be between ' + min + ' and ' + max + ' characters long';
		else if (min) var default_error = '%n must be at least ' + min + ' characters long';
		else var default_error = '%n must be at most ' + max + ' characters long';
        this.error = error || default_error;
        this.min = min;
        this.max = max;
        // end constructor
    
        // Functions
        this.validate = function(form, key, configuration) {
            if (document.getElementsByName(form.name + '[' + key + ']')[0].value.length == 0) return {};
            
            var data = document.getElementsByName(form.name + '[' + key + ']')[0].value;
            
            if (this.options.ignore != undefined) {
                if (is_array(this.options.ignore)) {
                    for (i in this.options.ignore) {
                        data = data.replace(new RegExp(RegExp.escape(this.options.ignore[i]), "g"), '');
                    }
                } else {
                    for (var i = 0; i < this.options.ignore.length; i++)
						data = data.replace(new RegExp(RegExp.escape(this.options.ignore.substr(i, i+1)), "g"), '');
                }
            }
            
            if (this.min && data.length < this.min || this.max && data.length > this.max)   {
                var ret = new Object();
                ret[key] = [this.error.replace(/%n/, labelize(key))];
                return ret; 
            } else return {};
        };
        // end functions
    
        return this;
    }

    var vtLengthBetween = vtLengthValidation;
    var vtMinimumLength = function(min, error) { return vtLengthBetween(min, null, error); };
    var vtMaximumLength = function(max, error) { return vtLengthBetween(null, max, error); };
    
    function vtOptionsValidation(options, _options, error) {
        // If it's being called as a function - call the constructor
        // (allow for shorthand)
        if (this.window) return new vtOptionsValidation(options, _options, error);
    
        // Member variables
        // end member variables
    
        // Constructor
        if (is_string(_options)) {
            error = _options
            _options = new Object()
        }
        this._options = _options
        
        if (_options.should_be_in == undefined) _options.should_be_in = true;
        
        if (_options.should_be_in) var default_error = '%n must be in the options';
		else var default_error = '%n must not be in the options';
        this.error = error || default_error;
        this.options = options;
        // end constructor
    
        // Functions
        this.validate = function(form, key, configuration) {
            if (document.getElementsByName(form.name + '[' + key + ']')[0].value.length == 0) return {};
            
            var value = document.getElementsByName(form.name + '[' + key + ']')[0].value;
            
            for (var option in this.options){
                if (value == this.options[option]) {
                    if (this._options.should_be_in) return {}
                    else {
                        var ret = new Object();
                        ret[key] = [this.error.replace(/%n/, labelize(key))];
                        return ret;
                    }
                }
            }
            if (this._options.should_be_in) {
                var ret = new Object();
                ret[key] = [this.error.replace(/%n/, labelize(key))];
                return ret;
            } else return {}
        };
        // end functions
    
        return this;
    }
    var vtOptions = vtOptionsValidation;
    var vtNotOptions = function(options, _options, error) { return vtOptions(options, array_merge({should_be_in: false}, _options), error); };
    
    function vtNumberRangeValidation(min, max, options, error) {
         // If it's being called as a function - call the constructor
         // (allow for shorthand)
         if (this.window) return new vtNumberRangeValidation(min, max, options, error);


         // Member variables
         // end member variables

         // Constructor
         if (is_string(options)) {
             error = options
             options = new Object()
         }
         this.options = options
         if (min && max) var default_error = '%n must be between ' + min + ' and ' + max;
 		else if (min) var default_error = '%n must be at least ' + min;
 		else var default_error = '%n must be at most ' + max;
         this.error = error || default_error;
         this.min = min;
         this.max = max;
         // end constructor

         // Functions
         this.validate = function(form, key, configuration) {
             if (document.getElementsByName(form.name + '[' + key + ']')[0].value.length == 0) return {};

             if (this.min && parseInt(document.getElementsByName(form.name + '[' + key + ']')[0].value) < this.min || this.max && parseInt(document.getElementsByName(form.name + '[' + key + ']')[0].value) > this.max)   {
                 var ret = new Object();
                 ret[key] = [this.error.replace(/%n/, labelize(key))];
                 return ret; 
             } else return {};
         };
         // end functions

         return this;
     }

     var vtBetween = vtNumberRangeValidation;
     var vtMinimum = function(min, options, error) { return vtBetween(min, null, options, error); };
     var vtMaximum = function(max, options, error) { return vtBetween(null, max, options, error); };

     function vtDateValidation(options, error) {
          // If it's being called as a function - call the constructor
          // (allow for shorthand)
          if (this.window) return new vtDateValidation(options, error);


          // Member variables
          // end member variables

          // Constructor
          if (is_string(options)) {
              error = options
              options = new Object()
          }
          this.options = options
          this.error = error || '%n must be a date';
          // end constructor

          // Functions
          this.validate = function(form, key, configuration) {
              if (document.getElementsByName(form.name + '[' + key + ']')[0].value.length == 0) return {};
            
              // First check the format
              date = extract_date(document.getElementsByName(form.name + '[' + key + ']')[0].value, configuration.date_format);
              if (!date) {
                  var ret = new Object();
                  ret[key] = [this.error.replace(/%n/, labelize(key))];
                  return ret;
              }
              
              if (checkdate(date.month, date.day, date.year)) return {}
               
              var ret = new Object();
              ret[key] = [this.error.replace(/%n/, labelize(key))];
              return ret;
          };
          // end functions

          return this;
      }

      var vtDate = vtDateValidation;
      
      function vtDateRangeValidation(earliest, latest, options, error) {
           // If it's being called as a function - call the constructor
           // (allow for shorthand)
           if (this.window) return new vtDateRangeValidation(earliest, latest, options, error);


           // Member variables
           // end member variables

           // Constructor
           if (is_string(options)) {
               error = options
               options = new Object()
           }
           this.options = options
           if (earliest && latest) var default_error = '%n must be between ' + earliest + ' and ' + latest;
   		   else if (earliest) var default_error = '%n must be at least ' + earliest;
   		   else var default_error = '%n must be at most ' + latest;
           this.error = error || default_error;
           this.earliest = earliest;
           this.latest = latest;
           // end constructor

           // Functions
           this.validate = function(form, key, configuration) {
               if (document.getElementsByName(form.name + '[' + key + ']')[0].value.length == 0) return {};
               date = extract_date(document.getElementsByName(form.name + '[' + key + ']')[0].value, configuration.date_format);
               // Only validate if it is a date
               if (!date) return {};
               
               var date_object = new Date();
               date_object.setFullYear(date.year, date.month, date.day);
               if (this.earliest) {
                   var earliest_date = extract_date(this.earliest, configuration.date_format);
                   if (!earliest_date) return {};
                   earliest_date_object = new Date()
                   earliest_date_object.setFullYear(earliest_date.year, earliest_date.month, earliest_date.day);
                   if (date_object.getTime() < earliest_date_object.getTime()) {
                       var ret = new Object();
                       ret[key] = [this.error.replace(/%n/, labelize(key))];
                       return ret;
                   }
               }
               if (this.latest) {
                   var latest_date = extract_date(this.latest, configuration.date_format);
                   if (!latest_date) return {};
                   latest_date_object = new Date()
                   latest_date_object.setFullYear(latest_date.year, latest_date.month, latest_date.day);
                   if (date_object.getTime() > latest_date_object.getTime()) {
                       var ret = new Object();
                       ret[key] = [this.error.replace(/%n/, labelize(key))];
                       return ret;
                   }
               }
               
               return {};
           };
           // end functions

           return this;
       }

       var vtDateBetween = vtDateRangeValidation;
       var vtDateEarliest = function(earliest, options, error) { return vtBetween(earliest, null, options, error); };
       var vtDateLatest = function(latest, options, error) { return vtBetween(null, latest, options, error); };

       function vtCustomValidation(javascript_function, javascript_array) {
            // If it's being called as a function - call the constructor
            // (allow for shorthand)
            if (this.window) return new vtCustomValidation(javascript_function, javascript_array);


            // Member variables
            // end member variables

            // Constructor
            this.javascript_function = javascript_function;
            this.javascript_array = javascript_array
            // end constructor

            // Functions
            
            // The custom function gets given the form name, the current key, and configuration
            // To get the value use document.getElementsByName(form.name + '[' + key + ']')[0].value
            // Return an "associative array" like:
            //  var ret = new Object();
            //  ret[key] = "ERROR!";
            //  return ret;
            // if there is an error, otherwise return {}
            this.validate = function(form, key, configuration) {
                if (document.getElementsByName(form.name + '[' + key + ']')[0].value.length == 0) return {};
                return window[this.javascript_function](form, key, configuration, this.javascript_array);
            };
            // end functions

            return this;
        }

        var vtCustom = vtCustomValidation;
        
        function vtSameAsValidation(other_field, options, error) {
             // If it's being called as a function - call the constructor
             // (allow for shorthand)
             if (this.window) return new vtSameAsValidation(other_field, options, error);


             // Member variables
             // end member variables

             // Constructor
             if (is_string(options)) {
                 error = options
                 options = new Object()
             }
             this.options = options
             this.other_field = other_field;
             this.error = error || '%n must be the same as ' + labelize(other_field);
             // end constructor

             // Functions
             this.validate = function(form, key, configuration) {
                 if (document.getElementsByName(form.name + '[' + key + ']')[0].value == document.getElementsByName(form.name + '[' + this.other_field + ']')[0].value) {
                    return {};
                 }
                 var ret = new Object();
                 ret[key] = [this.error.replace(/%n/, labelize(key))];
                 return ret;
             };
             // end functions

             return this;
         }

         var vtSameAs = vtSameAsValidation;
         
         
         function vtOptionalValidation(validation, field, requirement) {
             // If it's being called as a function - call the constructor
             // (allow for shorthand)
             if (this.window) return new vtOptionalValidation(validation, field, requirement);


             // Member variables
             // end member variables

             // Constructor
             this.validation = validation;
             this.field = field;
             this.requirement = requirement;
             // end constructor

             // Functions
             this.validate = function(form, key, configuration) {
                if (!is_empty(this.requirement.validate(form, this.field, configuration))) {
                    return this.validation.validate(form, key, configuration);
                }
                return [];
             };
             // end functions

             return this;
         }

         var vtOptional = vtOptionalValidation;

         function vtReverseValidation(validation, error) {
             // If it's being called as a function - call the constructor
             // (allow for shorthand)
             if (this.window) return new vtReverseValidation(validation, error);


             // Member variables
             // end member variables

             // Constructor
             this.validation = validation;
             this.error = error || "%n failed the reverse validation" // TODO: better error
             // end constructor

             // Functions
             this.validate = function(form, key, configuration) {
                if (!is_empty(this.validation.validate(form, key, configuration))) {
                    return [];
                }
                var ret = new Object();
                ret[key] = [this.error.replace(/%n/, labelize(key))];
                return ret;
             };
             // end functions

             return this;
         }

         var vtReverse = vtReverseValidation;
// end validations


function labelize(string) {
    return ucwords(string.replace(/_/g,' '));
}

function is_empty(o) {
    var i, v;
    if (typeof(o) === 'object') {
        for (i in o) {
            v = o[i];
            if (v !== undefined && typeof(v) !== 'function') {
                return false;
            }
        }
    }
    return true;
}

// TODO: The following three functions may just be hiding coding problems
// look into them and determine if they can be improved
function has_class(element,value) {
	if (element !== undefined && element.className !== undefined)
		return element.className.match(new RegExp('(\\s|^)'+value+'(\\s|$)'));
}
 
function add_class(element, value) {
	if (element !== undefined && element.className !== undefined)
		if (!has_class(element,value)) element.className += " "+value;
}
 
function remove_class(element, value) {
	if (element !== undefined && element.className !== undefined) {
		if (has_class(element,value)) {
	    	var reg = new RegExp('(\\s|^)+'+value+'(\\s|$)+');
			element.className=element.className.replace(reg,' ');
		}
	}
}

function extract_date(date, format) {
    var regex = new RegExp(format.replace("DD", "(\\d+)").replace("MM", "(\\d+)").replace("YYYY", "(\\d+)"));
    // To get around javascripts lack of named parameters - figure out the order
    // todo: make this more efficient - this is a stupid way there must be something easier
    var dd_pos = format.indexOf('DD');
    var mm_pos = format.indexOf('MM');
    var yyyy_pos = format.indexOf('YYYY');
    var matches = regex.exec(date);
    if (!matches) return null;
    if (dd_pos < mm_pos) {
        if (mm_pos < yyyy_pos) {
            // DD - MM - YYYY
            var day = matches[1];
            var month = matches[2];
            var year = matches[3];
        } else {
            if (dd_pos < yyyy_pos) {
                // DD - YYYY - MM
                var day = matches[1];
                var month = matches[3];
                var year = matches[2];
            } else {
                // YYYY - DD - MM
                var day = matches[2];
                var month = matches[3];
                var year = matches[1];
            }
        }
    } else {
        if (dd_post < yyyy_pos) {
            // MM - DD - YYYY
            var day = matches[2];
            var month = matches[1];
            var year = matches[3];
        } else {
            if (mm_pos < yyyy_pos) {
                // MM - YYYY - DD
                var day = matches[3];
                var month = matches[1];
                var year = matches[2];
            } else {
                // YYYY - MM - DD
                var day = matches[3];
                var month = matches[2];
                var year = matches[1];
            }
        }
    }
    var ret = new Object();
    ret.month = month;
    ret.day = day;
    ret.year = year;
    return ret;
}

/**
 * Escape Regular Expressions
 */
RegExp.escape = function(text) {
  if (!arguments.callee.sRE) {
    var specials = [
      '/', '.', '*', '+', '?', '|',
      '(', ')', '[', ']', '{', '}', '\\'
    ];
    arguments.callee.sRE = new RegExp(
      '(\\' + specials.join('|\\') + ')', 'g'
    );
  }
  return text.replace(arguments.callee.sRE, '\\$1');
}


/**
 * The following functions are taken from the MIT licensed PHP.js
 * The license is available in licenses/php.js.txt
 */

function ucwords(string) {
    return string.replace(/^(.)|\s(.)/g, function (letter) { 
                                            return letter.toUpperCase(); 
                                        }
                         );
}

function array_merge_recursive (arr1, arr2){
    var idx = '';
 
    if ((arr1 && (arr1 instanceof Array)) && (arr2 && (arr2 instanceof Array))) {
        for (idx in arr2) {
            arr1.push(arr2[idx]);
        }
    } else if ((arr1 && (arr1 instanceof Object)) && (arr2 && (arr2 instanceof Object))) {
        for (idx in arr2) {
            if (idx in arr1) {
                if (typeof arr1[idx] == 'object' && typeof arr2 == 'object') {
                    arr1[idx] = array_merge(arr1[idx], arr2[idx]);
                } else {
                    arr1[idx] = arr2[idx];
                }
            } else {
                arr1[idx] = arr2[idx];
            }
        }
    }
    
    return arr1;
}

function array_merge() {
    var args = Array.prototype.slice.call(arguments);
    var retObj = {}, k, j = 0, i = 0;
    var retArr;
    
    for (i=0, retArr=true; i < args.length; i++) {
        if (!(args[i] instanceof Array)) {
            retArr=false;
            break;
        }
    }
    
    if (retArr) {
        return args;
    }
    var ct = 0;
    
    for (i=0, ct=0; i < args.length; i++) {
        if (args[i] instanceof Array) {
            for (j=0; j < args[i].length; j++) {
                retObj[ct++] = args[i][j];
            }
        } else {
            for (k in args[i]) {
                if (this.is_int(k)) {
                    retObj[ct++] = args[i][k];
                } else {
                    retObj[k] = args[i][k];
                }
            }
        }
    }
    
    return retObj;
}

function is_int( mixed_var ) {
    if (typeof mixed_var !== 'number') {
        return false;
    }
 
    if (parseFloat(mixed_var) != parseInt(mixed_var, 10)) {
        return false;
    }
    
    return true;
}

function is_string( mixed_var ) {
    return (typeof(mixed_var) == 'string');
}

function is_array( mixed_var ) {
    var key = '';
    var getFuncName = function (fn) {
        var name = (/\W*function\s+([\w\$]+)\s*\(/).exec(fn);
        if(!name) {
            return '(Anonymous)';
        }
        return name[1];
    };
 
    if (!mixed_var) {
        return false;
    }
 
    // BEGIN REDUNDANT
    this.php_js = this.php_js || {};
    this.php_js.ini = this.php_js.ini || {};
    // END REDUNDANT
 
    if (typeof mixed_var === 'object') {
 
        if (this.php_js.ini['phpjs.objectsAsArrays'] &&  // Strict checking for being a JavaScript array (only check this way if call ini_set('phpjs.objectsAsArrays', 0) to disallow objects as arrays)
            (
            (this.php_js.ini['phpjs.objectsAsArrays'].local_value.toLowerCase &&
                    this.php_js.ini['phpjs.objectsAsArrays'].local_value.toLowerCase() === 'off') ||
                parseInt(this.php_js.ini['phpjs.objectsAsArrays'].local_value, 10) === 0)
            ) {
            return mixed_var.hasOwnProperty('length') && // Not non-enumerable because of being on parent class
                            !mixed_var.propertyIsEnumerable('length') && // Since is own property, if not enumerable, it must be a built-in function
                                getFuncName(mixed_var.constructor) !== 'String'; // exclude String()
        }
 
        if (mixed_var.hasOwnProperty) {
            for (key in mixed_var) {
                // Checks whether the object has the specified property
                // if not, we figure it's not an object in the sense of a php-associative-array.
                if (false === mixed_var.hasOwnProperty(key)) {
                    return false;
                }
            }
        }
 
        // Read discussion at: http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_phps_is_array/
        return true;
    }
 
    return false;
}

function checkdate(month, day, year) {
    var myDate = new Date();
    myDate.setFullYear( year, (month - 1), day );
    return ((myDate.getMonth()+1) == month && day<32); 
}
