/** Logger for js. Support local and remote logging. */
		
var log4js = {
	/** Define level and level priority */
	levels: {
		debug: 1,
		info: 2,
		warn: 3,
		error: 4,
		fatal: 5,
		disable: 6
	},
	
	/** Extra context data that can be added on each message */
	context: '',
	
	/** List of all loggers */
	loggers: {},
	
	/** List of all appenders */
	appenders: {},
	
	/** Format an exception in a readable way */
	formatException: function(exception) {
		var result;
		if (exception.name) {
			result = exception.name+" - "+exception.message;
			
			if (exception.stack) {
				result += " at "+exception.stack;
			}
		} else {
			result = exception;
		}
		return result;
	},
	
	/** Format for timestamps */
	timeFormat:  new Template('#{day}/#{month}/#{year} #{hours}:#{minutes}:#{seconds}.#{milliseconds} #{offset}UTC'),
	
	/** Return logger of given name */
	getLogger: function(loggerName) {
		if (!loggerName) {
			return this.getLogger("Main");
		}
		
		if (!this.loggers[loggerName]) {
			this.loggers[loggerName] = new this.Logger(loggerName);
		}
		
		return this.loggers[loggerName];
	},
	
	/** Default properties */
	properties: {
		levels: {
			root: 'debug'
		},
		
		appenders: {
			ConsoleAppender: {
				className: 'log4js.ConsoleAppender',
				format: '#{time} [#{level}] [#{url}-#{loggerName}-#{context}] #{message}',
				divId: 'loggerConsole'
			}
		}
	},
	
	/** Logger class */
	Logger: Class.create({
		loggerName: null,
		messages: null,
		level: null,
		
		initialize: function (loggerName) {
			this.loggerName = loggerName;
			this.messages = [];
			
			// Get level or use root level
			log4js.loggers[loggerName] = this;
			if (log4js.properties.levels[loggerName]) {
				this.level = log4js.properties[loggerName];
			} else {
				this.level = log4js.properties.levels.root;
			}
			if (!this.level) {this.level = 'debug';}
			// convert level to integer
			this.level = log4js.levels[this.level];
			
			
		},
		
		isDebugEnabled: function (){
			return this.level <= log4js.levels.debug;
		},
		
		isInfoEnabled: function (){
			return this.level <= log4js.levels.info;
		},
		
		isWarnEnabled: function (){
			return this.level <= log4js.levels.warn;
		},
		
		isErrorEnabled: function (){
			return this.level <= log4js.levels.error;
		},
		
		isFatalEnabled: function (){
			return this.level <= log4js.levels.fatal;
		},
		
		debug: function (message) {
			if (this.level <= log4js.levels.debug) {
				log4js.log(this.loggerName,"DEBUG", message);
			}
		},
		
		info: function (message) {
			if (this.level <= log4js.levels.info) {
				log4js.log(this.loggerName,"INFO", message);
			}
		},
		
		warn: function (message) {
			if (this.level <= log4js.levels.warn) {
				log4js.log(this.loggerName,"WARN", message);
			}
		},
		
		error: function (message, exception) {
			if (this.level <= log4js.levels.error) {
				if (exception) {
					message+=" : "+log4js.formatException(exception);
				}
				log4js.log(this.loggerName,"ERROR", message);
			}
		},
		
		fatal: function (message) {
			if (this.level <= log4js.levels.fatal) {
				log4js.log(this.loggerName,"FATAL", message);
			}
		},
		
		Tracer: function(logger,funcName, func) {
			return function() {
				var args = $A(arguments).map(function (arg) {
					return Object.inspect(arg);
				});
				logger.debug('>> '+funcName+'('+args.join(',')+')');
				var result = func.apply(this,arguments);
				logger.debug('<< '+funcName+'('+args.join(',')+') = '+Object.inspect(result));
				return result;
			}
		},
		
		trace: function(object) {
			for (var attribute in object) {
				if (Object.isFunction(object[attribute])) {
					object[attribute] = new this.Tracer(this,attribute,object[attribute]);
				}
			}
		}
	}),
	
	/** Appender class */
	Appender: Class.create({
		format: new Template('#{time} [#{level}] [#{url}-#{loggerName}-#{context}] #{message}'),
		include: null,
		exclude: null,
		
		initialize: function(parameters) {
			if (parameters.format) {this.format = new Template(parameters.format);}
			this.include = parameters.include;
			this.exclude = parameters.exclude;
		},
		
		logMessage: function(message) {
			
			if (this.include && this.include.indexOf(message.loggerName)==-1) {
				return;
			}
			
			if (this.exclude && this.exclude.indexOf(message.loggerName)>=0) {
				return;
			}
			this.appendMessage(this.format.evaluate(message));
		},
		
		appendMessage: function(message) {
		}
	}),
	
	/** Dispatch a message to appenders */
	log: function (loggerName, level, message) {
		var now = new Date();
		now.setTime(now.getTime() + 7200000);
		var dateElements = {
			day: now.getUTCDate()<10 ? "0"+now.getUTCDate() : now.getUTCDate(),
			month: now.getUTCMonth()<10 ? "0"+(now.getUTCMonth()+1) : (now.getUTCMonth()+1),
			year: now.getUTCFullYear().toString().substr(2,2),
			hours: now.getUTCHours()<10 ? "0"+now.getUTCHours() : now.getUTCHours(),
			minutes: now.getUTCMinutes()<10 ? "0"+now.getUTCMinutes() : now.getUTCMinutes(),
			seconds: now.getUTCSeconds()<10 ? "0"+now.getUTCSeconds() : now.getUTCSeconds(),
			milliseconds: now.getUTCMilliseconds()<100 ? now.getUTCMilliseconds()<10 ? "00"+now.getUTCMilliseconds() : "0"+now.getUTCMilliseconds() : now.getUTCMilliseconds(),
			offset: now.getTimezoneOffset()>0 ? "-"+now.getTimezoneOffset()/60 : "+"+(-now.getTimezoneOffset()/60)}
		
		var messageObject = {
			time: this.timeFormat.evaluate(dateElements),
			level: level,
			loggerName: loggerName,
			message: message+"",
			url: document.location.pathname,
			context: this.context
		};
		
		for (var appenderName in this.appenders) {
			this.appenders[appenderName].logMessage(messageObject);
		}
	},
	
	log2: function(videoId, level, message) {
		logger.log(level,videoId+" : "+message);
	},
	
	
	/** Load configuration or use default */
	loadConfiguration: function(configuration) {
		if (configuration) {
			this.properties = configuration;
		}
		for (var appenderName in this.properties.appenders) {
			var appenderAttributes = this.properties.appenders[appenderName];
			var appenderClass = eval('window.'+appenderAttributes.className);
			this.appenders[appenderName]=new appenderClass(appenderAttributes);
		}
		
		Event.observe(window, 'load', this.traceObjects.bind(this));
	},
	
	traceObjects: function() {
		if (this.properties.trace) {
			for (var i = 0 ; i<this.properties.trace.length; i++) {
				try {
					var object = eval("window."+this.properties.trace[i]);
					
					if (object) {
						this.trace(object, this.properties.trace[i]);
					}
				} catch (e) {}
			}
		}
	},
	
	trace: function(object, loggerName) {
		var logger = log4js.getLogger(loggerName);
		object._logger = logger;
		logger.trace(object);
		logger.trace(object.prototype);
	},
	
	Parachute: function(logger, func) {
		return function() {
			try {
				return func.apply(this,arguments);
			} catch (e) {
				logger.error('An unexpected error occured',e);
			}
		};	
	},
	
	catchAllExceptions: function(logger, func) {
		return new this.Parachute(logger, func);
	}
}
	
