/**
 *  @fileoverview This file contains global functions used throughout site.
 *//*
 *  Functions:
 *    addBreaks(s)
 *    autoFill()
 *    colAdjust()
 *    formatPhone()
 *    formatZip()
 *    isBlank(value)
 *    isDate(value)
 *    isEmail(value)
 *    isNumeric(value)
 *    isTime(value)
 *    numcmp(n1, n2)
 *    swapImg()
 *    swrite_r(a)---DEBUG
 *    verifyDate()
 *    verifyThisDate()
 *    verifyFields()
 *    verifyForm()
 *    verifyInt(strN)
 *    verifyZip()
 *    write_r(a)---DEBUG
 *    zapSpecials()
 *
 *  AJAX function
 *    makeXMLHttpRequest()
 *
 *  String utils
 *    addSeparators()
 *    stripSeparators()
 *    strcmp(s1, s2)
 *    String.repeat(n)
 *    String.replaceChar(tar, rep)
 *    String.stripSpaces()
 *    String.reverse()
 *    String.trimLeft()
 *    String.trimRight()
 *    String.trim()
 *    String.ungetc()
 */

/**
 *  If isDebug true, sets up System object (using window.Packages.java.*). Use
 *  System.out.println("From JavaScript ..."); to write to Java console.
 */
var isDebug = false;

if (isDebug) {
  try {
    var System = java.lang.System;
  }
  catch (e) {
    alert(e);
  }
}

var isIE = false;
var ver = 0;
var regxIE  = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
if (regxIE.exec(navigator.userAgent) != null) {
  ver = parseFloat( RegExp.$1 );
  if (ver < 7) {
    isIE = true;
  }
}

/**
 *  Adds a break element for each new line in text.
 *  @param  {String} s
 *  @type   String
 */
function addBreaks(s)
{
  if (s) {
    if (s.indexOf("\r\n") != -1) {       // Win
      return s.replaceChar("\r\n", "<br />\r\n");
    }
    else if (s.indexOf("\r") != -1) {      // Mac
      return s.replaceChar("\r", "<br />\r");
    }
    else if (s.indexOf("\n") != -1) {      // Unix
      return s.replaceChar("\n", "<br />\n");
    }
    return s;
  }
  return "";
}

/**
 *  Assigns the value of one text input field to another (if it is blank).
 *  @param  {HTMLInputElement} source       text input field
 *  @param  {HTMLInputElement} destination  text input field
 *  @type   void
 */
function autoFill(source, destination)
{
  if (source.type == "text" && destination.type == "text") {
    if (destination.value === "") {
      destination.value = source.value;
    }
  }
}

/**
 *  Adjusts scan and main columns height to screen size. Assigned as window.onunload event
 *  handler.
 *  @type   void
 */
function colAdjust()
{
  var docEl = (isIE) ? "body" : "html"; // diff ie < 7

  var docScrollH = document.getElementsByTagName(docEl)[0].scrollHeight;
  var docClientH = document.getElementsByTagName(docEl)[0].clientHeight;

  var logoH = document.getElementById('logo').scrollHeight;
  var menuH = (document.getElementById('menubar')) ?
    document.getElementById('menubar').scrollHeight :
    0;

  var headerH = logoH + menuH;

  var colH = docScrollH - headerH;

  docClientH -= headerH;

  var scanColH = document.getElementById("scancol").scrollHeight;
  var mainColH = document.getElementById("maincol").scrollHeight;

  if (scanColH < docClientH && mainColH < docClientH) {
    document.getElementById("scancol").style.height = docClientH + "px";
    document.getElementById("maincol").style.height = docClientH + "px";
  }
  else {
    document.getElementById("scancol").style.height = colH + "px";
    document.getElementById("maincol").style.height = colH + "px";
  }
}

/**
 *  Formats and validates phone number string to (nnn) nnn-nnnn.
 *  @param  {String} s  String to convert
 *  @return If success then converted string else -1
 *  @type   mixed
 */
