//////// object extensions ////////////

//obtain remainder of string after first index of ch
String.prototype.from = function (ch)
{
   return this.substr(this.indexOf(ch) + ch.length);
}

// obtain part of string before first index of ch
String.prototype.to = function (ch)
{
   return this.substr(0,this.indexOf(ch));
}

//clear spaces from start and end
String.prototype.trim = function ()
{
   return this.replace(/^\s+|\s+$/g,'');
}

// additions of janne (16-11-2005)
/** round
 * rounds the number to the nearest whole number. If a precision is defined
 *    that number of decimals is used. A negative number means that the number
 *    is rounded to the nearest power of ten of the absolute value of precision.
 */
Number.prototype.round = function (intPrecision) {
   intPrecision = ( isNaN(parseInt(intPrecision)) ? 0 : parseInt(intPrecision) );
   var intPower = Math.pow(10, intPrecision);
   return Math.round(this.valueOf() * intPower) / intPower;
}
/** floor
 * floor the number to the whole number. If a precision is defined that number
 * of decimals is used. A negative number means that the number is floored to the
 * power of ten of the absolute value of precision.
 */
Number.prototype.floor = function (intPrecision) {
   intPrecision = ( isNaN(parseInt(intPrecision)) ? 0 : parseInt(intPrecision) );
   var intPower = Math.pow(10, intPrecision);
   return Math.floor(this.valueOf() * intPower) / intPower;
}
/** precision
 * returns the precision of the least significant digit of number.
 * precision is the floored -10log of the digit: .1-.9 is 1, 1-9 is 0, 1-9 * 10 is -1;
 */
Number.prototype.precision = function () {
   var numRemainder;
   var numTolerance = Math.pow(10, (-15 + (Math.log(this.valueOf()) / Math.LN10 ).round() ));
   var intPrecision = 0;
   var numPower = 1;
   numRemainder = this.valueOf() % numPower;
   if (numRemainder == 0 ) {
      do {
         --intPrecision;
         numPower *= 10;
         numRemainder = this.valueOf() % numPower;
      } while ( numRemainder == 0 && this.valueOf() >= numPower );
      ++intPrecision;
   }
   else {
      while (numRemainder > numTolerance && numPower - numRemainder > numTolerance) {
         numPower = numPower / 10;
         ++intPrecision;
         numRemainder = this.valueOf() % numPower;
      }
   }
   return intPrecision;
}
Date.prototype.equals = function (date) {
   if (typeof date != 'object' || ! ( date instanceof Date ) ) {
      return false;
   }
   return !( this.valueOf() > date || this.valueOf() < date );
}
Date.prototype.round = function () {
   this.setHours(0);
   this.setMinutes(0);
   this.setSeconds(0);
   this.setMilliseconds(0);
   return this;
}
Date.prototype.copy = function () {
   return new Date(this.getTime());
}
/** daysInMonth
 */
Date.prototype.daysInMonth = function () {
   var i, dateLoop;
   for (i = 31; i >= 28; --i) {
      dateLoop = this.copy();
      dateLoop.setDate(i);
      if (dateLoop.getDate() == i) {
         return i;
      }
   }
   return 0;
}
//////// element selection ////////////

function elemById(id)
{
   if (document.getElementById)
      return document.getElementById(id);
   else if (document.all)
      return document.all[id];
   else if (document.layers)
      return document.layers[id];
}

function elemsByTag(tagName, elParent)
{
	var arElems = []; //requires new instance of Array, because DOM method returns nodeList, not Array.

   if (isDefined(elParent))
      var parent = elParent;
   else
      var parent = document;

   var listElems = parent.getElementsByTagName(tagName);

   for (var i=0; i < listElems.length; i++ ) arElems[arElems.length] = listElems[i];

   return arElems;
}

function elemsByClass(className)
{
   var allNodes = Everything(document);
   var classNodes = [];

   for (var i=0; i < allNodes.length ;i++ )
   {
      if (String(allNodes[i].className).indexOf(className) != -1)
         classNodes.push(allNodes[i]);
   }

   return classNodes;
}

function Style(id)
{
	return elemById(id).style;
}

//////// measurement ////////////

