(function(){
  
  this.rsv = {};
  
  /**
   * trim function, stolen from jQuery
   * @param text
   * @return
   */
  function trim(text) {
    return (text || "").replace( /^\s+|\s+$/g, "" );
  }
  
  this.rsv.Validators = {
    digits: /^[0-9]*$/,
    letters: /^[A-Za-z]*$/,
    email: /^[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-zis]{2,4}$/,
    
    /**
     * Ensures that a field exists and is not empty string
     */
    required: function(rule, fields) {
      var value = fields[rule[1]];
      
      if( value === undefined || value === null ) {
        return [rule[1], rule[2]];
      }
      
      if( trim(value).length === 0 ) {
        return [rule[1], rule[2]];
      }
      
      return null;
    },
    
    /**
     * Ensures that a field only contains digits.
     */
    digits_only: function(rule, fields) {
      var value = fields[rule[1]];
      var match = value.match(rsv.Validators.digits);
      if( !match ) {
        return [rule[1], rule[2]];
      }
      
      return null;
    },
    
    /**
     * Ensures that a field only contains letters
     */
    letters_only: function(rule, fields) {
      var value = fields[rule[1]];
      var match = value.match(rsv.Validators.letters);
      if( !match ) {
        return [rule[1], rule[2]];
      }
      
      return null;
    },
    
    /**
     * Ensures that email isprovided and exists.
     */
    valid_email: function(rule, fields) {
      var value = fields[rule[1]];
      if( value === undefined || value === null ) {
        return [rule[1], rule[2]];
      }
      
      var match = value.match(rsv.Validators.email);
      if (!match) {
        return [rule[1], rule[2]];
      }
      
      return null;
    },
    
    /**
     * Ensures that web URL is well-formed.
     */
    valid_web_url: function(rule, fields) {
      var value = fields[rule[1]];
      if (value === undefined || value === null) {
        return [rule[1], rule[2]];
      }
      
      var match = value.match(rsv.Validators.web_url);
      if (!match) {
        return [rule[1], rule[2]];
      }
      
      return null;
    },
    
    /**
     * Compares 2 fields, ok if both values the same.
     */
    same_as: function(rule, fields) {
      var val1 = fields[rule[1]];
      var val2 = fields[rule[2]];
      
      if( val1 != val2 ) {
        return [rule[1], rule[3]];
      }
      
      return null;
    },
    
    /**
     * Compares a field to a regular expression. Failure if no match.
     */
    reg_exp: function(rule, fields) {
      var flags = '';
      if( rule.length == 5 ) {
        flags = rule[3].toLowerCase();
      }
        
      var regex = new RegExp(rule[2], flags);
      
      var value = fields[rule[1]];
      var match = value.match(regex);
      if( !match ) {
        return [rule[1], rule[rule.length-1]];
      }
      
      return null;
    },
    
    /**
     * SYNTAX:      
     * rule[0] => 'length'
     * rule[1] => FIELD-NAME (string)
     * rule[2] => COMPARISON (string)
     * rule[3] => LENGTH (integer)
     * 
     * COMPARISON can be one of:
     *   - '<' or 'lt': assert "len(FIELD) < INTEGER"
     *   - '<=' or 'lt': assert "len(FIELD) <= INTEGER"
     *   - '=', '==', or 'eq': assert "len(FIELD) == INTEGER"
     *   - '>=' or 'gte': assert "len(FIELD) >= INTEGER"
     *   - '>' or 'gt': assert "len(FIELD) > INTEGER"
     */
    length: function(rule, fields) {
      var value = fields[rule[1]];
      var target = parseInt(rule[3]);
      var op = rule[2];
      var comp = null;
      
      if (op == '<' || op == 'lt') {
        comp = function(a, b) { return a < b; }
      } else if (op == '<=' || op == 'lte') {
        comp = function(a, b) { return a <= b; }
      } else if (op == '=' || op == '==' || op == 'eq') {
        comp = function(a, b) { return a == b; }
      } else if (op == '>=' || op == 'gte') {
        comp = function(a, b) { return a >= b; }
      } else if (op == '>' || op == 'gt') {
        comp = function(a, b) { return a > b; }
      }
      
      if (!comp(value.length, target)) {
        return [rule[1], rule[rule.length - 1]];
      }
      
      return null;
    }

    //TODO: range
    //TODO: valid_date
    //TODO: is_alpha
    //TODO: custom_alpha
    
  };
  
  /**
   * Validates an object of fields using the passed in rules.
   */
  this.rsv.validate = function(fields, rules) {
    var errors = {};
    
    for( var i = 0; i < rules.length; i++ ) {
      var rule = rules[i];
      
      // Skip this rule if its field has already been tagged as in-error
      if (errors[rule[1]] !== undefined) { continue; }
      
      //TODO: conditionals
      
      var requirement = rule[0];
    
      if( rsv.Validators[requirement] !== undefined ) {
        var result = rsv.Validators[requirement](rule, fields);
        
        if( result !== null ) {
          errors[result[0]] = result[1];
        } else {
          //flag a warning.
        }
      }
    }
    
    return errors; //TODO: think about this I do not think it works (ie. is useful).
  };
  
})();
