/*********************************************************
 * WHO:			Michael Philippone
 * 
 * WHAT:		collection of functions and algorithms 
 * 				for creating verbose debugging output for 
 * 				object introspection
 * 
 * WHEN:		28 July - 03 Aug 2009
 * 
 * VERSION:		2.1
 * 
 * COPYRIGHT:	2009 Corporate Zen
 * 
 * HOW:		
 * 		PROPERTIES / GLOBALS:
 * 			--> For each dump(..) call:
 *	 			->	window['dumpElement_<UNIQUE_ID>'] 
 *					::	overarching javascript object element being inspected
 *				->	window['opts_<UNIQUE_ID>'] 
 *					::	collection of options passed from initial call (top of the function call stack)
 * 
 * 		FUNCTIONS:
 * 			-->	dump - top of the call stack, calls the print and buildstring functions
 * 			--> print - actually renders the output to the screen
 * 			--> buildString - generates (recursion!) a string of all of the output
 * 			--> testIncludes - looks for functions in the jsDump_utils.js file and alerts if missing.
 * 
 * 
 * CHANGELOG:	
 * 	-> 03 AUG 2009: (by michael@corporatezen.com)
 * 		-> moved utility functions to "jsDump_utils.js"
 * 		-> added HTML scrubbing, then ref-formatting for output of the 
 * 			output that involves source code or HTML
 * 		-> added sorting of properties when generating object output
 *	-> 04 AUG 2009: (by michael@corporatezen.com)
 *		-> fixed recursion error, wasn't outputting all elements in a structure, works now
 * 	 
 *********************************************************/

/* ********************************************************************************************* */
/*
 * tests for functions that are declared in the jsDump_utils.js file
 * alerts the user if the dump function is called without them
 */
function testIncludes() {
	var fxns = "isArray,isObject,scrubHTML,makeButton,UID,minidump,getKeys";
	fxns = fxns.split(",");
	
	for (var i = 0; i < fxns.length; i++) 
	{
		if ( !Boolean( window[ fxns[i] ] ) ) 
		{
			alert("Please be sure you have included the file, 'jsDump_utils.js' in your page.\n\n" +
			"There are required functions in that file that appear to be missing.");
			return false;
		}
	}
	return true;	
}

/* ********************************************************************************************* */

/*
 * Given a string and some optional HTML style properties, print the 
 * string into a div and append it to the page's body element
 */
function print(props) {	
	if( !props || (props && !props.text) ) { alert('there has been an error'); return; }
	
	var legend = 
		"<div> legend: " + 
			"<span style='padding:5px; background-color:#FF6FA0;'>SCALAR</span>" +
			"<span style='padding:5px; background-color:#33FF33;'>ARRAY</span>" +
			"<span style='padding:5px; background-color:#9966FF;'>OBJECT</span>" + 
		"</div>";
	
	var lvlTxt = "";
	if( Boolean(props.maxlevel) ) lvlTxt += "<br/>maximum recursive depth: <strong>" + props.maxlevel + "</strong>";
		
	var pre = Boolean( props.prepend );	
	
	// ELSE:
	try {
		var ret = document.createElement('div');
		ret.innerHTML = "";
		
		ret.style.marginTop = (props.marginTop && props.marginTop != '')?(props.marginTop):('10px');
		ret.style.display = (props.display && props.display != '')?(props.display):('block');
		ret.style.textAlign = (props.textAlign && props.textAlign != '')?(props.textAlign):('center');
		ret.style.border = (props.border && props.border != '')?(props.border):('3px solid red');
		ret.style.padding = (props.padding && props.padding != '')?(props.padding):('10px 10px 10px 10px');
		ret.style.backgroundColor = (props.backgroundColor && props.backgroundColor != '')?(props.backgroundColor):("#CCCCCC");
		
		ret.innerHTML = props.text + legend + lvlTxt; 
			
	} 
	catch(error) {
		alert('there was an error creating the element. \n(print function)');
	}
	
	try {
		if( document )
			if( document.getElementsByTagName )
				if( document.getElementsByTagName("body") )
					if( document.getElementsByTagName("body")[0] )
						if( document.getElementsByTagName("body")[0].appendChild )
							if(pre)
								document.getElementsByTagName("body")[0].insertBefore( ret , document.getElementsByTagName("body")[0].firstChild );
							else
								document.getElementsByTagName("body")[0].appendChild(ret);						
						else
							alert('document.getElementsByTagName("body")[0].appendChild is not specified');
					else
						alert('document.getElementsByTagName("body")[0] is not specified');
				else
					alert('document.getElementsByTagName("body") is not specified');
			else
				alert('document.getElementsByTagName is not specified');
		else
			alert('document is not specified');
	}
	catch(error) {
		alert("There was an error appending the element to the page body. \n(print function)");
	}
}

