function FormValidation(form, options) {
  if (arguments.length > 0) this.init(form, options)
}

FormValidation.prototype.init = function(form, options){
  var self = this
  self.form = getElem(form)
  self.options = dict(this.defaultOptions).merge(options || {})
  self.validations = dict()
  addEvent(self.form, 'submit', function(ev){ self.handleSubmit(ev) })
}

FormValidation.prototype.registerValidation = function(fieldName, fn, options){
  var self = this
  if (undef(self.validations[fieldName])) self.validations[fieldName] = []
  var validation = { 
    field   : self.form.elements[fieldName],
    fn      : fn, 
    options : options
  }
  if (!validation.field) throw("Error registering validation, field does not exist: " + fieldName)
  validation.options.isRadio = isList(validation.field) && map(validation.field).all("item.type == 'radio'")
  self.validations[fieldName].push(validation)
  if (!validation.field._validateOnChange) {
    list('change, blur').each(function(evname){ 
      addEvent(validation.field, evname, function(ev){ self.validateField(ev.currentTarget) }) 
    })
  }
  validation.field._validateOnChange = true
  
}

FormValidation.prototype.fixOptions = function(options, messageName) {
  if (undef(options))            options            = {}
  if (undef(options.message))    options.message    = this.defaultMessages[messageName]
  if (undef(options.trim))       options.trim       = true
  if (undef(options.allowEmpty)) options.allowEmpty = true
  return options
}

FormValidation.prototype.isFieldEmpty = function(field, options) {
  return options.isRadio ?
    !map(field).any(function(radio){ return radio.checked }) : // no radio in set checked
    !field.value.toString().length                             // string empty
}

FormValidation.prototype.validatesWithFunction = function(fieldNames, fn, options) {
  var self = this
  options = self.fixOptions(options, 'default')
  list(fieldNames).each(function(fieldName){ self.registerValidation(fieldName, fn, options) })
}

FormValidation.prototype.validatesWithRegexp = function(fieldNames, rx, options){
  options = this.fixOptions(options, 'default')
  this.validatesWithFunction(fieldNames, function(field){ return rx.test(field.value) }, options)
}

FormValidation.prototype.validatesPresenceOf = function(fieldNames, options){
  var self = this
  options = self.fixOptions(options, 'presence')
  options.allowEmpty = false
  self.validatesWithFunction(fieldNames, function(field){ return !self.isFieldEmpty(field, options) }, options)
}

FormValidation.prototype.validatesEmail = function(fieldNames, options){
  options = this.fixOptions(options, 'email')
  this.validatesWithRegexp(fieldNames, /^[\w&+-]+(\.[\w&+-]+)*@[\w-]+(\.[\w-]+)*\.[\w-]{2,}$/, options)
}

function isNumberWithinRange(number, range, excluding) {
  var n = parseInt(number)
  var min = range[0]
  var max = range[1]
  return (undef(min) || n > min) && (undef(max) || n < max) || (!excluding && (min == n || max == n))
}

FormValidation.prototype.validatesRange = function(fieldNames, range, options){
  options = this.fixOptions(options, 'range')
  function fn(field) { return isNumberWithinRange(field.value, range, options.excluding) }
  this.validatesWithFunction(fieldNames, fn, options)
}

FormValidation.prototype.validatesLength = function(fieldNames, range, options){
  options = this.fixOptions(options, 'length')
  function fn(field) { return isNumberWithinRange(field.value.length, range, options.excluding) }
  this.validatesWithFunction(fieldNames, fn, options)
}

FormValidation.prototype.validatesNumeric = function(fieldNames, options){
  options = this.fixOptions(options, 'numeric')
  var rx = "^"
  if (options.negative) rx += "-?"
  rx += "\\d+"
  if (options.real) rx += "(\\.\\d+)?"
  rx += "$"
  rx = new RegExp(rx)
  this.validatesWithRegexp(fieldNames, rx, options)
  var range_options = dict(options).merge({message : undefined})
  if (options.range) this.validatesRange(fieldNames, options.range, options)
}

FormValidation.prototype.validatesCPF = function(fieldNames, options){
  function fn(field){
    var v = field.value.replace(/\D+/g, '')
    return v.substr(9,2) == CalcularDV(v.substr(0,9), 11)
  }
  this.validatesWithFunction(fieldNames, fn, options)
}

FormValidation.prototype.validatesCNPJ = function(fieldNames, options){
  function fn(field){
    var v = field.value.replace(/\D+/g, '')
    return v.substr(12,2) == CalcularDV(v.substr(0,12), 9)
  }
  this.validatesWithFunction(fieldNames, fn, options)
}

FormValidation.prototype.validatesCreditCard = function(fieldName, typeFieldName, options){
  function fn(field){ return isValidCreditCard(field.value, field.form.elements[typeFieldName].value) }
  this.validatesWithFunction(fieldName, fn, options)
}

FormValidation.prototype.validatesFileExtension = function(fieldNames, extensions, options){
  options = this.fixOptions(options, 'file_extension')
  options.trim = false
  this.validatesWithRegexp(fieldNames, new RegExp("\\.({0})$".subn(list(extensions).join("|")), 'i'), options)
}

