/*
 * SHOPATRON framework + utilities, version 0.1.3 created by Nathan Locke
 * Export primitive yet immenently useful routines. (ie: intentionally doesn't require another library like prototype, etc)
 * Functionality intended for use via methods, etc of a default-initialized singleton 'Shopatron' object or its components.
 * Until futher documentation is drafted, see the code below or do a dump(Shopatron) for more specific info.
 *
 * Note: To turn off the export of the shorthand functions (like "dump") into the global namespace, before incliding this file, use:
 * < script type="text/javascript" >
 *      // totally turn off:
 *      //var Shopatron = false;
 *      // or, to just declutter; must access routines explicitly via Shopatron.*.*
 *      var Shopatron = { flag_add_shortcuts: false; };
 * < / script >
 *
 * (c) 2008 Shopatron, Inc.
 *
 */


// basic utility to see if a value is undefined or null
function shopatron_is_unull()
{
	return (typeof(default_div)=="undefined" || default_div==null) ? true : false;
}


// basic utility to return either the value or a specified default
function shopatron_val_default( value, value_default )
{
	if( typeof(value_default)=="undefined" ) value_default = "";
	return shopatron_is_unull(value) ? value_default : value;
}


// dump out information about the contents of the specified object, and/or return that info as a string
// 	- obj: the object (or scalar) to evaluate
// 	- bValues: boolean indicating whether to display values, or just properties (recursion is also of if this is off), true by default
// 	- RecurseLevels: can be 'true' or the number of levels for which analysis is desired; 'true' maxes out at 9 levels, false by default
// 	- TitlePrefix: a string to be prepended onto the info, not set by default
// 	- bAmmendCaller: defaults to true
// 	- bAlert: whether or not ot pop up an alert right now; set to false to just get the return string
function shopatron_utils_dump( obj, bValues, RecurseLevels, TitlePrefix, bAmmendCaller, bAlert )
{
	// init
	var str = "";
	var n = 0;

	try {  // unobtrusively wrap this whole function

	// parms
	bValues = (typeof(bValues)=="undefined" || bValues!=false) ? true : false;
	if( typeof(RecurseLevels)=="boolean" && RecurseLevels == true ) RecurseLevels = 9;
	RecurseLevels = (typeof(bValues)=="undefined" || bValues==null) ? 0 : parseInt(new String(RecurseLevels));
	if( isNaN(RecurseLevels) ) RecurseLevels = 0;
	//alert( RecurseLevels );
	bAlert = (typeof(bAlert)=="undefined" || bAlert!=false) ? true : false;
	//if( RecurseLevels==2 ) alert( bAlert );
	TitlePrefix = (typeof(TitlePrefix)=="undefined" || TitlePrefix==null) ? "" : new String(TitlePrefix);
	bAmmendCaller = (typeof(bAmmendCaller)=="undefined" || bAmmendCaller!=false) ? true : false;

	// now, look at the object
	if( bValues )  // get both values and property names
	{
		// first, deal with a regular scalar
		if( typeof(obj) != "object" )
		{
			str = "[" + typeof(obj) + "]";
			if( typeof(obj) == "function" ) {
				if( obj.name && obj.name != "" ) str += " " + obj.name + "()";
			} else if( typeof(obj) == "string" ) {
				str += ' "' + obj + '"';
			} else if( typeof(obj) != "undefined" ) {
				str += " " + obj;
			}
			str += "\n";
		}
		// be intelligent about the special null case
		else if( obj == null )
		{
			str += "{ NULL }";
		}
		// for simplicity, just do the string for a date
		else if( obj.getFullYear && obj.getMilliseconds )
		{
			str = "{date}: " + String(obj) + "\n";
		}
		else // enumerate all properties and their values
		{
			for( var i in obj )
			{
				// don't make the list too hufge to alert it
				n++;
				if( n > 25 ) {  // maybe should do this is bAlert is false? - to be effective, we should also copy this curtailing logic to the outermost level
					str += "\n...(abridged!)\n";
					break;
				}
				// intelligently display a string representation of this property
				 if( typeof(obj[i])=="undefined" || obj[i]==null ) {
					val = "{ NULL }";
				 } else if( typeof(obj[i])=="function" ) {
					val = "{ FUNCTION }"
					if( obj[i].name && obj[i].name != "" ) val += ' ' + obj[i].name + '()';
				// where requested, recurse into sub-objects
				} else if( typeof(obj[i])=="object" ) {
					if( RecurseLevels > 0 )
					{
						val = shopatron_utils_dump( obj[i], bValues, RecurseLevels - 1, null, false, false );
						if( typeof(val)=="string" && val!="{ EMPTY }" )
						{
							val = "\n" + val;
							val = val.replace(/\n\{ OBJECT \}/, "{ OBJECT }");
							val = val.replace(/\n\{ ERROR \}/, "{ ERROR }");  // may not ever happen, but we'll try
						}
						val = val.replace(/\n$/, "");
						val = val.replace(/\n/g, "\n\t");

					}
					// or, just let them know this property is an object
					else
					{
						if( typeof(obj.message)!="undefined" && typeof(obj.lineNumber)!="undefined" && typeof(obj.fileName)!="undefined" ) val = "{ ERROR }"; // may not be possible, but we'll try
						else val = "{ OBJECT }";
					}
				// for scalars
				} else {
					val = shopatron_utils_dump( obj[i], bValues, RecurseLevels - 1, null, false, false );
					if( val.length > 103 ) val = val.substring(0,100) + "...";
					val = val.replace(/\n/g, " ");
					val = val.replace(/\r/g, "");
					val = val.replace(/\t/g, "");
				}
				// tack this value onto the string we're building
				str += i + ": " + val + "\n";
			}
			// final prepend of the basic type
			if( n < 1 ) str += "{ EMPTY }";  // no properties iterated
			else if( obj.message && obj.lineNumber && obj.fileName ) str = "{ ERROR }\n" + str;  // special case for errors
			else str = "{ OBJECT }\n" + str;

		}
	}

	// just get a list of the property names (but not the values)
	else
	{
		for( var i in obj ) {
			n++;
			str += i + ", ";
		}
		// post-process the string
		if( str.length > 0 ) {
			str = str.substring(0, str.length - 2);
		}
		// list the properties
		if( n > 0 ) {
			str = n + ": " + str;
		}
		// or clue them in that there were none
		else
		{
			str = "(none)";
		}
	}

	// ammend/prepend additional info
	if( TitlePrefix != "" ) str = TitlePrefix +"...\n\n"+ str;
	if( bAmmendCaller ) str += "\n" + shopatron_utils_stack( 1 );

	// do the alert
	if( bAlert ) {
		alert( str );
	}
	return str;

	// when there was an internal problem detecting the object
	} catch( ERR ) {
		str += "Dump failed. "+ERR.message;
		if( typeof(bAlert)=="undefined" || bAlert!=false ) alert( str );
		return str;
	}
}


