/** Logger for js. Support local and remote logging. */

var Template = function (format) {
    this.format = format;

    this.evaluate = function (args) {
        var ret = this.format;
        jQuery.each(args, function(i, val) {
            ret = ret.replace(new RegExp("(#{"+i+"})"), val);
        });
        return ret;
    }
};

Function.prototype.bind = function(obj) {
    var object = obj;
    var method = this;
    return function() {
        method.apply(object);
    }
}

function $A(iterable) {
      if (!iterable) return [];
      var length = iterable.length || 0, results = new Array(length);
      while (length--) results[length] = iterable[length];
      return results;
    };

Object.isFunction = function(obj) {
    return typeof obj == 'function';
}


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,

        init: 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) {
            var tracer = function() {
                var args = $A(arguments);
                logger.debug('>> '+funcName+'('+args.join(',')+')');
                if (typeof func == 'String') {
                	var result = eval(func);
                } else {
                	var result = func.apply(this,arguments);
                }
                logger.debug('<< '+funcName+'('+args.join(',')+')' + (result != undefined ? '= '+result : ''));
                return result;
            }

            return $.extend(tracer,func);
        },

        trace: function(object, excludes, attribute) {
            if (attribute) {
                object[attribute] = new this.Tracer(this,attribute,object[attribute]);
            } else {
                for (var attribute in object) {
                    if (Object.isFunction(object[attribute])) {
                        if (excludes && $.inArray(attribute,excludes)!=-1) continue;
                        object[attribute] = new this.Tracer(this,attribute,object[attribute]);
                    }
                }
            }
        },

        parachute: function(object, excludes, attribute) {
            if (Object.isFunction(object)) {
                return new log4js.Parachute(this,object);
            } else if (attribute) {
                object[attribute] = new log4js.Parachute(this,object[attribute]);
            } else {
                for (var attribute in object) {
                    if (Object.isFunction(object[attribute]) && (attribute.indexOf("on") == 0)) {
                        if (excludes && $.inArray(attribute,excludes)!=-1) continue;
                        object[attribute] = new log4js.Parachute(this,object[attribute]);
                    }
                }
            }
        }
    }),

    /** Appender class */
    Appender: Class.create({
        format: new Template('#{time} [#{level}] [#{url}-#{loggerName}-#{context}] #{message}'),
        include: null,
        exclude: null,

        init: 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);
        }

        $(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 {
            	if (typeof func == 'String') {
            		return eval(func);
            	} else if(typeof func == 'function'){
            		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.prototype, {
    divId: 'loggerConsole',
    console: null,
    messages: null,

    init: function(parameters) {
        this.sup(parameters);
        if (parameters.divId) {this.divId = parameters.divId;}

        $(window).load(this.createConsole.bind(this));
        this.messages = [];
    },

    createConsole: function() {
        this.console = $('#'+this.divId);

        if (this.console.length == 0 && 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 doc = this.console.document ? this.console.document : document;
        var messageElement = doc.createTextNode(message);
        this.console.appendChild(messageElement);
        this.console.appendChild(doc.createElement('br'));
    }
}),

/** Console Appender */
log4js.PopUpAppender = Class.create(log4js.ConsoleAppender.prototype, {
    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 = this.popUp.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 HttpAppender */
log4js.HttpAppender = Class.create(log4js.Appender.prototype, {
    url: null,
    frequency: 5,
    messages: null,
    asynchronous: true,

    init: function(parameters) {
        this.sup(parameters);
        this.frequency = parameters.frequency || this.frequency;
        this.asynchronous = (parameters.asynchronous == undefined) ? this.asynchronous : parameters.asynchronous;
        this.url = parameters.url;
        this.messages = [];

        if (this.asynchronous) {
            setInterval(this.sendMessages.bind(this), this.frequency*1000);
            $(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/gi,"\\n").replace(/\|/gi," ") + "|" +
            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 = [];

            $.ajax({
                url: this.url,
                type: 'POST',
                contentType: 'text/plain',
                data: message
            });
        }
    }
});

/** Load configuration */
if (window.location.href.indexOf('debug=true') == -1) {
    log4js.loadConfiguration({
        levels: {
            root: 'info'
        },

        appenders: {
            LogAppender: {
                className: 'log4js.HttpAppender',
                flushFrequency: 5,
                asynchronous: true,
                url: '/log'
            }
        }
    });
} else {
    log4js.loadConfiguration({
        levels: {
            root: 'debug'
        },

        appenders: {
            LogAppender: {
                className: 'log4js.PopUpAppender'
            }
        }
    });
}

/** 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');

$(window).ready(function() {
    if (logger.level == log4js.levels.debug) logger.trace(window, excludes);
    logger.parachute(window, excludes);
});

var excludes = [];

for (var attribute in window) {
    try {
        if (Object.isFunction(window[attribute])) {
            excludes.push(attribute);
        }
    } catch (e) {}
}

$.fn.ready = (function (ready) {
    return function(fn) {
        ready(
                new log4js.Parachute(logger,
                        logger.level == log4js.levels.debug ? new logger.Tracer(logger,"onReady",fn) : fn
                )
        );
    };
}) ($.fn.ready);

window.setTimeout = (function (setTimeout) {
    return function(fn, delay) {
        return setTimeout(
                new log4js.Parachute(logger,
                        (logger.level == log4js.levels.debug) ? new logger.Tracer(logger,"onTimeout",fn) : fn
                ),
                delay
        );
    };
}) (window.setTimeout);

window.setInterval = (function (setInterval) {
    return function(fn, delay) {
    	return setInterval(
                new log4js.Parachute(logger,
                        (logger.level == log4js.levels.debug) ? new logger.Tracer(logger,"onInterval",fn) : fn
                ),
                delay
        );
    };
}) (window.setInterval);

