//********
//
//	FILE INFORMATION
//
//******
//
//	File Name:
//		lb_core.js
//
//	Purpose:
//		Some damn cool JavaScript functions!
//
//********

//************************************************************************
//
//	ARRAY FUNCTIONS
//
//************************************************************************

//************************************************************************
//
//	Array::@indexOf
//
//	Runs through an array and returns the index of a particular value.
//		... or -1 if it ain't there.
//
Array.prototype.indexOf = function(value) {
	for(var i = 0; i < this.length; i++)
		if(this[i] == value)
			return i;
	return -1;
}

//************************************************************************
//
//	Array::@inArray
//
//	Returns true if $value is in $this.
//		If $value is not in $this, go out and CONQUER THE WORLD!!!!!
//		... or just return false.
//		How boring.
//
Array.prototype.inArray = function(value) {
    for (var i = 0; i < this.length; i++)
        if(this[i] == value)
	   		return true;
    return false;
}

//************************************************************************
//
//	ELEMENT FUNCTIONS
//
//************************************************************************

//************************************************************************
//
//	Element::@clear
//
//	Removes all child nodes for an element.
//
//	$leaveNodes is the number of nodes to leave alone.  AS an example, if
//		$leaveNodes = 1, we will delete all but the first child.
//
//Element.prototype.clear = function(leaveNodes) {
//	if(isNaN(leaveNodes))
//		leaveNodes = 0;
//		
//	var element = this;	//	IE Bug Fix
//	while(element = this.childNodes[leaveNodes])
//		this.removeChild(element);
//}

//************************************************************************
//
//	Element::@underConstruction
//
//	Shows or hides the 'Under Construction' sign for an element when we
//		are dynamically loading content for it.
//
//	$parameters:
//	{
//		nodeName:		'div', 'span', etc.  The node name
//		className:		CSS class name for insta-formatting!
//		text:			The text to display.  Ex. 'Loading...', etc.
//		attributes:		JSON-format Object.  Whatever you want goes here.
//							Most stuff that you could put here will
//							probably be easier defined in the class name.
//							However, if you need to use custom attributes,
//							or if the element has an attribute that CSS
//							doesn't handle (very rare these days!), this
//							is where you'd put it.
//	}
//
//	Example of usage:
//	element.underConstruction(
//		{
//			nodeName:	'div',
//			className:	'constructionSign',
//			text:		'Loading...',
//			attributes:	{
//				width:	'85%',		//	s/b under constructionSign class
//				status:	'custom'	//		definition, but for the sake
//			}						//		of the example....
//		}
//	);
//
//Element.prototype.underConstruction = function(parameters) {
//	//	First, clear out the element.
//	this.clear();
//		
//	//	If $parameters is null, we're done.
//	if(parameters) {
//		var newElement = document.createElement(parameters.nodeName);
//		newElement.appendChild(document.createTextNode(parameters.text));
//		newElement.className = parameters.className;
//		for(a in parameters.attributes)
//			newElement.setAttribute(a, parameters.attributes[a]);
//		
//		//	Nice blueprints; where's the house?
//		this.appendChild(newElement);
//	}
//}

//************************************************************************
//
//	Element::@getStyleProperty
//
//	Returns the rendered style property of an element when that property
//		was not specifically specified at runtime.  As an example, to get
//		the position of an element when we did not specify the position in
//		a CSS file/block/inline.
//
//Element.prototype.getStyleProperty = function(property) {
//	if(document.defaultView)
//		return document.defaultView.getComputedStyle(this, '').getPropertyValue(property);
//	//	else... stupid Microsoft.
//	//	Keep in mind that for JS->CSS, we need to turn hyphenated
//	//		properties into camelCased.
//	if(this.currentStyle)
//		return this.currentStyle[strCssRule.replace(/\-(\w)/g, function(match, prop){ return prop.toUpperCase(); })];
//}

//************************************************************************
//
//	Element::@getPosition
//
//	Returns the position of an element as an object.
//
//Element.prototype.getPosition = function() {
//	//	The element's position is relative to its offsetParent... whatever
//	//		that element may be (varies per browser).  The good news is
//	//		that if we keep traversing upwards through offsetParents,
//	//		we'll eventually hit the document object.  So we will
//	//		eventually be able to return the element's position relative
//	//		to the top left corner of the document.
//	var curX = curY = 0;
//	
//	var element;
//	while(element = this.offsetParent) {
//			curX += element.offsetLeft;
//			curY += element.offsetTop;
//	}
//	
//	return {x:curX, y:curY};
//}

