/**
 * $Id$
 *
 *
 * Copyright (C) 2005, 2006 The Bearpaw Project Work Group
 *
 * This file is licensed under the CC-GNU GPL. To view a copy of this license,
 * visit http://creativecommons.org/licenses/GPL/2.0/ or send a letter to
 * Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA.
 *
 * @package bearPaw
 */

/* Common variables */
var agt = navigator.userAgent.toLowerCase();


/* Add PNG alpha support for IE 5.5 or IE 6.x */
var pngAlphaIE;
if (pngAlphaIE = agt.indexOf("msie") != -1 && agt.indexOf("opera") == -1 &&
    parseInt(navigator.appVersion) == 4 && navigator.platform == ("Win32") &&
    (agt.indexOf("msie 5.5") != -1 || agt.indexOf("msie 6.") != -1)) {
  try {
    document.styleSheets[0].
      addRule("img",   "behavior: url(/PNGAlphaSupportForIE.htc)");
    document.styleSheets[0].
      addRule("input", "behavior: url(/PNGAlphaSupportForIE.htc)");
  }
  catch (e) {}
}


/* Disable content selection to avoid unwanted behaviour */
document.onselectstart = function (event) {
  if (window.event && window.event.srcElement &&
      window.event.srcElement.nodeName &&
      window.event.srcElement.nodeName.toLowerCase() == "input")
    return true;
  return false; 
};
document.oncontextmenu = function (event) {
	var e = event ? event : window.event;
  var target = e.srcElement ? e.srcElement : e.target;
  if (target.nodeName && target.nodeName.toLowerCase() == "input")
    return true;
  return false; 
};


/* Cookie Helpers *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  */

/**
 * Sets a cookie with given values. Attributes other than name and value are
 * optional. If omitted corresponding declaration will be dropped out from the
 * cookie string. 
 *
 * @param  String  name    The name of the cookie.
 * @param  String  value   The value of the cookie.
 * @param  Date    date    The expirity date.
 * @param  String  path    The path the cookie is effective in.
 * @param  String  domain  The domain this cookie is effective in.
 * @param  boolean secure  Effective only when using a secured connection.
 */
function setCookie(name, value, expires, path, domain, secure)
{
  document.cookie = name + "=" + escape(value) +
    ((expires) ? "; expires=" + expires.toGMTString() : "") +
    ((path)    ? "; path=" + path : "; path=/") +
    ((domain)  ? "; domain=" + domain : "") +
    ((secure)  ? "; secure" : "");
}

/**
 * Returns the value of a cookie identified by the given name.
 *
 * @param  String  name  The name of the cookie to look for.
 * @return String  Returns the value of the cookie.
 */
function getCookie(name)
{
  var begin, end, prefix = name + "=";
  if ((begin = document.cookie.indexOf("; " + prefix)) == -1) {
    if ((begin = document.cookie.indexOf(prefix)) != -1)
      return null;
  } else begin += 2;
  
  if ((end = document.cookie.indexOf(";", begin)) == -1)
    end = document.cookie.length;
  return unescape(document.cookie.substring(begin + prefix.length, end));
}

/**
 * Deletes a cookie. This is basically same as resetting the cookie to have
 * expired date.
 *
 * @param  String  name    The name of the cookie.
 * @param  String  path    The path the cookie is effective in.
 * @param  String  domain  The domain this cookie is effective in.
 * @return String  Returns the value of the cookie.
 */
function deleteCookie(name, path, domain)
{
  if (getCookie(name))
    setCookie(name, "", new Date(1970, 1, 1), path, domain)
}


/* General Element/Information Getters *  *  *  *  *  *  *  *  *  *  *  *  *  */

/**
 * Returns an element identified by given id. This function aims to be
 * cross-browser.
 *
 * @param  String  The ID of the element to get.
 */
function getElement(elem)
{
  if (document.getElementById)
    return document.getElementById(elem);
  else if (document.all)
    return document.all[elem];
  else if (document.layers)
    return document.layers[elem];
  return null;
}

/**
 * Returns the left offset of an element.
 *
 * @param  HTMLElement  elem     The element whose offset to look for.
 * @return integer      Returns the left offset of the element.
 */
function getElementPositionX(elem)
{
  var tmp = 0;
  if (elem.offsetParent)
    while (elem) {
    	if (elem.offsetLeft > 0)
        tmp += elem.offsetLeft;
      elem = elem.offsetParent;
    }
  else if (elem.x)
    tmp = elem.x;
  return tmp;
}

