function hideIfUnset(a, b)
{
  a = document.getElementById(a);
  b = document.getElementById(b);
  b.style.display = (a.value == '' ? 'none' : '');
}

function displayOrHide(element, display)
{
  if (element)
  {
    element.style.display = display ? "" : "none";
  }
}

function updateDimensionElement(title)
{
  var valueElement = document.getElementById('value_' + title);
  var leElement = document.getElementById('rangeLe_' + title);
  var geElement = document.getElementById('rangeGe_' + title);
  if (leElement != null && geElement != null)
  {
    var v = valueElement.value;
    {
      var i = 0;
      var currentSelectedValue = leElement.selectedIndex != -1 ?
        leElement.options[leElement.selectedIndex].value : null;
      leElement.options.length = 0;
      var newSelectedIndex = 0;
      for (var j = 0; j <= valueElement.selectedIndex; j++)
      {
        leElement.options[i] = new Option(
          valueElement.options[j].text, valueElement.options[j].value);
        if (valueElement.options[j].value == currentSelectedValue)
        {
          newSelectedIndex = i;
        }
        i++;
      }
      leElement.selectedIndex =
        newSelectedIndex < leElement.options.length ? newSelectedIndex : 0;
    }
    {
      var currentSelectedValue = geElement.selectedIndex != -1 ?
        geElement.options[geElement.selectedIndex].value : null;
      geElement.options.length = 0;
      var newSelectedIndex = 0;
      var i = 0;
      geElement.options[i++] = new Option("", "-1");
      for (var j = valueElement.selectedIndex; j < valueElement.options.length; j++)
      {
        geElement.options[i] = new Option(
          valueElement.options[j].text, valueElement.options[j].value);
        if (valueElement.options[j].value == currentSelectedValue)
        {
          newSelectedIndex = i;
        }
        i++;
      }
      geElement.selectedIndex =
        newSelectedIndex < geElement.options.length ? newSelectedIndex : 0;
    }
  }
  var leRowElement = document.getElementById('widget_rangeLe_' + title);
  var geRowElement = document.getElementById('widget_rangeGe_' + title);
  displayOrHide(leRowElement, leElement != null &&
    valueElement.selectedIndex > 0 && leElement.options.length > 1);
  displayOrHide(geRowElement, geElement != null &&
    valueElement.selectedIndex > 0 && geElement.options.length > 1);
}

function getElementsByClass(out, haystack, needle)
{
  while (haystack) 
  {
    if (haystack.nodeType == Node.ELEMENT_NODE) 
    {
      if (haystack.hasAttribute("class")) 
      {
        var c = " " + haystack.className + " ";
        if (c.indexOf(" " + needle + " ") != -1)
          out.push(haystack);
      }
      getElementsByClass(out, haystack.firstChild, needle);
    }
    haystack = haystack.nextSibling;
  }
  return out;
}


/*
 *  Admin functions 
 */
function showExtendedAdmin()
{
  var out = new Array();
  needle = "extendedAdmin"; 
  getElementsByClass(out, document.documentElement, needle);
  for (var i = 0; i < out.length; i++)
  {
    out[i].style.display='';
  }
  return false;
} 

function hideExtendedAdmin()
{
  var out = new Array();
  needle = "extendedAdmin"; 
  getElementsByClass(out, document.documentElement, needle);
  for (var i = 0; i < out.length; i++)
  {
    out[i].style.display='none';
  }
  return false;
} 

/*
 * Tab functions
 */

function unsetCurrentTab(element)
{
  removeStyleClass(element, 'current');
}

function setCurrentTab(element)
{
  addStyleClass(element, 'current');
}

/* 
 *  Search form functions
 */
function showSearchForm()
{
  $('SearchFreeTextInput').value =  $("DrillDownFreeTextInput").value;

  if (false)
  {
    $('SearchFormContainer').style.display = "block";
    Effect.Fade('DrillDownFormContainer', {
      duration: 0.5,
      afterFinish: function() {
        $('DrillDownFormContainer').style.display = "none";
        Effect.Appear('SearchFormContainer', {duration: 0.5});
      }
    });
  }
  else
  {
    $('SearchFormContainer').style.display = "block";
    $('DrillDownFormContainer').style.display = "none";
  }
  setCurrentTab($('SearchTab'));
  unsetCurrentTab($('DrillDownTab'));
}

function showDrillDown()
{
  $('DrillDownFreeTextInput').value = $("SearchFreeTextInput").value;

  if (false)
  {
    Effect.Fade('SearchFormContainer', {
      duration: 0.5,
      afterFinish: function() {
        $('SearchFormContainer').style.display = "none";
        Effect.Appear('DrillDownFormContainer', {duration: 0.5});
      }
    });
  }
  else
  {
    $('SearchFormContainer').style.display = "none";
    $('DrillDownFormContainer').style.display = "block";
  }
  unsetCurrentTab($('SearchTab'));
  setCurrentTab($('DrillDownTab'));
}