// credit: functions getTop() and getLeft() graciously borrowed and edited from http://www.quirksmode.org/
// they now take ids as well as node objects.
function getTop(obj)
{
   if (typeof obj == 'string') obj = elemById(obj);

  var curtop = 0;
  if (obj.offsetParent)
  {
    while (obj.offsetParent)
    {
      curtop += obj.offsetTop;
      obj = obj.offsetParent;
    }
  }
  else if (obj.y)
    curtop += obj.y;
  return curtop;
}
function getLeft(obj)
{
	if (typeof obj == 'string') obj = elemById(obj);

  var curleft = 0;
  if (obj.offsetParent)
  {
    while (obj.offsetParent)
    {
      curleft += obj.offsetLeft;
      obj = obj.offsetParent;
    }
  }
  else if (obj.x)
    curleft += obj.x;
  return curleft;
}

//in string 'window' | 'document' | 'scroll'
//out: obj with .x and .y for width and height
// Many thanks to Peter-Paul Koch of quirksmode.org for doing my dirty work.
function measure(part)
{
   var Size = new Object();
   var x, y;

   switch (part)
   {
      case 'window':
         //viewport size
         if (self.innerHeight) // all except Explorer
         {
            x = self.innerWidth;
            y = self.innerHeight;
         }
         else if (document.documentElement && document.documentElement.clientHeight) // Explorer 6 Strict
         {
            x = document.documentElement.clientWidth;
            y = document.documentElement.clientHeight;
         }
         else if (document.body) // other Explorers
         {
            x = document.body.clientWidth;
            y = document.body.clientHeight;
         }
      break;
      case 'scroll':
         // pixels scrolled
         if (self.pageYOffset) // all except Explorer
         {
            x = self.pageXOffset;
            y = self.pageYOffset;
         }
         else if (document.documentElement && document.documentElement.scrollTop) // Explorer 6 Strict
         {
            x = document.documentElement.scrollLeft;
            y = document.documentElement.scrollTop;
         }
         else if (document.body) // all other Explorers
         {
            x = document.body.scrollLeft;
            y = document.body.scrollTop;
         }
      break;
      case 'document':
         //total doc height
         var test1 = document.body.scrollHeight;
         var test2 = document.body.offsetHeight
         if (test1 > test2) // all but Explorer Mac
         {
            x = document.body.scrollWidth;
            y = document.body.scrollHeight;
         }
         else // Explorer Mac;
              //would also work in Explorer 6 Strict, Mozilla and Safari
         {
            x = document.body.offsetWidth;
            y = document.body.offsetHeight;
         }
      break;
   }

   Size.x = x;
   Size.y = y;

   return Size;
}

//in: [idstring or objectelement], string css-syntax property, [optional bool nounit-switch]
//out: string CSS-value || int CSS-value
// The nounit boolean converts the value of a property to a number, eg '15px' -> 15
function getStyleValue(elem, property, nounit)
{
   if (typeof elem == 'string') elem = Thing(elem);

   //for Moz/Opera/Safari
   if (document.defaultView && document.defaultView.getComputedStyle)
   {
      var styleValue = document.defaultView.getComputedStyle(elem,null).getPropertyValue(property);
   }
   else if (elem.currentStyle) //if IE, rewrite property and get the style.
   {
      var cssSyntax = property.split('-'); //cut up property
      for (var i=1; i < cssSyntax.length ; i++) //capitalize element values from second word up
         cssSyntax[i] = cssSyntax[i].substr(0,1).toUpperCase() + cssSyntax[i].substr(1);
      var jsSyntax = cssSyntax.join(''); //glue the thing back together

      var styleValue = eval('elem.currentStyle.' + jsSyntax);
   }

   if (nounit) //convert string value to number
   {
      if (isNaN(styleValue.charAt(0))) //if it's no number, kill it and return NaN
      {
         styleValue = parseInt(styleValue);
      }
      else
      {
         var unitfree = '';
         for (var i=0; !isNaN(styleValue.charAt(i)); i++)
            unitfree += styleValue.charAt(i);
         styleValue = parseInt(unitfree);
      }
   }
   return styleValue;
}


