var rules = {
             'int':function (value,param) {
                                           var minMax = param.split(',');
                                           testValue = parseInt(value);
                                           if (value != testValue) {return false}
                                           if (minMax[0] && testValue < minMax[0]) {return false}
                                           if (minMax[1] && testValue > minMax[1]) {return false}
                                           return true;
                                          },
             'float':function (value,param) {
                                             var minMax = param.split(',');
                                             testValue = parseFloat(value);
                                             if (value != testValue) {return false}
                                             if (minMax[0] && testValue < minMax[0]) {return false}
                                             if (minMax[1] && testValue > minMax[1]) {return false}
                                             return true;
                                            },
             europeanDecimal:function (value,param) {
                                             value = value.replace(/\,/, '.');
                                             var minMax = param.split(',');
                                             testValue = parseFloat(value);
                                             if (value != testValue) {return false}
                                             if (minMax[0] && testValue < minMax[0]) {return false}
                                             if (minMax[1] && testValue > minMax[1]) {return false}
                                             return true;
                                            },
             email:function (value,param) {
                                           if (param == 'notRequired' && !value.length) {return true}
                                           var mailRegex = /^[^\@\s]+\@[^\.\s]+(\.[^\.\s]+)+$/;
                                           if (value.match(mailRegex)) { return true }
                                           return false;
                                          },
             hostname:function (value,param) {
                                           if (param == 'notRequired' && !value.length) {return true}
                                           var hostnameRegex = /[\w\d\.\-]+/;
                                           if (value.match(hostnameRegex)) { return true }
                                           return false;
                                          },
             url:function (value,param) {
                                           if (param == 'notRequired' && !value.length) {return true}
                                           var urlRegex = /^(http|ftp|mailto)\:\/\/[\%a-zA-z0-9\-\/\_\:\@\.]+$/;
                                           if (value.match(urlRegex)) { return true }
                                           return false;
                                          },
             'regex':function (value,param) {
                                             var regex = eval(param);
                                             return value.match(regex);
                                            },
             path:function (value,param) { alert("You should note that this is a fake test! Please implement rule: path");
					   return true;
                                            },
	     fileExt:function (value,param) { 
  		                             param = '/^.+\.'+param+'$/i';
                                             var regex = eval(param);
                                             return value.match(regex);
                                            },
             required:function (value,param) {
                                              if (!value) {return false}
                                              if (value.length <= 0) {return false}
                                              return true;
                                             },
             length:function (value,param) {
                                            var minMax = param.split(',');
                                            if (minMax[0]>0 && !value) {return false}
                                            if (value) {
                                              if (minMax[0]>0 && value.length < minMax[0]) { return false }
                                              if (minMax[1]>0 && value.length > minMax[1])  { return false }
                                            }
                                            return true;
                                           },
	     numChecked:function (value,param) {
  		                              return value;
                                           },
             'javascript':function(value,param) {
		                                 return eval(param);
             },
             repeat:function(value,param) {
                 var fieldName  = param;
                 var otherValue = "";
                 var elements = document.getElementsByTagName("input");
                 var numFound = 0;
                 for (var i = 0; i < elements.length; i++) {
                     if (elements[i].name == fieldName) {
                         numFound++;
                         otherValue = elements[i].value;
                     }
                 }
                 if (numFound == 0) {
                     alert("Internal error: Didn't find element with name " + fieldName);
                 }
                 else if (numFound > 1) {
                     alert("Internal error: More than one element exists with name " + fieldName);
                 }
                 return value == otherValue;
             }
            };

var formErrors     = new Array();
var __formElements = {};
var formHasErrors = false;
var _o2OnSubmitEval = {}; // add code to be evaluated on submit here. form name is key, codestring is value

function o2AddOnSubmitEval(formName, evalJs) {
  if( !_o2OnSubmitEval[formName] ) _o2OnSubmitEval[formName]='';
  _o2OnSubmitEval[formName] += evalJs+';';
}

function _o2SubmitForm(f, functionName) {
  if (_o2CheckForm(f, functionName)) {
    f.submit();
  }
}

function _o2CheckForm (f, functionName) {
  if (!f) return;   // vonheim@20060308 <form onsubmit> and <input type=submit> both calls this method. one of them does not include "f"

  // added by nilschd 20070115, submit button sends it self as an f with firefox 2.x
  if (f.type == "button") {
    f = f.form; // then we set the f to be the this buttons parent form
  }

  if (f.name && _o2OnSubmitEval[f.name]) {
    eval( _o2OnSubmitEval[ f.name ] );
  }
  verifyForm(f);

  if (formHasErrors) {
    onRuleCheckFailure(f, functionName);
    return false;
  }

  if (window._o2MultilingualSubmitForm) {
    _o2MultilingualSubmitForm();
  }
  // In case you want to have som javascript executed before submitting the form, but after the rules are checked
  if (window.onRuleCheckSuccess) {
    onRuleCheckSuccess();
  }
  return true;
}