// return a string of the stack trace
// modeled after: http://eriwen.com/javascript/js-stack-trace/
function shopatron_utils_stack( only_index )
{
	if( typeof(only_index)=="undefined" ) only_index = -1;
    var callstack = new Array();
    var isCallstackPopulated = false;
	var re = new RegExp("^\\s*([A-Za-z0-9_$]+\\([^\\)]*\\))?@"); // NL: don't really want this?

    // intentionally throw an error (won't work in NS4)
	try {
        i.dont.exist += 0; //does not exist - that's the point
    } catch( e ) {
        // Firefox
    	if( e.stack ) {
            var lines = e.stack.split("\n");
            for( var i = 0, len = lines.length; i < len; i++ ) {
            	if( lines[i].match(re) ) {
                    callstack.push( lines[i] );
                }
            }
            isCallstackPopulated = true;
        // Opera
        } else if( window.opera && e.message ) {
            for( var i = 0, len = lines.length; i < len; i++) {
            	if( lines[i].match(re) ) {
                    var entry = lines[i];
                    //Append next line also since it has the file info
                    if( lines[i+1] ) {
                        entry += " at " + lines[i+1];
                        i++;
                    }
                    callstack.push(entry);
                }
            }
	        isCallstackPopulated = true;
        }
		// remove this func
	    if( isCallstackPopulated ) {
	        callstack.shift();
	    }
    }

    // IE or Safari - different method
    if( !isCallstackPopulated ) {
    	if( arguments.callee && arguments.callee.caller ) {
        var currentFunction = arguments.callee.caller;
	        while( currentFunction ) {
	            var fn = currentFunction.toString();
	            var fname = fn.substring( fn.indexOf("function") + 8, fn.indexOf("(") ) || "anonymous";  // default as "anonymous" when no name
	            callstack.push( fname );
	            currentFunction = currentFunction.caller;
	        }
    	} else {
    		callstack.push( '[ stack indeterminate ]' );
    	}
    }

    // nerf the rest of things if we only wanted one line
    if( only_index >= 0 && callstack.length > 0 && callstack[only_index] )
    {
    	callstack = new Array( callstack[only_index] );
    }

    // done
    return callstack.join("\n");
}


// a quick string representation of an error object
function shopatron_error_string( ERR )
{
	// init
	str = new String("");
	if( typeof(ERR)=="undefined" ) ERR = "[undefined]";
	else if( ERR==null ) ERR = "[null] ";
	else if( ERR==null ) ERR = "[null] ";

	// try to extract the info
	if( typeof(ERR.message)!="undefined" ) {
		str += 'Caught error: "' + ERR.message + "\"\n\n";
		if( typeof(ERR.lineNumber)!="undefined" && typeof(ERR.fileName)!="undefined" )  // add these on if they are there (might not be there for custom errors)
		{
			str += "line " + ERR.lineNumber + ": " + ERR.fileName + "   \n ";
		}
	}

	// not an error object, try to convert whatever it is into a string
	else
	{
		str = new String(ERR);
		if( str=="" || str.match(/^\s*$/) ) str = "[empty]";
		str = "Nonspecific error.\n" + str;
	}

	// done
	return str;
}


