(function($) {

  $.fn.glossaryHighlighter = function(method) {

	var defaults = {
    path : 'glossary.json',
    wrap_tag : 'em',
    wrap_class : 'glossary-item',
    ghost_tag : 'span',
    ghost_class : 'glossary-ghost',
    identifier_prefix : 'glossary-num-',
    init_buttons : false,
    init_click_dialogs : false,
    on_button_id : 'glossary-on',
    off_button_id : 'glossary-off',
    button_highlight_class : 'active',
    initial_state : 'off',
    on_change: null,
    dialog_options : {
    	autoOpen : false,
    	minHeight: 0,
      height: 'auto',
      width: 350,
      resizable: false,
      position: 'center'
    }
  },
  terms = null,
  cur_open = null,
  check_json_interval_id = 0,
  check_json_count = 0,
  check_json_interval_ms = 100,
  check_json_timeout_ms = 3000,
  full_variants_list = '',
  $on = null, $off = null;
		
	function toggle($this, action) {
  	var current = ($this.find(defaults.wrap_tag+"."+defaults.wrap_class).length > 0) ? 'on' : 'off';
  	var ghostElements = $this.find(defaults.ghost_tag+"."+defaults.ghost_class);

  	if (action === '') {
  	  action = (current === "on") ? 'off' : 'on';
  	} else {
  		action = (action === "on") ? "on" : "off";
  	}
  	
  	// Set button highlights to reflect the current highlight status
  	if (defaults.init_buttons) highlightButtons($on,$off,current);
		
		// Don't proceed if the current state is the action we are trying to achieve
		if (current === action) return;
		
		// Toggle button classes to reflect new highlighted state
		if (defaults.init_buttons) highlightButtons($on,$off,action);
		
		if (action === "on") {
		  
      // We need some terms to proceed, set an interval until we get them
      // This shouldn't run because we don't call this function until we have the json
      if (!terms) { 
        check_json_count++;
        // If we exceed the timeout, just stop
        if (check_json_count * check_json_interval_ms > check_json_timeout_ms) {
          clearInterval(check_json_interval_id);
          return;
        }
        // Set up an interval to check if the json is loaded
        check_json_interval_id = setInterval(function() { toggle($this,action); }, check_json_interval_ms);
        return;
      }
      clearInterval(check_json_interval_id);
      
  		// First time wrapping terms so use findAndReplaceInDOM
  		if (ghostElements.length <= 0) {
  		  
  		  // Make regex and use to replace in DOM
  			var regex = RegExp("\\b\("+full_variants_list+"\)\\b","gim");

  			var frResults = findAndReplaceInDOM($this[0], regex, [], 0);
  			for (var i = 0, m = frResults.length; i < m; i++) {
  			  var r = frResults[i];
  			  r.el.html(r.el.html().replace(r.findText,r.replaceText));
  			}
  			
			  // After we wrap all the glossary terms we need to add the identifier class
  			$this.find(defaults.wrap_tag+'.'+defaults.wrap_class).each(function(index) {
          $(this).addClass($('div[rel*="'+$(this).text().toLowerCase()+'"]').attr('id'));
        });
        
      } else {
        
        // Grab any elements we tagged with the ghost tag.class and change them to the glossary tag.class
        ghostElements.each(function(index) {
  			  swapElements($(this),false);
        });
        
      } 
       
		} else {
		  
  		// Find each glossary item and replace it with the ghost element
  		$this.find(defaults.wrap_tag+"."+defaults.wrap_class).each(function(index) {
  			swapElements($(this),true);
  		});
  		// Close any open dialogs
  		closeOpenDialog();
  		
		}
			
		if (defaults.on_change) defaults.on_change(action);
	}
	
	function highlightButtons($on_btn,$off_btn,action) {
	  // Toggle classes for on and off buttons
	  if (!$on_btn || !$off_btn) return;
	  
	  if (action === "on") {
	    $on_btn.addClass(defaults.button_highlight_class);
	    $off_btn.removeClass(defaults.button_highlight_class);
	  } else {
	    $on_btn.removeClass(defaults.button_highlight_class);
	    $off_btn.addClass(defaults.button_highlight_class);
	  }
	}
	
	function swapElements($this,toGhost) {
	  // Used for changing the tag and the generic class on a glossary item
	  var newClass = (toGhost) ? defaults.ghost_class : defaults.wrap_class;
	  var oldClass = (toGhost) ? defaults.wrap_class : defaults.ghost_class;
	  var newTag = (toGhost) ? defaults.ghost_tag : defaults.wrap_tag;
	  var classes = $this.attr('class').replace(oldClass,newClass);
	  
    $this.replaceWith('<'+newTag+' class="'+classes+'">'+$this.html()+'</'+newTag+'>');
	}
	
	
  function findAndReplaceInDOM(node, regex, swaps, str) {

    var start, end, match, parent, leftNode,
        rightNode, replacementNode, text,
        d = document, _stringBuilder = "",
        leftNodeText = "", rightNodeText = "",
        replacementText = "", previousRight = "",
        lastIndex = 0, hasMatch = false;

    // Loop through all childNodes of "node"
    
    if (node = node && node.firstChild) {
      do {
        if (node.nodeType === 1) {
          
          // Regular element, recurse:
          swaps = findAndReplaceInDOM(node, regex, swaps, str + 1);

        } else if (node.nodeType === 3) {

          // Text node, introspect
          parent = node.parentNode;
          text = node.data;

          regex.lastIndex = 0;
          hasMatch = false;
          previousRight = _stringBuilder = leftNodeText = rightNodeText = replacementText = "";
          
          // Loop thru each match
          while (match = regex.exec(text)) {

            hasMatch = true;

            end = regex.lastIndex;
            start = end - match[0].length;
            
            // The goal here is to wrap all matched words in some HTML elements which will be replaced in the DOM later
            if (previousRight === "") {
              // First match will use the initial values
              replacementText = '<'+defaults.wrap_tag+' class="'+defaults.wrap_class+'">'+match[0]+'</'+defaults.wrap_tag+'>';
              leftNodeText = text.substring(0, start);
              rightNodeText = text.substring(end);
            } else {
              // Subsequent matches will use the values set by the previous loop
              replacementText = '<'+defaults.wrap_tag+' class="'+defaults.wrap_class+'">'+previousRight.substring(start - lastIndex,end - lastIndex)+'</'+defaults.wrap_tag+'>';
              leftNodeText = previousRight.substring(0, start - lastIndex);
              rightNodeText = previousRight.substring(end - lastIndex);
            }
            
            // Append the leftNodeText and replacementText to a string that will rebuild the text node as it loops
            _stringBuilder += leftNodeText + replacementText;
            
            // Set the lastIndex and the previous right text node for the next loop
            lastIndex = regex.lastIndex;
            previousRight = rightNodeText;            
          }
          
          // Loop is done, if we have a match then convert the identifying text to html
          if (hasMatch) {
            _stringBuilder += rightNodeText;
            var _p = $(node).parent();
            swaps.push({
              el: _p,
              findText: text,
              replaceText: _stringBuilder
            });            
          }
          
        }
      } while (node = node.nextSibling);
    }
    return swaps;
  }
		
	function glossaryItemClick($clicked) {
		// Make an array of all classes on $clicked
		var class_list = $clicked.attr('class').split(/\s+/);
		var target_name = "";	
		// Find the identifier class
		var i=class_list.length; while (i--) {
			if (class_list[i].indexOf(defaults.identifier_prefix) !== -1) {
				target_name = class_list[i];
				break;
			}
		}
		// If a dialog is open, close it
		closeOpenDialog();
		// Open targeted dialog, set it to cur_open
		$('#'+target_name).dialog('open');
		cur_open = target_name;	
	}
		
	function closeOpenDialog() {
		if (cur_open) {
			var $cur_open = $('#'+cur_open);
			if ($cur_open.dialog('isOpen')) {
				$cur_open.dialog('close');
			}
		}
	}
	
	var methods = {
		
		init : function(options) {
		  
			if (options) { $.extend(true, defaults, options); }
			
			var $this = $(this);
			// Load the json
			$.getJSON(defaults.path, function(data) {
			  // Loop thru data items
				var i=data.length; while (i--) {
				  // Create variants list for later regex use
				  var variants = data[i].variants.replace(/\s*,\s*/g,"|").toLowerCase();
				  if (!variants || variants === "undefined") {
				    // If there are not variants use the name
				    variants = data[i].name;
				  }
				  // Append variants to full list
				  full_variants_list += variants+"|";
				  
				  // If we want dialog popups when items are clicked
				  if (defaults.init_click_dialogs) {
  					// Create div for dialog
  					var $dialog = $('<div />', {
  						id : defaults.identifier_prefix+i,
  						text : data[i].definition,
  						rel : variants
  					});
  				  $('body').append($dialog);
  				  // Add the appropriate title and merge it into the default dialog options
  					var new_dialog_options = { title : data[i].name };
  					$.extend(defaults.dialog_options,new_dialog_options);  					  
  					// Initialize this dialog
  					$('#'+defaults.identifier_prefix+i).dialog(defaults.dialog_options);
  				}
				}
				full_variants_list = full_variants_list.substring(0, full_variants_list.length-1);
        
        // Only if we want to init the on and off buttons
        if (defaults.init_buttons) {
  				// Bind clicks for on buttons
  				$on = $('#'+defaults.on_button_id);
  				$off = $('#'+defaults.off_button_id);
  				
  				$on.bind('click', function(e) {
  					toggle($this,'on');
  					return false;
  				});

  				$off.bind('click', function(e) {
  					toggle($this,'off');
  					return false;
  				}); 
        }    
        
        if (defaults.init_click_dialogs) {
  				// Bind click for glossary items
  				$this.delegate(defaults.wrap_tag+'.'+defaults.wrap_class, 'click', function(e) {
  					e.preventDefault();
  					e.stopImmediatePropagation();
  					glossaryItemClick($(this));
  				});
			  }
				
				// Set terms so we can proceed with the initial_state
				terms = data;

				// Apply the initial state
				return $this.each(function() {
					$this.data('glossaryHighlighter', defaults);
					toggle($this,defaults.initial_state);
				});
				
			});
			
			
		},
		
		/* 	These functions can be called after initializing glossaryHighlighter like this:
				$('#content').glossaryHighlighter({ path : 'glossary.json', initial_state : 'on', init_button : false });
				$('#toggle_glossary_button').click(function() {
					$('#content').glossaryHighlighter('toggle');
				});
		*/
		
		
		// NOTE: true is needed as the first param of $.extend() since the default opts have nested objects
		on : function() {
			return this.each(function() {
				var $this = $(this);
				if ($this.data('glossaryHighlighter')) { $.extend(true, defaults, $this.data('glossaryHighlighter')); }
				toggle($this,'on');
			});
		},
		
		off : function() {
			return this.each(function() {
				var $this = $(this);
				if ($this.data('glossaryHighlighter')) { $.extend(true, defaults, $this.data('glossaryHighlighter')); }
				toggle($this,'off');
			});
		},
		
		toggle : function() {
			return this.each(function() {
				var $this = $(this);
				if ($this.data('glossaryHighlighter')) { $.extend(true, defaults, $this.data('glossaryHighlighter')); }
				toggle($this,'');
			});
		}
		
	};

	if ( methods[method] ) {
	   	return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
    } else if ( typeof method === 'object' || ! method ) {
      	return methods.init.apply( this, arguments );
    } else {
    	$.error( 'Method ' +  method + ' does not exist on jQuery.glossaryHighlighter' );
    }
  };

})(jQuery);

jq$(document).ready(function() {
  if (glossaryPathToTerms !== '') {
    jq$('.right-content').glossaryHighlighter({
      path : '.glossary.json?terms='+jcrPath+glossaryPathToTerms,
      init_buttons : true,
      init_click_dialogs : true,
      on_change : function(change_to) {
        site.setCookie('glossary', change_to, 365, '/');
        googleAnalytics._trackGlossary(change_to);
      },
      initial_state : (site.getCookie('glossary') || "off")
    });
  } else {
    jq$('#toolbar .glossary').addClass('no-glossary').find('a').click(function() {
      return false;
    });
  }
});
