/**
 * @param form - the form element or name of the form element to validate
 */
function Validator(form) {
  if (typeof(form)=='string') {
    form = $("form[name=" + form + "]")[0];
  }
  this.form = form;
  this.custom_validators = [];
  
  this.validate = function() {
    $(".invalid", this.form).removeClass("invalid");
    $(" :input[validation]", this.form).each(function () {
      var tests = $(this).attr("validation").split(" ");
      
      var valid = true;
      if (!this.disabled) {
        for (i = 0; i < tests.length; i++) {
          if (tests[i] == "notempty") {
            valid = valid && validateNotEmpty(this);
          } else if (tests[i] == "email") {
            valid = valid && validateEmail(this);
          } else if (tests[i] == "nospaces") {
            valid = valid && validateNoSpaces(this);
          } else if (tests[i] == "phone") {
            valid = valid && validatePhone(this);
          } else if (tests[i] == "date") {
            valid = valid && validateDate(this);
          } else if (tests[i] == "image") {
            valid = valid && validateImage(this);
          } else if (tests[i] == "integer") {
            valid = valid && validateInteger(this);
          } else if (tests[i].substring(0, 8) == "decimal:") {
            var parts = tests[i].split(":");
            valid = valid && validateDecimal(this, parts[1]);
          } else if (tests[i].substring(0, 10) == "minlength:") {
            var parts = tests[i].split(":");
            valid = valid && validateMinLength(this, parts[1]);
          } else if (tests[i].substring(0, 6) == "match:") {
            var parts = tests[i].split(":");
            valid = valid && validateMatch(this, this.form[parts[1]]);
          }
        }
      }
      setValid(this, valid);
    });
    
    for (var i=0; i< this.custom_validators.length; i++) {
      var cur_validator = this.custom_validators[i];
      if (cur_validator.target.disabled) continue;
      if ($(cur_validator.target).hasClass("invalid")) continue;
      setValid(cur_validator.target, cur_validator.is_valid(cur_validator.target.value));
    }
    
    return $("form[name=" + this.form.name + "] :input.invalid").size() == 0;
  }
  
  this.add_validator = function(/* function */ func, /* form element */ target) {
    this.custom_validators.push({is_valid: func, target: target});
  }
}

/**
 * setup an HTML form for validation
 * validated input elements should have the validation attribute set
 * values can be:
 *   notempty
 *   email
 *   phone
 *   match:<other element>
 * @param formName
 * @return Validator
 */
function setupFormValidation(formName) {
  var formElement = $("form[name=" + formName + "]")[0];
  
  $("<span>")
      .html("Form was not submitted because it contained errors.")
      .addClass("validationError")
      .prependTo(formElement);
  
  var validator = new Validator(formName);
  
  $(formElement).submit(function (event) {
    var valid = validator.validate();
    
    if (!valid) {
      this.scrollIntoView(true);
      $(".noticeBox").remove();
      $(".validationError")
        .css("display", "inline-block")
        .delay(4000)
        .slideUp();
      
      event.preventDefault();
    }
    return valid;
  });
  
  return validator;
}

// All validators except this one should return true if the element is empty
function validateNotEmpty(element) {
  return (element.value.length > 0);
}

function validateMinLength(element, min_length) {
  if (element.value.length == 0) return true;
  return (element.value.length >= parseInt(min_length));
}

function validateEmail(element) {
  if (element.value.length == 0) return true;
  return validateRegEx(element, /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,5}$/);
}

function validateNoSpaces(element) {
  return validateRegEx(element, /^[^\s]*$/);
}

function validatePhone(element) {
  return validateRegEx(element, /^\d*$/);
}

function validateInteger(element) {
  if (element.value.length == 0) return true;
  return validateRegEx(element, /^\d*$/);
}

function validateDecimal(element, numPlaces) {
  if (element.value.length == 0) return true;
  return validateRegEx(element, new RegExp("^\\d*(\\.\\d{1," + numPlaces + "})?$"));
}

function validateDate(element) {
  if (element.value.length == 0) return true;
  return validateRegEx(element, /^\d{4}-(0?[1-9]|1[0-2])-(0?[1-9]|[12][0-9]|3[01])$/);
}

function validateMatch(element, otherElement) {
  return (element.value == otherElement.value);
}

function validateRegEx(element, regEx) {
  return regEx.test(element.value);
}

function validateImage(element) {
  if (element.value.length == 0) return true;
  return validateRegEx(element, /[^\s]+(\.(jpeg|jpg|png|gif))$/i);
}

function setValid(element, valid) {
  if (valid) $(element).removeClass("invalid");
  else $(element).addClass("invalid");
}

var _is_dirty = false;
function trackUnsavedChanges(formname) {
  $("form[name=" + formname + "] :input").change(function() {
    _is_dirty = true;
  });
}

function navigateUnsaved(url) {
  if(_is_dirty) {
    if (!confirm("Are you sure you want to navigate away from this page?\n\n" + 
          "You will lose any unsaved changes.")) {
        return;
    }
  }
  document.location = url;
}