function formatPhone(s)
{
  var c;
  var d = "";
  var phonef = "";

  for (var i = 0; i < s.length; i++) {
    c = s.substr(i, 1);
    if (isNaN(parseInt(c))) {
      c = "";
    }
    d += c;
  }

  if (d.length != 10) {
    return -1;
  }
  else {
    phonef += "(";
    phonef += d.substr(0, 3);
    phonef += ") ";
    phonef += d.substr(3, 3);
    phonef += "-";
    phonef += d.substr(6);
    return phonef;
  }
}

/**
 *  Formats and validates zip code string to nnnnn or nnnnn-nnnn.
 *  @param  {String} s          String to convert
 *  @return If success then converted string else false.
 *  @type   mixed
 */
function formatZip(s)
{
  var c;
  var d = "";
  var zipf = "";

  // remove all chars except digits
  for (var i = 0; i < s.length; i++) {
    c = s.substr(i, 1);
    if (isNaN(parseInt(c))) {
      c = "";
    }
    d += c;
  }

  if (d.length == 5) {
    return d;
  }
  else if (d.length == 9) {
    zipf += d.substr(0, 5);
    zipf += "-";
    zipf += d.substr(5);
    return zipf;
  }
  else {
    return false;
  }
}

/**
 *  Tests (trimmed) string variable for content.
 *  @param  {String} s
 *  @type   boolean
 */
function isBlank(s)
{
  return (s.trim() == "");
}

/**
 *  Tests whether a string is in a valid date format.
 *  @param  {String} value
 *  @type   boolean
 */
function isDate(value)
{
  if (isNaN(Date.parse(value))) {
    return false;
  }
  return true;
}

/**
 *  Tests whether a string is in a valid email format. Input must match regx format
 *  /[%\+-\.\w]+&#64;[-\.a-z0-9]+\.[\w]+/i
 *  @param  {String} value
 *  @type   boolean
 */
function isEmail(value)
{
  if (value != "" && !/[%\+-\.\w]+@[-\.a-z0-9]+\.[\w]+/i.test(value)) {
    return false;
  }
  return true;
};

/**
 *  Tests whether value is numeric.
 *  @param  {String} value
 *  @type   boolean
 */
function isNumeric(value)
{
  return !isNaN(stripSeparators(value.trim()));
}

/**
 *  Tests whether a string is in a valid time format. Input must match regx format
 *  /\d\d:\d\d:\d\d/
 *  @param  {String} value
 *  @type   boolean
 */
function isTime(value)
{
  if (value != "" && !/\d\d:\d\d:\d\d/.test(value)) {
    return false;
  }
  return true;
};

/**
 *  Compares two numbers for sorting. See strcmp().
 *  @param  {numeric} n1
 *  @param  {numeric} n2
 *  @type   int
 *  @return If n1 > n2 then (> 0) else if n1 == n2 then 0 else (< 0) (i.e. n1 < n2)
 */
function numcmp(n1, n2)
{
  return n1 - n2;
}

var subwin = null;

/**
 *  Opens a window.
 *  @param  {String} url
 *  @param  {int} width   optional (0 for default)
 *  @param  {int} height  optional (0 for default)
 *  @type Window
 */
function openWindow(url)
{
  var winFeatures;

  var width = (arguments[1]) ? arguments[1] : 800;
  var height = (arguments[2]) ? arguments[2] : 600;

  try {
    subwin.close();
  }
  catch (e) {}

  winFeatures = "menubar,toolbar,location,scrollbars,status,resizable";
  winFeatures += ",width=" + width + ",height=" + height;

  subwin = window.open(url, "subwin", winFeatures);
}

/**
 *  Swaps an image by changing its src. Does nothing if swap fails.
 *  @param  {String} id    ID attribute of image element to replace
 *  @param  {Image} img   replacement Image object
 *  @type   void
 */
