function getElementsByAttribute(attribute, attributeValue)
{
  var elementArray = new Array();
  var matchedArray = new Array();

  if (document.all) {
    elementArray = document.all;
  } else {
    elementArray = document.getElementsByTagName("*");
  }

  for (var i = 0; i < elementArray.length; i++) {
    if (attribute == "class") {
      var pattern = new RegExp("(^| )" + attributeValue + "( |$)");

      if (pattern.test(elementArray[i].className)) {
        matchedArray[matchedArray.length] = elementArray[i];
      }
    } else if (attribute == "for") {
      if (elementArray[i].getAttribute("htmlFor") || elementArray[i].getAttribute("for")) {
        if (elementArray[i].htmlFor == attributeValue) {
          matchedArray[matchedArray.length] = elementArray[i];
        }
      }
    } else if (elementArray[i].getAttribute(attribute) == attributeValue) {
      matchedArray[matchedArray.length] = elementArray[i];
    }
  }

  return matchedArray;
}

function ordinal(n) {
  var sfx = ["th","st","nd","rd"];
  var val = n%100;
  return n + (sfx[(val-20)%10] || sfx[val] || sfx[0]);
}

function clearTooltips()
{
  var tips = getElementsByAttribute("class", "tooltip");
  var parent = null;
  for (var i = 0; i < tips.length; i++)
  {
    parent = tips[i].parentNode;
    parent.removeChild(tips[i]);
  }

  return true;
}

function trim(str) {
  return str.replace(/^\s+|\s+$/g, '');
};

function validRequired (trg) {
  var testval = /^.+$/
  var message = 'This field needs to be completed.';
  // are we being handed an event or a simple field?
  if(trg.target || trg.srcElement) {
    trg = (trg) ? trg : event;
    var trg = (trg.target) ? trg.target : ((trg.srcElement) ? trg.srcElement : null);
  } else {
    var trg = trg;
  }
  var myValue = trim(trg.value);  // value of field with whitespace trimmed off
  if (myValue == '') {
    // add a tooltip to show errors....
    showTooltip(trg,message);
    trg.className = trg.className + ' error';
    trg.focus();
    return false;
  }
  var temp = trg.className;
  var rg = /error/gi;
  trg.className = temp.replace(rg,'');
  // remove any previous tooltip
  var old_tooltip = document.getElementById( trg.id + "_tooltip");
  if( old_tooltip ) {
    parent = old_tooltip.parentNode;
    parent.removeChild(old_tooltip);
  }
  return true;
}

function getPosition(theElement)
{
  var positionX = 0;
  var positionY = 0;

  while (theElement != null)
  {
    positionX += theElement.offsetLeft;
    positionY += theElement.offsetTop;
    theElement = theElement.offsetParent;
  }

  return {x:positionX, y:positionY};
}

function getSize(theElement) {
  var pixelWidth = theElement.offsetWidth;
  var pixelHeight = theElement.offsetHeight;
  return {width:pixelWidth, height:pixelHeight};
}

function showTooltip(theElement,message) {
  var tip  = document.createElement("div");
  var para = document.createElement("p");
  tip.appendChild(para);
  
  if (theElement.getAttribute("id") != "") {
    tip.setAttribute("id", theElement.getAttribute("id") + "_tooltip");
  }

  tip.className = "tooltip";
  para.appendChild(document.createTextNode(message));
  
  var position       = getPosition(theElement);
  var size           = getSize(theElement);
  tip.style.position = "absolute";
  tip.style.left     = position.x + size.width - 10 + "px";
  tip.style.top      = position.y - 5 +"px";
  document.getElementsByTagName("body")[0].appendChild(tip);
  return true;
}

