var liveValidate = new Class({
  options : {
    invalidClass : 'invalid',
    validClass : 'valid',
    statusAppend : '_status',
    inputSpace : 5
  },
  
  currentFields : {}, 
  
  rules : {
    // **** STRING LENGTH **** 
    'at_least' : function(value, length){
      if(value.length >= length){
        return true;
      } else {
        return false;
      }
    },
    'at_most' : function(value, length){
      if(value.length <= length){
        return true;
      } else {
        return false;
      }
    },
    // **** REGEX ****
    'regex' : function(value, regex){
      if(value.match(regex)){
        return true;
      } else {
        return false;
      }
    },
    // *** COMPARISON ***
    'not_equal' : function(value, comparison){
      var compare = null;
      if($chk($(comparison))){
        other = $(comparison).value;
      } else {
        other = comparison;
      }
      if(other != value){
        return true;
      } else {
        return false;
      }
    },
    'equal' : function(value, comparison){
      var compare = null;
      if($chk($(comparison))){
        other = $(comparison).value;
      } else {
        other = comparison;
      }
      if(other == value){
        return true;
      } else {
        return false;
      }
    },
    // *** DATE/TIME ***
    'date' : function(value, comparison){
      if(value.match(/\b(0?[1-9]|1[012])[- /.](0?[1-9]|[12][0-9]|3[01])[- /.](19|20)?[0-9]{2}\b/)){
        if(comparison==true){
          return true;
        } else {
          return false;
        }
      } else {
        if(comparison==true){
          return false;
        } else {
          return true;
        }
      }
    },
    'time' : function(value, comparison){
      // TODO: create time regex
      return false;
    },
    // *** PHONE/EMAIL/ADDRESS ***
    'phone' : function(value, comparison){
      if(value.match(/\(?\b[0-9]{3}\)?[-. ]?[0-9]{3}[-. ]?[0-9]{4}\b/)){
        if(comparison==true){
          return true;
        } else {
          return false;
        }
      } else {
        if(comparison==true){
          return false;
        } else {
          return true;
        }
      }
    },
    'email' : function(value, comparison){
      if(value.match(/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b/i)){
        if(comparison==true){
          return true;
        } else {
          return false;
        }
      } else {
        return false;
      }
    }              
  },

  initialize : function(){
    var cObj = this;
    window.addEvent('resize', function(){
      var resizeClosure = cObj.adjustAllStatus.create({
        'bind' : cObj
      });
      resizeClosure();
    });
  },
      

  // ##################################################################
  // ++ addField(inputElement, elementRules)
  // == This is the primary method for interacting with liveValidate.
  // == Pass this method the input element you would like validation
  // == rules to apply to and then the rules object. For more information
  // == on the validation rules, see the documentation.
  // ##################################################################
  addField : function(el, rules){
    var cObj = this;   
        
    var statusEl = new Element('span').set({
      'id' : $(el).id + this.options.statusAppend,
      'title' : rules.message
    }).injectInside($$('body')[0]);
    
    if($chk(rules.message)){      
      var tips = new Tips(statusEl, {'fixed' : true});
    }
    
    this.adjustStatus(statusEl, el);
    
    this.currentFields[$(el).id] = new field($(el).id, rules);
    
    
    $(el).addEvents({
      'keyup' : function(){
        var statusClosure = cObj.checkStatus.create({
          'arguments' : {
            'el' : el,
            'rules' : rules
          },
          'bind' : cObj
        });
        statusClosure();
      },
      'focus' : function(){
        var statusClosure = cObj.checkStatus.create({
          'arguments' : {
            'el' : el,
            'rules' : rules
          },
          'bind' : cObj        
        });
        statusClosure();
      },
      'blur' : function(){
        var statusClosure = cObj.checkStatus.create({
          'arguments' : {
            'el' : el,
            'rules' : rules
          },
          'bind' : cObj        
        });
        statusClosure();
      }
    });      
  },
  
  // #############################################################################
  // ++ adjustStatus(statusElement, inputElement)
  // == This method is called internally when all the fields are added and also
  // == whenever a field's validation rules are checked. This method re-adjusts
  // == the validation status element in relationship to the input element.
  // #############################################################################
  adjustStatus : function(statusEl, el){
    var dimensions = $(el).getCoordinates();
    var inputWidth = dimensions.width;
    var inputHeight = dimensions.height;
    var inputX = dimensions.left;
    var inputY = dimensions.top;

    statusEl.setStyles({
      'position' : 'absolute',
      'top' : inputY + (inputHeight / 20) + 'px',
      'left' : (inputX + inputWidth) + this.options.inputSpace + 'px'
    });
  },
  
  adjustAllStatus : function(){
    var cObj = this;
    $each(this.currentFields, function(value, name){
      cObj.adjustStatus($(value.name + cObj.options.statusAppend), $(value.name));
    });
  },
  
  // ############################################################################
  // ++ addFields(arr)
  // == Pass an object of fields containing their rules to add a bunch at once.
  // ############################################################################
  addFields : function(obj){
    var cObj = this;
    $each(obj, function(value, name){
      cObj.addField(name, value);
    });
  },
  
  // ###########################################################################
  // ++ validateAll()
  // == Execute this method before submitting the form. It will run through all
  // == added fields and re-check them. Return true if everything passes
  // == validation, otherwise returns false.
  // ###########################################################################
  validateAll : function(){
    var cObj = this;
    var valid = true;
    $each(this.currentFields, function(value, name){
      value.valid = cObj.checkStatus({
        'el' : name,
        'rules' : value.rules
      });
      if(value.valid == false){
        valid = false;
      }
    });
    return valid;
  },  
  
  // ###########################################################################
  // ++ checkStatus(arguments)
  // == Internal function, not necessary for normal use. Main check method, runs 
  // == through all rules for the passed element and returns true if the element
  // == passes all of the validation requirements.
  // ###########################################################################
  checkStatus : function(args){
    var el = args.el;
    var rules = args.rules;
    var elId = $(el).id;   
    
    this.adjustStatus($(elId + this.options.statusAppend), $(el));

    var cObj = this;

    var elValue = $(el).value;
    var valid = true;
    $each(rules, function(value, name){
      if(typeof cObj.rules[name] == 'function'){
        if(cObj.rules[name](elValue, value) && valid){
          $(elId + cObj.options.statusAppend).setProperty('class', cObj.options.validClass);
        } else {
          $(elId + cObj.options.statusAppend).setProperty('class', cObj.options.invalidClass);
          valid = false;
        }
      }
    });
    return valid;
  }      
});

// #############################
// == class field(element_id, validation_rules)
// == Simple class used internally for storing add fields.
// #############################
var field = new Class({
  rules : null,
  name : null,
  valid: false,
  
  initialize : function(name, rules){
    this.name = name;
    this.rules = rules;
  }
});
