/**
 * array_search()
 *
 * @author   Sean McCann
 * @version  1.1
 * @date     2004.09.23
 *
 * @desc     Searches through an array for a value
 *
 * This function is a Javascript adaptation of PHP's array_search function.
 * It searches through an array looking for a given value.  If it finds the
 * value in the array, it returns the corresponding key for that value.
 * If the value cannot be found, it returns false.
 *
 * @todo  implement full capabilities of PHP's array_search
 * See <http://php.net/array_search> for more details.
 *
 * - Synopsis -
 * mixed array_search ( needle , haystack )
 *
 * @param  mixed needle    the array value being searched for
 * @param  array haystack  the array being searched
 * @return mixed  key for needle, or false if not found
 *
 * If array_search finds the value needle in the array haystack,
 * it returns the corresponding key for that value.  If the value
 * cannot be found, it returns false.
 *
 * It is important to note that unless you do a strict type comparison
 * with ===, Javascript will not distinguish between the boolean false
 * and the the first index of the array, the integer 0.
 *
 * If needle occurs multiple times within haystack, the index corresponding
 * to the first occurence is returned.
 *
 * - Usage -
 * var names = new Array('Matthew', 'Mark', 'Luke', 'John', 'Mark');
 * array_search('Luke', names);    // returns 2
 * array_search('Mark', names);    // returns 1
 * array_search('Matthew', names); // returns 0
 * array_search('Ringo', names);   // returns false
 *
 */

function array_search(needle, haystack)
{
	try
	{
		var i = 0;
		var found = false;
		var index = -1;

		// Type checking:  if our haystack is not a searchable
		// array, then throw an exception
		if (! (haystack instanceof Array))
		{
			throw new TypeError("Expecting argument 'haystack' to be of type Array");
		}

		while ( (i < haystack.length) && (!found) )
		{
			if (needle == haystack[i])
			{
				found = true;
				index = i;
			}
			i++;
		}

		if (!found)
		{
			return false;
		}
		else
		{
			return index;
		}
	}
	catch(e)
	{
		error_message(e, "common.functions.js", 'array_search');
		return false;
	}
}


/**
 * in_array() -- Checks if a value exists in an array
 *
 * @author   Sean McCann
 * @version  1.0
 * @date     2005.07.14
 *
 * This function is a JavaScript adaptation of PHP's in_array() function.
 *
 * @param  mixed needle    the array value being searched for
 * @param  array haystack  the array being searched
 * @return bool Searches haystack for needle and returns TRUE  if it is found in the array, FALSE otherwise.
 *
 * @todo  implement the "strict" parameter
 * Note: may not work correctly for non-scalar values
 *
 */
function in_array( needle, haystack )
{
	try
	{
		return ( array_search(needle, haystack) !== false );
	}
	catch(e)
	{
		error_message(e, "common.functions.js", 'in_array');
		return false;
	}
}

///////////////////////////////////////////////////////////////////////


/**
 * object_search()
 *
 * @author   Sean McCann
 * @version  1.1
 * @date     2004.09.23
 *
 * @desc     Searches through an object for a value
 *
 * This function is a Javascript adaptation of PHP's array_search function,
 * specifically designed to find values stored in an associative array.
 * Javascript stores associative array subscripts as properties of that
 * object.  In fact, adding an associative subscript to an Array doesn't
 * even change the Array's length!  It's just like adding a new property
 * to the Array (which is also an Object).  That's it...
 * object_search() looks in at all of an object's properties while looking
 * for a given value.  If it finds the value in the object, it returns the
 * corresponding property name for that value.  If the value cannot be found,
 * it returns false.
 *
 * @todo  implement full capabilities of PHP's array_search
 * See <http://php.net/array_search> for more details.
 *
 * - Synopsis -
 * mixed object_search ( needle , haystack )
 *
 * @param  mixed  needle     the property value being searched for
 * @param  Object haystack   the object whose properties are being searched
 * @return mixed  property name for needle, or false if not found
 *
 * If object_search() finds the value needle in the object haystack,
 * it returns the corresponding property for that value.  If the value
 * cannot be found, it returns false.
 *
 * It is important to note that unless you do a strict type comparison
 * with ===, Javascript will not distinguish between the boolean false
 * and the the first index of an Array, the integer 0.
 *
 * If needle occurs multiple times within haystack, the index corresponding
 * to the first occurence is returned.
 *
 */