function simpleValidation (event_or_target,testval,message,formatter) {
  // are we being handed an event or a simple field?
  if(event_or_target.target || event_or_target.srcElement) {
    event_or_target = (event_or_target) ? event_or_target : event;
    var trg = (event_or_target.target) ? event_or_target.target : ((event_or_target.srcElement) ? event_or_target.srcElement : null);
  } else {
    var trg = event_or_target;
  }
  var myValue = trim(trg.value);  // value of field with whitespace trimmed off
  if (myValue != '') {
    if(formatter) {
      myValue = formatter(myValue);
    }
    if (!testval.test(myValue)) {
      // add a tooltip to show errors....
      showTooltip(trg,message);
      trg.className = trg.className + ' error';
      trg.focus();
      return false;
    } else {
      trg.value = myValue;
    }
  }
  var temp = trg.className;
  var rg = /error/gi;
  trg.className = temp.replace(rg,'');
  // remove any previous tooltip
  var old_tooltip = document.getElementById( trg.id + "_tooltip");
  if( old_tooltip ) {
    parent = old_tooltip.parentNode;
    parent.removeChild(old_tooltip);
  }
  return true;
}

function validInteger (trg) {
  var testval = /^[\-]?(0|[1-9][0-9]*)$/;
  var message = 'Please enter a whole number';
  return simpleValidation(trg,testval,message,null);
} // validInteger

function validPosinteger (trg) {
  var testval = /^(0|[1-9][0-9]*)$/
  var message = 'Please enter a positive whole number';
  return simpleValidation(trg,testval,message,null);
} // validPosinteger

function validDecimal (trg) {
  var testval = /^[\-]?(0|[1-9][0-9]*)(\.[0-9]+)?$/
  var message = 'Please enter a decimal number';
  return simpleValidation(trg,testval,message,null);
} // validDecimal

function validPosdecimal (trg) {
  var testval = /^(0|[1-9][0-9]*)(\.[0-9]+)?$/
  var message = 'Please enter a positive decimal number';
  return simpleValidation(trg,testval,message,null);
} // validPosdecimal

function validEmail (trg) {
  var testval = /^[a-zA-Z][a-zA-Z0-9\-\._]*\@([0-9a-zA-Z\-]{2,}\.)+([a-zA-Z]{2,3})+$/
  var message = 'Please enter a valid email format e.g. xyz@abc.co.uk';
  return simpleValidation(trg,testval,message,null);
} // validEmail

function validPostcode (trg) {
  var testval = /^[A-Z]{1,2}[0-9]{1,2}[A-Z]? [0-9][A-Z]{2}$/
  var message = 'Please enter a valid postcode';
  return simpleValidation(trg,testval,message,uppercaseFormat);
} // validPostcode

function validPhone (trg) {
  var testval = /^(\+[0-9]{1,3} )?\(?[0-9]{1,5}\)?[\-0-9 ]{4,}$/
  var message = 'Please enter a valid telephone number, possibly including an international prefix e.g. +44 (0)20 7563 4343';
  return simpleValidation(trg,testval,message,null);
} // validPhone

function validDate(e_o_t) {
  if(e_o_t.target || e_o_t.srcElement) {
    e_o_t = (e_o_t) ? e_o_t : event;
    var trg = (e_o_t.target) ? e_o_t.target : ((e_o_t.srcElement) ? e_o_t.srcElement : null);
  } else {
    var trg = e_o_t;
  }
  
  var value = trim(trg.value.toLowerCase());
  if (value != '' || value == '0000-00-00') { // don't bother if it's empty
    var monthnames = new Array('jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec');
  
    var sqldate = /^(19[0-9]{2}|20[0-9]{2})\-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/
    var ukdate  = /^(0?[1-9]|[12][0-9]|3[01])(?:\-|\/|\.)(0?[1-9]|1[0-2])(?:\-|\/|\.)(19[0-9]{2}|20[0-9]{2})$/
    var jbdate  = /^([1-9]|[12][0-9]|3[01])(?: *)(?:st|nd|rd|th)?(?: +)(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)(?: +)(19[0-9]{2}|20[0-9]{2})$/
  
    if (sqldate.test(value)) {
      var parts = sqldate.exec(value)
      var day       = parts[3]*1;
      var month     = (parts[2]-1)*1;
      var year      = parts[1];
      //alert('Matches the sql date format yyyy-mm-dd: '+day+' '+month+' '+year);
    } else if (ukdate.test(value)) {
      var parts = ukdate.exec(value)
      var day       = parts[1]*1;
      var month     = (parts[2]-1)*1;
      var year      = parts[3];
      //alert('Matches the ukdate date format dd-mm-yyyy, dd/mm/yyyy, or dd.mm.yyyy: '+day+' '+month+' '+year);
    } else if (jbdate.test(value)) {
      var parts     = jbdate.exec(value)
      var day       = parts[1];
      var monthname = parts[2];
      var year      = parts[3];
      var monthname = monthname.substring(0,3);
      var month     = 0;
      for (i=0; i<monthnames.length; i++) {
        if (monthnames[i] == monthname) { month = i; break ;}
      }
      //alert('Matches the jbdate date: '+day+' '+month+' '+year);
    } else {
      showTooltip(trg,'This is not a recognised date format. Try using dd/mm/yyyy.');
      trg.className = trg.className + ' error';
      trg.focus();
      return false;
    }
  
    // okay we have the parts - now what do they mean?
    myDate = new Date(year,month,day);
    day    = myDate.getDate();
    month  = myDate.getMonth();
    year   = myDate.getFullYear(); // get 2007 not 107!
    var mn = monthnames[month];    // remember - still all lowercase!
    trg.value = ordinal(day)+' '+mn[0].toUpperCase()+mn.substring(1,3)+' '+year;
  }
  var temp = trg.className;
  var rg = /error/gi;
  trg.className = temp.replace(rg,'');
  // remove any previous tooltip
  var old_tooltip = document.getElementById( trg.id + "_tooltip");
  if( old_tooltip ) {
    parent = old_tooltip.parentNode;
    parent.removeChild(old_tooltip);
  }
  return true;
}

