const edgeSize = 2;
const highlightCSS = "highlighter.css";

window.addEventListener('load', init, false);

function init () {
    //alert("hi!");
    //document.write('<div id="firebug-console" style=""></div>');
    var consoleDiv = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
    consoleDiv.firebugIgnore = true;
    consoleDiv.className = "firebugConsole";
    consoleDiv.id = 'firebug-console';
    document.body.appendChild(consoleDiv);

    var context = initContext();
    document.addEventListener('mouseover', function (e) {
        if (!context.selected) {
            highlightObject(e, context);
        }
    }, true);
    document.addEventListener('mouseout', function (e) {
        if (!context.selected) {
            unhighlightObject(e, context);
        }
    }, true);
    document.addEventListener('mousedown', function (e) {
        return select(e, context);
    }, true);

    document.addEventListener('keyup', function (e) {
        if (e.keyCode == 17) { // CTRL
            //debug("Control up...");
            context.ctrlDown = false;
        }
    }, true);

    document.addEventListener('keydown', function (e) {
        if (e.keyCode == 27) { // ESC
            unselect(e, context);
            return;
        }
        if (e.keyCode == 17) { // CTRL
            //debug("Control down...");
            context.ctrlDown = true;
            return;
        }
        var key = String.fromCharCode(e.keyCode).toLowerCase();
        if (context.ctrlDown) {
            if (key == 'k' || e.keyCode == 38 /* up arrow */) {
                var selected = context.selected;
                if (!selected) {
                    debug("No selected node found");
                    return;
                }
                if (selected.tagName == 'BODY') {
                    debug("Already BODY, no parentNode.");
                    return;
                }
                var p = selected.parentNode;
                if (!p) {
                    debug("parentNode null.");
                    return;
                }
                unhighlight(context);
                highlight(context, p);
                context.selected = p;

                e.preventDefault();
                return;
            }
            if (key == 'j' || e.keyCode == 40 /* down arrow */) {
                var selected = context.selected;
                if (!selected) {
                    debug("No selected node found");
                    return;
                }
                var son = selected.firstChild;
                do {
                    son = son.nextSibling;
                } while (son && (son.nodeType != Node.ELEMENT_NODE ||
                    son.tagName == 'BR' || son.offsetWidth == 0));

                if (!son) {
                    debug("firstChild null.");
                    return;
                }
                unhighlight(context);
                highlight(context, son);
                context.selected = son;

                e.preventDefault();
                return;
            }

        }
        if (key == 'k' || key == 'h' || e.keyCode == 37 || e.keyCode == 38) {
            //alert('j pressed!');
            var selected = context.selected;
            if (!selected) {
                debug("No selected node found");
                return;
            }
            var previous = selected;
            do {
                previous = previous.previousSibling;
            } while (previous && (previous.nodeType != Node.ELEMENT_NODE ||
                previous.tagName == 'BR' || previous.offsetWidth == 0));

            if (previous) {
                debug("previous: " + previous.textContent);
                unhighlight(context);
                highlight(context, previous);
                context.selected = previous;
            } else {
                debug("No previous Sibling node found");
            }
            e.preventDefault();
        } else if (key == 'j' || key == 'l' || e.keyCode == 39 || e.keyCode == 40) {
            //alert('j pressed!');
            var selected = context.selected;
            if (!selected) {
                debug("No selected node found");
                return;
            }
            var next = selected;
            do {
                next = next.nextSibling;
            } while (next && (next.nodeType != Node.ELEMENT_NODE ||
                next.tagName == 'BR' || next.offsetWidth == 0));

            if (next) {
                debug("next: " + next.textContent);
                unhighlight(context);
                highlight(context, next);
                context.selected = next;
            } else {
                debug("No next Sibling node found");
            }
            e.preventDefault();
        }
    }, true);
}

function unselect (e, context) {
    if (context.selected) {
        delete context.selected;
        unhighlight(context);
    }
}