function object_search(needle, haystack)
{
	try
	{
		var i = 0;
		var found = false;
		var index = false;

		if (typeof(haystack) != 'object')
		{
			throw new TypeError("Expecting argument 'haystack' to be of type Object");
		}

		for (i in haystack)
		{
			if (!found && (needle == haystack[i]))
			{
				found = true;
				index = i;
			}
		}

		if (!found)
		{
			return false;
		}
		else
		{
			return index;
		}
	}
	catch(e)
	{
		error_message(e, "<?php echo basename($_SERVER['PHP_SELF']);?>", 'object_search');
		return false;
	}
}


///////////////////////////////////////////////////////////////////////


/**
 * strcmp()
 *
 * @author   Sean McCann
 * @version  1.0
 * @date     2004.07.15
 *
 * @desc     Compares two strings
 *
 * This function is a Javascript adaptation of the C function with the
 * same name.  strcmp() compares two strings, and returns a value indicating
 * the result of the comparison.  -1 means that string a < string b, 0 means
 * that string a and string b are the equal, and 1 means that string a >
 * string b.
 *
 * - Synopsis -
 * int strcmp( a, b )
 *
 * @param  string a   first string in the comparison
 * @param  string b   second string in the comparison
 * @return int    -1 if a < b, 1 if a > b, and 0 if a and b are identical
 *
 * - Usage -
 * // Example 1:
 * some_array.sort(strcmp);
 *
 * // Example 2:
 * var a = 'bob';
 * var b = 'bob';
 * var comparison = strcmp(a,b);
 * if (comparison == 0) alert("String A and string B are identical!");
 *
 */

function strcmp(a,b)
{
	try
	{
		// @todo  type checking on a and b
		if (a < b)
		{
			return -1;
		}
		if (a > b)
		{
			return 1;
		}
		else return 0;
	}
	catch(e)
	{
		error_message(e, "<?php echo basename($_SERVER['PHP_SELF']);?>", 'strcmp');
		return null;
	}
}



///////////////////////////////////////////////////////////////////////


/**
 * clone_object()
 *
 * @author   Sean McCann
 * @version  0.2
 * @date     2004.09.26
 *
 * @desc     Makes a copy of an Object
 *
 * Javascript has no copy constructor, and the assignment operator
 * only creates a copy of that object's reference.  However, there
 * are times when we need a brand new copy of an object to work with,
 * so we need a way to create one...  Here it is!
 *
 * - Synopsis -
 * Object clone_object( obj )
 *
 * @param  Object obj  the object to clone
 * @return Object a clone of the original object
 *
 * - Usage -
 * // Example:
 * var abc = new Array('a','b','c');
 * var abc_copy = clone_object(abc);
 * abc_copy.push('d');
 *
 */

function clone_object( obj )
{
	try
	{
		var i;
		var clone;

		// Type checking: make sure that
		// obj is an Object
		if (typeof(obj) != 'object' || obj === null)
		{
			return null;
		}

		// Create the new object
		clone = new obj.constructor();

		for (i in obj)
		{
			clone[i] = obj[i];
		}

		return clone;
	}
	catch(e)
	{
		error_message(e, "<?php echo basename($_SERVER['PHP_SELF']);?>", 'clone_object');
		return null;
	}
}



///////////////////////////////////////////////////////////////////////

/**
 * Creates a dropdown from an associative array (ie. a javascript object)
 *
 * @param Object options   an object whose
 */
function select_from_array(options, select_name, initial_value)
{
	var select_element;

	select_element = document.createElement('select');
	select_element.name = select_name;

	_select_from_array_create_options(options, select_element);

	if (typeof(initial_value) != 'undefined')
	{
		select_element.value = initial_value;
	}

	return select_element;
}




function _select_from_array_create_options(options, parent_node)
{
	var i;
	var option;
	var optgroup;

	for (i in options)
	{
		if (typeof(options[i]) == 'object' && typeof(options[i].__LABEL__) != 'undefined')
		{
			optgroup = document.createElement('optgroup');
			optgroup.label = options[i].__LABEL__;
			_select_from_array_create_options(options[i], optgroup);
			parent_node.appendChild(optgroup);
		}
		else if (i != '__LABEL__')
		{
			option = document.createElement('option');
			option.value = i;
			option.label = options[i];
			option.appendChild(document.createTextNode(options[i]));
			parent_node.appendChild(option);
		}
	}
}


///////////////////////////////////////////////////////////////////////

/**
 * enable_children()
 *
 * @author   Sean McCann
 * @version  1.1
 * @date     2005.10.10
 *
 * @desc     Enable any form element that is a child of the specified parent element
 *
 * - Changelog -
 * version 1.1
 * - corrected serious underlying error in the recursive call to alter_form_element_children() that prevented recursion in nearly every realistic case. (Sean McCann)
 * version 1.0 (working)
 * - All original code completed (Sean McCann)
 *
 * @param  string class_name   The name of a defined class/function/object that inherits from a parent
 * @return bool   true on success, false on failure
 */