function swapImg(id, img)
{
  if (document.getElementById) {
    document.getElementById(id).src = img.src;
  }
  else if (document.all) {
    document.all[id].src = img.src;
  }
}

/**
 *  DEBUG FUNCTION: Parses an array and converts the elements to a string.
 *  @param  {Array} a
 *  @type   String
 */
function swrite_r(a)
{
  var s = "array [key] : value\n";

  for (item in a) {
    s += "  [" + item + "] : " + a[item] + "\n";
  }

  return s;
}

/**
 *  Tests for valid date string and converts it to MySQL format.
 *  @param  {HTMLInputElement} dateField  text input field
 *  @type   boolean
 */
function verifyDate(dateField)
{
  var value = dateField.value;

  if (!value) {   // pass through blank field
    return true;
  }

  var c;
  var d = "";
  var day;
  var months = new Array("01", "02", "03", "04", "05", "06", "07",
    "08", "09", "10", "11", "12");

  // JavaScript doesn't like yyyy-mm-dd format; yyyy/mm/dd is ok.
  if (value.indexOf("-") != -1) {
    value.replaceChar("-", "/");
  }

  if (isDate(value)) {
    var date = new Date(value);

    day = date.getDate();

    if (day < 10) {
      day = "0" + day;
    }

    dateField.value = date.getFullYear() + "-" +
      months[date.getMonth()] + "-" + day;

    return true;
  }
    return false;
}

/**
 *  Checks that specified form fields have values. Alerts user and returns false on 1st blank
 *  field; clears and puts focus on offending field. Skips items in fields array which are not
 *  defined elements in the form. HTML syntax:
 *  <blockquote>
 *    <code>
 *      &lt;form ... onsubmit="return verifyFields(this, ['name1', 'name2', 'name3']);"&gt;<br />
 *      &nbsp;&nbsp;&lt;input name="name1" ... title="verbose element description" /&gt;<br />
 *      &nbsp;&nbsp;&lt;input name="name2" ... title="verbose element description" /&gt;<br />
 *      &lt;/form&gt;
 *    </code>
 *  </blockquote>
 *  @param  {HTMLformElement} form    HTMLformElement
 *  @param  {Array} fields            array of required field names
 *  @return If all required fields have values then true else false
 *  @type   boolean
 */
function verifyFields(form, fields)
{
  var els = form.elements;
  var field;
  var title;
  var errormsg = "A required field is blank or contains only spaces.\n";
  errormsg += "Please enter info for ";

  for (var i = 0; i < fields.length; i++) {
    if (!els[fields[i]]) {
      continue;
    }

    field = els[fields[i]];

    // test non-select elements
    if (field.type.indexOf("select") == -1) {
      // test for blank or all white space
      if (field.value === "" ||
          !(/\S+/.test(field.value))) {
        title = (field.title) ? field.title : field.name;
        window.alert(errormsg + title + ".");
        els[fields[i]].value = "";
        els[fields[i]].focus();
        return false;
      }
    }
    // test select element
    else if (field.value == "-1" || field.value == "0") {
      title = (field.title) ? field.title : field.name;
      window.alert(errormsg + title + ".");
      field.focus();
      return false;
    }
  }
  return true;
}

/**
 *  Verifies that required fields have a value. Fails and alerts on first ommission. Does
 *  not test checkboxes or radio buttons. Set the class of the element to 'noverify' to
 *  skip verification.
 *  @param  {HTMLFormElement} form   form to be verified
 *  @return If is blank required field then false else true
 *  @type   boolean
 */
function verifyForm(form)
{
  var els = form.elements;
  var type;
  var errormsg = "A required field is blank or contains only spaces.\n";
  errormsg += "Please enter info for ";

  for (var i = 0; i < els.length; i++) {
    // skip these fields
    if (els[i].disabled || els[i].className.indexOf("noverify") != -1) {
      continue;
    }

    type = els[i].type;

    // test non-select elements
    if (type.indexOf("select") == -1) {
      // test for blank or all white space
      if (!els[i].value.trim()) {
        alert(errormsg + els[i].name);
        els[i].value = "";
        els[i].focus();
        return false;
      }
    }
    // test select element
    else {
      if (els[i].value == "-1") {
        alert(errormsg + els[i].name);
        els[i].focus();
        return false;
      }
    }
  }
  return true;
}

