text: /*
    @author: remy sharp / http://remysharp.com
    @url: http://remysharp.com/2007/12/28/jquery-tag-suggestion/
    @usage: setGlobalTags(['javascript', 'jquery', 'java', 'json']); // applied tags to be used for all implementations
  		  $('input.tags').tagSuggest(options);
  		  
  		  The selector is the element that the user enters their tag list
    @params:
  	matchClass - class applied to the suggestions, defaults to 'tagMatches'
  	tagContainer - the type of element uses to contain the suggestions, defaults to 'span'
  	tagWrap - the type of element the suggestions a wrapped in, defaults to 'span'
  	sort - boolean to force the sorted order of suggestions, defaults to false
  	url - optional url to get suggestions if setGlobalTags isn't used.	Must return array of suggested tags
  	tags - optional array of tags specific to this instance of element matches
  	delay - optional sets the delay between keyup and the request - can help throttle ajax requests, defaults to zero delay
  	separator - optional separator string, defaults to ' ' (Brian J. Cardiff)
    @license: Creative Commons License - ShareAlike http://creativecommons.org/licenses/by-sa/3.0/
    @version: 1.4
    @changes: fixed filtering to ajax hits
  */
  
  (function ($) {
  	var globalTags = [];
  
  	// creates a public function within our private code.
  	// tags can either be an array of strings OR
  	// array of objects containing a 'tag' attribute
  	window.setGlobalTags = function(tags /* array */) {
  		globalTags = getTags(tags);
  	};
  	
  	function getTags(tags) {
  		var tag, i, goodTags = [];
  		for (i = 0; i < tags.length; i++) {
  			tag = tags[i];
  			if (typeof tags[i] == 'object') {
  				tag = tags[i].tag;
  			} 
  			goodTags.push(tag.toLowerCase());
  		}
  		
  		return goodTags;
  	}
  	
  	$.fn.tagSuggest = function (options) {
  		var defaults = { 
  			'matchClass' : 'tagMatches', 
  			'tagContainerId' : 'tagContainer', 
  			'tagWrap' : 'span', 
  			'sort' : true,
  			'tags' : null,
  			'url' : null,
  			'delay' : 0,
  			'separator' : ' '
  		};
  
  		var i, tag, userTags = [], settings = $.extend({}, defaults, options);
  
  		if (settings.tags) {
  			userTags = getTags(settings.tags);
  		} else {
  			userTags = globalTags;
  		}
  
  		return this.each(function () {
  			var tagsElm = $(this);
  			var elm = this;
  			var matches, fromTab = false;
  			var suggestionsShow = false;
  			var workingTags = [];
  			var currentTag = {"position": 0, tag: ""};
  			
  			// var tagMatches = document.createElement(settings.tagContainer);
  			var tagMatches = $('#'+settings.tagContainerId)[0];
  			
  			
  			
  			function showSuggestionsDelayed(el, key) {
  				if (settings.delay) {
  					if (elm.timer) clearTimeout(elm.timer);
  					elm.timer = setTimeout(function () {
  						showSuggestions(el, key);
  					}, settings.delay);
  				} else {
  					showSuggestions(el, key);
  				}
  			}
  
  			function showSuggestions(el, key) {
  				
  				workingTags = el.value.split(settings.separator);
  				
  				matches = [];
  				var i, html = '', chosenTags = {}, tagSelected = false;
  
  				// we're looking to complete the tag on currentTag.position (to start with)
  				currentTag = { position: currentTags.length-1, tag: '' };
  				
  				for (i = 0; i < currentTags.length && i < workingTags.length; i++) {
  					if (!tagSelected && 
  						currentTags[i].toLowerCase() != workingTags[i].toLowerCase()) {
  						currentTag = { position: i, tag: workingTags[i].toLowerCase() };
  						tagSelected = true;
  					}
  					// lookup for filtering out chosen tags
  					chosenTags[currentTags[i].toLowerCase()] = true;
  				}
  
  				if (currentTag.tag) {
  					// collect potential tags
  					if (settings.url) {
  						$.ajax({
  							'url' : settings.url,
  							'dataType' : 'json',
  							'data' : { 'tag' : currentTag.tag },
  							'async' : false, // wait until this is ajax hit is complete before continue
  							'success' : function (m) {
  								matches = m;
  							}
  						});
  					} else {
  						for (i = 0; i < userTags.length; i++) {
  							if (userTags[i].indexOf(currentTag.tag) === 0) {
  								matches.push(userTags[i]);
  							}
  						}						 
  					}
  					
  					matches = $.grep(matches, function (v, i) {
  						return !chosenTags[v.toLowerCase()];
  					});
  
  					if (settings.sort) {
  						matches = matches.sort();
  					}					 
  
  					for (i = 0; i < matches.length; i++) {
  						html += '<' + settings.tagWrap + ' class="_tag_suggestion">' + matches[i] + '</' + settings.tagWrap + '>';
  					}
  
  					tagMatches.html(html);
  
  
  					tagMatches.show();
  					
  					tagMatches.css('max-height', 200);
  					tagMatches.css('top', tagsElm.offset().top - tagMatches.height() - 10);
  					// if (tagMatches.position().top < 10) {
  					//	Spaz.dump('setting tagMatches top to 10')
  					//	tagMatches.css('top', 10);
  					// }
  					
  					tagMatches.css('left', tagsElm.offset().left);
  					tagMatches.css('overflow', 'hidden');
  
  
  					
  					Spaz.dump('tagMatches Position and Offset');
  					Spaz.dump(tagMatches.position());
  					Spaz.dump(tagMatches.offset());
  
  					Spaz.dump('tagsElm Position and Offset');
  					Spaz.dump(tagsElm.position());
  					Spaz.dump(tagsElm.offset());
  
  					
  
  
  					suggestionsShow = !!(matches.length);
  				} else {
  					hideSuggestions();
  				}
  			}
  
  			function hideSuggestions() {
  				tagMatches.hide();
  				tagMatches.empty();
  				matches = [];
  				suggestionsShow = false;
  			}
  
  			function setSelection() {
  				var v = tagsElm.val();
  
  				// tweak for hintted elements
  				// http://remysharp.com/2007/01/25/jquery-tutorial-text-box-hints/
  				if (v == tagsElm.attr('title') && tagsElm.is('.hint')) v = '';
  
  				currentTags = v.split(settings.separator);
  				hideSuggestions();
  			}
  
  			function chooseTag(tag) {
  				var i, index;
  				for (i = 0; i < currentTags.length; i++) {
  					if (currentTags[i].toLowerCase() != workingTags[i].toLowerCase()) {
  						index = i;
  						break;
  					}
  				}
  
  				if (index == workingTags.length - 1) tag = tag + settings.separator;
  
  				workingTags[i] = tag;
  
  				tagsElm.val(workingTags.join(settings.separator));
  				tagsElm.blur().focus();
  				setSelection();
  				
  				
  			}
  
  			function handleKeys(ev) {
  				fromTab = false;
  				var type = ev.type;
  				var resetSelection = false;
  				
  				switch (ev.keyCode) {
  					case 37: // ignore cases (arrow keys)
  					case 38:
  					case 39:
  					case 40: {
  						hideSuggestions();
  						return true;
  					}
  					case 224:
  					case 17:
  					case 16:
  					case 18: {
  						return true;
  					}
  
  					case 8: {
  						// delete - hide selections if we're empty
  						if (this.value == '') {
  							hideSuggestions();
  							setSelection();
  							return true;
  						} else {
  							type = 'keyup'; // allow drop through
  							resetSelection = true;
  							showSuggestionsDelayed(this);
  						}
  						break;
  					}
  
  					case 9: // return and tab
  					case 13: {
  						if (suggestionsShow) {
  							// complete
  							chooseTag(matches[0]);
  							
  							fromTab = true;
  							return false;
  						} else {
  							return true;
  						}
  					}
  					case 27: {
  						hideSuggestions();
  						setSelection();
  						return true;
  					}
  					case 32: {
  						setSelection();
  						return true;
  					}
  				}
  
  				if (type == 'keyup') {
  					switch (ev.charCode) {
  						case 9:
  						case 13: {
  							return true;
  						}
  					}
  
  					if (resetSelection) { 
  						setSelection();
  					}
  					showSuggestionsDelayed(this, ev.charCode);			  
  				}
  			}
  
  			// tagsElm.after(tagMatches).keypress(handleKeys).keyup(handleKeys).blur(function () {
  			tagsElm.keypress(handleKeys).keyup(handleKeys).blur(function () {
  				if (fromTab == true || suggestionsShow) { // tweak to support tab selection for Opera & IE
  					fromTab = false;
  					tagsElm.focus();
  				}
  			});
  
  			// replace with jQuery version
  			Spaz.dump('binding click to tagmatches')
  			Spaz.dump(tagMatches);
  			// tagMatches = $(tagMatches).click(function (ev) {
  			// 	Spaz.dump('clicked on Tag')
  			// 	Spaz.dump(ev);
  			// 	if (ev.target.nodeName == settings.tagWrap.toUpperCase() && $(ev.target).is('._tag_suggestion')) {
  			// 		chooseTag(ev.target.innerHTML);
  			// 	}				 
  			// }).addClass(settings.matchClass);
  			tagMatches = $(tagMatches).addClass(settings.matchClass);
  						
  			$('body').intercept('click', {
  				'._tag_suggestion':function(ev){
  					chooseTag(this.innerHTML);
  				}
  			})
  			
  			// $('#'+settings.tagContainerId).children(settings.tagWrap).click(function(ev) {
  			// 	chooseTag(this.innerHTML);
  			// })
  
  			// initialise
  			setSelection();
  		});
  	};
  })(jQuery);
  