var global = this;
if (this.API && typeof this.API == 'object' && this.API.isOwnProperty) {
	(function() {
		var api = global.API;
		var isOwnProperty = api.isOwnProperty;
		var isRealObjectProperty = api.isRealObjectProperty;
		var isEmptyTextNode = api.isEmptyTextNode;
		var getElementNodeName = api.getElementNodeName;
		var setElementScrollPositionBottom;
		var aSkippedMessages = [];
		var log, bDebug, elConsole, objConsole, bSkipCallAdvice, methods = {}, registeredAdvisors = {}, elementTraversal;

		function applyRegisteredAdvisors(advisors) {
			var l = advisors.length;

			while (l--) {
				if (registeredAdvisors[advisors[l]]) {
					registeredAdvisors[advisors[l]]();
				}
				else {
					api.warn('Advisor not found: ' + advisors[l]);
				}
			}
		}

		api.registerAdvisor = function(name, fn) {
			if (registeredAdvisors[name]) {
				if (registeredAdvisors[name] == fn) {
					api.warn('Duplicate advisor registration: ' + name);
				}
				else {
					api.warn('Advisor registration overwritten: ' + name);
				}
			}
			else {
				registeredAdvisors[name] = fn;
				api.log('Advisor registered: ' + name);
			}
		};

		function applyDebugAdvisors(o, b, sName) {
			var i;

			function convertArguments(args) {
				var l = args.length, a = [];
				while (l--) { if (typeof args[l] != 'undefined' || a.length) { a[l] = args[l]; } }
				return a;
			}

			for (i in o) {
				if (isOwnProperty(o, i)) {
					if (i == 'log') { continue; }
					if (typeof(o[i]) == 'function') {
						if (b) {
							methods[i] = o[i];
							o[i] = (function(n, m) {
								return function() {
									var a = convertArguments(arguments);
									var b = [];
									b.length = a.length;
									if (!bSkipCallAdvice) {
										for (var i = a.length; i--;) {
											if (typeof a[i] == 'string') {
												b[i] = ["'", a[i].replace("'", "\\'"), "'"].join('');
											} else if (Object.prototype.toString.call(a[i]) == '[object Array]') {
												b[i] = ['[', a[i], ']'].join('');
											} else {
												b[i] = a[i];
											}
										}
										log('Call: ' + n + '(' + b.join(', ') + ')');
									}
									return m.apply(o, a);
								};
							})(i, o[i]);
						}
						else {
							o[i] = methods[i];
						}
					}
					else {
						if (isRealObjectProperty(o, i) && i != 'attachedListeners' && i != 'eventContexts' && !o[i].dontdebug) {
							applyDebugAdvisors(o[i], b);
						}
					}
				}
			}
		}

		if (Function.prototype.apply) {
			api.debug = function(b, advisors, bSkipCalls) {
				if (b != bDebug) {
					bDebug = b;
					bSkipCallAdvice = true;
					applyDebugAdvisors(api, b);
					bSkipCallAdvice = !!bSkipCalls;
					if (advisors) {
						if (b) {
							applyRegisteredAdvisors(advisors);
						}
						else {
							api.warn('Advisors may only be specified when enabling debugging');
						}
					}
					log('Debugging ' + ((b)?'enabled':'disabled'));
				}
				else {
					api.warn('Debugging is already ' + (bDebug)?'on':'off');
				}
			};

			api.toggleDebug = function(advisors) {
				api.debug(!bDebug, !bDebug && advisors);
			};
		}

		var logToElement = (function() {
			if (api.addElementHTML) {
				return function(el, msg, msgHTML) { api.addElementHTML(el, ((msgHTML)?msgHTML:msg) + '<br>'); };
			}
			if (api.addElementText) {
				return function(el, msg) { api.addElementText(el, msg + '\r\n'); };
			}
		})();

		function logToElementSmart(msg, msgHTML) {
			var bOldSkipAdvice;
			if (elConsole) {
				bOldSkipAdvice = bSkipCallAdvice;
				bSkipCallAdvice = true;
				if (getElementNodeName(elConsole) == 'textarea') {
					elConsole.value += ((elConsole.value)?'\r\n':'') + msg;
				}
				else {
					if (logToElement) { logToElement(elConsole, msg, msgHTML); }
				}
				if (setElementScrollPositionBottom) { setElementScrollPositionBottom(elConsole); }
				bSkipCallAdvice = bOldSkipAdvice;
			}
			else {
				aSkippedMessages[aSkippedMessages.length] = {text:msg, html:msgHTML};
			}
		}

		function catchUpMessages() {
			var l;
			aSkippedMessages = aSkippedMessages.reverse();
			l = aSkippedMessages.length;
			while (l--) { logToElementSmart(aSkippedMessages[l].text, aSkippedMessages[l].html); }
			aSkippedMessages = [];
		}

		api.setConsoleElement = function(el) {
			elConsole = el;
			if (aSkippedMessages.length) { catchUpMessages(); }
			setElementScrollPositionBottom = api.setElementScrollPositionBottom;
		};

		if (isRealObjectProperty(this, 'console') && api.isHostMethod(this.console, 'log')) {
			objConsole = global.console;
		}

		api.clearLog = function() {			
			if (elConsole) {
				if (getElementNodeName(elConsole) == 'textarea') {
					elConsole.value = '';
				}
				else {
					api.setElementText(elConsole, '');
				}
			}
		};

		log = api.log = function(msg, msgHTML, bNoConsole) {
			if (objConsole && !bNoConsole) { objConsole.log(msg + ''); }
			if (elConsole && aSkippedMessages.length) { catchUpMessages(); }
			logToElementSmart(msg, msgHTML);
		};

		api.warn = function(msg) {
			var bNoConsole;
			if (objConsole && api.isHostMethod(objConsole, 'warn')) {
				objConsole.warn(msg);
				bNoConsole = true;
			}
			log('WARNING: ' + msg, null, bNoConsole);
		};

		api.error = function(msg) {
			var bNoConsole;
			if (objConsole && api.isHostMethod(objConsole, 'error')) {
				objConsole.error(msg);
				bNoConsole = true;
			}
			log('ERROR: ' + msg, null, bNoConsole);
		};

		api.properties = function(o, bAll) {
			o = o || api;
			var i;
			var a = [];
			for (i in o) { if (typeof(o[i]) != 'undefined' && (bAll || isOwnProperty(o, i))) { a[a.length] = i; } }
			if (a.sort) { a.sort(); }
			return a;
		};

		var runUnitTest = api.runUnitTest = function(test) {
			var expected = test.expected;
			if (typeof expected == 'undefined') {
				expected = true;
			}
			var result = test.run() === expected;
			log(test.name + ': ' + (result ? 'success' : 'failure'));
			return result;
		};

		api.runUnitTests = function(tests) {
			var succeeded = 0;
			var startTime = +(new Date());
			var l= tests.length, i = l;
			tests = tests.slice(0).reverse();
			if (i) {
				log('Running ' + i + ' test' + (i == 1 ? '' : 's'));
				while (i--) {
					runUnitTest(tests[i]) && succeeded++;
				}
				var elapsedTime = +(new Date()) - startTime;
				log(succeeded + ' of ' + l + ' succeeded (' + ((succeeded / l) * 100) + '%)');
				log('Time: ' + elapsedTime);
			}
		};

                if (api.getHtmlElement && api.getHtmlElement()) {
			elementTraversal = (function() {
				var pad = '                                                      ';
				var a;
				var nn;
				function add(text, depth) {
					a[a.length] = pad.substring(0, depth - 1) + text;
				}
				return function(el, depth) {
					if (!depth) { a = []; }
					depth = depth || 1;
					switch (el.nodeType) {
						case 3:
							add('[' + (isEmptyTextNode(el)?'whitespace':'text') + ']', depth);
							break;
						case 4:
							add('[cdata]', depth);
							break;
						case 8:
							add('[comment]', depth);
							break;
						case 10:
							add('[doctype]', depth);
							break;
						case 9:
							add('[document]', depth);
						default:
							if (el.nodeType == 1) {
								nn = getElementNodeName(el);
								if (nn) {
									if (nn == '!') {
										add('[comment]', depth);
									}
									else {
										add(nn + ((el.id)?'(' + el.id + ')':''), depth);
									}
								}
							}
							else {
								if (!el.nodeType && !el.tagName) { // IE5 document.firstChild is undefined
									el = api.getHtmlElement(el);
								}
								if (!el) { return a; } else if (el.nodeType != 9) { add('html'); }
							}
							if (el.firstChild) {
								elementTraversal(el.firstChild, ++depth);
								--depth;
							}
					}
					if (el.nextSibling) { elementTraversal(el.nextSibling, depth); }
					return a;
				};
			})();

			api.logElementTraversal = function(el) {
				log(elementTraversal(el).join('\n')); 
			};
		}
	})();
}