/**
 *  Casts String argument as a number and rounds to integer.
 *  @param  {String}  strN
 *  @return {mixed}          if strN isNaN then false else cast as integer
 */
function verifyInt(strN)
{
  var intN = Math.round(strN);

  if (isBlank(strN) || isNaN(intN)) {
    return false;
  }

  return intN;
}

/**
 *  Verifies valid date format and alerts on error.
 *  <br />
 *  Simpler than verifyDate() above, this function only checks for a valid date
 *  format. Since it does no formatting, the value must be changed before it is
 *  saved to a MySQL DATE field.
 *  @param    {HTMLInputElement}  field   text input field
 *  @return   If valid date format then true else false.
 *  @type     boolean
 */
function verifyThisDate(field) {
  var msg;

  if (field.value != "") {  // pass through blank fields
    if (!isDate(field.value)) {
      msg = "Date format not recognized.\n";
      msg += "Use mm/dd/yyyy format.";

      alert(msg);
      field.value = "";
      return false;
    }
  }
  return true;
}

/**
 *  Verifies zip is correct length and formats it.
 *  @param    {HTMLInputElement} el   text input field
 *  @type     void
 *  @requires formatZip()
 */
function verifyZip(el)
{
  var zipf;
  var errmsg = "Something was wrong with the zipcode.\n" +
    "Please use format nnnnn or nnnnn-nnnn.";

  if (el.value) {
    zipf = formatZip(el.value);
    if (zipf) {
      el.value = zipf;
    }
    else {
      el.value = "";
      window.alert(errmsg);
    }
  }
}

/**
 *  DEBUG FUNCTION: Prints array items.
 *  @param  {Array} a
 *  @type   void
 */
function write_r(a)
{
  document.write(swrite_r(a));
}

/**
 *  Converts curly double and single quotes, dashes, and dots to cross-platform
 *  characters. Test string:
 *  &lsquo; &rsquo; &ldquo; &rdquo; &bull; &ndash; &mdash;
 *  @param  {String} s        String to be zapped
 *  @type   String
 */
function zapSpecials(s) {
  var c;
  var d = "";

  for (var i = 0; i < s.length; i++) {
    c = s.charAt(i);

    switch (c.charCodeAt(0)) {
      case 145:
      case 146:
      case 8216:
      case 8217:
        d += "'"; // single quotes
        break;

      case 147:
      case 148:
      case 8220:
      case 8221:
        d += "\"";  // double quotes
        break;

      case 149:
      case 150:
      case 151:
      case 8211:
      case 8212:
      case 8226:
        d += "-"; // dots and dashes
        break;

      default:
        d += c;
        break;
    }
  }
  return d;
}

// AJAX GLOBAL VARIABLE AND FACTORY FUNCTION

var xhRequest;

/**
 *  Sets global var xhRequest to an XML-HTTP request (AJAX) instance.
 *  @global {XMLHttpRequest} xhRequest
 *  @type   boolean
 *  @todo   Consolidate all handleResponse functions into one.
 */
function makeXMLHttpRequest()
{
  xhRequest = null;

  // Mozilla, Safari, ...
  if (window.XMLHttpRequest) {
    xhRequest = new XMLHttpRequest();
    if (xhRequest.overrideMimeType) {
      xhRequest.overrideMimeType('text/xml');
    }
  }
  // IE
  else if (window.ActiveXObject) {
    try {
      xhRequest = new ActiveXObject("Msxml2.XMLHTTP");
    }
    catch (e) {
      try {
        xhRequest = new ActiveXObject("Microsoft.XMLHTTP");
      }
      catch (e) {}
    }
  }
  if (!xhRequest) {
    alert('Error: this browser cannot create an XMLHttpRequest instance');
    return false;
  }
  else {
    xhRequest.post = function (reqFile, reqData)
    {
      xhRequest.onreadystatechange = handleResponse;
      xhRequest.open('POST', reqFile, true);
      xhRequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
      xhRequest.send(reqData);
    };
  }

  return true;
}