/* ********************************************************************************************* */

/*
 * Given an element (not the ID, the actual object)
 * dump out its contents (verbosely!)
 * into a div that gets tacked onto the BODY tag
 * TODO:
 * 	- make for alpha order sorting of object properties
 * ACCEPTED OPTIONS (properties of the 'opts' argument:
 * 	-> 	label: the label to use for the top-level object in the output
 * 	-> 	backgroundColor: the CSS color value for the object output
 *  -> 	maxlevel: how deep should the recursion be allowed to go
 *  ->	level: (internal use ONLY) how deep is the recursion currently
 */
function buildString(element , opts) {
	try {
		var scalarBg = '#FF6FA0';
		var objBg = '#9966FF';
		var arrBg = '#33FF33';
		
		if(!opts) opts = {};
		
		var text ='';
		var label = false;
		if (opts.label)	label = true;
		if(label) 		text = opts.label;
		
		var level = 0;
		var maxlevel = 0;
		if(Boolean(opts.level)) level = opts.level;
		if(Boolean(opts.maxlevel)) maxlevel = opts.maxlevel;
		
		var bgColor = scalarBg;
		if(opts.backgroundColor) bgColor = opts.backgroundColor;
		if (isObject(element)) 	bgColor = objBg;
		if (isArray(element)) 	bgColor = arrBg;
		
		// style to apply to all list item elements
		var liStyle = 
			'padding:10px; 10px 10px 10px; ' + 
			'border-bottom: 1px solid #000000; ' + 
			'width:90%; ';  
		
		// style to apply to text when dumping array element line items
		var arrText = 
			"color:#FFFFFF; " + 
			"font-weight:bold; " + 
			"" + 
			"background-color:#6F6F6F; ";
		
		// style to apply to text when dumping object element line items	
		var objText = 
			"color:#FFFFFF; " +
			"font-weight:bold; " +  
			"" +
			"background-color:inherit; ";
		
		// style to apply to text when dumping scalar element line items
		var scalarText = 
			"color:#D09F6F; " +
			"font-weight:bold; " +  
			"" +
			"background-color:inherit; ";
		
		var ulStyle = 
			'margin-left:auto; ' + 
			'margin-right:auto; ' +
			'text-align:left; ' +
			'border:4px dotted #303030; ' + 
			'text-align:left; ' + 
			'background-color:' + bgColor + '; ' + 
			'width:90%; ';
				
		// the string we'll build with all of the properties:
		var str = "<ul style='" + ulStyle + "'>";
	}
	catch(error) {
		alert('There was an error creating the local variables: ' + 
		'scalarBg = ' + scalarBg + '\n' +
		'objBg = ' + objBg + '\n' +
		'arrBg = ' + arrBg + '\n' +
		'Boolean(opts) = ' + Boolean(opts) + '\n' +
		'text = ' + text + '\n' +
		'label = ' + label + '\n' +
		'level = ' + level + '\n' +
		'maxlevel = ' + maxlevel + '\n' +
		'bgColor = ' + bgColor + '\n' +
		'Boolean(element) = ' + Boolean(element) + '\n' +
		'liStyle = '  + liStyle + '\n' +
		'arrText = '  + arrText + '\n' +
		'objText = '  + objText + '\n' +
		'scalarText = '  + scalarText + '\n' +
		'ulStyle = '  + ulStyle + '\n' +
		'str = '  + str + '\n' +
		'\n(print function)');
	}
	
	try {
		// cut out if we are too deep or just deep enough in the recursion:
		if( Boolean(maxlevel) ) 
			if( level >= maxlevel) 
			{
				if (Boolean(opts.isArr) && opts.isArr) 
					return str + "<li style='" + liStyle + "'>" + "<span style='" + arrText + "'>" + text + "</span></li></ul>";
				else if (Boolean(opts.isObj) && opts.isObj) 
					return str + "<li style='" + liStyle + "'>" + "<span style='" + objText + "'>" + text + "</span></li></ul>";
				else 
					return str + "<li style='" + liStyle + "'>" + "<span style='" + scalarText + "'>" + text + "</span></li></ul>";
			}
		
		
		if (isArray(element)) {
			if(!label) text = "ARRAY";
			for (var i = 0; i < element.length; i++) 
			{
				str += "<li style='" + liStyle + "'>"; 
				try {				
					if( isArray(element[i]) )
					{ 
						str += 
							buildString( 
									element[i] , 
									{ 
										label: text+'['+i+']' , 
										backgroundColor: arrBg , 
										level:level+1 , 
										maxlevel:maxlevel ,
										isObj:false ,
										isArr:true
									} 
								);
					}
					else if ( isObject(element[i]) )   
					{ 
						str +=
							buildString( 
									element[i] , 
									{ 
										label: text+'['+i+']' , 
										backgroundColor: objBg , 
										level:level+1 , 
										maxlevel:maxlevel ,
										isObj:true ,
										isArr:false 
									} 
								);
					}				
					else 
						str += "<span style='" + arrText + "'>" + text + "</span>" + "[" + i + "] = " + scrubHTML( element[i].toString() );
				} catch(err) {
					str += "<h4>There was an error parsing item: <em>" + text + "["+i+"]</em>. ("+err.message+")</h4>";
				}
				str += "</li>";
			}		
		}
		else if (isObject(element)) {
			
			// get the sorted array of the object's property keys
			var keys = getKeys(element , true);
			var X = null;
			
			if(!label) text = "OBJECT";
			
			for(var i=0; i<keys.length; i++) 
			{
				X = keys[i];
				str += "<li style='" + liStyle + "'>";
				try {
					if( isArray( element[X] ) ) 
					{
						str += 
							buildString(
								element[X] ,
								{
									label: text + '[' + X + ']',
									backgroundColor: arrBg,
									level: level + 1,
									maxlevel: maxlevel,
									isObj: false,
									isArr: true
								}
							);
					}
					else if (isObject(element[X])) 
					{
						str += 
							buildString(
								element[ X ] , 
								{
									label: text + '[' + X + ']',
									backgroundColor: objBg,
									level: level + 1,
									maxlevel: maxlevel,
									isObj: true,
									isArr: false
								}
							);
						}
						else 
						{
							str += 
								"<span style='" + objText + "'>" +  text + "</span>" +
								"[" + X + "] = " +
								scrubHTML(element[X].toString());
						}
				} catch(err) {
					str += "<h4>There was an error parsing item: <em>" + text + "[" + X + "]</em>.("+err.message+")</h4>";
				}
				str += "</li>";
			}
		}
		else {
			if(!label) text = "ELEMENT";
			try {
				str +=  
					"<li style='" + liStyle + "'>" + 
					"<span style='" + objText + "'>" + text + "</span>" + " = " + scrubHTML( element.toString() ) + 
					"</li>";
			} catch(err) {
				str += "<h4>There was an error parsing item: <em>" + text + "</em>.</h4>";
			}
		}
	}
	catch(outerErr) {
		str += "<li><h4>There was an error parsing the structure.</h4></li>";
	}
	str += "</ul>";
	
	return str;
}

