//
// Rules (applied in the order specified in the structure)
// =======================================================================================
// id: The field ID
//   value: The HTML id of the field
//
// trim: How the value is trimmed
//   value: "left" - Trim the leading  spaces
//          "right" - Trim the trailing spaces
//          "both" - Trim the leading and trailing spaces
//
// case: The case that the value is forced to
//   value: "lower" - Set to lower-case
//          "upper" - Set to upper-case
//          "proper" - Set to proper-case (only the first letter of each word is upper-case)
//
// ignore: The field will not be validated (default = false)
//   value: Anything that evaluates to a boolean
//
// required: The field value cannot be blank (default = false)
//   value: Anything that evaluates to a boolean
//
// alpha: Only alphabetic characters are allowed (A-Z, a-z and space)
//   value: Not required (ignored)
//
// alphanumeric: Only alphanumeric characters are allowed (A-Z, a-z, 0-9 and space)
//   value: Not required (ignored)
//
// alphanumericNoSpace: Only alphanumeric characters are allowed (A-Z, a-z, 0-9)
//   value: Not required (ignored)
//
// float: Only float numbers are allowed
//   value: "signed" - Allow signed floats
//
// integer: Only integers are allowed
//   value: "signed" - Allow signed integers
//
// currency: Only currency values are allowed (with or without the leading dollar sign)
//   value: "allowCommas" - Allow commas as a thousands separator
//
// date: Only valid MM/DD/YYYY (leading zeros required) dates are allowed
//   value: Not required (ignored)
//
// currentOrFutureDate: Must be the current date or a future date in MM/DD/YYYY format (leading zeros required)
//   value: Not required (ignored)
//
// birthDate: Only valid MM/DD/YYYY (leading zeros required) dates are allowed and the date
//       must less than or equal to today's date.
//   value: Not required (ignored)
//
// birthDateMin18: Only valid MM/DD/YYYY (leading zeros required) dates are allowed and
//       the date must less than or equal to today's date and the age must be at least 18.
//   value: Not required (ignored)
//
// minimum: The minimum allowable value
//   value: Not required (ignored)
//
// maximum: The maximum allowable value
//   value: Not required (ignored)
//
// minLength: The minimum allowable string length of the value
//   value: The string length
//
// maxLength: The maximum allowable string length of the value
//   value: The string length
//
// noSpecialChar: Each character of the value must be in the ASCII range of 32-126
//   value: Not required (ignored)
//
// regExp: Checked with passed regular expression
//   value: The regular expression used to test the validity
//
// custom: A function to be invoked to test the validity (must return true/false)
//  value: The anonymous function that will be invoked
//   NOTE: This overrides the "required" property, if it exists.
//
// ifValid: A function to be invoked if the field is valid
//   value: The anonymous function that will be invoked
//
// ifInvalid: A function to be invoked if the field is invalid
//   value: The anonymous function that will be invoked
//
// classIfValid: The CSS added if the field is valid ("classIfInvalid" class removed first)
//   value: The CSS class name to be added
//
// classIfInvalid: The CSS added if the field is invalid ("classIfValid" class removed first)
//   value: The CSS class name to be added
//
// Example Usage
// =============
//   var example = [ {"id" : "optionDesc",
//                    "trim" : "both",
//                    "case" : "proper",
//                    "required" : $("#checkboxList input[type='checkbox']:last").attr("checked"),
//                    "alpha" : null,
//                    "alphanumeric" : null,
//                    "float" : "signed",
//                    "integer" : "signed",
//                    "currency" : "allowCommas",
//                    "minimum" : 0,
//                    "maximum" : 100,
//                    "minLength" : 1,
//                    "maxLength" : 3,
//                    "noSpecialChar" : null,
//                    "regExp" : /^\d+$/,
//                    "custom" : function(){alert('This is a custom validation function'); return true;},
//                    "ifValid" : function(){alert('It\'s valid!');},
//                    "ifInvalid" : function(){alert('It\'s invalid!');},
//                    "classIfValid" : "valid",
//                    "classIfInvalid" : "invalid"},