/**
 * Returns the top offset of an element.
 *
 * @param  HTMLElement  elem  The element whose offset to look for.
 * @return integer      Returns the top offset of the element.
 */
function getElementPositionY(elem, relative)
{
  var tmp = 0;
  if (elem.offsetParent)
    while (elem) {
    	if (elem.offsetTop > 0)
        tmp += elem.offsetTop;
      elem = elem.offsetParent;
    }
  else if (elem.y)
    tmp = elem.y;
  return tmp;
}

/**
 * Returns the left scroll offset of an element or the document window.
 *
 * @param  HTMLElement  elem  The element whose offset to look for. Defaults to
 *                      document element if omitted.
 * @return integer      Returns the left scroll offset of the element.
 */
function getScrollOffsetX(elem)
{
	if (document.compatMode && document.compatMode == "CSS1Compat")
		return (elem ? elem : document.documentElement).scrollLeft;
	else if (document.body)
		return (elem ? elem : document.body).scrollLeft;
  else
    return window.pageXOffset;
}

/**
 * Returns the top scroll offset of an element or the document window.
 *
 * @param  HTMLElement  elem  The element whose offset to look for. Defaults to
 *                      document element if omitted.
 * @return integer      Returns the top scroll offset of the element.
 */
function getScrollOffsetY(elem)
{
	if (document.compatMode && document.compatMode == "CSS1Compat")
		return (elem ? elem : document.documentElement).scrollTop;
	else if (document.body)
		return (elem ? elem : document.body).scrollTop;
  else
    return window.pageYOffset;
}

/**
 * Returns the left offset of the mouse cursor.
 *
 * @return integer Returns the left offset of the mouse cursor.
 */
function getMousePositionX(event)
{
  var e = event ? event : window.event;

  if (e.pageX)
    return e.pageX;
  else
    return e.clientX +
      document.documentElement.scrollLeft + document.body.scrollLeft;
}

/**
 * Returns the top offset of the mouse cursor.
 *
 * @return integer Returns the top offset of the mouse cursor.
 */
function getMousePositionY(event)
{
  var e = event ? event : window.event;
  
  if (e.pageY)
    return e.pageY;
  else
    return e.clientY +
      document.documentElement.scrollTop + document.body.scrollTop;
}

/**
 * Returns the width of the visible client area.
 *
 * @return integer Returns the width of the visible client area.
 */
function getClientWidth()
{
	if (document.compatMode && document.compatMode == "CSS1Compat")
		return document.documentElement.clientWidth;
	else if (document.body)
		return document.body.clientWidth;
  else
    return window.innerWidth;
}

/**
 * Returns the height of the visible client area.
 *
 * @return integer Returns the height of the visible client area.
 */
function getClientHeight()
{
	if (document.compatMode && document.compatMode == "CSS1Compat")
		return document.documentElement.clientHeight;
	else if (document.body)
		return document.body.clientHeight;
  else
    return window.innerHeight;
}


/* Ajax Helper Functions   *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  */


/**
 * Returns XML HTTP Object.
 *
 * @return object Returns XML HTTP object to be used for asyncronous HTTP calls.
 */
function getXMLHTTP()
{
  if (!window.ActiveXObject)
    return new XMLHttpRequest();
  else if (agt.indexOf("msie 5") == -1)
    return new ActiveXObject("Msxml2.XMLHTTP");
  else
    return new ActiveXObject("Microsoft.XMLHTTP");
}


/* Window Load/Unload Hooks   *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  */

var loadHooks = [];
var loaded = false;

/**
 * Adds a function to be called on document load.
 *
 * @param   function  hook  The function to be called on document load.
 */
function addLoadHook(hook) {
  if (typeof hook == "function") {
    if (!loaded)
      loadHooks.push(hook);
    else {
      try {
        hook();
      } catch (e) {}
    }
  }
}

/**
 * Fires all the hooked load functions.
 */
function fireLoadHooks() {
  loaded = true;
  for (var i = 0; i < loadHooks.length; i++) {
    try {
      loadHooks[i]();
    } catch (e) {}
  }
}
window.onload = fireLoadHooks;


var unloadHooks = [];

/**
 * Adds a function to be called on document unload.
 *
 * @param   function  hook  The function to be called on document unload.
 */