//************************************************************************
//
//	Element::@getChildNodesByTagName
//
//	Similar to $document->@getElementsByTagName, but can be used with any
//		DOM element.
//
//	$limit will stop @getChildNodesByTagName after it hits that many
//		child nodes.  Note that null, 0 or <0 is considered 'infinite'.
//
//Element.prototype.getChildNodesByTagName = function(tagName, limit) {
//	if(! limit)
//		limit = -1;
//	
//	var retVal = [];
//	for(var i = 0; (i < this.childNodes.length) && ((limit < 0) || (retVal.length <= limit)); i++)
//		if(this.childNodes[i].nodeName == tagName)
//			retVal.push(this.childNodes[i]);
//	
//	return retVal;
//}

//************************************************************************
//
//	Element::@addClassName
//
//	Adds a CSS class to $this' className property.  Note that you can use
//		as many arguments as you want.
//
//Element.prototype.addClassName = function() {
//	for(var i = 0; i < arguments.length; i++)
//		if(! this.className.match(arguments[i]))
//			this.className += ' ' + arguments[i];
//}

//************************************************************************
//
//	Element::@removeClassName
//
//	Removes a CSS class from $this.
//
//Element.prototype.removeClassName = function() {
//	for(var i = 0; i < arguments.length; i++)
//		this.className = this.className.replace(arguments[i], '');
//}

//************************************************************************
//
//	@changePosition
//
//	Moves a 'row' element (usually a TR) up or down in a list (usually a
//		table, but also works in lists, as well as arbitrary ordering).
//
//	$direction represents both the direction and the number of slots to
//		move (+ = move down, - = move up, 0 = you made a mistake).
//
//		Examples:
//		$direction		Action:
//		0				Don't move.
//		-1				Move up 1 slot.
//		4				Move down 4 slots.
//
//		If there are not enough elements to move $rowPointer to the proper
//			slot, $rowPointer just gets moved to the end of the list.
//
//Element.prototype.changePosition = function(direction) {
//	//	Just in case...
//	if(direction == 0)
//		return this;
//	
//	//	Make a copy of the row (c=a, a=b, b=c)
//	var rowCopy = this.cloneNode(true);
//	
//	//	Since we'll be using @insertBefore, we need to know which node
//	//		we'll be inserting before (so this.nextSibling isn't
//	//		particularly useful unless there's another sibling after it).
////	var target = ((direction > 0) ? (this.nextSibling ? this.nextSibling.nextSibling : null) : this.previousSibling);
//
//	//	Find the element we need to @insertBefore.  Note that if we're
//	//		moving down, we need to find the node that is one slot lower
//	//		than our target (because we have to insert *before* it).
//	var nSlots = Math.abs(direction) + parseInt(1 * (direction > 0));
//	var nCurrent = 0;
//	var target = this;
//	do {
//		target = ((direction > 0) ? target.nextSibling : target.previousSibling);
//		//	Watch out for whitespace!
//		if(target && (target.nodeName != '#text'))
//			nCurrent++;
//	} while(target && (nCurrent < nSlots))
//	
//	//	Handle not enough elements; NB if $direction > 0, @insertBefore
//	//		will automatically add $this to the end if $target ==
//	//		null.
//	if((target == null) && (direction < 0))
//		target = this.parentNode.firstChild;
//		
//	//	Boom.
//	this.parentNode.insertBefore(rowCopy, target);
//	this.parentNode.removeChild(this);
//	
//	//	Since we probably need to keep track of the moved row (but we
//	//		destroyed the original, remember)....
//	return rowCopy;
//}

//************************************************************************
//
//	MAGIC FUNCTIONS
//
//************************************************************************

//************************************************************************
//
//	@whatsMyName
//
//	Defying the very laws of JavaScript, I present to you @whatsMyName!
//
//	@whatsMyName will return the NAME of the variable you pass as its
//		argument!  No, really!
//
//	whatsMyName(variable_name) will return 'variable_name'!
//
//	There's a couple of minor caveats.
//		- @whatsMyName will return more than one result if you call it
//			more than once in a function.  That's because it uses the
//			mystical $caller property of the function.
//
//		- You also can't use @whatsMyName outside of a function (since
//			@whatsMyName->$caller would be null.
//
function whatsMyName() {
	var myRE = new RegExp('whatsMyName\\(([a-z0-9_]+)\\)', 'gi');
	
	var retVal = [];
	var temp = null;
	while(temp = myRE.exec(whatsMyName.caller))
		retVal.push(temp[1]);
		
	return retVal;
}

