// This contains form validation and manipulation scripts.

// Must match the values in new_form_lib.php:
FIELD_ID_NUM_OBJECTS = "_numobjects";

// Trim leading/trailing whitespace
function trim(str) {
  if (str == null) {
    return "";
  } else {
    return str.replace(/^\s+|\s+$/g, '');
  }
}

// Matches whitespace strings:
var reEmpty         = /^\s*$/;
// Matches an optionally signed integer:
var reSignedInteger = /^(\+|-)?\d+$/;
// Matches an optionally signed float
var reSignedFloat   = /^(\+|-)?((\d+(\.\d*)?)|((\d*\.)?\d+))$/;
// Matches an email address (not fully RFC822-compliant):
var reEmail         = /^[a-z0-9._+-]+@([a-z0-9-]+(\.[a-z0-9-]+)+)$/i;
// Matches a date in YYYY-MM-DD format:
var reDate          = /^[12]\d\d\d-\d\d?-\d\d?$/;
// Matches a date/time in YYYY-MM-DD [HH-mm[-ss]] format:
var reDateTime      = /^[12]\d\d\d-\d\d?-\d\d?( +\d\d+:\d\d+(:\d\d+)?)?$/;
// Matches a password (just dislikes whitespace):
var rePassword      = /^[^\s]+$/;

var form_errors = 0;

// Delayed focus setting to get around IE bug.  This also ensures that
// if it's called in rapid succession, the first request wins.
var global_focus_target = null;

function setFocusNow() {
  if (global_focus_target != null) {
    try {
      global_focus_target.focus();
    } catch (error) {}
    global_focus_target = null;
  }
}

function setFocus(target) {
  if (global_focus_target == null) {
    global_focus_target = target;
    setTimeout('setFocusNow()', 100);
  }
}

function setSpanValue(node, message) {
  // TODO: does thi work universally or should we create a new node
  // and swap it or try to find the child text node
  node.innerHTML = message;
}

function showBlock(node) {
  node.style.display = 'block';
}

function hideBlock(node) {
  node.style.display = 'none';
}

// TODO: a message type class name to affect how they're displayed?
// Requires that all other types are removed first.
function showError(focus_field, field_id, message) {
  setFocus(focus_field);
  var elem = document.getElementById(field_id);
  if (elem != null) {
    setSpanValue(elem, "Error: " + message);
    showBlock(elem);
    form_errors++;
  }
  return false;
}

function clearError(field_id) {
  var elem = document.getElementById(field_id);
  if (elem != null) {
    setSpanValue(elem, '');
    hideBlock(elem);
  }
  return true;
}

function assertNoErrors() {
  if (form_errors == 0) {
    return true;
  } else {
    var errors = (form_errors == 1) ? "error was" : "errors were";
    var itthem = (form_errors == 1) ? "it" : "them";
    alert(form_errors + " " + errors + " found on the form.\n"
		+ "Please fix " + itthem +" and re-submit.");
    return false;
  }
}

// Returns true if validation passed.  This actually ignores the
// "required" argument because it's redundant in this case.
function valNotEmpty(val_field, info_field_id, required) {
  if (!reEmpty.test(val_field.value)) {
    return clearError(info_field_id);
  } else {
    return showError(val_field, info_field_id, 'Required');
  }
}

function valInteger(val_field, info_field_id, required) {
  if (!required && reEmpty.test(val_field.value)) {
    return clearError(info_field_id);
  } else if (required && !valNotEmpty(val_field, info_field_id, required)) {
    return false;
  } else if (reSignedInteger.test(trim(val_field.value))) {
    return clearError(info_field_id);
  } else {
    return showError(val_field, info_field_id, 'Must be an integer');
  }
}

function valNumber(val_field, info_field_id, required) {
  if (!required && reEmpty.test(val_field.value)) {
    return clearError(info_field_id);
  } else if (required && !valNotEmpty(val_field, info_field_id, required)) {
    return false;
  } else if (reSignedFloat.test(trim(val_field.value))) {
    return clearError(info_field_id);
  } else {
    return showError(val_field, info_field_id, 'Must be a number');
  }
}

function valNonNegative(val_field, info_field_id, required) {
  if (!required && reEmpty.test(val_field.value)) {
    return clearError(info_field_id);
  } else if (!valNumber(val_field, info_field_id, required)) {
    return false;
  }
  var val = parseFloat(val_field.value);
  if (val >= 0) {
    return clearError(info_field_id);
  } else {
    return showError(val_field, info_field_id, "Can't be negative");
  }
}

