﻿
/* sample usage 

window.addEvent('domready', function() {

    // instance
    var highlighter = new Highlighter({
        elements: '#sample-content li',
        className: 'highlight',
        autoUnhighlight: false
    });

    // submit listener 
    document.id('submit').addEvent('click', function() { if (document.id('search').value) { highlighter.highlight(document.id('search').value); } });
    document.id('submit3').addEvent('click', function() { if (document.id('search3').value) { highlighter.highlight(document.id('search3').value, '*', 'highlight1'); } });
    document.id('submit2').addEvent('click', function() { if (document.id('search2').value) { highlighter.unhighlight(document.id('search2').value); } });

    document.id('search').addEvent('keypress', function(e) { if (e.key == 'enter') { document.id('submit').fireEvent('click'); } });
    document.id('search3').addEvent('keypress', function(e) { if (e.key == 'enter') { document.id('submit3').fireEvent('click'); } });
    document.id('search2').addEvent('keypress', function(e) { if (e.key == 'enter') { document.id('submit2').fireEvent('click'); } });

});

*/

var Highlighter = new Class({

    /* implements */
    Implements: [Options],

    /* options */
    options: {
        autoUnhighlight: true,
        caseSensitive: false,
        elements: '*',
        className: '',
        onlyWords: false,
        tag: 'span'
    },

    /* initialization */
    initialize: function(options) {
        /* set options */
        this.setOptions(options);
        this.elements = $$(this.options.elements);
        this.words = [];
    },

    /* directs the plugin to highlight elements */
    highlight: function(words, elements, className) {

        /* figure out what we need to use as element(s) */
        var elements = $$(elements || this.elements);
        var klass = className || this.options.className;
        if (words.constructor === String) { words = [words]; }

        /* auto unhighlight old words? */
        if (this.options.autoUnhighlight) { this.unhighlight(); }

        /* set the pattern and regex */
        var pattern = this.options.onlyWords ? '\\b' + pattern + '\\b' : '(' + words.join('|') + ')';
        var regex = new RegExp(pattern, this.options.caseSensitive ? '' : 'i');

        /* run it for each element! */
        elements.each(function(el) { this.recurse(el, regex, klass); }, this);

        /* make me chainable! */
        return this;
    },

    /* unhighlights items */
    unhighlight: function(words) {
        //var selector = this.options.tag + (word ? '[rel=' + word + ']' : '');
        if (words.constructor === String) { words = [words]; }
        words.each(function(word) {
            word = (this.options.caseSensitive ? word : word.toUpperCase());
            if (this.words[word]) {
                var elements = $$(this.words[word]);
                elements.set('class', '');
                elements.each(function(el) {
                    var tn = document.createTextNode(el.get('text'));
                    el.getParent().replaceChild(tn, el);
                });
            }
        }, this);
        return this;
    },

    /* recursed function */
    recurse: function(node, regex, klass) {
        if (node.nodeType === 3) {
            var match = node.data.match(regex);
            if (match) {
                /* new element */
                var highlight = new Element(this.options.tag);
                highlight.addClass(klass);
                var wordNode = node.splitText(match.index);
                wordNode.splitText(match[0].length);
                var wordClone = wordNode.cloneNode(true);
                highlight.appendChild(wordClone);
                wordNode.parentNode.replaceChild(highlight, wordNode);
                highlight.set('rel', highlight.get('text'));
                var comparer = highlight.get('text');
                if (!this.options.caseSensitive) { comparer = highlight.get('text').toUpperCase(); }
                if (!this.words[comparer]) { this.words[comparer] = []; }
                this.words[comparer].push(highlight);
                return 1;
            }
        } else if ((node.nodeType === 1 && node.childNodes) && !/(script|style)/i.test(node.tagName) && !(node.tagName === this.options.tag.toUpperCase() && node.className === klass)) {
            for (var i = 0; i < node.childNodes.length; i++) {
                i += this.recurse(node.childNodes[i], regex, klass);
            }
        }
        return 0;
    }
});