// report the specified error to the user
//!!! somdeay, also offer to a 'confirm' dialog (if from err.handle) that says to click cancel to ignore all future errors (at least when called via Shopatorn.err.report)
function shopatron_error_report( ERR, bFullDump )
{
	// see if error handling is off
	if( this && typeof(this._typeof)=="string" && this._typeof=="shopatron" )
	{
		//if( typeof(this.err.ignore) != "undefined" && this.err.ignore == true ) // don't re-do this
			return false;
	}

	// do it
	bFullDump = (typeof(bFullDump)=="undefined" || bFullDump!=true) ? false : true;
	str = shopatron_error_string(ERR);
	if( bFullDump ) str += "\n(Full error object dump to follow.)";
	alert( str );
	if( bFullDump ) shopatron_utils_dump( ERR );
	return true;

}


// decide how to handle the error, and do it
function shopatron_error_handle( ERR, bFullDump )
{
	// see if error handling is off
	if( this && typeof(this._typeof)=="string" && this._typeof=="shopatron" )
	{
		if( this.err && this.err.ignore == true )
		{
			return false;
		}
	}

	// do the error handling
	if( this.err && typeof(this.err.report)=="function" )
	{
		try {
			return this.err.callback( ERR, bFullDump );
		} catch( Err2 ) {
			alert( "shopatron_error_report: callback error\n" + Err2.message );  // nothing fancy
			return false;
		}
	}

	// default handler (use 'alert' directly instead?)
	shopatron_error_report( ERR, bFullDump );
	return true;
}


/* example of a proposed code enhancement
// a real constructor function
function Shopatron_object()
{
	//this.whatever = '';
	//this.method = shopatron_func;
}
// utilizes the prototype extensions instead
function Shopatron$()
{
	//Class.Create( { } ); // ?
}
/**/


//
// Set up the main 'Shopatron' object that we want to export.
//

// MAIN - - - - - - - - - -

try {  // avoid script inclusion errors messing up the whole page

// handle the quick flag for Shopatron=false
if( typeof(Shopatron)=="undefined" || (typeof(Shopatron)=="boolean" && Shopatron != true) )
{

	// constructor [really should put this in a proper function; alas, another day]
	// but. don't override any pre-init settings flagged for us
	if( typeof(Shopatron) != "object" )
	{
		var Shopatron = new Object();
	}
	Shopatron._typeof = "shopatron";  // info flag

	// utils - - - - - - - - - -
	Shopatron.utils = new Object();
	Shopatron.utils.dump = shopatron_utils_dump;
	Shopatron.utils.unull = shopatron_is_unull;
	Shopatron.utils.valdefault = shopatron_val_default;
	Shopatron.utils.stack = shopatron_utils_stack;

	// err - - - - - - - - - -
	// chiefly exposes the .handle() method and the .ignore property to not actually report errors when handle() is called
	Shopatron.err = new Object();
	Shopatron.err.string = shopatron_error_string;
	Shopatron.err.report = shopatron_error_report; // always reports
	Shopatron.err.handle = shopatron_error_handle; // only reports if ignore!=true; supports a callback
	Shopatron.err.callback = null;
	Shopatron.err.ignore = false; // true short-circuits the handler
	//Shopatron.err.target // add?

	// ui - - - - - - - - - -
	Shopatron.ui = new Object();
	//Shopatron.ui.xfader = shopatron_ui_xfader;

	// [unimplemented] - - - - - - - - - -
	//Shopatron.instance = new Object(); // container for custom relection into which site/page this is?

	// core - - - - - - - - - -
	// certain utils also in our main namespace ?
	Shopatron.dump = Shopatron.utils.dump;
	Shopatron.errhandle = Shopatron.err.handle;
	Shopatron.reporterr = Shopatron.err.report;

	//
	// GLOBALS - - - - - - - - - -
	//

	// extra-specially useful shortcut globals
	// to turn this off, set flag_add_shortcuts=false
	if( typeof(Shopatron.flag_add_shortcuts)=="undefined" || Shopatron.flag_add_shortcuts != false )
	{
		Shopatron.flag_add_shortcuts = true; // flag that we did this
		var dump = Shopatron.utils.dump;
		var unull = Shopatron.utils.unull;
		var valdefault = Shopatron.utils.valdefault;
		var errhandle = Shopatron.err.handle;
		var reporterr = Shopatron.err.report;
	}

}


//
} catch( ERR ) {
	// just ignore an error here; re-enable when debugging this script itself
	//
	alert( ERR.message );
}