/** 
 * Showing/Hiding "More..." divs consisting of  Incredibly crude logic to show
 * element box and keep it open while the mouse pointer is within its
 * boundaries.
 */
function showMoreBox(id, link)
{
  var linkBounds = getAbsoluteBounds(link);
  $(id).style.display = "block";
  $(id).style.position = "absolute";
  $(id).style.left = (linkBounds.left + 30) + "px";
  var bounds = getAbsoluteBounds($(id));
  var height = bounds.bottom - bounds.top; 
  var top = linkBounds.bottom - height + 20;
  $(id).style.top = (top < 100 ? 100 : top) + "px";    
  if (height > 500)
  {
    $(id).style.height = "500px";
    $(id).style.overflow = "auto";
  }
  var mouseMove = function(event)
    {
      var scrollX = typeof window.scrollX != typeof undefined ? 
        window.scrollX : document.documentElement.scrollLeft;
      var scrollY = typeof window.scrollY != typeof undefined ? 
        window.scrollY : document.documentElement.scrollTop;
      var bounds = getAbsoluteBounds($(id));
      var x = event.clientX + scrollX;
      var y = event.clientY + scrollY;
      var buffer = 8;
      var insideMoreBox = (
        x >= bounds.left - buffer && 
        x <= bounds.right + buffer &&
        y >= bounds.top - buffer && 
        y <= bounds.bottom + buffer)
      var insideMoreLink = (
        x >= linkBounds.left - buffer && 
        x <= linkBounds.right + buffer &&
        y >= linkBounds.top - buffer && 
        y <= linkBounds.bottom + buffer);
      var inside = insideMoreBox || insideMoreLink;
      if (!inside)
      {
        hideDivWithId(id);
        removeEventListenerFrom(document, "mousemove", mouseMove);
      }
    };
  addEventListenerTo(document, "mousemove", mouseMove);
}


/**
 * simple div hiding
 */
function showDiv(div)
{
  div.style.display = "";
}

function hideDiv(div)
{
  div.style.display = "none;";
}

function hideDivWithId(id)
{
  $(id).style.display = "none;";
}

function showDivWithId(id)
{
  document.getElementById(id).style.display = "";
}

/**
 * Construct a query string from a form's input elements.
 */
function getQueryStringFromForm(form)
{
  var pairs = new Array();
  getQueryStringPairs(form, pairs);
  var qs = "";
  for (var i in pairs)
  {
    var pair = pairs[i];

    if (pair[1] == "" || pair[1] == null)
    {
      continue;
    }
    else if (pair[0] == 's.clickedButton')
    {
      /* uggh, this is just so wrong, but i'm having trouble getting this 
       * information any other way, as *both* button's values come through with
       * getQueryStringPairs
      */
      
      // add 'legacy' or 'fuzzy' = 'Search'
      pair[0] = pair[1]; 
      pair[1] = 'Search';
    }
    else if (pair[0] == 'legacy' || pair[0] == 'fuzzy')
    {
      /* ignore buttons */
      continue;
    }
    if (qs != "")
    {
      qs += "&";
    }
    qs += escape(pair[0]);
    qs += "=";
    qs += escape(pair[1]);
  }
  return qs;
}

/**
 * Construct query string key/value pairs from an element's child input
 * elements.
 */
function getQueryStringPairs(element, pairs)
{
  for (var child = element.firstChild; child != null; child = child.nextSibling)
  {
    if (!child.tagName)
    {
      continue;
    }
    var tag = new String(child.tagName);
    if (child.name && child.name != "")
    {
      var elementName = tag.toLowerCase();
      if (elementName == "input")
      {
        if (child.type && child.type != "submit")
        {
          var value = null;
          if (child.type && child.type == "checkbox")
          {
            value = (child.checked && child.value) ? child.value : null;
          }
          else
          {
            // a check for the child.value property
            value = child.value;
          }
          if (value != null && !isEmpty(value)) //&& (isString(value))
          {
            pairs[pairs.length] = new Array(child.name, value);
          }
        }
      }
      else if (elementName == "select" && child.childNodes && child.childNodes.length)
      {
        for(var i = 0; i < child.childNodes.length; i++)
        {
          var cn = child.childNodes[i];
          if (cn.tagName && cn.tagName.toLowerCase() == 'option' && cn.selected)
          {
            pairs[pairs.length] = new Array(child.name, cn.value);
          }
        }
      }
    }
    getQueryStringPairs(child, pairs);
  }
}

/**
 * Converts HSV triplet to numeric RGB value.
 */