//simple showHide
function showHide(id,state)
{
	if (state == 'hide')
		Style(id).display = 'none';
	else if (state == 'show')
		Style(id).display = 'block';

	return false;
}
//advanced showHide
function Toggle(elem)
{
   if (typeof elem == 'string')
      elem = elemById(elem);

   var displayState = getStyleValue(elem,'display');

   if (displayState == 'none')
   {
      elem.style.display = 'block';
   }
   else if (displayState == 'block')
   {
      elem.style.display = 'none';
   }
}

/// DOM-proof treewalkers that only return useful nodes.
function isValidNode(node)
{
   //8 is comment, 3 is text node
   return !(node.nodeType == 8 || (node.nodeType == 3 && !Chars(node.nodeValue)) )
}

function _nextSibling(node)
{
   while (node.nextSibling && !isValidNode(node.nextSibling))
		node = node.nextSibling;

   return node.nextSibling;
}
function _previousSibling(node)
{
   while (node.previousSibling && !isValidNode(node.previousSibling))
		node = node.previousSibling;

   return node.previousSibling;
}

function _childNodes(parent)
{
  var oldChilds = parent.childNodes;
  var newChilds = [];
  for (var i = 0; i < oldChilds.length; i++)
  {
    if (isValidNode(oldChilds[i]))
      newChilds[newChilds.length] = oldChilds[i]; //IE 5.2Mac does not support array.push()
  }
  return newChilds;
}

function parentNodes(node)
{
   if (typeof node == 'string') node = elemById(node);

   var ancestors = new Array(node);
   while (node.parentNode)
   {
      node = node.parentNode;
      ancestors.push(node);
   }
   ancestors.reverse();

   return ancestors;
}

function Everything(root, doRoot, schwing)
{
  if (typeof schwing == 'undefined')
	 everything = new Array();

  if (doRoot)
	 everything.push(root);
  doRoot = true;

  var childs = _childNodes(root);
  if (childs.length != 0)
  {
	 for (var i=0; i < childs.length; i++)
		Everything(childs[i],doRoot,true);
  }
  return everything;
}

//event assignment. e.g.: Listen(window,'load',prepLayout);
function Listen(elem, event, func)
{
  if (elem.addEventListener)
    elem.addEventListener(event,func,false);
  else if (elem.attachEvent)
    elem.attachEvent('on'+event,func);
  else
    eval('elem.on'+event+' = func;');
}
function Deaf(elem, event, func)
{
  if (elem.removeEventListener)
    elem.removeEventListener(event,func,false);
  else if (elem.detachEvent)
    elem.detachEvent('on'+event,func);
  else
    eval('elem.on'+event+' = null;');
}

///// Cookie baking and eating /////


/*  getCookie()
 *  retrieves cookie value by cookie name
 *  in: string strCookieName
 *  out: string strCookieValue OR <empty string>
 */
function getCookie(strCookieName)
{
   var strCookieValue = '';

   if (document.cookie.length > 0)
   {
      //create regex that matches name, =, and value
      var regFull = new RegExp('(^|; )'+ strCookieName +'=[^;]*','gi');
      var fullMatch = document.cookie.match(regFull);

      if (fullMatch)
      {
         //if JS supported lookbehind, the replace would be unnecessary. But alas.
         strCookieValue = fullMatch[0].replace(strCookieName+'=','');
      }
   }
   return strCookieValue;
}

/*  bakeCookie()
 *  adds or updates cookie in document.cookie
 *  in: string strCookieName, string strCookieValue, int intExpireDays
 *  out: a complete cookie string
 */
function bakeCookie(strCookieName, strCookieValue, intExpireDays)
{
   var expiryDate = new Date();
   expiryDate.setTime( expiryDate.getTime() + intExpireDays*24*3600*1000 );

   var strCookie = strCookieName + '=' +escape(strCookieValue) + ((intExpireDays == null) ? '' : '; expires=' + expiryDate.toGMTString());

   document.cookie = strCookie;
   return strCookie;
}
var setCookie = bakeCookie;

///// Element creation functions

//simple spawn
function Spawn(tagname)
{
  return document.createElement(tagname);
}
function spawnText(content)
{
  return document.createTextNode(content);
}

//advanced spawn: specify the parent object, the tag to spawn, and the contents of the spawned element
function makeAndFill(parent,elem,content)
{
  var newElem = Spawn(elem);
  if (typeof content != 'undefined')
  {
     var newText = spawnText(content);
     newElem.appendChild(newText);
  }
  parent.appendChild(newElem);

  return newElem;
}

