/*
 * jQuery OCD Designer Plugin
 *
 * Copyright (c) 2009 Karl Shea <kshea@matharts.com>
 *
 * Licensed under the BSD License:
 *   http://creativecommons.org/licenses/BSD/
 */
(function($) {
	
	/**
	 * Check all elements marked with the ocd class
	 * 
	 * @param {Object} options
	 */
	$.ocd = function(options) {
		
		// Extend the default settings with the passed options
		var opts = $.extend({}, $.ocd.defaults, options);
		
		// Find all of the OCD elements and run each check function
		$('.ocd').each(function() {
			recursiveCheck(this, opts, function(el, opts) { widont(el, opts.widow_limit, opts.nbsp); });
			recursiveCheck(this, opts, function(el, opts) { hang(el, opts.hanging_punctuation); });
			
			// Below was a function to pass to recursiveCheck, but the parent element's attributes
			// were being clobbered while doing $(el).replaceWith(replacement) (where el was a TextNode), 
			// and I couldn't find another way around it.
			
			// Run text replacements
			var $el = $(this);
			var html = $el.html();
			
			// Replace all of the characters in the found element
			$.each(opts.replacements, function(character, classSuffix){
				var re = new RegExp(character, 'gi');
				html = html.replace(re, '<span class="ocd-' + classSuffix + '">' + character + '</span>');
			});
			
			// Replace the element's html with the modified html
			$el.html(html);
		});
	};
		
	/**
	 * Recurse through nodes and run the passed function on them if the node type is text
	 * 
	 * @param {Element} el
	 * @param {Object} opts
	 * @param {Function} fun
	 */
	function recursiveCheck(el, opts, fun) {
		// Recurse through all of the children
		$.each(el.childNodes, function() {
			recursiveCheck(this, opts, fun);
		});
		
		if(el.nodeType === 3) { // Text node
			// Run the function
			fun(el, opts);
		}
	}
		
	/**
	 * Replace widows if the last word is less than 8 characters (may include period at the end)
	 * 
	 * @param {Element} el
	 * @param {Integer} widow_limit
	 * @param {String} nbsp
	 */
	function widont(el, widow_limit, nbsp) {
		var text = $.trim(el.nodeValue);
		var lastSpace = text.match(/\s[^\s]*$/); // Find the last whitespace character
		
		if(lastSpace) {
			var lastWordLength = text.length - lastSpace.index;
		
			// If the last space is less than the widow_limit characters in, 
			// replace all of the last spaces with one non-breaking space
			if(lastWordLength < widow_limit && lastWordLength > 1) {
				el.nodeValue = text.replace(/\s+([^\s]*)$/, nbsp + '$1');
			}
		}
	}
	
	/**
	 * Add a class to any container whose first character is in the hanging punctuation list
	 * 
	 * @param {Element} el
	 * @param {Object} hanging_punctuation
	 */
	function hang(el, hanging_punctuation) {
		// Add the hanging punctuation class to the parent of any text element
		// whose first character is in the hanging punctuation list
		if($.inArray($.trim(el.nodeValue).charAt(0), hanging_punctuation) > -1) {
			$(el).parent().addClass('ocd-has-hanging-punc');
		}
	}
	
	/**
	 * Default settings
	 */
	$.ocd.defaults = {
		
		// Character to replace as the key, css class suffix as the value
		replacements: {
			'\u2122': 'trademark',	// TRADE MARK SIGN
			'\u00AE': 'registered'	// REGISTERED SIGN
		},
		
		// Characters that trigger hanging punctuation
		hanging_punctuation: [ 
			
				'\u00AB',

			'\u0022',	// QUOTATION MARK  
			'\u201C', 	// LEFT DOUBLE QUOTATION MARK
			'\u0027',	// APOSTROPHE 
			'\u2018', 	// LEFT SINGLE QUOTATION MARK
			'\u0060'	// GRAVE ACCENT
		],
		
		// Widow last word limit (may include ending punctuation)
		widow_limit: 2,
		
		// Replacment character to suppress widows
		nbsp: '\u00A0'	// NO-BREAK SPACE 
	};
	
})(jQuery);
