if (!window.console || !console.firebug) {
    (function() {
        window.console = {
            log       : function() { logFormatted(arguments, ""); },
            debug     : function() { logFormatted(arguments, "debug"); },
            info      : function() { logFormatted(arguments, "info"); },
            warn      : function() { logFormatted(arguments, "warning"); },
            error     : function() { logFormatted(arguments, "error"); },
            assert    : function(truth, message) {
                if (!truth) {
                    var args = [];
                    for (var i = 1; i < arguments.length; ++i)
                        args.push(arguments[i]);

                    logFormatted(args.length ? args : ["Assertion Failure"], "error");
                    throw message ? message : "Assertion Failure";
                }
            },
            dir       : function(object) {
                var html = [];

                var pairs = [];
                for (var name in object) {
                    try {
                        pairs.push([name, object[name]]);
                    } catch (exc) { }
                }

                pairs.sort(function(a, b) { return a[0] < b[0] ? -1 : 1; });

                html.push('<table>');
                for (var i = 0; i < pairs.length; ++i) {
                    var name = pairs[i][0], value = pairs[i][1];

                    html.push('<tr>', '<td class="propertyNameCell"><span class="propertyName">',
                            escapeHTML(name), '</span></td>', '<td><span class="propertyValue">');
                    appendObject(value, html);
                    html.push('</span></td></tr>');
                }
                html.push('</table>');

                logRow(html, "dir");
            },
            dirxml     : function(node) {
                var html = [];

                appendNode(node, html);
                logRow(html, "dirxml");
            },
            group      : function() { logRow(arguments, "group", pushGroup); },
            groupEnd   : function() { logRow(arguments, "", popGroup); },
            time       : function(name) { timeMap[name] = (new Date()).getTime(); },
            timeEnd    : function(name) {
                if (name in timeMap) {
                    var delta = (new Date()).getTime() - timeMap[name];
                    logFormatted([name+ ":", delta+"ms"]);
                    delete timeMap[name];
                }
            },
            count      : function() { this.warn(["count() not supported."]); },
            trace      : function() { this.warn(["trace() not supported."]); },
            profile    : function() { this.warn(["profile() not supported."]); },
            profileEnd : function() { },
            clear      : function() { consoleBody.innerHTML = ""; },
            open       : function() { toggleConsole(true); },
            close      : function() { if (frameVisible) toggleConsole(); }
        };

        // ********************************************************************************************

        var consoleFrame = null;
        var consoleBody = null;
        var commandLine = null;

        var frameVisible = false;
        var messageQueue = [];
        var groupStack = [];
        var timeMap = {};

        var clPrefix = ">>> ";

        var isFirefox = navigator.userAgent.indexOf("Firefox") != -1;
        var isIE = navigator.userAgent.indexOf("MSIE") != -1;
        var isOpera = navigator.userAgent.indexOf("Opera") != -1;
        var isSafari = navigator.userAgent.indexOf("AppleWebKit") != -1;

        // ********************************************************************************************

        function toggleConsole(forceOpen) {
            frameVisible = forceOpen || !frameVisible;
            if (consoleFrame)
                consoleFrame.style.visibility = frameVisible ? "visible" : "hidden";
            else
                waitForBody();
        }

        function focusCommandLine() {
            toggleConsole(true);
            if (commandLine) commandLine.focus();
        }

        function waitForBody() {
            if (document.body) createFrame(); else setTimeout(waitForBody, 200);
        }

        function createFrame() {
            if (consoleFrame) return;

            window.onFirebugReady = function(doc) {
                window.onFirebugReady = null;

                var toolbar = doc.getElementById("toolbar");
                toolbar.onmousedown = onSplitterMouseDown;

                commandLine = doc.getElementById("commandLine");
                addEvent(commandLine, "keydown", onCommandLineKeyDown);

                addEvent(doc, isIE || isSafari ? "keydown" : "keypress", onKeyDown);

                consoleBody = doc.getElementById("log");
                layout();
                flush();
            }

            var baseURL = getFirebugURL();

            consoleFrame = document.createElement("iframe");
            consoleFrame.setAttribute("src", baseURL+"/firebug.html");
            consoleFrame.setAttribute("frameBorder", "0");
            consoleFrame.style.visibility = (frameVisible ? "visible" : "hidden");
            consoleFrame.style.zIndex = "2147483583";
            consoleFrame.style.position = document.all ? "absolute" : "fixed";
            consoleFrame.style.width = "100%";
            consoleFrame.style.left = "0";
            consoleFrame.style.bottom = "0";
            consoleFrame.style.height = "200px";
            document.body.appendChild(consoleFrame);
        }

        function getFirebugURL() {
            var scripts = document.getElementsByTagName("script");
            for (var i = 0; i < scripts.length; ++i) {
                if (scripts[i].src.indexOf("firebug.js") != -1) {
                    var lastSlash = scripts[i].src.lastIndexOf("/");
                    return scripts[i].src.substr(0, lastSlash);
                }
            }
        }

        function evalCommandLine() {
            var text = commandLine.value;
            commandLine.value = "";

            logRow([clPrefix, text], "command");

            var value;
            try {
                value = eval(text);
            } catch (exc) { }

            console.log(value);
        }

        function layout() {
            var toolbar = consoleBody.ownerDocument.getElementById("toolbar");
            var height = consoleFrame.offsetHeight - (toolbar.offsetHeight + commandLine.offsetHeight);
            consoleBody.style.top = toolbar.offsetHeight + "px";
            consoleBody.style.height = height + "px";

            commandLine.style.top = (consoleFrame.offsetHeight - commandLine.offsetHeight) + "px";
        }

        function logRow(message, className, handler) {
            if (consoleBody)
                writeMessage(message, className, handler);
            else
            {
                messageQueue.push([message, className, handler]);
                waitForBody();
            }
        }

        function flush() {
            var queue = messageQueue;
            messageQueue = [];

            for (var i = 0; i < queue.length; ++i) writeMessage(queue[i][0], queue[i][1], queue[i][2]);
        }

        function writeMessage(message, className, handler) {
            var isScrolledToBottom = consoleBody.scrollTop + consoleBody.offsetHeight >= consoleBody.scrollHeight;
            if (!handler) handler = writeRow;
            handler(message, className);
            if (isScrolledToBottom) consoleBody.scrollTop = consoleBody.scrollHeight - consoleBody.offsetHeight;
        }

        function appendRow(row) {
            var container = groupStack.length ? groupStack[groupStack.length-1] : consoleBody;
            container.appendChild(row);
        }

        function writeRow(message, className) {
            var row = consoleBody.ownerDocument.createElement("div");
            row.className = "logRow" + (className ? " logRow-"+className : "");
            row.innerHTML = message.join("");
            appendRow(row);
        }

        function pushGroup(message, className) {
            logFormatted(message, className);

            var groupRow = consoleBody.ownerDocument.createElement("div");
            groupRow.className = "logGroup";
            var groupRowBox = consoleBody.ownerDocument.createElement("div");
            groupRowBox.className = "logGroupBox";
            groupRow.appendChild(groupRowBox);
            appendRow(groupRowBox);
            groupStack.push(groupRowBox);
        }

        function popGroup() {
            groupStack.pop();
        }

        // ********************************************************************************************

        function logFormatted(objects, className) {
            var html = [];

            var format = objects[0];
            var objIndex = 0;

            if (typeof(format) != "string") {
                format = "";
                objIndex = -1;
            }

            var parts = parseFormat(format);
            for (var i = 0; i < parts.length; ++i){
                var part = parts[i];
                if (part && typeof(part) == "object") {
                    var object = objects[++objIndex];
                    part.appender(object, html);
                } else appendText(part, html);
            }

            for (var i = objIndex+1; i < objects.length; ++i) {
                appendText(" ", html);

                var object = objects[i];
                if (typeof(object) == "string") appendText(object, html); else appendObject(object, html);
            }

            logRow(html, className);
        }

        function parseFormat(format) {
            var parts = [];

            var reg = /((^%|[^\\]%)(\d+)?(\.)([a-zA-Z]))|((^%|[^\\]%)([a-zA-Z]))/;
            var appenderMap = {s: appendText, d: appendInteger, i: appendInteger, f: appendFloat};

            for (var m = reg.exec(format); m; m = reg.exec(format)) {
                var type = m[8] ? m[8] : m[5];
                var appender = type in appenderMap ? appenderMap[type] : appendObject;
                var precision = m[3] ? parseInt(m[3]) : (m[4] == "." ? -1 : 0);

                parts.push(format.substr(0, m[0][0] == "%" ? m.index : m.index+1));
                parts.push({appender: appender, precision: precision});

                format = format.substr(m.index+m[0].length);
            }

            parts.push(format);
            return parts;
        }

        function escapeHTML(value) {
            function replaceChars(ch) {
                switch (ch) {
                    case "<" : return "&lt;";
                    case ">" : return "&gt;";
                    case "&" : return "&amp;";
                    case "'" : return "&#39;";
                    case '"' : return "&quot;";
                }
                return "?";
            };
            return String(value).replace(/[<>&"']/g, replaceChars);
        }

        function objectToString(object) {
            try {
                return object+"";
            } catch (exc) {
                return null;
            }
        }

        // ********************************************************************************************

        function appendText(object, html) {
            html.push(escapeHTML(objectToString(object)));
        }

        function appendNull(object, html) {
            html.push('<span class="objectBox-null">', escapeHTML(objectToString(object)), '</span>');
        }

        function appendString(object, html) {
            html.push('<span class="objectBox-string">&quot;', escapeHTML(objectToString(object)), '&quot;</span>');
        }

        function appendInteger(object, html) {
            html.push('<span class="objectBox-number">', escapeHTML(objectToString(object)), '</span>');
        }

        function appendFloat(object, html) {
            html.push('<span class="objectBox-number">', escapeHTML(objectToString(object)), '</span>');
        }

        function appendFunction(object, html) {
            var reName = /function ?(.*?)\(/;
            var m = reName.exec(objectToString(object));
            var name = m ? m[1] : "function";
            html.push('<span class="objectBox-function">', escapeHTML(name), '()</span>');
        }

        function appendObject(object, html) {
            try {
                if (object == undefined)
                    appendNull("undefined", html);
                else if (object == null)
                    appendNull("null", html);
                else if (typeof object == "string")
                    appendString(object, html);
                else if (typeof object == "number")
                    appendInteger(object, html);
                else if (typeof object == "function")
                    appendFunction(object, html);
                else if (object.nodeType == 1)
                    appendSelector(object, html);
                else if (typeof object == "object")
                    appendObjectFormatted(object, html);
                else
                    appendText(object, html);
            } catch (exc){ }
        }

        function appendObjectFormatted(object, html) {
            var text = objectToString(object);
            var reObject = /\[object (.*?)\]/;

            var m = reObject.exec(text);
            html.push('<span class="objectBox-object">', m ? m[1] : text, '</span>')
        }

        function appendSelector(object, html) {
            html.push('<span class="objectBox-selector">');

            html.push('<span class="selectorTag">', escapeHTML(object.nodeName.toLowerCase()), '</span>');
            if (object.id)
                html.push('<span class="selectorId">#', escapeHTML(object.id), '</span>');
            if (object.className)
                html.push('<span class="selectorClass">.', escapeHTML(object.className), '</span>');

            html.push('</span>');
        }

        function appendNode(node, html) {
            if (node.nodeType == 1) {
                html.push('<div class="objectBox-element">', '&lt;<span class="nodeTag">', node.nodeName.toLowerCase(), '</span>');

                for (var i = 0; i < node.attributes.length; ++i) {
                    var attr = node.attributes[i];
                    if (!attr.specified) continue;

                    html.push('&nbsp;<span class="nodeName">', attr.nodeName.toLowerCase(),
                            '</span>=&quot;<span class="nodeValue">', escapeHTML(attr.nodeValue),
                            '</span>&quot;')
                }

                if (node.firstChild) {
                    html.push('&gt;</div><div class="nodeChildren">');

                    for (var child = node.firstChild; child; child = child.nextSibling) appendNode(child, html);

                    html.push('</div><div class="objectBox-element">&lt;/<span class="nodeTag">',
                            node.nodeName.toLowerCase(), '&gt;</span></div>');
                } else html.push('/&gt;</div>');
            } else if (node.nodeType == 3) {
                html.push('<div class="nodeText">', escapeHTML(node.nodeValue), '</div>');
            }
        }

        // ********************************************************************************************

        function addEvent(object, name, handler) {
            if (document.all) object.attachEvent("on"+name, handler); else object.addEventListener(name, handler, false);
        }

        function removeEvent(object, name, handler) {
            if (document.all) object.detachEvent("on"+name, handler);
                else object.removeEventListener(name, handler, false);
        }

        function cancelEvent(event) {
            if (document.all) event.cancelBubble = true; else event.stopPropagation();
        }

        function onError(msg, href, lineNo) {
            var html = [];

            var lastSlash = href.lastIndexOf("/");
            var fileName = lastSlash == -1 ? href : href.substr(lastSlash+1);

            html.push('<span class="errorMessage">', msg, '</span>',
                      '<div class="objectBox-sourceLink">', fileName, ' (line ', lineNo, ')</div>');

            logRow(html, "error");
        };

        function onKeyDown(event) {
            if (event.keyCode == 123)
                toggleConsole();
            else if ((event.keyCode == 108 || event.keyCode == 76) && event.shiftKey
                    && (event.metaKey || event.ctrlKey))
                focusCommandLine();
            else return;

            cancelEvent(event);
        }

        function onSplitterMouseDown(event) {
            if (isSafari || isOpera) return;

            addEvent(document, "mousemove", onSplitterMouseMove);
            addEvent(document, "mouseup", onSplitterMouseUp);

            for (var i = 0; i < frames.length; ++i) {
                addEvent(frames[i].document, "mousemove", onSplitterMouseMove);
                addEvent(frames[i].document, "mouseup", onSplitterMouseUp);
            }
        }

        function onSplitterMouseMove(event) {
            var win = document.all ? event.srcElement.ownerDocument.parentWindow : event.target.ownerDocument.defaultView;

            var clientY = event.clientY;
            if (win != win.parent) clientY += win.frameElement ? win.frameElement.offsetTop : 0;

            var height = consoleFrame.offsetTop + consoleFrame.clientHeight;
            var y = height - clientY;

            consoleFrame.style.height = y + "px";
            layout();
        }

        function onSplitterMouseUp(event) {
            removeEvent(document, "mousemove", onSplitterMouseMove);
            removeEvent(document, "mouseup", onSplitterMouseUp);

            for (var i = 0; i < frames.length; ++i) {
                removeEvent(frames[i].document, "mousemove", onSplitterMouseMove);
                removeEvent(frames[i].document, "mouseup", onSplitterMouseUp);
            }
        }

        function onCommandLineKeyDown(event) {
            if (event.keyCode == 13) evalCommandLine(); else if (event.keyCode == 27) commandLine.value = "";
        }

        window.onerror = onError;
        addEvent(document, isIE || isSafari ? "keydown" : "keypress", onKeyDown);

        if (document.documentElement.getAttribute("debug") == "true")
            toggleConsole(true);
    })();
}