/**
 * logging
 * a javascript logging system.
 * designed to have similiar functionality to the log4j logging package
 * credit to: http://logging.apache.org/log4j
 * NOTE: currently does not implement a RootLogger.
 */
es_util_logging = new es_lang.Package(true,
	/*Title*/   'Logging',
	/*Docs */   '-- none --',
	/*Package*/ function() {
		// Imports
		var implement = es_lang.Class.implement;
		var extend = es_lang.Class.extend;
		var enumerate = es_lang.Enum.enumerate;
		var StringBuilder = es_lang.StringBuilder;

		// Level
		var Level = enumerate({
			OFF: 8,
			ALL: 0,
			FINEST: 1,
			FINER: 2,
			FINE: 3,
			CONFIG: 4,
			INFO: 5,
			WARNING: 6,
			SEVERE: 7
		});
		Level.pick = /*Level*/ function(/*Level|String|int|Object*/ level) {
			if (!level) {
				return undefined;
			} else if (level instanceof Level) {
				return level;
			} else if (level.constructor == String) {
				return Level[level];
			} else if (parseInt(level) > 0) {
				return Level.items[level];
			} else if (level) {
				return Level[level.toString()];
			}
			return null;
		}

		// LogManager
		var LogManager = function() {
			_loggerMap = {};

			this.addLogger = function(/*Logger*/ logger) {
				var name;
				var parent;
				var parentName;
				var loggerName;
				var loggerParentName;
				var l;

				loggerName = ( logger.getName() || '' );
				loggerParentName = '';
				for (name in _loggerMap) {
					if (_loggerMap.hasOwnProperty(name)) {
						l = _loggerMap[name];

						if (loggerName.length > name.length && name.length > loggerParentName.length) {
							if (loggerName.indexOf(name + '.') == 0 || name == null || name == undefined || name.length == 0) {
								loggerParentName = name;
							}
						} else {
							parent = l.getParent();
							parentName = (parent) ? parent.getName() : '';
							if (loggerName.length < name.length && (loggerName.length > parentName.length || !parent)) {
								if (name.indexOf(loggerName) == 0) {
									l.setParent(logger);
								}
							}
						}
					}
				}
				logger.setParent(_loggerMap[loggerParentName]);
				_loggerMap[loggerName] = logger;
			}
			this.getLogger = /*Logger*/ function(/*String*/ name) {

				if (name) {
					if (_loggerMap[name]) {
						return _loggerMap[name];
					} else {
						undefined;
					}
				}
			}
		}
		LogManager.logManager = new LogManager();

		// Logger
		var Logger = implement({
				filter:							/*Filter*/	undefined,
				level:							/*Level*/		undefined,
				useParentHandlers:	/*boolean*/ true
			},
			
			function(/*String*/ name, /*Level*/ level, /*Handler*/ handler) {
				var _handlers = [];
				var _parent;
				var _name;
				var _level;

				_level = Level.pick((level) ? level : Level.WARNING);
				_name = ( name || '' );
				//_handlers.push(handler || new Handler( _level));

				function createLogRecord(level, message, throwable) {
					logRecord = new LogRecord(level, message, throwable);
					logRecord.loggerName = _name;
					logRecord.timestamp = new Date();
					return logRecord;
				}
				function dispatch(rec) {
					if (this.isLoggable(rec.level)) {
						for (var i = 0; i < _handlers.length; i++) {
							handler = _handlers[i];
							//alert('loggable: name="' + _name + '", handler: ' + handler.constructor + ' (message="' + rec.message + '")');
							handler.publish(rec);
						}
					}
					if (_parent && this.useParentHandlers) {
						_parent.logRecord(rec);
					}
				}

				// == Public ==
				this.getName = /*String*/ function() {
					return _name;
				}
				this.getParent = /*Logger*/ function() {
					return _parent;
				}
				this.setParent = function(/*Logger*/ parent) {
					_parent = parent;
				}
				this.addHandlers = function(/*Handler*/ handler) {
					_handlers.push(handler);
				}
				this.getHandlers = /*Handlers[]*/ function() {
					var handlers = new Array(_handlers.length);
					for (var i = 0; i < _handlers.length; i++) {
						handlers[i] = _handlers[i];
					}
					return handlers;
				}
				this.removeHandler = function(/*Handler*/ handler) {
					for (var i = 0; i < _handlers.length; i++) {
						if (_handlers[i] == handler) {
							_handlers = _handlers.splice(i);
							break;
						}
					}
				}
				this.isLoggable = /*boolean*/ function(/*Level*/ level) {
					return _level <= Level.pick(level);
				}
				this.addHandler = function(/*Handler*/ handler) {
					_handlers.push(handler);
				}
				this.logRecord = function(/*LogRecord*/ logRecord) {
					dispatch.call(this, logRecord);
				}
				this.setLevel = function(/*Level*/ newLevel) {
					_level = Level.pick(newLevel);
					for (var i = 0; i < _handlers.length; i++) {
						_handlers[i].level = _level;
					}
				}
				this.getLevel = function() {
					return _level.toString();
				}
				this.log = function(/*Level*/ level, /*String*/ message, /*Error|Object|Object[]*/ param) {
					var rec = createLogRecord(level, message);
					if (param) {
						if (param instanceof Error) {
							rec.thrown
						} else if (param && param.constructor == Array) {
							rec.parameters = param;
						} else {
							rec.parameters = [param];
						}
					}
				}
				this.logp = function(/*Level*/ level,
						/*String*/ sourceClass,
						/*String*/ sourceMethod,
						/*String*/ message,
						/*Error|Object|Object[]*/ param) {
					var rec = createLogRecord(level, message);
					rec.sourceClass = sourceClass;
					rec.sourceMethod = sourceMethod;
					if (param) {
						if (param instanceof Error) {
							rec.thrown
						} else if (param && param.constructor == Array) {
							rec.parameters = param;
						} else {
							rec.parameters = [param];
						}
					}
				}
				this.finest = function(/*String*/ message) {
					dispatch.call(this, createLogRecord(Level.FINEST, message), this.useParentHandlers);
				}
				this.finer = function(/*String*/ message) {
					dispatch.call(this, createLogRecord(Level.FINER, message), this.useParentHandlers);
				}
				this.fine = function(/*String*/ message) {
					dispatch.call(this, createLogRecord(Level.FINE, message), this.useParentHandlers);
				}
				this.config = function(/*String*/ message) {
					dispatch.call(this, createLogRecord(Level.CONFIG, message), this.useParentHandlers);
				}
				this.info = function(/*String*/ message) {
					dispatch.call(this, createLogRecord(Level.INFO, message), this.useParentHandlers);
				}
				this.warning = function(/*String*/ message) {
					dispatch.call(this, createLogRecord(Level.WARNING, message), this.useParentHandlers);
				}
				this.throwing = function(/*String*/ sourceClass, /*Error*/ sourceMethod, /*Error*/ throwable) {
					rec = createLogRecord(Level.FINER, message, throwable);
					rec.sourceClass = sourceClass;
					rec.sourceMethod = sourceMethod;
					dispatch.call(this, rec, this.useParentHandlers);
				}
				this.severe = function(/*String*/ message) {
					dispatch.call(this, createLogRecord(Level.SEVERE, message), this.useParentHandlers);
				}
			}
		);
		Logger.getLogger = function(/*String*/ name) {
			var logger;
			var logmgr = LogManager.logManager;

			if (!(logger = logmgr.getLogger(name))) {
				logger = new Logger(name);
				logmgr.addLogger(logger);
			}
			return logger;
		};

		// Filter
		var Filter = function() {
			this.isLoggable = /*boolean*/ function(/*LogRecord*/ logRecord) {
				return true;
			}
		};

		// LogRecord
		var LogRecord = implement({
				level:				/*Level*/			undefined,
				loggerName:		/*String*/		undefined,
				message:			/*String*/		undefined,
				parameters:		/*Object[]*/	undefined,
				sequenceno:		/*int*/				undefined,
				sourceClass:	/*String*/		undefined,
				sourceMethod:	/*String*/		undefined,
				thrown:				/*Error*/			undefined
			},
			function( /*Level*/			level,
								/*String*/		message,
								/*Error*/ 		error,
								/*String*/    loggerName) {
				this.level = Level.pick(level);
				this.message = message;
				this.thrown = error;
				this.loggerName = loggerName;
				this.millis = (new Date()).getTime();
			}
		);

		// Formatter -- formats log records for display
		var Formatter = function(logTime, logDate, logName) {
			// == Public ==
			function padl(value, length) {
				var str = value.toString();
				while (str.length < length) {
					str = '0' + str;
				}
				return str;
			}
			this.format = /*String*/ function(/*LogRecord*/ logRecord) {
				var err;
				var date = new Date(logRecord.millis);
				var buffer = new StringBuilder();

				if (logDate) {
					buffer.append(date.getFullYear() + '-' + padl(date.getMonth() + 1, 2) + '-' + padl(date.getDate(), 2));
				}
				if (logTime) {
					if (buffer.length() > 0) buffer.append(' ');
					buffer.append(padl(date.getHours(), 2) + ':' + padl(date.getMinutes(), 2) + ':' + padl(date.getSeconds(), 2));
				}
				if (logName) {
					if (buffer.length() > 0) buffer.append(' ');
					buffer.append(logRecord.loggerName);
				}
				if (err = logRecord.thrown) {
					if (buffer.length() > 0) buffer.append(' -- ');
					buffer.append('Error: ' + err + ' thrown (' + err.message + ')');
				} else {
					if (buffer.length() > 0) buffer.append(' -- ');
				}
				if (buffer.length() > 0) buffer.append('\r\n');
				if (logRecord.message) buffer.append(logRecord.message);
				if (logRecord.sequenceno) buffer.append("#" + logRecord.sequenceno);
				if (logRecord.sourceClass) buffer.append(" :: class: " + logRecord.sourceClass);
				if (logRecord.sourceMethod) buffer.append("." + logRecord.sourceMethod);
				if (logRecord.parameters && parameters.constructor == Array) {
					buffer.append('[');
					for (var i = 0; i < logRecord.parameters.length; i++) {
						if (i > 0) buffer.append(',');
						if (logRecord.parameters[i]) {
							buffer.append(logRecord.parameters[i].toString());
						} else {
							buffer.append(logRecord.parameters[i]);
						}
					}
					buffer.append(']');
				}

				return buffer.toString();
			}
			this.formatMessage = function(/*String*/ message) {
				return message;
			}
		};

		// Handler
		var Handler = implement({
				filter:     /*Filter*/		undefined,
				formatter:	/*Formatter*/ new Formatter()
			},
			function(/*Level*/ newLevel) {

				// == Public ==
				this.level = newLevel;
				this.isLoggable = /*boolean*/ function(/*LogRecord*/ rec) {
					if (this.level > rec.level) {
						return false;
					} else {
						return true;
					}
				}
				this.publish = function(/*LogRecord*/ rec) {
					if (this.isLoggable(rec)) {
						alert(this.formatter.format(rec));		// The default handler creates an alert box.
					}
				}
			}
		);

		// ****** Textarea Log Handler ******
		var TextareaHandler = extend(Handler,
			function(textarea, logTime, logDate, logName) {
				var _formatter = new Formatter(logTime, logDate, logName);

				if (!textarea) {
					alert("The textarea was undefined");
				} else if (textarea.tagName.toLowerCase() != 'textarea') {
					alert(textarea + " does not point to a TEXTAREA element, but instead points to a " + textarea.tagName + " element");
				}

				Handler.call(this);

				this.name = name;
				this.publish = function(/*LogRecord*/ logRecord) {
					var level;
					var message; 
					var entries = [];

					if (this.isLoggable(logRecord)) {
						textarea.value += '\r\n';
						textarea.value += _formatter.format(logRecord);
					}
				}
			}
		);

		// === PACKAGE PUBLIC ===
		this.LogManager = LogManager;
		this.Logger = Logger;
		this.Level = Level;
		this.Handler = Handler;
		this.Formatter = Formatter;
		this.TextareaHandler = TextareaHandler;
	}
);