//************************************************************************
//
//	@whatsMyArgs
//
//	Similarly to @whatsMyName, we have @whatsMyArgs.
//
//	@whatsMyArgs is basically a @whatsMyName call, but it returns the name
//		of the argument used in the enclosing function.
//
//	Example of usage:
//	function wmaTest(param) {
//		return whatsMyArgs(wmaTest);
//	}
//
//	calling wmaTest(item) returns 'item'.
//
//	Caveats:
//		- Returns more than one value when called multiple times.
//		- Right now, only returns for one-argument functions.
//		- Doesn't handle objects or arrays particularly well.  As an
//			example, see @serialize below.
//
function whatsMyArgs() {
	if(! (whatsMyArgs.caller) && (whatsMyArgs.caller.caller))
		return -1;
		
	var fName = new RegExp('function ([a-z0-9_]+)\\(', 'i').exec(whatsMyArgs.caller.toString());
	if(! fName)
		return -1;
	fName = fName[1];
	
	var myRE = new RegExp(fName + '\\(([a-z0-9_]+)\\)', 'gi');
	
	var retVal = [];
	var temp = null;
	while(temp = myRE.exec(whatsMyArgs.caller.caller))
		retVal.push(temp[1]);
	
	return retVal;
}

//************************************************************************
//
//	MISC. FUNCTIONS
//
//************************************************************************

//************************************************************************
//
//	@include
//
//	Forget something?  Here's the JavaScript equivalent to PHP's @include.
//
function include(filePath) {
	if(! filePath)
		return false;
	
	var objScript = window.document.createElement('script');
	
	objScript.src = filePath;
	objScript.type = 'text/javascript';
	
	document.getElementsByTagName('body')[0].appendChild(objScript);
	
//	//	[DBuG]
//	var includes = document.getElementsByTagName('script');
//	for(var i = 0; i < includes.length; i++)
//		alert(includes[i].src);

	return true;
}

//************************************************************************
//
//	@include_once
//
//	Similar to a certain PHP function of the same name, @include_once
//		includes a file if it hasn't already been included before.
//
function include_once(str) {
	var includes = document.getElementsByTagName('script');
	for(var i = 0; i < includes.length; i++)
		if((includes[i].src) && (includes[i].src.match(str)))
			return;
	
	//	Not here; let's add it.
	return include(str);
}

//************************************************************************
//
//	isDefined
//
//	Hm.  Did we define this variable?  I forget.
//
function isDefined(str) {
	return(typeof(window[str]) != 'undefined');
}

//************************************************************************
//
//	@getType
//
//	Use this when @typeof returns 'Object'.
//
//	How it works:
//		Any given object in JavaScript has a constructor function
//			(accessed via $theObject.constructor[.toString() on some
//			browsers]).  If you try to look at it, you'll probably see
//			this:
//
//		function Array() {
//			[native code]
//		}
//
//		At first, not too useful... except hey look at that.  We can tell
//			that this must be an array!
//
//		Now, all we have to do is get the text between 'function' and '()'
//			and return it.
//
function getType(theObject) {
	//	Non-objects (increasingly rare in JavaScript) don't need @getType.
	if(! theObject.constructor)
		return typeof(theObject);
	
	var test = new RegExp('function ([a-zA-Z]+)\\(\\)').exec(theObject.constructor);
	
	//	DOM Elements and the like will have a constructor, but it will not
	//		appear as a function.
	if(! test)
		return theObject.constructor;
	
	//	else
	return test[1];
}

//************************************************************************
//
//	@print_r
//
//	Similar to the PHP function of the same name, except it always returns
//		it output (I never liked that about PHP's @print_r).
//
//	$htmlEncode determines whether we put '<br />' in front of every line
//		break.
//
//	$prefix is generally internal, but if you needed to use a custom
//		prefix for some reason, this is where you'd put it.
//
function print_r(content, htmlEncode, prefix) {
	if(! prefix)
		prefix = '';
	
	basePrefix = (prefix ? prefix : (htmlEncode ? '&emsp;' : '\t'));
	var newLine = (htmlEncode ? '<br />' : '') + '\n';
	var retVal = '';
	
	var myType = getType(content);
	if(myType == 'Array') {
		retVal += myType + ':' + newLine;
		prefix += basePrefix;
		
		//	for...in not useful on Arrays, since that will also return
		//		member functions.
		for(var i = 0; i < content.length; i++) {
			retVal += prefix + i + ' => ';
			var innerType = getType(content[i]);
			if((innerType == 'Array') || (innerType == 'Object'))
				retVal += print_r(content[i], htmlEncode, prefix);
			else
				retVal += "'" + content[i] + "'" + newLine;
		}
	} else if(myType == 'Object') {
		retVal += myType + ':'  + newLine;
		prefix += basePrefix;
		
		//	for not useful on Objects, since they have no length property.
		for(i in content) {
			retVal += prefix + i + ' => ';
			var innerType = getType(content[i]);
			if((innerType == 'Array') || (innerType == 'Object'))
				retVal += print_r(content[i], htmlEncode, prefix);
			else
				retVal += "'" + content[i] + "'" + newLine;
		}
	} else
		retVal += content;
	
	return retVal;	
}