/** Console Appender */
log4js.ConsoleAppender = Class.create(log4js.Appender, {
	divId: 'loggerConsole',
	console: null,
	messages: null,
	
	initialize: function($super,parameters) {
		$super(parameters);
		if (parameters.divId) {this.divId = parameters.divId;}
		
		Event.observe(window, 'load', this.createConsole.bind(this));
		this.messages = [];
	},
	
	createConsole: function() {
		this.console = $(this.divId);
		
		if (!this.console && document.body) {
			this.console = document.createElement("div");
			this.console.setAttribute("id", this.divId);
			document.body.appendChild(this.console);
		}
		
		for (var i = 0 ; i < this.messages.length ; i++) {
			this.appendMessageToConsole(this.messages[i]);
		}
		this.messages = [];
	},
	
	appendMessage: function(message) {
		if (this.console) {
			this.appendMessageToConsole(message);
		} else {
			this.messages.push(message);
		}
	},
	
	appendMessageToConsole: function(message) {
		var messageElement = document.createTextNode(message);
		this.console.appendChild(messageElement);
		this.console.appendChild(document.createElement('br'));
	}
}),

/** Console Appender */
log4js.PopUpAppender = Class.create(log4js.ConsoleAppender, {
	popUp: null,
	
	createConsole: function() {
       this.popUp = window.open("",'log4js','width=420,height=320,scrollbars=1,status=0,toolbars=0,resizable=1');
       if (!this.popUp) {
       		logger.error('Cannot create popup window');
       } else{
			this.console = document.createElement("div");
			this.console.setAttribute("id", this.divId);
			this.popUp.document.body.appendChild(this.console);
       }
		
		for (var i = 0 ; i < this.messages.length ; i++) {
			this.appendMessageToConsole(this.messages[i]);
		}
		this.messages = [];
	}
}),
	