function highlightObject (e, context) {
    if (e.target.firebugIgnore) {
        return;
    }
    highlight(context, e.target);
}

function unhighlightObject (e, context) {
    //debug("unhighlighting " + e.target);
    unhighlight(context);
}

function initContext () {
    return {
        'edgeSize': 5,
        'window': window
    }
}

function highlight (context, element)
{
    if (element instanceof XULElement)
        return;
    debug("Highlighting " + genSelector(element));
    var offset = getViewOffset(element, true);
    var x = offset.x, y = offset.y;
    var w = element.offsetWidth, h = element.offsetHeight;
    var wacked = isNaN(x) || isNaN(y) || isNaN(w) || isNaN(h);
    if (wacked)
        return;

    var nodes = this.getNodes(context, element);

    move(nodes.top, x, y-edgeSize);
    resize(nodes.top, w, edgeSize);

    move(nodes.right, x+w, y-edgeSize);
    resize(nodes.right, edgeSize, h+edgeSize*2);

    move(nodes.bottom, x, y+h);
    resize(nodes.bottom, w, edgeSize);

    move(nodes.left, x-edgeSize, y-edgeSize);
    resize(nodes.left, edgeSize, h+edgeSize*2);
    var body = getNonFrameBody(element);
    if (!body)
        return this.unhighlight(context);

    var needsAppend = !nodes.top.parentNode || nodes.top.ownerDocument != body.ownerDocument;
    if (needsAppend)
    {
        attachStyles(context, body);
        for (var edge in nodes)
        {
            try
            {
                body.appendChild(nodes[edge]);
            }
            catch(exc)
            {
            }
        }
    }
}

function unhighlight(context)
{
    var nodes = this.getNodes(context);
    var body = nodes.top.parentNode;
    if (body)
    {
        for (var edge in nodes)
            body.removeChild(nodes[edge]);
    }
}

function select (e, context) {
    if (!context.selected) {
        if (e.target.firebugIgnore) {
            return;
        }
        debug("Selected " + genSelector(e.target));
        context.selected = e.target;
        //alert("e.which: " + e.which);
        if (e.which != 1) { // right button pressed
            //alert("Selecting...");
            //e.stopPropagation();
            //e.preventDefault();
            //return false;
        }
    }
    return true;
}

function debug (s) {
    //var oldText = document.getElementById("console").textContent;
    document.getElementById("firebug-console").textContent = s + "\n";
}

function move (element, x, y)
{
    element.style.left = x + "px";
    element.style.top = y + "px";
}

function resize (element, w, h)
{
    element.style.width = w + "px";
    element.style.height = h + "px";
};

function getNodes (context) {
    if (!context.frameHighlighter)
    {
        var doc = context.window.document;

        function createEdge(name)
        {
            var div = doc.createElementNS("http://www.w3.org/1999/xhtml", "div");
            div.firebugIgnore = true;
            div.className = "firebugHighlight";
            return div;
        }

        context.frameHighlighter =
        {
            top: createEdge("Top"),
            right: createEdge("Right"),
            bottom: createEdge("Bottom"),
            left: createEdge("Left")
        };
    }

    return context.frameHighlighter;
}

function pad(element, t, r, b, l)
{
    element.style.padding = Math.abs(t) + "px " + Math.abs(r) + "px "
        + Math.abs(b) + "px " + Math.abs(l) + "px";
}