function enable_children(parent_element, recursive)
{
	return alter_form_element_children('enable', parent_element, recursive);
}

function disable_children(parent_element, recursive)
{
	return alter_form_element_children('disable', parent_element, recursive);
}

function alter_form_element_children( action, parent_element, recursive)
{
	try
	{
		var children = parent_element.childNodes;
		var i;

		// This function is for disabling/enabling form elements.  If the user
		// specifies an inappropriate action, then discontinue the opteration
		if (action != 'enable' && action != 'disable')
		{
			throw new Error("The parameter 'action' must have a value of 'enable' or 'disable'.");
		}

		// If the user didn't specify whether or not we are operating recursively, assume that we aren't.
		if (typeof(recursive) == 'undefined')
		{
			recursive = false;
		}

		// Loop through the children of the parent element and enable/disable them if possible
		for (i = 0; i < children.length; i++)
		{		
			if (typeof(children[i].disabled) != 'undefined')
			{
				children[i].disabled = (action == 'disable');
			}
			if (recursive)
			{
				alter_form_element_children(action, children[i], recursive)
			}
		}

		return true;
	}
	catch(e)
	{
		error_message(e, "common.functions.js", 'alter_form_element_children');
		return false;
	}
}


///////////////////////////////////////////////////////////////////////
/**
 * create_class_properties()
 *
 * @author   Sean McCann
 * @version  1.0
 * @date     2004.10.01
 *
 * @desc     Creates all properties from an Object's prototype within the new Object
 *
 * - Changelog -
 * version 1.0 (working)
 * - All original code completed (Sean McCann)
 *
 *
 * - Synopsis -
 * void create_class_properties( class_name )
 *
 * @param  string class_name   The name of a defined class/function/object that inherits from a parent
 * @return bool   true on success, false on failure
 *
 * - Example -
 * // Copies all properties from SuperClass into MyClass
 * function MyClass
 * {
 *    create_class_properties.call(this, MyClass);
 * }
 *
 * MyClass.prototype = new SuperClass();
 *
 */

function create_class_properties( class_name )
{
	try
	{
		var i;
		for (i in class_name.prototype)
		{
			if ( (i != '__CLASS__') && ( typeof(class_name.prototype[i]) != 'function' ) )
			{
				if ( (typeof(class_name.prototype[i]) == 'object')  &&  (class_name.prototype[i] !== null) )
				{
					this[i] = clone_object(class_name.prototype[i]);
				}
				else
				{
					this[i] = class_name.prototype[i];
				}
			}
		}
		return true;
	}
	catch(e)
	{
		error_message(e, "common.functions.js", 'create_class_properties');
		return false;
	}
}


///////////////////////////////////////////////////////////////////////
/**
 * var_dump()
 *
 * @author   Sean McCann
 * @version  1.1
 * @date     2005.07.28
 *
 * @desc     Shows information about a variable
 *
 * This function is a Javascript adaptation of the PHP function with the
 * same name.   This function displays structured information about a
 * variable, including its type and value. Arrays and objects are explored
 * recursively with values indented to show structure.
 *
 * - Synopsis -
 * void var_dump( variable )
 *
 * @param  mixed variable   the variable to examine
 * @param  bool  verbose    shows expanded information about the variable, including methods, long text, etc.
 *
 * Changelog
 * - 2005-07-28:  added verbose output support (Sean McCann)
 * - 2005-07-13:  initial release (Sean McCann)
 */

function var_dump( variable, verbose )
{
	try
	{
		var message = '';
		var i;
		var str_verbose_length = 50;

		if (typeof(verbose) == 'undefined')
		{
			verbose = false;
		}

		message += 'Value: ' + variable + "\n";
		message += 'Type: ' + typeof(variable) + "\n";
		message += "Properties: \n";
		for (i in variable)
		{
			if (typeof(variable[i]) == 'function')
			{
				if (verbose)
				{
					message += "  ." + i + ": " + "[Function]\n";
				}
			}
			else
			{
				message += "  ." + i + ": ";
				if (typeof(variable[i]) == 'string' && !verbose)
				{
					message += (variable[i].length > str_verbose_length) ?  '[lots of text]' : variable[i];
				}
				else
				{
					message += variable[i];
				}
				message += "\n";
			}
		}

		alert(message);
		return true;
	}
	catch(e)
	{
		alert(message);
		error_message(e, "common.functions.js", 'var_dump');
		return false;
	}
}