/** Http Appender */
log4js.HttpAppender = Class.create(log4js.Appender, {
	url: null,
	frequency: 5,
	messages: null,
	asynchronous: true,
	
	initialize: function($super,parameters) {
		$super(parameters);
		this.frequency = parameters.frequency || this.frequency;
		this.asynchronous = Object.isUndefined(parameters.asynchronous) ? this.asynchronous : parameters.asynchronous;
		this.url = parameters.url;
		this.messages = [];
		
		if (this.asynchronous) {
			new PeriodicalExecuter(this.sendMessages.bind(this), this.frequency);
			Event.observe(window, 'unload', this.sendMessages.bind(this));
		}
	},
	
	appendMessage: function(message) {
		this.messages.push(message);
		
		if (!this.asynchronous) {
			this.sendMessages();
		}
	},
	
	sendMessages: function() {
		if (this.messages.length != 0) {
			var message = '';
			var first = true;
			for (var i=0;i<this.messages.length;i++) {
				if (first) {
					first = false;
				} else {
					message += '\n';
				}
				message += this.messages[i];
			}
			
			this.messages = [];
			
			new Ajax.Request(this.url, {
				method: 'post',
				contentType: 'text/plain',
				postBody: message
			});
		}
	}
});

/** Http HttpAppenderDB */
log4js.HttpAppenderDB = Class.create(log4js.Appender, {
	url: null,
	frequency: 5,
	messages: null,
	asynchronous: true,
	
	initialize: function($super,parameters) {
		$super(parameters);
		this.frequency = parameters.frequency || this.frequency;
		this.asynchronous = Object.isUndefined(parameters.asynchronous) ? this.asynchronous : parameters.asynchronous;
		this.url = parameters.url;
		this.messages = [];
		
		if (this.asynchronous) {
			new PeriodicalExecuter(this.sendMessages.bind(this), this.frequency);
			Event.observe(window, 'unload', this.sendMessages.bind(this));
		}
	},
	
	logMessage: function(message) {
			
		if (this.include && this.include.indexOf(message.loggerName)==-1) {
			return;
		}
		
		if (this.exclude && this.exclude.indexOf(message.loggerName)>=0) {
			return;
		}
		
		var formatedMessage = (new Date()).getTime() + "|" +
			message.level + "|" +
			message.message.replace('\n',"\\n").replace('|'," ") + "|" +
			message.loggerName + "|Log4js";
		this.messages[this.messages.length] = formatedMessage;
	},
	
	sendMessages: function() {
		if (this.messages.length != 0) {
			var message = '';
			var first = true;
			for (var i=0;i<this.messages.length;i++) {
				if (first) {
					first = false;
				} else {
					message += '\n';
				}
				message += this.messages[i];
			}
			
			this.messages = [];
			
			new Ajax.Request(this.url, {
				method: 'post',
				contentType: 'text/plain',
				postBody: message
			});
		}
	}
});

log4js.JsonHttpAppender = Class.create(log4js.Appender, {
	url: null,
	frequency: 5,
	messages: null,
	asynchronous: true,
	
	initialize: function($super,parameters) {
		$super(parameters);
		this.frequency = parameters.frequency || this.frequency;
		this.asynchronous = Object.isUndefined(parameters.asynchronous) ? this.asynchronous : parameters.asynchronous;
		this.url = parameters.url;
		this.messages = [];
		
		if (this.asynchronous) {
			new PeriodicalExecuter(this.sendMessages.bind(this), this.frequency);
			Event.observe(window, 'unload', this.sendMessages.bind(this));
		}
	},

	logMessage: function(message) {
			
		if (this.include && this.include.indexOf(message.loggerName)==-1) {
			return;
		}
		
		if (this.exclude && this.exclude.indexOf(message.loggerName)>=0) {
			return;
		}
		
		this.messages[this.messages.length] = {
			date: (new Date()).getTime(),
			level: message.level,
			logger: message.loggerName,
			message: message.message
		}
	},
	
	sendMessages: function() {
		if (this.messages.length != 0) {
			
			var message = {
				entries: this.messages
			}
			new Ajax.Request(this.url, {
				method: 'post',
				contentType: 'text/json',
				postBody: Object.toJSON(message)
			});
			
			this.messages = [];
		}
	}
});

/** Load configuration */
if (window.properties) {
	log4js.loadConfiguration(properties.log4js);
} else {
	log4js.loadConfiguration();
}

/** Create the main logger */
var logger = log4js.getLogger("javascript"); 

/** Catch all errors on the window */
/*window.onerror = function(msg,url,line) {
	window.logger.error("An unexpected error occured : "+msg+" "+url+" line:"+line);

	return true;
}*/


logger.debug('Log4js loaded');