FormValidation.prototype.allValidations = function(){ return this.validations.values().flatten() }

FormValidation.prototype.runValidation = function(validation) {
  var self = this
  if (validation.options.trim && !isList(validation.field)) validation.field.value = validation.field.value.toString().trim()
  validation.valid = validation.options.allowEmpty && self.isFieldEmpty(validation.field, validation.options) || validation.fn(validation.field)
  return validation.valid
}

FormValidation.prototype.validateFieldByName = function(fieldName){
  var self = this
  var validations = self.validations[fieldName]
  if (!validations || !validations.length) return true 
  var valid = validations.all(function(validation){ return self.runValidation(validation) })
  setTimeout(function(){
    var fn = self.options[valid ? 'onChangePass' : 'onChangeFail']
    fn && fn.apply(self, [validations]) 
  }, 0)
  return valid
}

FormValidation.prototype.validateField = function(field){
  return this.validateFieldByName(field.name)
}

FormValidation.prototype.getFieldNamesWithin = function(node, withValidationOnly){
  var self = this
  var innerElements     = getAll('*', node)
  var formElements      = map(self.form.elements)
  var intersectElements = formElements.select(function(elt){ return innerElements.include(elt) })
  var fieldNames = innerElements.map("item.name").sort().inject([], "a.last() != b && a.push(b); a")
  if (withValidationOnly) fieldNames.reject(function(name){ return !self.validations.keys().include(name) })
  return fieldNames
}

FormValidation.prototype.getValidationsWithin = function(node){
  var self = this
  return self.validations.select(function(k,v){return self.getFieldNamesWithin(node).include(k) }).values().flatten()
}

FormValidation.prototype.validateFieldsWithin = function(node){
  var self = this
  var fieldNames = self.getFieldNamesWithin(node)
  var valid = fieldNames.all(function(fieldName){ return self.validateFieldByName(fieldName) })
  var fn = self.options[valid ? 'onBlockPass' : 'onBlockFail']
  fn && fn.apply(self, [node, self.getValidationsWithin(node)]) 
  return valid
}

FormValidation.prototype.validateForm = function(){
  var self = this
  return self.validations.keys().all(function(fieldName){ return self.validateFieldByName(fieldName) })
}

FormValidation.prototype.handleSubmit = function(ev){
  var valid = this.validateForm()
  var fn = this.options[valid ? 'onSubmitPass' : 'onSubmitFail']
  fn && fn.apply(this, [this.form, this.allValidations()]) 
  valid || ev.preventDefault()
  return valid
}




;(function(){
  function getOneFieldElementFromValidation(validation){
    return validation.options.isRadio ? map(validation.field).last() : validation.field
  }

  function getFieldContainerFromValidation(validation){
    return ancestors(getOneFieldElementFromValidation(validation)).find(function(an){ return hasClass(an, 'form-field') })
  }
  
  function setMessageForContainer(container, className, message){
    var msgElt = getFirstOfClass(className, container)
    if (undef(msgElt)) msgElt = container.appendChild(ce('div.'+className))
    clearElement(msgElt)
    if (isdef(message)) {
      message = [message].flatten()
      message.each(function(item){
        var ch;
        if (isString(item))  ch = document.createTextNode(item)
        if (isElement(item)) ch = item
        msgElt.appendChild(ch)
      })
    }
  }
  
  function clearMessageForContainer(container, className) {
    var msgElt = getFirstOfClass(className, container)
    if (msgElt) removeElement(msgElt)
  }

	/* Mensagens de Controle de Erros da validação do Form - alterado para tableLess Forms: form >> document.getElementById('form-error')*/
  FormValidation.prototype.defaultOptions = {
    onChangeFail : function(validations) {
      var container = getFieldContainerFromValidation(validations.first())
      addClass(container, 'fail')
      setMessageForContainer(container, 'field-error-message', validations.select("!item.valid").map("ce('div', {}, item.options.message)"))
    },
    onChangePass : function(validations) {
      var container = getFieldContainerFromValidation(validations.first())
      remClass(container, 'fail')
      clearMessageForContainer(container, 'field-error-message')
      if (this.allValidations().all("item.valid")) clearMessageForContainer(document.getElementById('form-error'), 'form-error-message')
    },
    onSubmitFail : function(form, validations) { 
      var self = this
      setMessageForContainer(document.getElementById('form-error'), 'form-error-message', this.defaultMessages.form)
    },
    onSubmitPass : function(form, validations) { clearMessageForContainer(document.getElementById('form-error'), 'form-error-message') }
  }
})()

FormValidation.prototype.defaultMessages = {
  'default'      : 'preenchimento inválido',
  presence       : 'preenchimento obrigatório',
  email          : 'formato de email inválido',
  numeric        : 'deve ser um campo numérico positivo',
  range          : 'faixa não aceita',
  length         : 'faixa não aceita',
  file_extension : 'tipo de extensão incorreta',
  form           : 'Alguns campos foram preenchidos de forma incorreta, por favor verifique cuidadosamente.'
}