function valPositive(val_field, info_field_id, required) {
  if (!required && reEmpty.test(val_field.value)) {
    return clearError(info_field_id);
  } else if (!valNumber(val_field, info_field_id, required)) {
    return false;
  }
  var val = parseFloat(val_field.value);
  if (val > 0) {
    return clearError(info_field_id);
  } else {
    return showError(val_field, info_field_id, "Must be positive");
  }
}

function valGreaterThan(val_field, info_field_id, required, x) {
  if (!required && reEmpty.test(val_field.value)) {
    return clearError(info_field_id);
  } else if (!valNumber(val_field, info_field_id, required)) {
    return false;
  }
  var val = parseFloat(val_field.value);
  if (val > x) {
    return clearError(info_field_id);
  } else {
    return showError(val_field, info_field_id, "Must be greater than " + x);
  }
}

function valGreaterThanOrEqual(val_field, info_field_id, required, x) {
  if (!required && reEmpty.test(val_field.value)) {
    return clearError(info_field_id);
  } else if (!valNumber(val_field, info_field_id, required)) {
    return false;
  }
  var val = parseFloat(val_field.value);
  if (val >= x) {
    return clearError(info_field_id);
  } else {
    return showError(val_field, info_field_id,
		     "Must be greater than or equal to " + x);
  }
}

function valLessThan(val_field, info_field_id, required, x) {
  if (!required && reEmpty.test(val_field.value)) {
    return clearError(info_field_id);
  } else if (!valNumber(val_field, info_field_id, required)) {
    return false;
  }
  var val = parseFloat(val_field.value);
  if (val < x) {
    return clearError(info_field_id);
  } else {
    return showError(val_field, info_field_id, "Must be less than " + x);
  }
}

function valLessThanOrEqual(val_field, info_field_id, required, x) {
  if (!required && reEmpty.test(val_field.value)) {
    return clearError(info_field_id);
  } else if (!valNumber(val_field, info_field_id, required)) {
    return false;
  }
  var val = parseFloat(val_field.value);
  if (val <= x) {
    return clearError(info_field_id);
  } else {
    return showError(val_field, info_field_id,
		     "Must be less than or equal to " + x);
  }
}

// Checks that the value is in the range [x, y] (inclusive).
function valInRange(val_field, info_field_id, required, x, y) {
  if (!required && reEmpty.test(val_field.value)) {
    return clearError(info_field_id);
  } else if (!valNumber(val_field, info_field_id, required)) {
    return false;
  }
  var val = parseFloat(val_field.value);
  if (val >= x && val <= y) {
    return clearError(info_field_id);
  } else {
    return showError(val_field, info_field_id,
		     "Must be in the range " + x + " to " + y);
  }
}

function valEmail(val_field, info_field_id, required) {
  if (!required && reEmpty.test(val_field.value)) {
    return clearError(info_field_id);
  } else if (required && !valNotEmpty(val_field, info_field_id, required)) {
    return false;
  } else if (reEmail.test(trim(val_field.value))) {
    return clearError(info_field_id);
  } else {
    return showError(val_field, info_field_id, "Must be a valid email address");
  }
}

// Parses a date in either IETF format (Wed, 18 Oct 2000 13:00:00 EST)
// or YYYY-MM-DD format:
function parseDate(date_string) {
  var date = Date.parse(date_string);
  if (isNaN(date) && reDate.test(date_string)) {
    var pieces = date_string.split('-');
    date = new Date(pieces[0], pieces[1] - 1, pieces[2]);
  }
  return date;
}

function valDate(val_field, info_field_id, required, start, end) {
  if (!required && reEmpty.test(val_field.value)) {
    return clearError(info_field_id);
  } else if (required && !valNotEmpty(val_field, info_field_id, required)) {
    return false;
  }
  var date  = parseDate(trim(val_field.value));
  var start_date = parseDate(start);
  var end_date   = parseDate(end);

  if (isNaN(date)) {
    return showError(val_field, info_field_id, "Not a valid date");
  } else if (!isNaN(start_date) && date < start_date) {
    return showError(val_field, info_field_id,
		     "Can't be earlier than " + start);
  } else if (!isNaN(end_date) && date > end_date) {
    return showError(val_field, info_field_id, "Can't be later than " + end);
  }
  return clearError(info_field_id);
}

