/*
 * Max Spicer, 22/10/2002
 * Checkboxes added by Andy Pomfret, 01/09/2004
 */
function validateForm(f)
{
  var date;
  var maxdate;
  var mindate;
  var dateArray;
  var time;
  var maxtime;
  var mintime;
  var timeArray;
  var emptyFields = "";
  var errors = "";
  var msg;
  var elementName;
  // If set, checks for empty fields aren't made, but content validation (e.g. valid date) checks are still
  // performed
  var alloptional = f.alloptional!=null && f.alloptional;

  // Check validation hasn't been specifically turned off.
  if(f.validate!=null && !f.validate)
  {
    return true;
  }

  for(var i=0; i<f.length; i++)
  {
    var e=f.elements[i];

    /* If no display name is given, and the name attribute exists, use the name of the form element but with the first letter capitalised */
    if(e.displayname == null && e.name){
      elementName = e.name.charAt(0).toUpperCase() + e.name.substring(1);
    } else {
      elementName = e.displayname;
    }

    if(!e.disabled)
    {
      // Start of radio form validation - Dan Saltmer
      if(e.type == "radio" && e.optional == false && !alloptional)
          // AJP: Loop only if e.optional is defined; this loop is consequently
          // only ever executed for the first radio button in a set.
      {
        var radioGroup=eval('f.' + e.name);
        var radioChecked=false;

        for(var j=0; j < radioGroup.length; j++)
        {
          if(radioGroup[j].checked)
          {
            radioChecked=true;
            break;
          }
        }

        if(!radioChecked)
        {
          emptyFields += "\n    " + elementName;
        }
      }

      // Start of checkbox form validation - Andy Pomfret
      if(e.type == "checkbox" && !alloptional && (e.optional!=null || e.min!=null || e.max!=null))
                                                   // Checkboxes are harder since it is possible that an optional
                                                   // checkbox set could be filled in; if this is the case, the number
                                                   // of checked boxes still needs validating against the min and max
                                                   // parameters.
      {
        var optionalCheckGroup = (e.optional==null?false:e.optional);
        var checkGroup=eval('f.' + e.name);
        var numChecked=0;

        for(var j=0; j<checkGroup.length; j++)
        {
          if(checkGroup[j].checked) {
            numChecked+=1;
          }
        }

        if(!optionalCheckGroup || numChecked>0)
        {
          if((e.min != null && numChecked<e.min)
            || (e.max != null && numChecked>e.max))
          {
            if(e.min != null && e.max == null)
            {
              errors += "- At least " + e.min;
            }
            else if(e.min != null && e.max != null)
            {
              errors += "- At least " + e.min + " and no more than " + e.max;
            }
            else
            {
              errors += "- No more than " + e.max;
            }
            errors += " boxes from the list, '" + elementName + "', must be ticked.\n";
          }
        }
        if(!optionalCheckGroup && numChecked == 0)
        {
          emptyFields += "\n    " + elementName;
        }
      }
      // End of checkbox validation

      if((e.type == "text" || e.type == "password" || e.type == "textarea" || e.type == "file" || (e.type == "hidden" && e.required)))
      {
        // If the field is empty and mandatory, flag an error, if empty but optional, skip this field.
        if(!alloptional && (e.value == null || !e.value.search(/^\s*$/)))
        {
          if(!e.optional)
          {
            emptyFields += "\n    " + elementName;
          }
          continue;
        }

        // Now check elements that should be numeric
        if((e.numeric || e.min != null || e.max != null || e.integer != null) && e.value.search(/^\s*$/))
        {
          var v = new Number(e.value);
          if(isNaN(v)
            || (e.min != null && v < e.min)
            || (e.max != null && v > e.max)
            || (e.integer != null && e.value.search(/\./) != -1))
          {
            errors += "- The text box '" + elementName + "' must contain a";

            if(e.integer != null) {
              errors += " whole";
            }

            errors += " number";

            if (e.min != null && e.max != null) {
              errors += " between " + e.min + " and " + e.max + ", inclusive";
            } else if (e.min != null) {
              errors += " that is greater than or equal to " + e.min;
            } else if (e.max != null) {
              errors += " that is less than or equal to " + e.max;
            }
            errors += ".\n";

          }
        }

        // Now check elements that should have a minimum length
        if (e.minLength != null && e.minLength != -1)
        {
          if (e.value.length < e.minLength)
          {
            errors += "- The contents of text box '" + elementName + "' must be at least " + e.minLength;
            if (e.numeric) {
              errors += " digits long.\n";
            } else {
              errors += " characters long.\n";
            }
          }
        }

        // Now check elements that should have a maximum length
        if (e.maxLength != null && e.maxLength != -1)
        {
          if (e.value.length > e.maxLength)
          {
            errors += "- The contents of text box '" + elementName + "' cannot be longer than " + e.maxLength;
            if (e.numeric) {
              errors += " digits.\n";
            } else {
              errors += " characters.\n";
            }
          }
        }

        // Now check elements that should be a date
        if((e.date || e.maxdate || e.mindate) && e.value.search(/^\s*$/))
        {
          if(e.maxdate)
          {
            dateArray = e.maxdate.split("/");
            maxdate = new Date(dateArray[2],dateArray[1]-1,dateArray[0]);
          }
          if(e.mindate)
          {
            dateArray = e.mindate.split("/");
            mindate = new Date(dateArray[2],dateArray[1]-1,dateArray[0]);
          }
          dateArray = e.value.split("/");
          if(dateArray.length != 3
            || dateArray[2].length != 4
            || dateArray[0] > 31 || dateArray[0] < 1
            || dateArray[1] > 12 || dateArray[1] < 1
            || isNaN(date = new Date(dateArray[2], dateArray[1]-1, dateArray[0]))
            || date.getDate() != dateArray[0] // Check the actual date is valid e.g. reject 31/09/2002 (30 days hath September ...)
            || e.maxdate && Math.floor((maxdate.getTime()-date.getTime())/(1000*60*60*24)) < 0
            || e.mindate && Math.floor((mindate.getTime()-date.getTime())/(1000*60*60*24)) > 0)
          {
            errors += "- The text box '" + elementName + "' must contain a valid date in dd/mm/yyyy format"
            if(e.mindate)
            {
              errors += " that cannot be before " + e.mindate;
              if(e.maxdate)
              {
                errors += " and cannot be after " + e.maxdate;
              }
            } else if(e.maxdate)
            {
              errors += " that cannot be after " + e.maxdate;
            }
            errors += ".\n";
          }
        }

        // Now check elements that should be a time
        if((e.time || e.maxtime || e.mintime) && e.value.search(/^\s*$/))
        {
          if(e.maxtime)
          {
            timeArray = e.maxtime.split(":");
            maxtime = new Date(0,0,0,timeArray[0],timeArray[1]);
          }
          if(e.mintime)
          {
            timeArray = e.mintime.split(":");
            mintime = new Date(0,0,0,timeArray[0],timeArray[1]);
          }
          timeArray = e.value.split(":");
          if(timeArray.length != 2
            || timeArray[1].length != 2
            || timeArray[0] < 0 || timeArray[0] > 23
            || timeArray[1] < 0 || timeArray[1] > 59
            || isNaN(time = new Date(0,0,0,timeArray[0],timeArray[1]))
            || e.maxtime && (maxtime.getTime()-time.getTime()) < 0
            || e.mintime && (mintime.getTime()-time.getTime()) > 0)
          {
            errors += "- The text box '" + elementName + "' must contain a valid time in hh:mm format"
            if(e.mintime)
            {
              errors += " that cannot be before " + e.mintime;
              if(e.maxtime)
              {
                errors += " and cannot be after " + e.maxtime;
              }
            } else if(e.maxtime)
            {
              errors += " that cannot be after " + e.maxtime;
            }
            errors += ".\n";
          }
        }
      }

      // Check for selects that have not been selected
      // (assumes the first option in a select is a dummy option, e.g. '-- Select --' if it's a single select)
      if(
        !alloptional 
        && !e.optional 
        && (
          (e.type == "select-one" && e.selectedIndex < 1)
          || (e.type == "select-multiple" && e.selectedIndex == -1)
        )
      )
      {
        emptyFields += "\n    " + elementName;
      }
    }
  }

  if(!emptyFields && !errors) return true;

  msg = "This page could not be submitted because of the following error(s):\n"
  msg += "Please correct these errors and try again.\n"

  if(emptyFields)
  {
    msg += "- The following required text boxes are empty:" + emptyFields + "\n";
    if(errors) msg += "\n";
  }
  msg += errors;
  alert(msg);
  return false;
}