//                   {"id" : "someField",
//                    "required"  : false,
//                    "minLength" : 10,
//                    "custom" : function(){alert('Foo Bar');}}
//                 ];
//
//   $("#btnSubmit").click(function() {
//     validateFields(fields1);
//     return false;
//   });
//
function validateFields(fieldRuleSet) {
  var invalidFieldCount = 0;

  for (var i in fieldRuleSet) {
    var valid = true;

    try {

      var fieldRules = fieldRuleSet[i];
      var fieldId = fieldRules.id;
      var ignoreValue = fieldRules.ignore;
      var ignore = (typeof ignoreValue == "function") ? ignoreValue() : (typeof ignoreValue == "boolean") ? ignoreValue : false;

      if (!ignore && fieldId != undefined) {

        var field = $("#" + fieldId);

        if (field.length > 0) {

          for (var j in fieldRules) {

            if (valid) {
              if (j != "required" || (j == "required" && typeof fieldRules.custom == 'undefined')) {
                valid = validateProperty(field, j, fieldRules[j]);
                invalidFieldCount += (valid) ? 0 : 1;
              }
            }
            else {
              break;
            }

          }

          // Validity actions
          var addClass = (valid) ? fieldRules.classIfValid : fieldRules.classIfInvalid;
          var removeClass = (valid) ? fieldRules.classIfInvalid : fieldRules.classIfValid;
          var callFunction = (valid) ? fieldRules.ifValid : fieldRules.ifInvalid;

          if (addClass != undefined) {
            $(field).addClass(addClass);
          }

          if (removeClass != undefined) {
            $(field).removeClass(removeClass);
          }

          if (typeof callFunction == "function") {
            callFunction();
          }

        }

      }

    }
    catch(e) {
      // For debugging
      // alert("JS Validation Error: " + e.message);
    }
  }

  return invalidFieldCount;
}

function validateProperty(field, propertyName, propertyValue) {
  var fieldValue;
  var tagName = field.get(0).tagName.toLowerCase();
  var tagType = $(field).attr("type")
  var valid = true;

  if (tagType == "radio" || tagType == "checkbox") {
    var radioName = $(field).attr("name");

    fieldValue = $("input[name='" + radioName + "']:checked").val();
  }
  else {
    fieldValue = $(field).val();
  }

  fieldValue = (fieldValue == undefined) ? "" : fieldValue;

  var hasValue = fieldValue.length > 0;

  switch (propertyName) {

    case "trim" :
      switch (propertyValue.toLowerCase()) {

        case "left" :
          fieldValue = fieldValue.lTrim();
          break;

        case "right" :
          fieldValue = fieldValue.rTrim();
          break;

        case "both" :
          fieldValue = $.trim(fieldValue);
          break;

      }
      break;

    case "case" :
      switch (propertyValue.toLowerCase()) {

        case "lower" :
          fieldValue = fieldValue.toLowerCase();
          break;

        case "upper" :
          fieldValue = fieldValue.toUpperCase();
          break;

        case "proper" :
          fieldValue = fieldValue.toProperCase();
          break;

      }
      break;

    case "required" :
      if (typeof propertyValue == "function") {
        valid = (propertyValue()) ? fieldValue.length > 0 : true;
      }
      else if (typeof propertyValue == "boolean") {
        valid = (propertyValue) ? fieldValue.length > 0 : true;
      }
      break;

    case "alpha" :
      valid = (hasValue) ? fieldValue.isAlpha() : true;
      break;

    case "alphanumeric" :
      valid = (hasValue) ? fieldValue.isAlphanumeric() : true;
      break;

    case "alphanumericNoSpace" :
      valid = (hasValue) ? fieldValue.isAlphanumericNoSpace() : true;
      break;

    case "date" :
      valid = (hasValue) ? fieldValue.isDate() : true;
      break;

    case "currentOrFutureDate" :
      valid = (hasValue) ? (fieldValue.isDate() && isCurrentOrFutureDate(fieldValue)) : true;
      break;

    case "birthDate" :
      valid = (hasValue) ? (fieldValue.isDate() && isValidBirthDate(fieldValue)) : true;
      break;

    case "birthDateMin18" :
      valid = (hasValue) ? (fieldValue.isDate() && isValidBirthDate(fieldValue) && (ageInYears(fieldValue) >= 18)) : true;
      break;

    case "float" :
      valid = (hasValue) ? fieldValue.isFloat(propertyValue != null && propertyValue.toLowerCase() == "signed") : true;
      break;

    case "integer" :
      valid = (hasValue) ? fieldValue.isInteger(propertyValue != null && propertyValue.toLowerCase() == "signed") : true;
      break;

    case "currency" :
      valid = (hasValue) ? fieldValue.isCurrency(propertyValue != null && propertyValue.toLowerCase() == "allowcommas") : true;
      break;

    case "minimum" :
      valid = (hasValue) ? fieldValue >= propertyValue : true;
      break;

    case "maximum" :
      valid = (hasValue) ? fieldValue <= propertyValue : true;
      break;

    case "minLength" :
      valid = (hasValue) ? fieldValue.length >= propertyValue : true;
      break;

    case "maxLength" :
      valid = (hasValue) ? fieldValue.length <= propertyValue : true;
      break;

    case "noSpecialChar" :
      valid = (hasValue) ? !fieldValue.hasSpecialChars() : true;
      break;

    case "regExp" :
      valid = (hasValue) ? propertyValue.test(fieldValue) : true;
      break;

    case "custom" :
      if (typeof propertyValue == "function") {
        valid = propertyValue();
      }
      else if (typeof propertyValue == "boolean") {
        valid = propertyValue;
      }
      break;
  }

  // Set the value
  if (tagType != "radio" && tagType != "checkbox") {
    $(field).val(fieldValue);
  }

  return valid;
}