function validTime(e_o_t) {
  if(e_o_t.target || e_o_t.srcElement) {
    e_o_t = (e_o_t) ? e_o_t : event;
    var trg = (e_o_t.target) ? e_o_t.target : ((e_o_t.srcElement) ? e_o_t.srcElement : null);
  } else {
    var trg = e_o_t;
  }
  
  var value = trim(trg.value.toLowerCase());  // value of field with whitespace trimmed off
  
  if (value != '') {
    // use non-capturing brackets to add optional secondss...
    var time_24hr = /^(0?[0-9]|1[0-9]|2[0-3])(?:\.|\-|\:)([0-5][0-9])(?:(?:\.|\-|\:)([0-5][0-9]))?$/;
    var time_12hr = /^([1-9]|1[0-2]|)(?:(?:(?:\.|\-|\:)([0-5][0-9]))?(?:(?:\.|\-|\:)([0-5][0-9]))?) *(am|pm|AM|PM)$/;

    if (time_24hr.test(value)) {
      var parts  = time_24hr.exec(value)
      var hours   = parts[1];
      var minutes = parts[2];
      var seconds = (parts[3]?parts[3]:'00');
      //alert('Matches the sql date format yyyy-mm-dd: '+day+' '+month+' '+year);
    } else if (time_12hr.test(value)) {
      var parts  = time_12hr.exec(value)
      var amorpm = parts[4];
      var hours   = parts[1]
      if ( hours == 12 ) {
        hours = amorpm.toLowerCase() == 'am'?'00':'12';
      } else {
        hours = amorpm.toLowerCase() == 'am'?hours:(hours*1+12)+'';
      }
      var minutes = (parts[2]?parts[2]:'00');
      var seconds = (parts[3]?parts[3]:'00');
      //alert('Matches the ukdate date format dd-mm-yyyy, dd/mm/yyyy, or dd.mm.yyyy: '+day+' '+month+' '+year);
    } else {
      showTooltip(trg,'This is not a recognised time format. Try using hh:mm.');
      trg.className = trg.className + ' error';
      trg.focus();
      return false;
    }
    trg.value = (hours == 0?12:((hours-1)%12)+1)+(minutes*1 == 0?'':':'+minutes)+(hours>11?'pm':'am');
  }  
  var temp = trg.className;
  var rg = /error/gi;
  trg.className = temp.replace(rg,'');
  // remove any previous tooltip
  var old_tooltip = document.getElementById( trg.id + "_tooltip");
  if( old_tooltip ) {
    parent = old_tooltip.parentNode;
    parent.removeChild(old_tooltip);
  }
  return true;
}

function validTimestamp(e_o_t) {
  // fixme Actually write the validator!!!
  return true;
}