// Parses a date in either IETF format (Wed, 18 Oct 2000 13:00:00 EST)
// or YYYY-MM-DD HH:mm:ss format:
function parseDateTime(date_string) {
  var date = Date.parse(date_string);
  if (isNaN(date) && reDateTime.test(date_string)) {
    var pieces = date_string.split(/ +/);
    var date_pieces = pieces[0].split('-');
    var year  = date_pieces[0];
    var month = date_pieces[1] - 1;
    var day   = date_pieces[2];
    if (pieces.length > 1) {
      time_pieces = pieces[1].split(':');
      var hour = time_pieces[0];
      var minute = time_pieces[1];
      var second = (time_pieces.length > 2) ? time_pieces[2] : 0;
      return new Date(year, month, day, hour, minute, second);
    } else {
      return new Date(year, month, day);
    }
  }
  return date;
}

function valDateTime(val_field, info_field_id, required, start, end) {
  if (!required && reEmpty.test(val_field.value)) {
    return clearError(info_field_id);
  } else if (required && !valNotEmpty(val_field, info_field_id, required)) {
    return false;
  }
  var date  = parseDateTime(trim(val_field.value));
  var start_date = parseDateTime(start);
  var end_date   = parseDateTime(end);

  if (isNaN(date)) {
    return showError(val_field, info_field_id, "Not a valid date/time");
  } else if (!isNaN(start_date) && date < start_date) {
    return showError(val_field, info_field_id,
		     "Can't be earlier than " + start);
  } else if (!isNaN(end_date) && date > end_date) {
    return showError(val_field, info_field_id, "Can't be later than " + end);
  }
  return clearError(info_field_id);
}

function valPassword(val_field, info_field_id, required, min, max) {
  var val = val_field.value;  // not trimmed
  if (val == '') {
    if (required) {
      return showError(val_field, info_field_id, 'Required');
    } else {
      return clearError(info_field_id);
    }
  }
  if (!rePassword.test(val)) {
    return showError(val_field, info_field_id,
		     "Not a valid password (no spaces allowed)");
  } else if (val.length < min) {
    return showError(val_field, info_field_id,
		     "Must be at least " + min + " characters");
  } else if (val.length > max) {
    return showError(val_field, info_field_id,
		     "Can't be more than " + max + " characters");
  }
  return clearError(info_field_id);
}

// This only enforces the required arg at the moment because the
// MAX_FILE_SIZE hidden field ought to enforce the file limit.
function valFile(val_field, info_field_id, required, max_bytes) {
  if (required) {
    return valNotEmpty(val_field, info_field_id, required);
  } else {
    return clearError(info_field_id);
  }
}

function valSame(val_field, info_field_id, required,
		    other_field, other_field_label) {
  if (required && !valNotEmpty(val_field, info_field_id, required)) {
    return false;
  }
  if (val_field.value != other_field.value) {
    return showError(val_field, info_field_id,
		     "Doesn't match " + other_field_label);
  }
  return clearError(info_field_id);
}


// Given a form id, returns the value of the next form object.
// To be used when dynamically creating form elements.
function getNextFormObjNum(formNum) {
  if (document.forms[formNum] == null) {
    return 0;
  }
  eval("el = document.forms[formNum]." + FIELD_ID_NUM_OBJECTS);
  if (el === null) {
    return 0;
  }
  val = el.value;
  el.value = (+val) + 1;
  return val;
}

// This doesn't support validations for the form extensions.  If it did, we
// would have to do replacements in the javascript code, and disable the
// validations for the template object.  It's also a problem doing the
// PHP-side validation of the extended fields.
var extendVForm = function(form_name, template_id, template_num_code) {
  var form = $(form_name);
  var num_objects_input = $(form_name + '_numobjects');
  var num_objects = parseInt(num_objects_input.value);

  // Clone the template
  var template = $(template_id);
  var new_row = template.cloneNode(true);
  new_row.id = '';
  new_row.style.display = 'block';
  
  // Rename the fields
  var fields = new_row.getElementsByTagName('*');
  var first_field = null;
  for (var i = 0; i < fields.length; ++i) {
    var field_name = fields[i].name;
    if (field_name != null && field_name.search(template_num_code) != -1) {
      fields[i].name = field_name.replace(template_num_code, num_objects);
      if (first_field == null && fields[i].type != 'hidden') {
        first_field = fields[i];
      }
    }
  }
  // Find, copy, rename the hidden field:
  var fields = Form.getInputs(form, 'hidden');
  for (var i = 0; i < fields.length; ++i) {
    var field_name = fields[i].name;
    if (field_name != null && field_name.search(template_num_code) != -1) {
      new_field = fields[i].cloneNode(true);
      new_field.name = new_field.name.replace(template_num_code, num_objects);
      form.appendChild(new_field);
    }    
  }

  template.parentNode.appendChild(new_row);
  Field.focus(first_field);

  num_objects_input.value = num_objects + 1;
}