/* ********************************************************************************************* */

/*
 * Transparently call the string builder function
 * and the HTML printing function
 * 
 * Allowable options:
 * 	 	wait: the number of seconds to wait before 
 * 				appending the output element to the page body
 *		label: the name to use when writing the top-level object
 *		prepend: true/false , whether the output element should 
 *				prepended to the page body
 *		backgroundColor: the CSS color value for the object output
 *				(passed to buildString() fxn)
 *		maxlevel: how deep should the recursion be allowed to go
 *  	level: (internal use ONLY) how deep is the recursion currently
 *  	button: use the button interface to trigger object dump
 */
function dump( element , opts ) {	

	var includes = testIncludes();
	if( !includes ) return false;

	//ELSE:
	try {
		if(arguments.length < 1) 
		{
			alert("please specify a javascript variable to introspect");
			return false;
		}
		
		if (!Boolean(opts)) {
			opts = {};
			opts.wait = 2;
			opts.label = "";
			opts.button = false;
			opts.prepend = false;
		}
		
		opts.label = ( Boolean(opts.label) )?(opts.label):"ELEMENT" ;
		var button = Boolean(opts.button); 
		
		var uid = UID();
		window['dumpElement_'+uid] = element;
		window['opts_'+uid] = opts;
		
		///////////////////
		// build up all the options for the print fxn call:
		///////////////////
		var timeO = false;
		if( Boolean(opts.wait) ) timeO = opts.wait;
		
		var textTxt = "text: buildString( window.dumpElement_"+uid+" , window.opts_"+uid+" ) ";
		
		var prependTxt = "";
		if( Boolean(opts.prepend) )
			prependTxt = " , prepend: " + opts.prepend;
		
		var lvlTxt = "";
		if(Boolean(opts.maxlevel)) 
			lvlTxt = " , maxlevel:"+opts.maxlevel;
		
		var waitTxt = "";
		if(timeO)
			waitTxt = " , wait:" + opts.wait;
		
		var buttonTxt = "";
		if(button) 
			buttonTxt = " , button: " + button;
			
		/////////////
		// actually build up the print fxn call:
		/////////////
		var fxn = 
			"print( " + 
				"{ " + 
					textTxt +
					waitTxt + 
					prependTxt + 
					lvlTxt + 
					buttonTxt +
				"} " + 
			");" ;
	}
	catch(err) {
		alert(
			"There was an error setting up local variables for the dump function:\n" +
			"   Boolean(element) = " + Boolean(element) + "\n" +
			"   Boolean(opts) = " + Boolean(opts) + "\n" +
			"   window['dumpElement_'"+uid+"] = " + window['dumpElement_'+uid] + "\n" +
			"   window['opts_'"+uid+"] = " + window['opts_'+uid] + "\n" +
			"   fxn = " + fxn + "\n" + 
			"   uid = " + uid + "\n" +
			"   timeO = " + timeO + "\n\n" +
			"The function may not have initialized correctly, there may be errors or non-functionality in the output."
		);
	}
		
	try {
		
		if(button)
			makeButton( opts.label , fxn);		
		else if ( Boolean(timeO) )
			setTimeout(fxn, timeO * 1000);
		else
			setTimeout( fxn , 1 ); 
	} 
	catch (err) {
		alert("There was an error making the appropriate print calls in the dump function:\n" +
		"   Boolean(element) = " + Boolean(element) + "\n" +
		"   Boolean(opts) = " + Boolean(opts) + "\n" +
		"   window['dumpElement_'" + uid + "] = " + window['dumpElement_' + uid] + "\n" +
		"   window['opts_'" + uid + "] = " + window['opts_' + uid] + "\n" +
		"   fxn = " + fxn + "\n" +
		"   uid = " + uid + "\n" +
		"   timeO = " + timeO + "\n\n" +
		"The function may not have initialized correctly, there may be errors or non-functionality in the output.");
	} 
	
}
