// 
//  jquery.glossary.js
//  for Zilveren Kruis
//  
//  Created by John van Dijk on 2011-03-07.
//  Copyright 2011 Ruby Libre. All rights reserved.
//  Version 0.1.1

// Glossary Class
var Glossary = function(elements, options) {

    this.elements = elements;
    this.options = jQuery.extend({}, this.defaults, options);

    // make one array skip_elements
    if (this.options.skip_elements !== null)
        this.options.skip += ',' + this.options.skip_elements;

    this.used_terms = [];
    if (this.options.terms) this.attach();
    else jQuery.error('No terms defined.');

};

Glossary.prototype = {
    // defaults
    defaults: {
        terms: null,
        skip_elements: null,
        skip: 'script,base,link,meta,style,title,applet,object,a,form,h1,h2,h3,h4,h5,h6',
        case_sensitive: true,
        marker_start_tag: '~~',
        marker_end_tag: '~~',
        skip_chars: '[\\u00a0\\s!;,%\"\'\\(\\)\\{\\}\\.]'
    },

    // Recursively find allowed textNodes
    markTermsInTextNodes: function(node) {
        jQuery.each(node.childNodes, jQuery.proxy(function(i, child) {
            // If it has childNodes, and allowed, recurse!
            if (child.childNodes.length && !jQuery(child).is(this.options.skip)) {
                this.markTermsInTextNodes(child);
            }

            // Is textnode not empty and not a comment?
            if (child.tagName === undefined && child.nodeValue.toString().replace(/\s+/, '').length &&
          child.nodeName != "#comment" && child.nodeType != 8) {

                this.findTerm(child, node);
            }

        }, this));
    },

    findTerm: function(node, parent) {
        for (var term in this.options.terms) {
            if (term.length <= node.nodeValue.length && jQuery.inArray(term, this.used_terms) == -1)
                node.nodeValue = this.markTerm(term.toString(), node.nodeValue.toString());
        };
    },

    // Cannot replace textNode value with HTML
    // So we replace with temp chars
    markTerm: function(term, value) {
        var modifier = this.options.case_sensitive ? "g" : "gi";

        // nodeValue equals term
        var re = new RegExp("^(" + term + ")$", modifier);
        if (re.test(value)) return this.options.marker_start_tag + value + this.options.marker_end_tag;

        // nodeValue starts with term
        re = new RegExp("^(" + term + ")(" + this.options.skip_chars + ")", modifier);
        value = value.replace(re, this.options.marker_start_tag + "$1" + this.options.marker_end_tag + "$2");

        // nodeValue ends with term
        re = new RegExp("(" + this.options.skip_chars + ")(" + term + ")$", modifier);
        value = value.replace(re, "$1" + this.options.marker_start_tag + "$2" + this.options.marker_end_tag);

        // nodeValue contains term
        re = new RegExp("(" + this.options.skip_chars + ")(" + term + ")(" + this.options.skip_chars + ")", modifier);
        value = value.replace(re, "$1" + this.options.marker_start_tag + "$2" + this.options.marker_end_tag + "$3");

        // return modified nodeValue
        return value;
    },

    wrapMarkedTerm: function(node) {
        var html = $(node).html(), re, match, value;
        // Replace each term only once
        for (var term in this.options.terms) {
            match, value = null;
            re = new RegExp(this.options.marker_start_tag + "(" + term + ")" + this.options.marker_end_tag, 'i');

            // Store in global
            if (re.test(html)) this.used_terms.push(term);

            // When multi word term
            if (term.split(/[\-\s]/).length > 1) {
                match = re.exec(html)
                if (match && match.length == 2) {
                    value = match[1];
                    match = /^([^\-\s]+)(.*)/.exec(value);
                    value = '<span class="marker">' + match[1] + '</span><span class="trigger">' + match[2] + '</span>';
                }
            }

            // Replace first occurrence
            if (value) {
                html = html.replace(re, '<dfn class=\"tip\" data-title=\"' + term + '\">' + value + '</dfn>');
            } else {
                html = html.replace(re, '<dfn class=\"tip\" data-title=\"' + term + '\"><span class=\"marker\">$1</span></dfn>');
            }
        }
        // Remove remaining occurrences
        re = new RegExp(this.options.marker_start_tag + "(.*?)" + this.options.marker_end_tag, 'gi');
        html = html.replace(re, '$1');
        // Move html back
        $(node).html(html);
    },

    callPoshyTip: function(node) {
        // proxy to real poshytip element
        $(node).find('dfn.tip span.trigger').bind('mouseenter', function() {
            $(this).prev('span.marker').poshytip('show');
        }).bind('mouseleave', function() {
            $(this).prev('span.marker').poshytip('hide');
        });
    },

    // attach
    attach: function() {
        var self = this;
        this.elements.each(function(index, element) {
            // bootstrap in 2 stages
            self.markTermsInTextNodes(element);
            self.wrapMarkedTerm(element);
            // poshytip on first word
            self.callPoshyTip(element);
        });
    }

};

jQuery.fn.glossary = function(options) {
    new Glossary(this, options);
    return this;
};