function onRuleCheckFailure(form, functionName) {
  var errorTitle = form.getAttribute("ruleTitle") || "";
  var errorBody = '';
  if (functionName) {
    if (formErrors && formErrors.length) {
      errorBody = formErrors.join("<br>");
    }
    if (functionName && !window[functionName]) {
      return alert("Method '" + functionName + "' does not exist");
    }
    window[functionName].call(this, errorTitle, errorBody);
  }
  else if (form.getAttribute("errorMessageHandler")) {
    // Want to allow client code to format the error messages. Not doing this inside "if (functionName)" because we need to be backward compatible.
    functionName = form.getAttribute("errorMessageHandler");
    if (functionName && !window[functionName]) {
      return alert("Method '" + functionName + "' does not exist");
    }
    window[functionName].call(this, errorTitle, formErrors);
  }
  else {
    if (formErrors && formErrors.length) {
      errorBody = formErrors.join("\n");
    }
    alert(errorTitle + "\n" + errorBody);
  }
}

function verifyForm (f) {
  formErrors.length = 0;
  formHasErrors = false;
  __radioOrCheckBoxGroups = new Array();
  __ruleMsgSeen           = {};
  __formElements          = {};
  checkNode(f);

  var defaultRuleHandler = f.getAttribute("ruleHandler");
  for (var nodeName in __formElements) {
    var element = __formElements[nodeName];
    if (typeof element !== "object") {
      continue;
    }
    var ruleHandler = element.node.getAttribute("ruleHandler") || defaultRuleHandler;
    if (!ruleHandler) {
      continue;
    }
    if (!window[ruleHandler]) {
      alert("Method '" + ruleHandler + "' does not exist");
      continue;
    }
    window[ruleHandler].call(this, f, element.node, element.ruleMsg, element.success);
  }

  return !formHasErrors;
}

function getVerifyErrors () {
  return formErrors;
}

function checkNode(node) {
  if (!node) return;
  if (node.nodeType==1) {
    if (node.disabled) { // Håkon, 20070228
      return;
    }
    if ((node.tagName == 'INPUT' || node.tagName == 'SELECT' || node.tagName == 'TEXTAREA') && node.getAttribute('RULE') && !node.disabled ) {
      var rule = node.getAttribute('RULE')
      var ruleParts = rule.split(':');
      if (!rules[ruleParts[0]]) {
        alert("No such rule: "+ruleParts[0]);
        markError(node);
      }
      else {
        var params = new Array;
        for (var i = 1; i < ruleParts.length; i++) {
          params[params.length] = ruleParts[i];
        }
        var param = params.join(':');
        var value;
	

        if (node.tagName == 'SELECT') {
          value = node.options[node.selectedIndex].value;
        }
	else if (node.type.toLowerCase() == 'checkbox' && ruleParts[0] == 'numChecked') {
	  value = __checkCheckBoxGroup(node,param);
	}
	else if (node.type.toLowerCase() == 'radio') {
          if (ruleParts[0] == 'required') {
            node.setAttribute("rule", "numChecked:1,1");
            ruleParts[0] = "numChecked";
          }
          if (ruleParts[0] == 'numChecked') {
            value = __checkCheckBoxGroup(node,param);
          }
	}
	else if (node.id.substring(0,9) == 'comboBox_' && node.getAttribute("mode") == 'select') {
	  //20080208 nilschd, adding support for ComboBox in mode "select".
	  //Select mode allows you to use ComboBox as a select compononent with "type ahead feature"
	  var comboBoxId = node.id.substring(9, node.id.length-4 );
	  value = document.getElementById(comboBoxId).value;
	}
        else {
          value = node.value;
        }

        var ok = rules[ ruleParts[0] ](value, param);
        if (ok) {
          removeClassName( node,            "formRuleFailed"            );
          removeClassName( node.parentNode, "descendantsFormRuleFailed" );
          var type = node.type.toLowerCase();

          var wrapper = _getRadioOrCheckboxWrapper(node);
          if (wrapper) {
            removeClassName(wrapper, "descendantsFormRuleFailed");
            if (type === "radio"  ||  type === "checkbox") {
              removeClassName(wrapper, type + "RuleFailed");
            }
          }

          __formElements[ node.name ] = {
            node    : node,
            ruleMsg : node.getAttribute("ruleMsg"),
            success : true
          };
        }
        else {
          markError(node);
        }
      }
    }
    else if (node.hasChildNodes) {
      var nodes = node.childNodes;
      for (var i = 0; i < nodes.length; i++) {
        checkNode( nodes[i] );
      }
    }
  }
}

