/*
    inspired by http://code.google.com/p/jquery-highlight/

    jQuery(document.body).wordUp({
        needle: "haha"                // mandatory, string or array of strings
    });

    jQuery(document.body).wordUp({
        "case": true,               // optional, case sensitive, default false 
        "class": "highlighted",     // optional, default "highlight"
        findFirstOnly: true,        // optional, only one match, default false
        forbiddenParents: "a, .h1", // optional, don't look into those
        needle: ["haha", "hehe"],   // mandatory, string or array of strings
        standAlone: true            // optional, needle is not part of another word, default false
    });
*/

(function (jQuery) {
    jQuery.fn.extend({
        wordUp: function (params) {
            var isHighlighted,  // flag: already highlighted?

                // functions
                highlightText,
                iterateNodes,
                searchNodeForText,
                standsAlone;

            highlightText = function (node, position, needle) {
                if (params.findFirstOnly && isHighlighted) {
                    return;
                }

                var needleNode,
                    needleNodeClone,
                    span;

                span = document.createElement("span");
                span.className = params["class"] || "highlight";

                needleNode = node.splitText(position);
                needleNode.splitText(needle.length);
                needleNodeClone = needleNode.cloneNode(true);

                span.appendChild(needleNodeClone);
                needleNode.parentNode.replaceChild(span, needleNode);
                isHighlighted = true;
            };

            iterateNodes = function (node, needle) {
                if (node.nodeType === 3) {  // is text node
                    return searchNodeForText(node, needle);
                } else if (node.nodeType === 1 && node.childNodes) {
                    var jqnode = jQuery(node);
                    if (jqnode.filter("script, style").length || params.forbiddenParents &&
                            jqnode.filter(params.forbiddenParents).length) {
                        return;
                    }
                    jQuery(node.childNodes).each(function (i, childNode) {
                        iterateNodes(childNode, needle);
                    });
                }
            };

            searchNodeForText = function (node, needle) {
                var needleChanged, position, text;

                needleChanged = params["case"] ? needle : needle.toLowerCase();
                text = params["case"] ? node.data : node.data.toLowerCase();
                position = text.indexOf(needle);

                if (position < 0) {
                    return;
                }

                if (params.standAlone) {
                    if (standsAlone(position, needleChanged, text)) {
                        highlightText(node, position, needle);
                    }
                } else {
                    highlightText(node, position, needle);
                }
            };

            standsAlone = function (position, needle, text) {
                var length = needle.length;

                // if char just before needle is a \w, the needle does not stand alone
                if (position && /\w/.test(text.charAt(position - 1))) {
                    return false;
                }

                // if char just behind needle is a \w, the needle does not stand alone
                if (text.length > position + length && /\w/.test(text.charAt(position + length))) {
                    return false;
                }

                return true;
            };

            return this.each(function () {
                if (!params) {
                    jQuery.error("WordUpException: no arguments given");
                }

                if (typeof params.needle === "string") {
                    iterateNodes(this, params.needle);
                } else if (jQuery.isArray(params.needle)) {
                    jQuery(params.needle).each(function (i, needle) {
                        isHighlighted = false;
                        iterateNodes(this, needle);
                    });
                } else {
                    jQuery.error("WordUpException: needle shall be string or array of strings");
                }
            });
        }
    });
}(jQuery));
