/**
 * Copyright 2015 Tim Down.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

if (!Array.prototype.shift) {
	Array.prototype.shift = function() {
		if (this.length > 0) {
			var firstItem = this[0];
			for (var i = 0, len = this.length - 1; i < len; i++) {
				this[i] = this[i + 1];
			}
			this.length--;
			return firstItem;
		}
	};
}

var log4javascript;

(function() {
	var newLine = "\r\n";
	function Log4JavaScript() {}
	log4javascript = new Log4JavaScript();
	log4javascript.version = "1.4.13";
	log4javascript.edition = "log4javascript_lite";

	function getExceptionMessage(ex) {
		if (ex.message) {
			return ex.message;
		} else if (ex.description) {
			return ex.description;
		} else {
			return String(ex);
		}
	}

	// Gets the portion of the URL after the last slash
	function getUrlFileName(url) {
		var lastSlashIndex = Math.max(url.lastIndexOf("/"), url.lastIndexOf("\\"));
		return url.substr(lastSlashIndex + 1);
	}

	// Returns a nicely formatted representation of an error
	function getExceptionStringRep(ex) {
		if (ex) {
			var exStr = "Exception: " + getExceptionMessage(ex);
			try {
				if (ex.lineNumber) {
					exStr += " on line number " + ex.lineNumber;
				}
				if (ex.fileName) {
					exStr += " in file " + getUrlFileName(ex.fileName);
				}
			} catch (localEx) {
			}
			if (showStackTraces && ex.stack) {
				exStr += newLine + "Stack trace:" + newLine + ex.stack;
			}
			return exStr;
		}
		return null;
	}

	function isError(err) {
		return (err instanceof Error);
	}

	function bool(obj) {
		return Boolean(obj);
	}

	var enabled = (typeof log4javascript_disabled != "undefined") &&
		log4javascript_disabled ? false : true;

	log4javascript.setEnabled = function(enable) {
		enabled = bool(enable);
	};

	log4javascript.isEnabled = function() {
		return enabled;
	};

	var showStackTraces = false;

	log4javascript.setShowStackTraces = function(show) {
		showStackTraces = bool(show);
	};

	/* ---------------------------------------------------------------------- */
	// Levels

	var Level = function(level, name) {
		this.level = level;
		this.name = name;
	};

	Level.prototype = {
		toString: function() {
			return this.name;
		},
		equals: function(level) {
			return this.level == level.level;
		},
		isGreaterOrEqual: function(level) {
			return this.level >= level.level;
		}
	};

	Level.ALL = new Level(Number.MIN_VALUE, "ALL");
	Level.TRACE = new Level(10000, "TRACE");
	Level.DEBUG = new Level(20000, "DEBUG");
	Level.INFO = new Level(30000, "INFO");
	Level.WARN = new Level(40000, "WARN");
	Level.ERROR = new Level(50000, "ERROR");
	Level.FATAL = new Level(60000, "FATAL");
	Level.OFF = new Level(Number.MAX_VALUE, "OFF");

	log4javascript.Level = Level;

	/* ---------------------------------------------------------------------- */
	// Appenders

	function Appender() {
		var getConsoleHtmlLines = function() {
			return [
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
'<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">',
'	<head>',
'		<title>log4javascript</title>',
'		<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />',
'		<!-- Make IE8 behave like IE7, having gone to all the trouble of making IE work -->',
'		<meta http-equiv="X-UA-Compatible" content="IE=7" />',
'		<script type="text/javascript">',
'			//<![CDATA[',
'			var loggingEnabled = true;',
'			var messagesBeforeDocLoaded = [];',
'',
'			function toggleLoggingEnabled() {',
'				setLoggingEnabled($("enableLogging").checked);',
'			}',
'',
'			function setLoggingEnabled(enable) {',
'				loggingEnabled = enable;',
'			}',
'',
'			function scrollToLatestEntry() {',
'				var l = getLogContainer();',
'				if (typeof l.scrollTop != "undefined") {',
'					var latestLogEntry = l.lastChild;',
'					if (latestLogEntry) {',
'						l.scrollTop = l.scrollHeight;',
'					}',
'				}',
'			}',
'',
'			function log(logLevel, formattedMessage) {',
'				if (loggingEnabled) {',
'					if (loaded) {',
'						doLog(logLevel, formattedMessage);',
'					} else {',
'						messagesBeforeDocLoaded.push([logLevel, formattedMessage]);',
'					}',
'				}',
'			}',
'',
'			function doLog(logLevel, formattedMessage) {',
'				var logEntry = document.createElement("div");',
'				logEntry.appendChild(document.createTextNode(formattedMessage));',
'				logEntry.className = "logentry " + logLevel.name;',
'				getLogContainer().appendChild(logEntry);',
'				scrollToLatestEntry();',
'			}',
'',
'			function mainPageReloaded() {',
'				var separator = document.createElement("div");',
'				separator.className = "separator";',
'				separator.innerHTML = "&nbsp;";',
'				getLogContainer().appendChild(separator);',
'			}',
'',
'			var loaded = false;',
'			var logLevels = ["DEBUG", "INFO", "WARN", "ERROR", "FATAL"];',
'',
'			window.onload = function() {',
'				setLogContainerHeight();',
'				toggleLoggingEnabled();',
'				for (var i = 0; i < messagesBeforeDocLoaded.length; i++) {',
'					doLog(messagesBeforeDocLoaded[i][0], messagesBeforeDocLoaded[i][1]);',
'				}',
'				messagesBeforeDocLoaded = [];',
'				loaded = true;',
'',
'				// Workaround to make sure log div starts at the correct size',
'				setTimeout(setLogContainerHeight, 20);',
'			};',
'',
'			function getLogContainer() {',
'				return $("log");',
'			}',
'',
'			function clearLog() {',
'				getLogContainer().innerHTML = "";',
'			}',
'',
'			/* ------------------------------------------------------------------------- */',
'',
'			// Other utility functions',
'',
'			// Syntax borrowed from Prototype library',
'			function $(id) {',
'				return document.getElementById(id);',
'			}',
'',
'			function getWindowHeight() {',
'				if (window.innerHeight) {',
'					return window.innerHeight;',
'				} else if (document.documentElement && document.documentElement.clientHeight) {',
'					return document.documentElement.clientHeight;',
'				} else if (document.body) {',
'					return document.body.clientHeight;',
'				}',
'				return 0;',
'			}',
'',
'			function getChromeHeight() {',
'				return $("toolbar").offsetHeight;',
'			}',
'',
'			function setLogContainerHeight() {',
'				var windowHeight = getWindowHeight();',
'				$("body").style.height = getWindowHeight() + "px";',
'				getLogContainer().style.height = "" +',
'					Math.max(0, windowHeight - getChromeHeight()) + "px";',
'			}',
'',
'			window.onresize = function() {',
'				setLogContainerHeight();',
'			};',
'',
'			//]]>',
'		</script>',
'		<style type="text/css">',
'			body {',
'				background-color: white;',
'				color: black;',
'				padding: 0;',
'				margin: 0;',
'				font-family: tahoma, verdana, arial, helvetica, sans-serif;',
'				overflow: hidden;',
'			}',
'',
'			div#toolbar {',
'				border-top: solid #ffffff 1px;',
'				border-bottom: solid #aca899 1px;',
'				background-color: #f1efe7;',
'				padding: 3px 5px;',
'				font-size: 68.75%;',
'			}',
'',
'			div#toolbar input.button {',
'				padding: 0 5px;',
'				font-size: 100%;',
'			}',
'',
'			div#log {',
'				font-family: Courier New, Courier;',
'				font-size: 75%;',
'				width: 100%;',
'				overflow: auto;',
'				clear: both;',
'			}',
'',
'			*.logentry {',
'				overflow: visible;',
'				white-space: pre;',
'			}',
'',
'			*.TRACE {',
'				color: #666666;',
'			}',
'',
'			*.DEBUG {',
'				color: green;',
'			}',
'',
'			*.INFO {',
'				color: #000099;',
'			}',
'',
'			*.WARN {',
'				color: #999900;',
'			}',
'',
'			*.ERROR {',
'				color: red;',
'			}',
'',
'			*.FATAL {',
'				color: #660066;',
'			}',
'',
'			div#log div.separator {',
'				background-color: #cccccc;',
'				margin: 5px 0;',
'				line-height: 1px;',
'			}',
'		</style>',
'	</head>',
'',
'	<body id="body">',
'		<div id="toolbar">',
'			Options:',
'			<input type="checkbox" id="enableLogging" onclick="toggleLoggingEnabled()" class="stateful" checked="checked" title="Enable/disable logging" /><label for="enableLogging" id="enableLoggingLabel">Enable logging</label>',
'			<input type="button" id="clearButton" value="Clear" onclick="clearLog()" class="stateful button" title="Clear all log messages"  />',
'			<input type="button" id="closeButton" value="Close" onclick="window.close()" class="stateful button" title="Close the window" />',
'		</div>',
'		<div id="log" class="TRACE DEBUG INFO WARN ERROR FATAL"></div>',
'	</body>',
'</html>'
];
		};

		var popUp = null;
		var popUpsBlocked = false;
		var popUpClosed = false;
		var popUpLoaded = false;
		var complainAboutPopUpBlocking = true;
		var initialized = false;
		var isSupported = true;
		var width = 600;
		var height = 400;
		var focusPopUp = false;
		var queuedLoggingEvents = new Array();

		function isLoaded(win) {
			try {
				return bool(win.loaded);
			} catch (ex) {
				return false;
			}
		}

		function finalInit() {
			popUpLoaded = true;
			appendQueuedLoggingEvents();
		}

		function writeHtml(doc) {
			var lines = getConsoleHtmlLines();
			doc.open();
			for (var i = 0, len = lines.length; i < len; i++) {
				doc.writeln(lines[i]);
			}
			doc.close();
		}

		function pollConsoleWindow() {
			function pollConsoleWindowLoaded() {
				if (popUpLoaded) {
					clearInterval(poll);
				} else if (bool(popUp) && isLoaded(popUp)) {
					clearInterval(poll);
					finalInit();
				}
			}

			// Poll the pop-up since the onload event is not reliable
			var poll = setInterval(pollConsoleWindowLoaded, 100);
		}

		function init() {
			var windowProperties = "width=" + width + ",height=" + height + ",status,resizable";
			var windowName = "log4javascriptLitePopUp" + location.host.replace(/[^a-z0-9]/gi, "_");

			popUp = window.open("", windowName, windowProperties);
			popUpClosed = false;
			if (popUp) {
				if (isLoaded(popUp)) {
					popUp.mainPageReloaded();
					finalInit();
				} else {
					writeHtml(popUp.document);

					// Check if the pop-up window object is available
					if (isLoaded(popUp)) {
						finalInit();
					} else {
						pollConsoleWindow();
					}
				}
			} else {
				isSupported = false;
				if (complainAboutPopUpBlocking) {
					alert("log4javascript: pop-up windows appear to be blocked. Please unblock them to use pop-up logging.");
				}
			}
			initialized = true;
		}

		function safeToAppend() {
			if (!popUpsBlocked && !popUpClosed) {
				if (popUp.closed) {
					popUpClosed = true;
					return false;
				}
				if (!popUpLoaded && popUp.loaded) {
					popUpLoaded = true;
				}
			}
			return !popUpsBlocked && popUpLoaded && !popUpClosed;
		}

		function padWithZeroes(num, len) {
			var str = "" + num;
			while (str.length < len) {
				str = "0" + str;
			}
			return str;
		}

		function padWithSpaces(str, len) {
			while (str.length < len) {
				str += " ";
			}
			return str;
		}

		this.append = function(loggingEvent) {
			if (!initialized) {
				init();
			}
			queuedLoggingEvents.push(loggingEvent);
			if (safeToAppend()) {
				appendQueuedLoggingEvents();
			}
		};

		function appendQueuedLoggingEvents() {
			if (safeToAppend()) {
				while (queuedLoggingEvents.length > 0) {
					var currentLoggingEvent = queuedLoggingEvents.shift();
					var date = currentLoggingEvent.timeStamp;
					var formattedDate = padWithZeroes(date.getHours(), 2) + ":" +
						padWithZeroes(date.getMinutes(), 2) + ":" + padWithZeroes(date.getSeconds(), 2);
					var formattedMessage = formattedDate + " " + padWithSpaces(currentLoggingEvent.level.name, 5) +
						" - " + currentLoggingEvent.getCombinedMessages();
					var throwableStringRep = currentLoggingEvent.getThrowableStrRep();
					if (throwableStringRep) {
						formattedMessage += newLine + throwableStringRep;
					}
					popUp.log(currentLoggingEvent.level, formattedMessage);
				}
				if (focusPopUp) {
					popUp.focus();
				}
			}
		}
	}

	log4javascript.Appender = Appender;

	/* ---------------------------------------------------------------------- */
	// Loggers

	function Logger() {
		var appender = new Appender();
		var loggerLevel = Level.ALL;

		this.log = function(level, params) {
			if (enabled && level.isGreaterOrEqual(this.getLevel())) {
				// Check whether last param is an exception
				var exception;
				var finalParamIndex = params.length - 1;
				var lastParam = params[params.length - 1];
				if (params.length > 1 && isError(lastParam)) {
					exception = lastParam;
					finalParamIndex--;
				}

				// Construct genuine array for the params
				var messages = [];
				for (var i = 0; i <= finalParamIndex; i++) {
					messages[i] = params[i];
				}

				var loggingEvent = new LoggingEvent(
					this, new Date(), level, messages, exception);

				appender.append(loggingEvent);
			}
		};

		this.setLevel = function(level) {
			loggerLevel = level;
		};

		this.getLevel = function() {
			return loggerLevel;
		};
	}

	Logger.prototype = {
		trace: function() {
			this.log(Level.TRACE, arguments);
		},

		debug: function() {
			this.log(Level.DEBUG, arguments);
		},

		info: function() {
			this.log(Level.INFO, arguments);
		},

		warn: function() {
			this.log(Level.WARN, arguments);
		},

		error: function() {
			this.log(Level.ERROR, arguments);
		},

		fatal: function() {
			this.log(Level.FATAL, arguments);
		},

		isEnabledFor: function(level) {
			return level.isGreaterOrEqual(this.getLevel());
		},

		isTraceEnabled: function() {
			return this.isEnabledFor(Level.TRACE);
		},

		isDebugEnabled: function() {
			return this.isEnabledFor(Level.DEBUG);
		},

		isInfoEnabled: function() {
			return this.isEnabledFor(Level.INFO);
		},

		isWarnEnabled: function() {
			return this.isEnabledFor(Level.WARN);
		},

		isErrorEnabled: function() {
			return this.isEnabledFor(Level.ERROR);
		},

		isFatalEnabled: function() {
			return this.isEnabledFor(Level.FATAL);
		}
	};

	/* ---------------------------------------------------------------------- */
	// Logger access methods

	var defaultLogger = null;
	log4javascript.getDefaultLogger = function() {
		if (!defaultLogger) {
			defaultLogger = new Logger();
		}
		return defaultLogger;
	};

	log4javascript.getLogger = log4javascript.getDefaultLogger;

	var nullLogger = null;
	log4javascript.getNullLogger = function() {
		if (!nullLogger) {
			nullLogger = new Logger();
			nullLogger.setLevel(Level.OFF);
		}
		return nullLogger;
	};

	/* ---------------------------------------------------------------------- */
	// Logging events

	var LoggingEvent = function(logger, timeStamp, level, messages,
			exception) {
		this.logger = logger;
		this.timeStamp = timeStamp;
		this.level = level;
		this.messages = messages;
		this.exception = exception;
	};

	LoggingEvent.prototype = {
		getThrowableStrRep: function() {
			return this.exception ?
				getExceptionStringRep(this.exception) : "";
		},

		getCombinedMessages: function() {
			return (this.messages.length === 1) ? this.messages[0] :
				   this.messages.join(newLine);
		}
	};

	log4javascript.LoggingEvent = LoggingEvent;

	// Ensure that the log4javascript object is available in the window. This
	// is necessary for log4javascript to be available in IE if loaded using
	// Dojo's module system
	window.log4javascript = log4javascript;
})();