
// validators are functions assigned to particular HTML form fields
//
// when a validation is occuring, these functions are used to validate the 
// fields' values
//
// assoc array of validators
//
var validators = {};

//
// used to split the HTML form field name into cls and field
//
// ex: user[email_address] => ['user', 'email_address']
var nameRE = /(.*)\[(.*)\]/;

// 
// when_unchanged(fields, timeout, callback)
//
//  takes a node list of fields to watch, a timeout in ms, and callback
//
// after +timeout, check if the values in the +watching node list have changed
// values.  if so, call +callback
//

function when_unchanged(fields, timeout, callback) {

  // var input_being_validated = this;

  // "val" gets the current concatenation of all watched fields
  var val = '';
  $(fields).each(function() {
    val += $(this).val();
  });

  // clear all validation
  $('.validation_error, .validation_ok, .validation_spinner', $($(this).parents('.validated_input')[0]) ).hide();

  setTimeout(function() {
    // if none of the watched values have changed, do validation
    var new_val = '';
    $(fields).each(function() {
      new_val += $(this).val();
    });
    if (new_val == val) {
      callback();
//      input_being_validated.validate();
    }
  }, timeout)
}

// do validation
jQuery.fn.validate = function() {

  var cls;  // class name of object
  var field;  // field name of object to validate

  // split name into field and class
  var name = this.attr('name');
  var name_arr = nameRE.exec(name);

  // save name, class and field name for field to validate
  name = name_arr[0];
  cls = name_arr[1];
  field = name_arr[2];

  // hide any current errors
  Validation.hide_all(cls, field);  
  
  // abort if no validators
  if (! validators[cls]) return '';

  // get value to check validity of
  var value = this.val();

  // run client side validation
  var client_err = Validation.validate_on_client.apply(this, [cls, field, value]);
  if (client_err != '') {
    Validation.show_validation_error(cls, field, client_err);
    
    // exit here if failure occurred
    return false;
  }
  
  // do we have server data in our validator?
  var server_data = validators[cls][field].validate_with_server;
  
  if (server_data) {
  
    // if so...
    
    // run server validation
    Validation.validate_on_server.apply(
      this, 
      [cls, 
       field, 
       value]
        .concat(server_data)
        // ok callback
        .concat(function() { Validation.show_validation_ok(cls, field) })
        // error callback
        .concat(function(msg) { Validation.show_validation_error(cls, field, msg) })  
    );
    return false;
  }

  // no errors
  Validation.show_validation_ok(cls, field);
  return true;
}

Validation={};

// 
// run all client validators 
//
Validation.validate_on_client = function(cls, field, value) {    
  var input = this;
  for (var i = 0; i < validators[cls][field].length; i++) {
    var validator = validators[cls][field][i];
    var v = validator[0];
    var msg = v.apply(input, validator.slice(1));
    if (msg != '') {
      return msg;
    }
  };
  
  // all validators successful
  return '';  
}

// run server validation
// 
// generally used like:
//
// validate__server.apply(
//    fields,             // collection of nodes
//    cls,                // class name,
//    field,              // field name,
//    value,              // value of field
//    id,                 // id of object if any
//    additional_params,  // array of other field names to include in query
//    success,            // success callback
//    failure(msg)       // failure callback with error message
//

Validation.validate_on_server = 
  function(
    cls, 
    field, 
    value, 
    id, 
    additional_params,
    success,
    error
  ) {
  
  // save reference to this
  var self = this
  
  // remove any previously running validations
  var ajax = self.data('validation_running');
  if (ajax) {
    ajax.abort();
  }
  
  // show spinner for this field
  $('.validation_spinner', $('#' + cls + '_' + field + '_validated_input')).show();
  
  // serialize this + additional_params
  var params = this.serialize();
  $.each(additional_params, function(i, param) {
    params += '&' + $('#' + cls + '_' + param).serialize();
  });
  
  // generate ajax call
  ajax = $.ajax({
    type: 'GET',
    dataType: 'html',
    url: '/validation/' + cls + '/' + field + id + '?' + params,
    success: function(data, textStatus) {
      
      self.removeData('validation_running');
      // hide spinner
      $('##{ spinner_id }').hide();
      
      // if something was returned, it was an error
      if (data != '') {
        error(data);

      } else {
        success();

      }
    }
  });
  
  self.data('validation_running', ajax);
}


Validation.show_validation_error = 
  function(cls, field, msg) {
    var wrapper = $('#' + cls + '_' + field + '_validated_input');
    $('.validation_error_msg', wrapper).text(msg)
    $('.validation_error', wrapper).show();
    $('.validation_ok, .validation_spinner', wrapper).hide();
  }
  
Validation.show_validation_ok = 
  function show_validation_ok(cls, field) {
    var wrapper = $('#' + cls + '_' + field + '_validated_input');
    $('.validation_error, .validation_spinner', wrapper).hide();

    // only show OK checkmark when value != ''
    if ($('#' + cls + '_' + field).val() != '') $('.validation_ok', wrapper).show();
  }
  
Validation.hide_all = 
  function hide_all(cls, field) {
    var wrapper = $('#' + cls + '_' + field + '_validated_input');
    $('.validation_error, .validation_ok, .validation_spinner', wrapper).hide();
  }

// makes sure a checkbox is checked
Validation.validate_acceptance = 
  function (error_msg) {
    return this.attr('checked') ? '' : error_msg;
  }

// makes sure a value exists
Validation.validate_presence = 
  function (error_msg){
    return (this.val().trim() != '') ? '' : error_msg;
  }

Validation.validate_length = 
  function (min, max, too_short_msg, too_long_msg) {
    var value = this.val().trim();
    if (value.length > 0) {
      if (value.length > max) {
        return too_long_msg;
      }
      if (value.length < min) {
        return too_short_msg;
      }
    }
    return '';
  }

Validation.validate_format = 
  function (error_msg, regexp, allow_blank) {
    if (allow_blank && this.val().trim() == '')
      return '';
    
    if (! regexp.test( this.val().trim() ) ) {
      return error_msg;
    }
    return '';
  }