//create a quick div in body with specified id, classname and contents.
function spawnBox(id,className,content)
{
   if (typeof content == 'undefined') content = '';
   if (typeof className == 'undefined') className = '';

   var newElem = makeAndFill(Things('body')[0],'div',content);
   newElem.id = id;
   newElem.className = className;

   return newElem;
}

//deletes a node and returns it. Yes, that's possible.
function deleteNode(node)
{
   return node.parentNode.removeChild(node);
}

//obtain depth in tree. Takes object.
function level(elem)
{
  var levelCount = 0;
  while (elem.parentNode) levelCount++;
  return levelCount;
}


///////////  general purpose functions ////////////
function isDefined(input)
{
   return (typeof input != 'undefined' || (typeof input  == 'object' && String(input) != 'undefined'));
}

// returns the largest of a given set of numbers. Takes argument list eg. (this,that, so) or array with ints eg. (arrNums).
function largestValue()
{
   var constr = String(arguments[0].constructor).toLowerCase();
   if (constr.indexOf('array') != -1)
      var numbers = arguments[0];
   else
      var numbers = arguments;

   if (numbers.length == 0) return 0;

   var largest = numbers[0];

   for (var i=0; i < numbers.length ; i++)
   {
      if (largest < numbers[i])
         largest = numbers[i];
   }

   return largest;
}


//replaces all instances of string with other string inside specified String object.
function Replace(theString, chars, Replacement)
{
   return theString.replace(new RegExp(chars,'g'),Replacement);
}

//returns true if a string contains valid characters, i.e. is NOT empty.
function Chars(data)
{
  return /[^\t\n\r ]/.test(String(data));
}

function isValidEmail(strEmail)
{
   var strPattern = "^[-_.\\w]+@((([\\w]|[\\w][\\w-]*[\\w])\\.)+(ad|ae|aero|af|ag|ai|al|am|an|ao|aq|ar|arpa|as|at|au|aw|az|ba|bb|bd|be|bf|bg|bh|bi|biz|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|com|coop|cr|cs|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|edu|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gh|gi|gl|gm|gn|gov|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|in|info|int|io|iq|ir|is|it|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|mg|mh|mil|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|museum|mv|mw|mx|my|mz|na|name|nc|ne|net|nf|ng|ni|nl|no|np|nr|nt|nu|nz|om|org|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|pro|ps|pt|pw|py|qa|re|ro|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw)|(([0-9][0-9]?|[0-1][0-9][0-9]|[2][0-4][0-9]|[2][5][0-5])\\.){3}([0-9][0-9]?|[0-1][0-9][0-9]|[2][0-4][0-9]|[2][5][0-5]))$";
   return new RegExp(strPattern).test(strEmail.toLowerCase());
}

function outputBox(leftCoord) {
   if (!leftCoord) {
      leftCoord = 2;
   }

   var box = spawnBox('bugbox');

   box.style.position = 'absolute';
   box.style.top = '2px';
   box.style.left = leftCoord  + 'px';
   box.style.backgroundColor = '#ffffff';
   box.style.padding = '3px';
   box.style.zIndex = '999';
   box.style.fontFamily = 'Courier New';
   box.style.fontSize = '11px';
   box.set = function (str) { this.innerHTML += str+'<br />';};
   box.setAll = function (str) { this.innerHTML = str+'<br />';};
   box.escaped = function (str) { this.innerHTML = str.replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\n([\t\s]*)/g,'<br />$1') + '<br />';};

   return box;
}


//////// legacy ////////////
function getObj(name)
{
   if (document.getElementById)
      return document.getElementById(name).style;
   else if (document.all)
      return document.all[name].style;
   else if (document.layers)
      return document.layers[name];
}
function elem(id)
{
   return elemById(id);
}
function Thing(id)
{
   if (document.getElementById)
      return document.getElementById(id);
   else if (document.all)
      return document.all[id];
   else if (document.layers)
      return document.layers[id];
}
function Things(el,parent)
{
   var els = [];

   parent = (!isDefined(parent))? document:parent;
   var things = parent.getElementsByTagName(el);

   for (var i=0; i < things.length; i++ ) els.push(things[i]);

   return els;
}