//************************************************************************
//
//	@sizeof
//
//	Counts the number of elements (array/object) or length (string)
//
function sizeof(content) {
	if(content == null)
		return 0;
	
	if(content.length)
		return content.length;
		
	if(getType(content) == 'Object') {
		var retVal = 0;
		for(p in content)
			retVal++;
		return retVal;
	}
	
	//	Otherwise, I have no idea what to do with you.
	return -1;
}

//************************************************************************
//
//	@serialize
//
//	Outputs data for use by PHP's @unserialize.
//
//	Anatomy of a serialize()'ed value:
//	----------------------------------
//
//	String
//		s:size:value;
//
//	Integer
//		i:value;
//
//	Boolean
//		b:value; (does not store "true" or "false", does store '1' or '0')
//
//	Null
//		N;
//
//	Array
//		a:size:{key definition;value definition;(repeated per element)}
//
//	Object
//		O:strlen(object name):object name:object size:
//			{s:strlen(property name):property name:property definition;
//			(repeated per property)}
//
//	String values are always in double quotes
//	Array keys are always integers or strings
//		"null => 'value'" equates to 's:0:"";s:5:"value";',
//		"true => 'value'" equates to 'i:1;s:5:"value";',
//		"false => 'value'" equates to 'i:0;s:5:"value";',
//		"array(whatever the contents) => 'value'" equates to an "illegal
//			offset type" warning because you can't use an array as a key;
//			however, if you use a variable containing an array as a key,
//			it will equate to 's:5:"Array";s:5:"value";', and attempting
//			to use an object as a key will result in the same behavior as
//			using an array will.
//
//	All strings appear inside quotes. This applies to string values,
//		object class names and array key names. For example:
//
//		s:3:"foo"
//		O:7:"MyClass":1:{...
//		a:2:{s:3:"bar";i:42;...
//
//	Object property names and values are delimited by semi-colons, not colons.
//		For example:
//
//		O:7:"MyClass":2:{s:3:"foo";i:10;s:3:"bar";i:20}
//
//	Double/float values are represented as:
//
//		d:0.23241446
//
function serialize(content) {
	var retVal = ''
	var myType = getType(content);
	
	switch(myType) {
		case 'Array':
			retVal += 'a:' + content.length + ':{';
			for(var i = 0; i < content.length; i++)
				retVal += 'i:' + i + ';' + serialize(content[i]);
			retVal += '}';
			break;
		case 'Boolean':
			retVal += 'b:' + (content * 1) + ';';
			break;
		case 'Number':
			retVal += ((parseInt(content) < content) ? 'd' : 'i') + ':' + content + ';';
			break;
		case 'Object':
			//	Because PHP objects and JS objects are like night and day,
			//		we're going to treat JS objects as PHP associative
			//		arrays.
			retVal += 'a:' + sizeof(content) + ':{';
			for(var p in content)
				retVal += 's:' + p.length + ':"' + p + '";' + serialize(content[p]);
			retVal += '}';
			break;
		case 'String':
			retVal += 's:' + content.length + ':"' + content + '";';
			break;
		case 'undefined':
			retVal += 'N;';
		default:
			//	References and HTML Objects, etc. go here.
			retVal += 's:' + eval('\'' + content + '\'').length + ':"' + content + '";';
	}
	
	return retVal;
}

//************************************************************************
//
//	@preloadImages
//
//	Preloads images in $images
//
function preloadImages(images) {
	if(! images)
		return false;
	
	//	We want to work with an array here.
	if(typeof(images) == 'string')
		images = new Array(images);
		
	var preloader;
	for(image in images) {
		preloader = new Image();
		preloader.src = image;
	}
}