function hsvToRgb(h, s, v)
{
  h = Math.min(Math.max(h, 0), 1);
  s = Math.min(Math.max(s, 0), 1);
  v = Math.min(Math.max(v, 0), 1);
  h *= 6.0
  var i = Math.floor(h);
  var f = h - i;
  if ((i & 1) == 0)
  {
    f = 1 - f;
  }
  var m = v * (1 - s);
  var n = v * (1 - s * f);
  var rgb;
  switch (i)
  {
    case 6:
    case 0:
      rgb = [v, n, m];
      break;
    case 1:
      rgb = [n, v, m];
      break;
    case 2:
      rgb = [m, v, n];
      break;
    case 3:
      rgb = [m, n, v];
      break;
    case 4:
      rgb = [n, m, v];
      break;
    case 5:
      rgb = [v, m, n];
      break;
    default:
      rgb = [0, 0, 0];
  }
  return rgb;
}

function _styleClassRegexp(styleClass)
{
  return new RegExp("\\b" + styleClass + "\\b", "g");
}

function addStyleClass(element, styleClass)
{
  if (!_styleClassRegexp(styleClass).test(element.className))
  {
    element.className = element.className + " " + styleClass;
  }
}

function removeStyleClass(element, styleClass)
{
  element.className = element.className.replace(
    _styleClassRegexp(styleClass), "");
}

/**
 * Calculates the absolute rectangular bounds of an element, relative to the
 * document body.
 *
 * @param element
 *   element to inspect.
 * @return
 *   an Object with the following fields set: <code>left</code>,
 *   <code>right</code>, <code>top</code> and </code>bottom</code>.
 */
function getAbsoluteBounds(element)
{
  var bounds = new Object();
  bounds.left = 0;
  bounds.top = 0;
  bounds.right = 0;
  bounds.bottom = 0;
  var e = element;
  while (e != null)
  {
    bounds.left += e.offsetLeft;
    bounds.top += e.offsetTop;
    e = e.offsetParent;
  }
  bounds.right = bounds.left + element.offsetWidth;
  bounds.bottom = bounds.top + element.offsetHeight;
  bounds.toString = function () 
  {
    return bounds.top + ", " + bounds.right + ", " + bounds.bottom + ", " + bounds.left;
  };
  return bounds;
}

function setAbsoluteBounds(element, bounds)
{
  element.style.left = bounds.left + "px";
  element.style.top = bounds.top + "px";
  element.style.right = bounds.right + "px";
  element.style.bottom = bounds.bottom + "px";
}

function addEventListenerTo(element, eventName, listener)
{
  if (typeof element.addEventListener != typeof undefined)
  {
    element.addEventListener(eventName, listener, false);
  }
  else if (typeof element.attachEvent != typeof undefined)
  {
    element.attachEvent("on" + eventName, listener);
  }
  else
  {
    throw "Element does not support events";
  }
}

function removeEventListenerFrom(element, eventName, listener)
{
  if (typeof element.removeEventListener != typeof undefined)
  {
    element.removeEventListener(eventName, listener, false);
  }
  else if (typeof element.detachEvent != typeof undefined)
  {
    element.detachEvent("on" + eventName, listener);
  }
  else
  {
    throw "Element does not support events";
  }
}

// an ill-fated attempt at using Firefox's javascript console for logging
var Logger = { 
  _consoleSvc: null,
  _permissionDenied: null,
  _bogus: {
    logStringMessage: function(msg) { return null; }
  },
  _getLogSvc: function() {
    if (Logger._permissionDenied === null) {
      try {
        netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
        Logger._consoleSvc = Components.classes["@mozilla.org/consoleservice;1"].getService(Components.interfaces.nsIConsoleService);
      }
      catch(e) {
        Logger._permissionDenied = true;
      }
    }
    return (Logger._permissionDenied) ? Logger._bogus : Logger._consoleSvc;
  },
  log: function(msg) {
    Logger._getLogSvc().logStringMessage(msg);
  }
};

/* several helpful "is*" methods from the guy who wrote JSLint
 *
 * http://www.crockford.com/javascript/remedial.html
 *
*/

// IE holds non-js object references, this tests for them
function isAlien(a) 
{
  return isObject(a) && typeof a.constructor != 'function';
}

function isArray(a) 
{
  return isObject(a) && a.constructor == Array;
}

function isBoolean(a) 
{
  return typeof a == 'boolean';
}

function isEmpty(o) 
{
  var i, v;
  if (isObject(o)) 
  {
    for (i in o) 
    {
      v = o[i];
      if (isUndefined(v) && isFunction(v)) 
      {
        return false;
      }
    }
  }
  return true;
}

function isFunction(a) 
{
  return typeof a == 'function';
}

function isNull(a) 
{
  return typeof a == 'object' && !a;
}

function isNumber(a) 
{
  return typeof a == 'number' && isFinite(a);
}

function isObject(a) 
{
  return (a && typeof a == 'object') || isFunction(a);
}

function isString(a) 
{
  return typeof a == 'string';
}

function isUndefined(a) 
{
  return typeof a == 'undefined';
} 