function addUnloadHook(hook) {
  if (typeof hook == "function")
    unloadHooks.push(hook);
}

/**
 * Fires all the hooked unload functions.
 */
function fireUnloadHooks() {
  for (var i = 0; i < unloadHooks.length; i++) {
    try {
      unloadHooks[i]();
    } catch (e) {}
  }
}
window.onbeforeunload = fireUnloadHooks;


/* Storable Options  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  */

var options = {};

var cookieString = getCookie("Options");
cookieString = cookieString ? cookieString.split(";") : [];
for (var i in cookieString) {
  var cookieParts = cookieString[i].split("=");
	try {
		if (cookieParts[0])
      eval("options[\"" + cookieParts[0].replace("\"", "\\\"") + "\"]=" + 
           (cookieParts[1] ? cookieParts[1] : "false") + ";");
	} catch (e) {}
}

/**
 * Saves options stored in <var>options</var> associated table into a cookie.
 */
function saveOptions()
{
  var cookieString = "";
  for (var i in options) {
    if (i && options[i]) {
      cookieString += i + "=";
      switch (typeof options[i]) {
      case "boolean":
      case "number":
        cookieString += options[i];
        break;
      case "string":
        cookieString += "\"" + options + "\"";
        break;
      case "object":
      default:
      }
      cookieString += ";";
    }
  }
  setCookie("Options", cookieString);
}

addUnloadHook(function () {
  saveOptions();
});


/* JavaScript Object Extensions  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  */

/**
 * Searches array for the needle. If found its index is returned.
 *
 * @param  Object  needle  The needle to look for.
 * @return integer Returns the index of the needle or -1 if no results.
 */
Array.prototype.search = function (needle)
{
  for (var i = 0; i < this.length; i++)
    if (this[i] == needle)
      return i;
  return (-1);
}

/**
 * Returns the string capitalized, ie the first letter in upper case and the
 * rest in lower case.
 *
 * @return String Returns the string capitalized.
 */
String.prototype.capitalize = function ()
{
  return this.replace(/\w+/g, function(word) {
    return word.charAt(0).toUpperCase() + word.substr(1).toLowerCase();
  });
}

/**
 * Returns the string as HTML safe by replacing tag start and end tags with
 * their HTML entities.
 *
 * @return String Returns the string HTML safe.
 */
