/**
 * The TLint class provides a method for logging debugging information to Firebug and
 * Microsoft Internet Explorer 8.0 debug consoles.
 * @class Defines the TLint type. However, always use the global Lint object
 * @constructor
 */

TLint = function()
{
	//## PUBLIC FIELDS

	/**
	 * Turns debug mode one or off (no logging if debug mode is off)
	 * @field
	 * @type {boolean}
	 * #default false
	 */
	this.debugMode = false;

	/**
	 * Enable problem reporting (See Lint::sendProblemReport)
	 * @field
	 * @type {boolean}
	 * #default true
	 */
	this.enableProblemReports = true;

	//## PUBLIC METHODS

	/**
	 * Writes a log entry to the console
	 * @param message Log message
	 */
	this.log = function(message)
	{
		if (this.enableProblemReports) this.addProblemText(message);
		if (!this.debugMode) return;
		if (typeof(console) != 'undefined' && typeof(console.log) != 'undefined')
			console.log(message);
	}

	/**
	 * Writes an info line to the console (if not supported, writes a normal log)
	 * @param message Info message
	 */
	this.info = function(message)
	{
		if (this.enableProblemReports) this.addProblemText("INFO: " + message);
		if (!this.debugMode) return;
		if (typeof(console) != 'undefined')
		{
			if (typeof(console.info) != 'undefined') console.info(message);
			else console.log("INFO: " + message);
		}
	}

	/**
	 * Writes a warning line to the console (if not supported, writes a normal log)
	 * @param message Warning message
	 */
	this.warn = function(message)
	{
		if (this.enableProblemReports) this.addProblemText("WARNING: " + message);
		if (!this.debugMode) return;
		if (typeof(console) != 'undefined')
		{
			if (typeof(console.warn) != 'undefined') console.warn(message);
			else console.log("WARNING: " + message);
		}
	}

	/**
	 * Writes an error line to the console (if not supported, writes a normal log)
	 * @param message Warning message
	 */
	this.error = function(message)
	{
		if (this.enableProblemReports) this.addProblemText("ERROR: " + message);
		if (!this.debugMode) return;
		if (typeof(console) != 'undefined')
		{
			if (typeof(console.error) != 'undefined') console.error(message);
			else console.log("ERROR: " + message);
		}
	}

	/**
	 * Writes the contents of an object to the console (if not supported, writes a normal log line with the object's toString output)
	 * @param obj The object to display
	 */
	this.dir = function(obj)
	{
		if (!this.debugMode) return;
		if (typeof(console) != 'undefined')
		{
			if (typeof(console.dir) != 'undefined') console.dir(obj);
			else console.log("OBJECT: " + obj + " ID: " + obj.id + " NAME: " + obj.name);
		}
	}

	/**
	 * Perform a full stack trace from the current location
	 */
	this.stackDump = function(iLevel, bArguments)
	{
		var asStack = [];
		var iCalls = 0;
		// Walk the stack:
		for (var oCaller = arguments.callee.caller; oCaller != null; oCaller = oCaller.caller) {
			// Skip the first X calls on the stack
			if (typeof(iLevel) == "number" && iLevel-- > 0) continue;
			iCalls++;
			var bLastCall = !(oCaller.caller);
			// Convert caller function to a string and cut the function
			// body. For simplicity, I'm not taking into account inline
			// comments in the function declaration, otherwise the regular
			// expressions would become quite complex.
			sCaller = (oCaller+"").replace(/function\s*|\s*{[\s\S]*/g, "");
			// sCaller is now something like "stackDump()", let's seperate
			// the function name from the argument list:
			var asCaller = sCaller.match(/(\w*\s*)\(([^\)]*)\)/);
			// If this failed, we'll use what we have so far and not try to be
			// smart about the arguments:
			var sCallHeadHeader = " " + (bLastCall ? "\u2514":"\u251C") + "\u2500 ";
			var sCallBodyHeader = " " + (bLastCall ? " " : "\u2502") + "  ";
			if (asCaller == null) {
				asStack.push(sCallHeadHeader + visualize(sCaller));
				if (bArguments) asStack.push(sCallBodyHeader + "    Argument info unavailable.");
			} else {
				// We've split the function name from the arguments:
				var sName = asCaller[1];
				// Now let's split the individual arguments, if any:
				var asArgumentNames = asCaller[2].match(/^\s*$/) ?
					[]: asCaller[2].split(",");
				// Add the function name + argument names to the call stack:
				asStack.push(sCallHeadHeader + sName + " (" + asArgumentNames.join(", ") + ")");
				// Next we'll walk the argument names and the argument values
				// to process them and see if there are any arguments in the
				// function definition that were not supplied in the call or
				// extra arguments supplied in the call that are not in the
				// definition:
				if (bArguments) {
					for (var i = 0; i < asArgumentNames.length || i < oCaller.arguments.length; i++) {
						var sArgumentName = "arguments[" + i + "]";
						if (i < asArgumentNames.length) {
							// This argument exists in the function definition, use
							// the name rather than the number:
							asArgumentNames[i] = asArgumentNames[i].replace(/^\s*|\s*$/g, "");
							sArgumentName = asArgumentNames[i];
						}
						// We'll display each named argument from the definition,
						// even if it is not defined in the call. We'll also
						// display any arguments defined in the call that are not
						// in the function definition:
						var bLastArgument = (i + 1 >= asArgumentNames.length && i + 1 >= oCaller.arguments.length);
						var sArgumentHeadHeader = " " + (bLastArgument ? "\u2514":"\u251C") + "\u2500 ";
						var sArgumentBodyHeader = " " + (bLastArgument ? " " : "\u2502") + "  ";
						asStack.push(
							sCallBodyHeader +
							sArgumentHeadHeader +
							sArgumentName + "=" +
							visualize(oCaller.arguments[i], sCallBodyHeader + sArgumentBodyHeader)
						);
					}
				}
			}
		}
		if (iCalls == 0) return "Call stack info unavailable.";
		// Combine all information found and return it:
		this.log("Call stack (" + iCalls + " calls):\r\n" + asStack.join("\r\n"));
	}

	/**
	 * Sends a problem report to the Special Audio problem reporting server. Problem reports must be enabled.
	 * To send a problem report, call this function directly after the problem
	 */
	this.sendProblemReport = function(caption)
	{
		if (!this.enableProblemReports) return;
		var pid = (typeof(doitall) != 'undefined' ? doitall.publisherID : 0);
		if (pid == null) pid = 0;

		var theMessage = escape("PROBLEM REPORT \n\n" + caption + "\n\n" + problemText);
		Touch.touch("http://65.44.71.138/adspace/debug_log.php?pid=" + pid + "&logstring=" + theMessage);
		Lint.log("Lint: Send problem report to Spacial Audio reporting service - " + caption);
		problemText = '';
	}

	// PRIVATE VARIABLES
	var problemText = '';

	// PRIVATE METHODS
	this.addProblemText = function(message)
	{
		if (problemText.length > 2048) problemText = "..." + problemText.substring(problemText.length - 2044);
		problemText += message + "\n";
	}
}

var Lint = new TLint();
