/**
 * $Id: bearpaw-common.js 116 2007-11-30 20:00:02Z svollbehr $
 *
 *
 * Copyright (C) 2005, 2006, 2007 The Bearpaw Project Work Group
 * Copyright (C) 2007 BEHR Software Systems. All Rights Reserved.
 *
 * @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) {}
}


/* 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.
 * @ignore
 */
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 Returns the value of the cookie.
 * @type   String
 * @ignore
 */
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.
 * @ignore
 */
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.
 * @ignore
 */
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 Returns the left offset of the element.
 * @type   Integer
 * @ignore
 */
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 Returns the top offset of the element.
 * @type   Integer
 * @ignore
 */
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 Returns the left scroll offset of the element.
 * @type   Integer
 * @ignore
 */
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 ? window.pageXOffset : self.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 Returns the top scroll offset of the element.
 * @type   Integer
 * @ignore
 */
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 ? window.pageYOffset : self.pageYOffset);
}

/**
 * Returns the left offset of the mouse cursor.
 *
 * @return Returns the left offset of the mouse cursor.
 * @type   Integer
 * @ignore
 */
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 Returns the top offset of the mouse cursor.
 * @type   Integer
 * @ignore
 */
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 Returns the width of the visible client area.
 * @type   Integer
 * @ignore
 */
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 Returns the height of the visible client area.
 * @type   Integer
 * @ignore
 */
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 Returns XML HTTP object to be used for asyncronous HTTP calls.
 * @type   Object
 * @ignore
 */
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.
 * @ignore
 */
function addLoadHook(hook) {
  if (typeof hook == "function") {
    if (!loaded)
      loadHooks.push(hook);
    else {
      try {
        hook();
      } catch (e) { alert("Exception in load hook: " + e.message); }
    }
  }
}

/**
 * Fires all the hooked load functions.
 *
 * @ignore
 */
function fireLoadHooks() {
  loaded = true;
  for (var i = 0; i < loadHooks.length; i++) {
    try {
      loadHooks[i]();
    } catch (e) { alert("Exception in load hook: " + e.message); }
  }
}
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.
 * @ignore
 */
function addUnloadHook(hook) {
  if (typeof hook == "function")
    unloadHooks.push(hook);
}

/**
 * Fires all the hooked unload functions.
 *
 * @ignore
 */
function fireUnloadHooks() {
  for (var i = 0; i < unloadHooks.length; i++) {
    try {
      unloadHooks[i]();
    } catch (e) { alert("Exception in unload hook: " + e.message); }
  }
}
if (window.onbeforeunload)
  window.onbeforeunload = fireUnloadHooks;
else
  window.onunload = fireUnloadHooks;


/* Storable Options  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  */

var options = {};

var cookieString = getCookie("Options");
cookieString = cookieString ? cookieString.split(";") : [];
for (var i in cookieString) {
  try {
    var cookieParts = cookieString[i].split("=");
    if (cookieParts[0])
      eval("options[\"" + cookieParts[0].replace(/"/g, "\\\"") + "\"]=" +
           (cookieParts[1] ? cookieParts[1] : "false") + ";");
  } catch (e) {}
}

/**
 * Saves options stored in global options associated table into a cookie.
 *
 * @ignore
 */
function saveOptions()
{
  var cookieString = "";
  for (var i in options) {
    if (typeof options[i] != "function") {
      cookieString += i + "=";
      switch (typeof options[i]) {
      case "boolean":
      case "number":
        cookieString += options[i];
        break;
      case "string":
        cookieString += "\"" + options[i].addslashes() + "\"";
        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 Returns the index of the needle or -1 if no results.
 * @type   Integer
 * @addon
 */
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 Returns the string capitalized.
 * @type   String
 * @addon
 */
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 Returns the string HTML safe.
 * @type   String
 * @addon
 */
String.prototype.toEncodedHTML = function ()
{
  return this.replace(/&/g, "&amp;").replace(/</g, "&lt;")
    .replace(/>/g, "&gt;").replace(/"/g, "&quot;");
}

/**
 * Returns the string as all the whitespaces removed from both ends of it.
 *
 * @return Returns the string trimmed from preceding and following whitespaces.
 * @type   String
 * @addon
 */
String.prototype.trim = function ()
{
  return this.replace(/^\s+|\s+$/g, "");
}

/**
 * Pad string from the right.
 *
 * @type   Integer
 * @addon
 */
String.PAD_RIGHT = 1;

/**
 * Pad string from the left.
 *
 * @type   Integer
 * @addon
 */
String.PAD_LEFT = 2;

/**
 * Pad string from the both ends.
 *
 * @type   Integer
 * @addon
 */
String.PAD_BOTH = String.PAD_RIGHT | String.PAD_LEFT;

/**
 * Returns the string padded with padstr either from right, left or both ends.
 *
 * @param  {Integer} length The total length of the string.
 * @param  {String}  padstr The padding string.
 * @param  {Integer} type   The pad type, either of: PAD_RIGHT, PAD_LEFT,
           or PAD_BOTH.
 * @return Returns the padded string.
 * @type   String
 * @addon
 */
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;
}

/**
 * Returns the string with the following characters escaped: ', ", \, \nil.
 *
 * @return Returns the escaped string.
 * @type   String
 * @addon
 */
String.prototype.addslashes = function()
{
  return this
    .replace(/\\/g, "\\\\")
    .replace(/\0/g, "\\0")
    .replace(/'/g,  "\\'")
    .replace(/"/g,  "\\\"");
}

/**
 * Returns the string with the following characters unescaped: ', ", \, \nil.
 *
 * @return Returns the unescaped string.
 * @type   String
 * @addon
 */
String.prototype.stripslashes = function()
{
  return this
    .replace(/\\"/g,  "\"")
    .replace(/\\'/g,  "'")
    .replace(/\\0/g,  "\0")
    .replace(/\\\\/g, "\\");
}

/**
 * @private
 * @addon
 */
Date.datesInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

/**
 * Returns the last date in currently set month.
 *
 * @return Returns the last date in currently set month.
 * @type   Number
 * @addon
 */
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  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  */

/**
 * Default constructor
 *
 * @class     The Event class provides cross-browser compatible means to add
 * event handles to objects. The objects can be of HTMLElement type or, in fact,
 * of any type at all. The only requirement for non-DOM objects is that they
 * must take advantage of the dispatching facilities of this class.
 * @author    Sven Vollbehr <sven.vollbehr@behrss.eu>
 * @copyright 2005, 2006, 2007 The Bearpaw Project Work Group
 * @version   $Rev: 116 $
 * @constructor
 */
function Event() {}

/**
 * @private
 */
Event.listeners = {};

/**
 * @private
 */
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 Returns the handle of the event that can later be used to remove the
 *         listener.
 * @type   Integer
 */
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 Returns the handle of the event that can later be used to remove the
 *         listener.
 * @type   Integer
 */
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;
}

/**
 * Returns the wheel delta in cross-browser manner. A positive value denotes the
 * wheel has been scrolled upwards, and a negative downwards.
 *
 * @return Returns the wheel data.
 * @type   Integer
 */
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   *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  */

/**
 * @ignore
 */
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);
}