String.prototype.toEncodedHTML = function ()
{
  return this.replace(/&/g, "&amp;").replace(/</g, "&lt;")
    .replace(/>/g, "&gt;").replace(/"/g, "&quot;");
}

String.prototype.trim = function ()
{
  return this.replace(/^\s+|\s+$/, "");
}

String.PAD_RIGHT = 1;
String.PAD_LEFT = 2;
String.PAD_BOTH = String.PAD_RIGHT | String.PAD_LEFT;

String.prototype.pad = function (length, padstr, type)
{
  var s = this;
  while (s.length < length && padstr.length > 0) {
    if (type & String.PAD_RIGHT)
      s = s.concat(padstr.substr(0, Math.ceil((length - s.length) / (type & String.PAD_BOTH ? 2 : 1))));
    if (type & String.PAD_LEFT) {
      var padlen = Math.ceil((length - s.length) /
                             (type & String.PAD_BOTH ? 2 : 1))
      s = padstr.substr(padstr.length > padlen ? padstr.length - padlen : 0, padlen) + s;
    }
  }
  return s;
}

Date.datesInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

/**
 * Returns the last date in currently set month.
 *
 * @return Number Returns the last date in currently set month.
 */
Date.prototype.getLastDateInMonth = function()
{
  var dateCount = Date.datesInMonth[this.getMonth()];
  if (this.getMonth() == 1 &&
      (this.getFullYear() % 400 == 0 ||
       (this.getFullYear() % 4 == 0 && this.getFullYear() % 100 != 0)))
    dateCount++;
  return dateCount;
}


/* Cross-browser Event Class  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  */

function Event() {}

Event.listeners = {};
Event.listenerCount = 0;

/**
 * Adds the event to specified source. The source can be any class that has a
 * support for our event listeners.
 *
 * @param  Object    source  The source whose events to listen.
 * @param  String    event   The event type to listen.
 * @param  Function  handler The function that handles dispatched events once
                     raised.
 * @return integer   Returns the handle of the event that can later be used to
 *                   remove the listener.
 */
Event.addListener = function (source, event, handler)
{
	if (!source || !event || !handler)
	  throw "Invalid arguments for Event.addListener";
  return Event.addDOMListener(source, event, handler);
}

/**
 * Adds the event to specified DOM source element. The source must be a valid
 * DOM element or an instance of a class that supports this type of event
 * listening.
 *
 * @param  Object    source  The source whose events to listen.
 * @param  String    event   The event type to listen.
 * @param  Function  handler The function that handles dispatched events once
                     raised.
 * @return integer   Returns the handle of the event that can later be used to
 *                   remove the listener.
 */
Event.addDOMListener = function (source, event, handler)
{
	if (!source || !event || !handler)
	  throw "Invalid arguments for Event.addDOMListener";
  if (typeof handler != "function")
    handler = new Function(handler);
  if (source.addEventListener) {
  	if (event == "mousewheel")
  	  event = "DOMMouseScroll";
    source.addEventListener(event, handler, false);
  } else if (source.attachEvent)
    source.attachEvent("on" + event, handler);
  Event.listeners[Event.listenerCount] = [source, event, handler];
  return Event.listenerCount++;
}

/**
 * Removes the registration of an event denoted by the given handle. The
 * registered handler will no longer be notified of raised events.
 *
 * @param  integer  handle  The handle of a registered event.
 */
Event.removeListener = function (handle)
{
	if (!handle)
	  throw "Invalid arguments for Event.removeListener";
	if (!Event.listeners[handle])
	  return;
  var source =  Event.listeners[handle][0];
  var event =   Event.listeners[handle][1];
  var handler = Event.listeners[handle][2];
  Event.removeDOMListener(source, event, handler);
}

/**
 * Removes the registration of an event. The registered handler will no longer
 * be notified of raised events.
 *
 * @param  Object    source  The source whose events listener to remove.
 * @param  String    event   The event type to remove.
 * @param  Function  handler The function that handles dispatched events.
 */
Event.removeDOMListener = function (source, event, handler)
{
	if (!source || !event || !handler)
	  throw "Invalid arguments for Event.removeDOMListener";
  if (source.removeEventListener)
    source.removeEventListener(event, handler, false);
  else if (source.detachEvent)
    source.detachEvent("on" + event, handler);
  for (var i in Event.listeners)
    if (Event.listeners[i] == [source, event, handler])
      Event.listeners[handle] = null;
}

/**
 * Dispatches a custom event. The registered handlers will be called with this
 * se to source.
 *
 * Should the event need its own arguments they can be given to this function
 * after the normal arguments. The number of arguments is not limited.
 *
 * @param  Object  source  The source object that causes the event.
 * @param  String  event   The event type.
 */
Event.dispatchEvent = function (source, event)
{
  for (var i in Event.listeners) {
    var listener;
    if (listener = Event.listeners[i])
      try {
        if (listener[0] == source &&
            listener[1].toLowerCase() == event.toLowerCase()) {
          var call = "listener[2].call(source";
          for (var i = 2; i < arguments.length; i++)
            call += ", arguments[" + i + "]";
          call += ");";
          eval(call);
        }
      } catch (e) {}
  }
}

/**
 * Stops the event from propagating up in the DOM tree.
 */
Event.stopPropagation = function(event)
{
  var e = event ? event : window.event;
  e.cancelBubble = true;
  if (e.stopPropagation)
    e.stopPropagation();
}

/**
 * Prevents the default action to be taken.
 */
Event.preventDefault = function(event)
{
  var e = event ? event : window.event;
  if (e.preventDefault)
    e.preventDefault();
  e.returnValue = false;
}

Event.getWheelDelta = function (event)
{
  var e = event ? event : window.event;
	var delta = 0;
	if (e.wheelDelta) {
		delta = e.wheelDelta / 120;
		if (window.opera)
		  delta = -delta;
	}
	if (e.detail)
    delta = - e.detail / 3;
  return delta;
}

/* Other / miscellaneous   *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  */

function debugObject (o, noFunctions)
{
  if (!o) return;
  var str = "";
  for (var i in o)
    if ((noFunctions && typeof o[i] != "function" && typeof o[i] != "object" &&
         i != "innerHTML" && i != "textContent") || !noFunctions)
      str += i + ": " + o[i] + "\n";
  alert(str);
}