__ruleMsgSeen = {};
function markError(node) {
  formHasErrors = true;
  var ruleMsg = node.getAttribute('ruleMsg');
  if (ruleMsg) {
    // XXX nilschd to disabled duplicate ruleMsg's for checkbox'es and radiogroups
    if (node.type.toLowerCase() == 'checkbox' ||  node.type.toLowerCase() == 'radio') {
      if (!__ruleMsgSeen[ node.name ]) {
	formErrors.push(ruleMsg);
	__ruleMsgSeen[ node.name ] = true;
      }
    }
    else {
      formErrors.push(ruleMsg);
    }
  }

  if (!node.form.getAttribute("ruleHandler") && !node.getAttribute("ruleHandler")) {
    addClassName(node, "formRuleFailed");
    // XXX 20060612 to allow colors on checkbox and radios
    var type = node.type.toLowerCase();

    // Set className=descendantsFormRuleFailed on element surrounding the input field whose rule failed.
    var wrapper = _getRadioOrCheckboxWrapper(node);
    if (wrapper) {
      addClassName(wrapper, "descendantsFormRuleFailed");
      if (type === "radio"  ||  type === "checkbox") {
        addClassName(wrapper, type + "RuleFailed");
      }
    }
  }

  __formElements[ node.name ] = {
    node    : node,
    ruleMsg : ruleMsg,
    success : false
  };
}

function _getRadioOrCheckboxWrapper(node) {
  var type    = node.type.toLowerCase();
  var wrapper = node.parentNode;
  if (hasClassName(wrapper.parentNode, "o2radioButtons")  ||  hasClassName(wrapper.parentNode, "o2checkboxes")) {
    wrapper = wrapper.parentNode.parentNode;
  }
  else if (type === "checkbox"  ||  type === "radio") {
    wrapper = _findCheckboxOrRadioWrapper(node);
  }
  return wrapper;
}

function _findCheckboxOrRadioWrapper(node) {
  var form = node.form;
  var checkboxesOrRadios = _getInputFields(form, node.name);
  return _findClosestCommonAncestor(checkboxesOrRadios);
}

function _findClosestCommonAncestor(nodes) {
  var parent = nodes[0].parentNode;
  while (parent) {
    var failure = false;
    for (var i = 0; i < nodes.length; i++) {
      if (nodes[i].type.toLowerCase() !== "radio" && nodes[i].type.toLowerCase() !== "checkbox") {
        throw new O2Exception(nodes[i].type);
      }
      if (!_hasAncestor(nodes[i], parent)) {
        failure = true;
        break;
      }
    }
    if (!failure) {
      return parent;
    }
    parent = parent.parentNode;
  }
  throw new O2Exception("Didn't find a common ancestor");
}

function _hasAncestor(node, potentialAncestor) {
  while (node) {
    node = node.parentNode;
    if (node === potentialAncestor) {
      return true;
    }
  }
  return false;
}

function _getInputFields(node, name) {
  var inputFields = new Array();

  var inputs    = node.getElementsByTagName("input");
  var textareas = node.getElementsByTagName("textarea");
  var selects   = node.getElementsByTagName("select");

  for (var i = 0; i < inputs.length; i++) {
    if (!name  ||  inputs[i].name === name) {
      inputFields.push( inputs[i] );
    }
  }
  for (var i = 0; i < textareas.length; i++) {
    if (!name  ||  textareas[i].name === name) {
      inputFields.push( textareas[i] );
    }
  }
  for (var i = 0; i < selects.length; i++) {
    if (!name  ||  selects[i].name === name) {
      inputFields.push( selects[i] );
    }
  }

  return inputFields;
}

// XXX 20060612 added by nilschd to allow have a group of checkboxes and to specify that
// XXX e.g. 2 or more must be selected
var __radioOrCheckBoxGroups = {};
function __checkCheckBoxGroup(node,param) {
    var f = node.form; // the form
    var groupName = node.name; // the group of checkBoxes
    if(	__radioOrCheckBoxGroups[f.name+'_'+groupName]!= null) { // we have allready checked this group, aka cached
	return __radioOrCheckBoxGroups[f.name+'_'+groupName];
    }
    var totalChecked = 0;
    if(param=='') { // ok lets use default rule 1,*. Then the checkboxes acts like a radiogroup
	param='1,*';
    }
    
    var rules = param.split(",");

    if(f[groupName].length != null) {
      for(var i=0; i <f[groupName].length;i++) {
	if( f[groupName][i].checked ) {
	  totalChecked++;
	}
      }
    }
    else { // f[groupName].length is undefined if there's just one element in the group.
        if (node.checked) {
            totalChecked++;
        }
    }

    if(totalChecked >= parseInt(rules[0]) && (rules[1] =='*' || totalChecked <= parseInt(rules[1]))) {
	__radioOrCheckBoxGroups[f.name+'_'+groupName]=true;
    }
    else {
	__radioOrCheckBoxGroups[f.name+'_'+groupName]=false; //didn't meet requirments in the provided rule
    }
    return __radioOrCheckBoxGroups[f.name+'_'+groupName]
}