///////////////////////////////////////////////////////////////////////
/**
 * error_message()
 *
 * @author   Sean McCann (modifications to the original code)
 * @version  1.1
 * @date     2004.08.26
 *
 * @desc     Outputs an error message based and debugging info
 *
 * - Changelog -
 * version 1.1 (working)
 * - perform type detection on error parameter (Sean McCann)
 * version 1.0 (released 2004.08.26)
 * - All original code completed (Sean McCann)
 *
 *
 * - Synopsis -
 * void error_message( error, filename, place )
 *
 * @param  Error  error     An Error object that has been thrown and caught.  Review "try/throw/catch" concepts if necessary
 * @param  string filename  the name of the file in which the error occurred.  Can be autodetected in Mozilla
 * @param  string place     the name of the function in which the error occurred
 * @return bool   true on success, false on failure
 *
 * - Usage -
 * catch(e)
 * {
 *     error_message(e, 'myfile.js', 'myfunction');
 * }
 */

function error_message(error, filename, place)
{
	try
	{
		var error_message = '';
		var line;
		var stack;

		if (! (error instanceof Error))
		{
			throw new TypeError("argument 'error' should be of type Error instead of type " + typeof(error) + "\n\n" + error);
		}

		// Perform type checking on the file
		if ( (typeof filename == 'undefined') || !filename)
		{
			// Check for the Mozilla's proprietary 'event.fileName'
			if (typeof event.fileName != 'undefined')
			{
				filename = event.fileName;
			}
			else
			{
				filename = '<unknown>';
			}
		}

		// Perform type checking on the place
		if ( (typeof place == 'undefined') || !place)
		{
			place = '<unknown>';
		}

		// Checks for the line number that the error occured on
		// At the time of this writing, this Error property is
		// not standards compliant and is only supported by Mozilla
		if (typeof error.lineNumber != 'undefined')
		{
			line = error.lineNumber;
		}
		else
		{
			line = '<unknown>';
		}

		// Checks for the stack associated with the error that occured.
		// At the time of this writing, this Error property is
		// not standards compliant and is only supported by Mozilla
		if (typeof error.stack != 'undefined')
		{
			stack = error.stack;
		}


		// Start composing the error message output
		error_message += error.message + "\n";
		error_message += "\n*** Debug Info ***\n";
		error_message += "Error Type: " + error.name + "\n";
		error_message += "File: " + filename + "\n";
		error_message += "Function: " + place + "\n";
//		error_message += "Line: " + line + "\n";

		// Check for the presence of the error stack
		// (this should only be present in Mozilla)
		// If we have an error stack, then put together
		// some special output
		if (stack)
		{
			var matches;
			var stack_file;
			var stack_function;
			var stack_line;
			var stack_array;

			// Beginning of the Error Stack output section
			error_message += "\n***Error Stack***\n";

			// The stack string created by Mozilla separates
			// each entry on the stack using the character 0A.
			// Take that string, and split it up for parsing
			stack_array = (new String(stack)).split("\x0A");

			// Loop through each stack entry, and print it out
			for(var i = 0; i < stack_array.length; i++)
			{
				// only print out stuff if our stack entry
				// is not empty (note that there may be two
				// \x0A characters at the end of the stack
				// string, so this is necessary)
				if(stack_array[i].length > 0)
				{
					// Each entry is in the following format:
					// function(arg1, arg2, ...) @ [filename] : linenumber
					// The pattern below captures the function, filename,
					// and line number of the entry.  Subpattern 1 captures
					// the function name, subpattern 3 captures the file
					// name, and subpattern 4 captures the line number
					matches = stack_array[i].match(/(.*)@(http\:\/\/)?([A-z0-9\.\/]*)\:([0-9]+)$/i);

					if (matches != null)
					{
						stack_function = matches[1];
						stack_file = matches[3];
						stack_line = matches[4];

						// Our entries may contain some empty values,
						// so replace them with something more meaningful
						if (!stack_function)
						{
							stack_function = '<unknown>';
						}

						if (!stack_file)
						{
							stack_file = '<unknown>';
						}

						if (!stack_line)
						{
							stack_line = '<unknown>';
						}

						// Print out everything about this entry on
						// the stack.
						error_message += 'Function: ' + stack_function + "\n";
						error_message += 'File: ' + stack_file + "\n";
						error_message += 'Line: ' + stack_line + "\n\n";
					}
					else
					{
						// in the unlikely event that we did not match something,
						// print out "<unknown>" and the whole stack entry
						error_message += '<unknown>: ' + stack_array[i] + "\n\n";
					}
				}

			}
		}

		// Pop the error message up on the screen
		alert(error_message);
		return true;
	}
	catch(e)
	{
		alert("common.functions::error_message \n" + e);
		return false;
	}
}