// STRING UTILS

/**
 *  Variable containing the thousands separator. Used by add- and
 *  stripSeparators().
 *  @type   String
 */
var separator = ",";

/**
 *  Casts integer as string and adds thousands separators to it.
 *  @param  {int}     n   number in format n,nnn
 *  @type   String
 */
function addSeparators(n)
{
  var d = "";
  var ndx = 1;
  var strN = n.toString();
  var len = strN.length;

  for (var i = len - 1; i >= 0; i--) {
    d += strN.charAt(i);
    if (ndx < len && ((ndx++ % 3) == 0)) {
      d += separator;
    }
  }
  return d.reverse();
}

/**
 *  Strips thousands separators from a number (string).
 *  @param  {String} s     String version of integer
 *  @type   String
 */
function stripSeparators(s)
{
  return s.replaceChar(separator, "");
}

/**
 *  Compares two strings for sorting. See numcmp().
 *  @param  {String} s1
 *  @param  {String} s2
 *  @type   int
 *  @return If s1 > s2 then 1 else if s1 == s2 then 0 else -1 (i.e. s1 < s2)
 */
function strcmp(s1, s2)
{
  if (s1 > s2) {
    return 1;
  }

  if(s1 == s2) {
    return 0;
  }

  return -1;
}

// STRING OBJECT PROTOTYPES

/**
 *  Returns the string repeated n times.
 *  @param  {int} n
 *  @return {String} If n is NaN then return the string else return repeated string.
 */
String.prototype.repeat = function (n)
{
  n = parseInt(n);

  if (isNaN(n)) {
    return this;
  }

  return new Array(n + 1).join(this);
};

/**
 *  Replaces all target characters with the replacement character. Works by
 *  splitting it into an array and then rejoining it. Method extends String object.
 *  @param  {String} tar    target character to replace
 *  @param  {String} rep    replacement character
 *  @return String with target characters replaced
 *  @type   String
 */
String.prototype.replaceChar = function (tar, rep)
{
  return this.split(tar).join(rep);
};

/**
 *  Strips spaces from a string. Method extends String object.
 *  @requires String
 *  @return   String with spaces removed.
 *  @type     String
 */
String.prototype.stripSpaces = function ()
{
  return this.replaceChar(" ", "");
};

/**
 *  Reverses the characters in a string. Method extends String object.
 *  @requires String
 *  @return   String in reversed order
 *  @type     String
 */
String.prototype.reverse = function ()
{
  return this.split("").reverse().join("");
};

/**
 *  Trims leading whitespace from a string. Method extends String object.
 *  @requires String
 *  @return   New string with spaces removed from the left.
 *  @type     String
 */
String.prototype.trimLeft = function ()
{
  return this.replace(/^\s+/, "");
};

/**
 *  Trims trailing whitespace from a string. Method extends String object.
 *  @requires String
 *  @return   New string with spaces removed from the right.
 *  @type     String
 */
String.prototype.trimRight = function ()
{
  return this.replace(/\s+$/, "");
};

/**
 *  Trims leading and trailing whitespace from a string. Method extends String
 *  object.
 *  @requires String
 *  @return   New string with leading and trailing spaces removed.
 *  @type     String
 */
String.prototype.trim = function ()
{
  return this.trimLeft().trimRight();
};

/**
 *  Removes the last char from a string.  Method extends String object.
 *  @requires String
 *  @return   New string with one last char than the original.
 *  @type     String
 */
String.prototype.ungetc = function ()
{
  return this.substr(0, this.length - 1);
};
