if (!("console" in window) || !("firebug" in console)) {
    (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 = "2147483647";
            consoleFrame.style.position = "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);
    })();
}