function getViewOffset (elt, singleFrame)
{
    function addOffset(elt, coords, view)
    {
        var p = elt.offsetParent;
        coords.x += elt.offsetLeft - (p ? p.scrollLeft : 0);
        coords.y += elt.offsetTop - (p ? p.scrollTop : 0);

        if (p)
        {
            if (p.nodeType == 1)
            {
                var parentStyle = view.getComputedStyle(p, "");
                if (parentStyle.position != "static")
                {
                    coords.x += parseInt(parentStyle.borderLeftWidth);
                    coords.y += parseInt(parentStyle.borderTopWidth);

                    if (p.localName == "TABLE")
                    {
                        coords.x += parseInt(parentStyle.paddingLeft);
                        coords.y += parseInt(parentStyle.paddingTop);
                    }
                    else if (p.localName == "BODY")
                    {
                        var style = view.getComputedStyle(elt, "");
                        coords.x += parseInt(style.marginLeft);
                        coords.y += parseInt(style.marginTop);
                    }
                }
                else if (p.localName == "BODY")
                {
                    coords.x += parseInt(parentStyle.borderLeftWidth);
                    coords.y += parseInt(parentStyle.borderTopWidth);
                }

                var parent = elt.parentNode;
                while (p != parent)
                {
                    coords.x -= parent.scrollLeft;
                    coords.y -= parent.scrollTop;
                    parent = parent.parentNode;
                }
                addOffset(p, coords, view);
            }
        }
        else
        {
            if (elt.localName == "BODY")
            {
                var style = view.getComputedStyle(elt, "");
                coords.x += parseInt(style.borderLeftWidth);
                coords.y += parseInt(style.borderTopWidth);

                var htmlStyle = view.getComputedStyle(elt.parentNode, "");
                coords.x -= parseInt(htmlStyle.paddingLeft);
                coords.y -= parseInt(htmlStyle.paddingTop);
            }

            if (elt.scrollLeft)
                coords.x += elt.scrollLeft;
            if (elt.scrollTop)
                coords.y += elt.scrollTop;

            var win = elt.ownerDocument.defaultView;
            if (win && (!singleFrame && win.frameElement))
                addOffset(win.frameElement, coords, win);
        }

    }

    var coords = {x: 0, y: 0};
    if (elt)
        addOffset(elt, coords, elt.ownerDocument.defaultView);

    return coords;
}

function removeClass (node, name)
{
    if (node && node.className)
    {
        var index = node.className.indexOf(name);
        if (index >= 0)
        {
            var size = name.length;
            node.className = node.className.substr(0,index-1) + node.className.substr(index+size);
        }
    }
}

function getNonFrameBody(elt)
{
    var body = getBody(elt.ownerDocument);
    return body.localName.toUpperCase() == "FRAMESET" ? null : body;
}

function getBody(doc)
{
    if (doc.body)
        return doc.body;

    var body = doc.getElementsByTagName("body")[0];
    if (body)
        return body;

    return doc.firstChild;  // For non-HTML docs
};

function attachStyles(context, body)
{
    var doc = body.ownerDocument;
    if (!context.highlightStyle)
        context.highlightStyle = createStyleSheet(doc, highlightCSS);

    if (!context.highlightStyle.parentNode || context.highlightStyle.ownerDocument != doc)
        addStyleSheet(body.ownerDocument, context.highlightStyle);
}

function createStyleSheet(doc, cssPath) {
    var head=doc.getElementsByTagName('HEAD').item(0);
    var style = doc.createElement('link');
    style.href = cssPath;
    style.rel = 'stylesheet';
    style.type = 'text/css';
    head.appendChild(style);
    return style;
}

function genSelector (node) {
    var selector = '';
    while (true) {
        if (node == null) break;
        var tagId = node.getAttribute('id');
        var tagName = node.tagName.toLowerCase();
        var tagClass = node.getAttribute('class');
        if (tagClass) {
            tagClass = tagClass.split(/\s+/)[0];
        } else {
            tagClass = "";
        }
        var locator = tagName;
        if (tagId && /^[-\w]+$/.test(tagId))
            locator += "#" + tagId;
        else if (tagClass && /^[-\w]+$/.test(tagClass))
        locator += "." + tagClass;

        if (selector != '')
            selector = locator + ">" + selector;
        else
            selector = locator;
        if (node.tagName == 'BODY') break;
        node = node.parentNode;
    }
    return selector;
};