String.prototype.lTrim = function() {
  return this.replace(/^\s+/,"");
}
String.prototype.rTrim = function() {
  return this.replace(/\s+$/,"");
}
String.prototype.toProperCase = function() {
  return this.replace( /(\b\w)(\w*)/g , function(x, a, b){return a.toUpperCase() + b.toLowerCase();});
};
String.prototype.isAlpha = function() {
  var re = /^\s*([a-zA-Z ]*)\s*$/;

  return re.test(this);
}
String.prototype.isAlphanumeric = function() {
  var re = /^\s*([0-9a-zA-Z ]*)\s*$/;

  return re.test(this);
}
String.prototype.isAlphanumericNoSpace = function() {
  var re = /^\s*([0-9a-zA-Z]*)\s*$/;

  return re.test(this);
}
String.prototype.isInteger = function(signed) {
  var re = (signed) ? /^[-+]?\d+$/ : /^\d+$/;

  return re.test(this);
}
String.prototype.isFloat = function(signed) {
  var re = (signed) ? /^[-+]?\d+(\.\d+)?$/ : /^\d+(\.\d+)?$/;

  return re.test(this);
}
String.prototype.isCurrency = function(allowCommas) {
  var re = (allowCommas) ? /^\$?[1-9][0-9]{0,2}(,?[0-9]{3})*(\.[0-9]{2})?$/ : /^\$?[1-9][0-9]{0,2}([0-9]{3})*(\.[0-9]{2})?$/;

  return re.test(this);
}
String.prototype.hasSpecialChars = function() {
  var result = false;

  for (var i = 0, n = this.length; i < n; i++) {
    if ((this.charCodeAt(i) != 10 && this.charCodeAt(i) != 13 && this.charCodeAt(i) < 32) || this.charCodeAt(i) > 126) {
      result = true;
      break;
    }
  }

  return result;
}
String.prototype.isDate = function() {
  // Supports the MM/DD/YYYY format only
  var result = false;
  var re = /^(\d{1,2})(\/)(\d{1,2})\2(\d{4})$/;
  var dateParts = this.match(re);

  if (dateParts != null) {
    var month = dateParts[1];
    var day = dateParts[3];
    var year = dateParts[4];

    if (month >= 1 && month <= 12) {
      if (day >= 1 && day <= 31) {
        if (((month == 4 || month == 6 || month == 9 || month == 11) && day <= 30) ||
            !(month == 4 || month == 6 || month == 9 || month == 11)) {
          if (month == 2) {
            var isLeapYear = (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
            if (day <= 28 || (day <= 29 && isLeapYear)) {
              result = true;
            }
          }
          else {
            result = true;
          }
        }
      }
    }
  }

  return result;
}

function isValidBirthDate(dob) {
  var dobDate = new Date(dob);
  var today = new Date();

  return (dobDate <= today);
}

function ageInYears(dob) {
  var result;
  var dobDate = new Date(dob);
  var now = new Date();
  var ms = now.valueOf() - dobDate.valueOf();
  var minutes = ms / 1000 / 60;
  var hours = minutes / 60;
  var days = hours / 24;
  var years = days/365;

  return years;
}

function isCurrentOrFutureDate(date) {
  var theDate = new Date(date);
  var today = new Date();

  return (daysElapsed(today, theDate) < 1);
}

function daysElapsed(date1, date2) {
  var difference = Date.UTC(y2k(date1.getYear()),date1.getMonth(),date1.getDate(),0,0,0) -
                   Date.UTC(y2k(date2.getYear()),date2.getMonth(),date2.getDate(),0,0,0);

  return difference/1000/60/60/24;
}

function y2k(year) {
  return (year < 1000) ? year + 1900 : year;
}

