var MindReader = Class.create
({
	// Constructor
	initialize: function(field, options)
	{
		// Settings
		this.debug_mode		= false;	// Bool tells whether the script should run in debug mode;
		this.min_chars 		= 1;		// Minimum characters before the search kicks in
		this.max_items		= 0;		// Maximum number of items allowed to select
		this.feed_size		= 5;		// Maximum size of the autocomplete feed before the scrollbar is added
		this.ajax_method	= 'GET';	// Method used by the AJAX calls (GET or POST)
		this.seperator		= ',';		// String that the result IDs are seperated by
		this.feed_url		= '';		// The URL to the search feed
		this.add_url		= '';		// The URL to the add script
		this.chars_allow	= '';		// String containing the only characters that are allowed to be used
		this.chars_disallow	= '';		// String container all the characters that are not allowed
		this.defaults		= null;		// Element containing the default list of values
		this.match_case		= false;	// Bool tells whether the case should be matched
		this.match_word		= false;	// Bool tells whether the search should match words only

		if ($(field))
		{
			if (options)
			{
				for (var index in options)
				{
					if ((index in this) && typeof(this[index]) != 'function')
					{
						this[index] = options[index];
					}
				}
			}

			if (this.feed_url.strip() != '')
			{
				this.initDefaults(field);
				
				this.initDebugBox();

				this.initField();
				this.initContainer();
				this.initNewInput();
				this.initActionButton();
				this.initFeedList();
				this.initSelectList();
			}
		}
	},

	// Initialize the default settings and variables
	initDefaults: function(field)
	{
		// Selections
		this.selections			= [];
		
		// Sub Search Key
		this.sub_key 			= '';

		// Settings
		this.in_action			= false;

		// Elements
		this.field				= $(field);
		this.container			= null;
		this.new_input			= null;
		this.btn_action			= null;
		this.feed_list			= null;
		this.feed_scroller		= null;
		this.select_list		= null;
		this.feed_selector		= null;
		this.selection_selector	= null;
		this.last_search		= null;
		this.debug_info			= null;
		this.feed_caller		= null;
		this.add_caller			= null;
		
		document.observe('click', this.blurAll.bindAsEventListener(this));
	},
	
	// Initialize the original input field
	initField: function()
	{
		this.field.setValue('');
		this.field.hide();
	},

	// Initialize the container that will hold all the autocomplete components
	initContainer: function()
	{
		this.container = new Element('div', {'class':'mr_container'});
		this.field.insert({after:this.container});
	},

	// Initialize the new autocomplete input field
	initNewInput: function()
	{
		this.new_input = this.field.cloneNode(true);

		this.new_input.removeAttribute('id');
		this.new_input.removeAttribute('name');
		this.new_input.show();

		this.new_input.observe(Prototype.Browser.IE ? 'keydown' : 'keypress', this.keyDownAction.bindAsEventListener(this));
		this.new_input.observe('keyup', this.keyUpAction.bindAsEventListener(this));

		this.container.insert(this.new_input);
	},

	// Initialize the add new item button
	initActionButton: function()
	{
		this.btn_action = new Element('button');

		this.btn_action.observe('click', this.addAction.bindAsEventListener(this));	

		this.btn_action.hide();
		
		this.container.insert('&nbsp;');
		this.container.insert(this.btn_action);
	},

	// Initialize the UL that holds the autocomplete feed items
	initFeedList: function()
	{
		this.feed_scroller = new Element('div', {'class':'mr_feed_scroller'});
		this.feed_list = new Element('ul', {'class':'mr_feed'});
		this.feed_scroller.hide();
		
		this.feed_scroller.insert(this.feed_list);
		this.container.insert(this.feed_scroller);
	},

	// Initialize the UL that contains all the items selected by the user
	initSelectList: function()
	{
		this.select_list = new Element('ul', {'class':'mr_selection'});
		this.select_list.hide();
		this.container.insert(this.select_list);
		
		var temp_clear = new Element('div', {'class':'mr_clear'});
		temp_clear.insert('&nbsp;');
		
		this.container.insert(temp_clear);

		this.addDefaultValues();
	},

	// Initialize the debug info box
	initDebugBox: function()
	{
		if (this.debug_mode)
		{
			if ($$('#mr_debugger').length > 0)
			{
				this.debug_info = $$('#mr_debugger').first();
			}
			
			else
			{
				var temp_body 		= $$('body').first();
				this.debug_info		= new Element('div', {id:'mr_debugger'});
				var temp_link_div	= new Element('div', {'class':'link'});
				var temp_link		= new Element('a', {href:'#'});
				var temp_header 	= new Element('h1');
				var temp_content	= new Element('ul');
				
				temp_header.insert('Mind Reader Debug');
				
				temp_link.insert('Show');
				temp_link_div.insert(temp_link);
				
				temp_content.hide();
				
				temp_link.observe('click', this.debugOpenClose.bindAsEventListener(this));
				
				this.debug_info.insert(temp_link_div);
				this.debug_info.insert(temp_header);
				this.debug_info.insert(temp_content);
				
				temp_body.insert(this.debug_info);
			}
		}
	},

	addAction: function(e)
	{
		e.stop();
		
		if (this.add_url.strip() != '')
		{
			this.actionCall();
		}
	},
	
	actionCall: function()
	{
		var temp_input = this.new_input.getValue().strip();
		
		this.btn_action.removeClassName('add');
		
		if (!this.in_action && !this.feed_scroller.visible() && temp_input.length >= this.min_chars)
		{
			this.in_action = true;
			
			this.add_caller = new Ajax.Request
			(
				this.add_url, 
				{
					parameters: {item: temp_input},
					method: this.ajax_method,
					onComplete: this.addComplete.bind(this)
				}
			);
		}
	},
	
	forceAdd: function(value)
	{
		var temp_input = value.strip();
		
		if (!this.in_action && temp_input != '')
		{
			this.in_action = true;
			
			this.add_caller = new Ajax.Request
			(
				this.add_url, 
				{
					parameters: {item: temp_input},
					method: this.ajax_method,
					onComplete: this.addComplete.bind(this)
				}
			);
		}
	},
	
	addComplete: function(transport)
	{
		this.in_action = false;
		
		this.btn_action.hide();
		this.resetInputField();
		
		if (transport.responseText != '')
		{
			var temp_list = new Element('ul');
			temp_list.insert(transport.responseText);
			
			var items = temp_list.select('li');
			
			if (items.length > 0 && this.selections.indexOf(items.first().value) == -1)
			{
				this.addSelection(items.first());
			}
		}
	},
	
	grabParentLI: function(elt)
	{
		if (!elt.match('li'))
		{
			elt = elt.up('li');
		}
		
		return elt;
	},
	
	blurAll: function(e)
	{
		var elt = $(Event.element(e));
		
		if (elt != this.new_input)
		{
			this.hideFeed();
			this.last_search = '';
		}
	},

	clearAll: function()
	{
		this.select_list.update(' ');
		this.updateSelectionValues();
		
		this.last_search = '';
		this.hideFeed();
		this.new_input.setValue('');
		
		this.btn_action.hide();
	},
	
	// Called when a key has been pressed down on the keyboard
	keyDownAction: function(e)
	{
		var code = e.keyCode;

		if (this.in_action)
		{
			e.stop();
		}
		
		else
		{
			switch(code)
			{
				case Event.KEY_UP:
				case Event.KEY_DOWN:
				case Event.KEY_TAB:
					e.stop();
					break;
	
				// The Return Key
				case Event.KEY_RETURN:
					e.stop();
					
					if (this.feed_scroller.visible())
					{
						this.addSelector();
					}
					
					else if (this.add_url != '' && this.btn_action.visible() && this.btn_action.match('.add'))
					{
						this.actionCall();
					}
					
					break;
	
				// The Escape Key
				case Event.KEY_ESC:
					e.stop();
					this.hideFeed();
					break;
			}
		}
	},

	// Called when a key is released on the keyboard
	keyUpAction: function(e)
	{
		var code = e.keyCode;

		if (this.in_action)
		{
			e.stop();
		}
		
		else
		{
			switch(code)
			{
				// The Up Arrow
				case Event.KEY_UP:
					e.stop();
					this.moveSelectorUp();
					break;
	
				// The Down Arrow & Tab Key
				case Event.KEY_DOWN:
				case Event.KEY_TAB:
					e.stop();
					this.moveSelectorDown();
					break;
				
				case Event.KEY_RETURN:
				case Event.KEY_ESC:
					e.stop();
					break;
	
				default:
					var temp_search = this.new_input.getValue().strip();
	
					this.btn_action.hide();
					this.btn_action.removeClassName('add');
					
					if (temp_search.length >= this.min_chars && this.last_search != temp_search && temp_search != '')
					{
						this.search(temp_search);
					}
						
					else if (this.last_search != temp_search)
					{
						this.hideFeed();
						this.last_search = '';
					}
						
					if (temp_search == '')
					{
						this.last_search = '';
					}
						
					break;
			}
		}
	},
	
	// Issues the AJAX request to search for matches to the typed in string
	search: function(word) 
	{
		this.last_search = word;
		
		this.btn_action.removeClassName('add');
		this.btn_action.show();
		
		this.feed_caller = new Ajax.Request
		(
			this.feed_url, 
			{
				parameters: {mr_search: word, mr_sub_key: this.sub_key, mr_selections: this.selections.join(this.seperator)},
				method: this.ajax_method,
				onComplete: this.completed.bind(this)
			}
		);
	},
	
	// Called when the AJAX request returns the result set
	completed: function(transport) 
	{
		var correct_search = false;
		var has_results = false;
		
		this.btn_action.removeClassName('add');
		this.btn_action.hide();
		this.hideFeed();
		
		this.bindSelectFeedItem = this.selectFeedItem.bindAsEventListener(this);
		this.bindFocusFeedItem = this.focusFeedItem.bindAsEventListener(this);
		this.bindUnfocusFeedItem = this.unfocusFeedItem.bindAsEventListener(this);
		
		var temp_list = new Element('ul');
		temp_list.update(transport.responseText.strip());
		
		var items = temp_list.select('li');
		
		if (items.length > 0)
		{			
			var first_item = items[0];
			
			if (first_item.innerHTML.strip().toLowerCase() == this.new_input.getValue().strip().toLowerCase())
			{
				correct_search = true;
			}
			
			if (items.length > 1)
			{
				has_results = true;
			}
			
			first_item.remove();
		}
		
		if (correct_search && has_results)
		{
			this.updateFeed(temp_list.innerHTML);
			this.showFeed();
			
			var temp_items = this.feed_list.select('li');
	
			for (var i = 0; i < temp_items.length; i++)
			{
				temp_items[i].observe('click', this.bindSelectFeedItem);
				temp_items[i].observe('mouseover', this.bindFocusFeedItem);
				temp_items[i].observe('mouseout', this.bindUnfocusFeedItem);
				
				var temp_spans = temp_items[i].select('span');
				
				for (var j = 0; j < temp_spans.length; j++)
				{
					temp_spans[j].observe('mouseover', this.bindFocusFeedItem);
					temp_spans[j].observe('mouseout', this.bindUnfocusFeedItem);
				}
			}
		}
		
		else if (correct_search && !has_results)
		{
			this.hideFeed();
			
			if (this.add_url.strip() != '' && this.new_input.getValue().strip().length >= this.min_chars)
			{
				this.btn_action.addClassName('add');
				this.btn_action.show();
			}
		}
	},
	
	// Hides the autocomplete results, clears the input field and focus the input field
	resetInputField: function()
	{
		this.last_search = '';
		this.hideFeed();
		this.new_input.setValue('');
		
		if (this.new_input.enabled)
		{
			this.new_input.focus();
		}
	},
	
	// Adds the selected feed item to the list of selected items
	addSelectedFeedItem: function(elt) 
	{
		elt = this.grabParentLI(elt);
		
		elt.stopObserving('click', this.bindSelectFeedItem);
		elt.stopObserving('mouseover', this.bindFocusFeedItem);
		elt.stopObserving('mouseout', this.bindUnfocusFeedItem);
		
		var spans = elt.select('span');
		
		for (var i = 0; i < spans.length; i++)
		{
			spans[i].stopObserving('mouseover', this.bindFocusFeedItem);
			spans[i].stopObserving('mouseout', this.bindUnfocusFeedItem);
		}
		
		this.addSelection(elt);
		
		this.resetInputField();
		
		this.feed_selector = null;
	},
	
	// Called when an item in the feed list has been clicked
	// adds the selected feed item to the list of selected items
	selectFeedItem: function(e) 
	{
		e.stop();
		
		var elt = $(Event.element(e));
		
		this.addSelectedFeedItem(elt);
	},
	
	// Called when a feed item is rolled over sets hover state
	focusFeedItem: function(e) 
	{
		e.stop();
				
		var elt = this.grabParentLI(Event.element(e));
		
		if (this.feed_selector)
			this.feed_selector.removeClassName('focus');
		
		elt.addClassName('focus');
		this.feed_selector = elt;
	},
	
	// Called when a feed item is rolled off and removes hover state
	unfocusFeedItem: function(e) 
	{
		e.stop();
				
		var elt = this.grabParentLI(Event.element(e));
		
		elt.removeClassName('focus');
		this.feed_selector = null;
	},
	
	// Called when enter key is clicked on selected feed item
	// adds the feed item to the list of selected items.
	addSelector: function()
	{
		if (this.feed_selector)
		{
			this.addSelectedFeedItem(this.feed_selector);
		}
	},

	// Called when the up arrow is hit on the keyboard
	// moves the feed item selector up to the next item
	moveSelectorUp: function()
	{
		if (this.feed_scroller.visible() && this.feed_list.select('li').length > 0)
		{
			if (this.feed_selector)
			{
				this.feed_selector.removeClassName('focus');
			}
			
			if (this.feed_selector && this.feed_selector.previous())
			{
				this.feed_selector = this.feed_selector.previous();		
			}
			
			else
			{
				this.feed_selector = this.feed_list.select('li').last();
			}
			
			this.feed_scroller.scrollTop = this.feed_selector.positionedOffset()[1] - this.feed_selector.getHeight();
			this.feed_selector.addClassName('focus');
		}
	},

	// Called when the down arrow is hit on the keyboard
	// moves the feed item selector down to the next item
	moveSelectorDown: function()
	{
		if (this.feed_scroller.visible() && this.feed_list.select('li').length > 0)
		{
			if (this.feed_selector)
			{
				this.feed_selector.removeClassName('focus');
			}
			
			if (this.feed_selector && this.feed_selector.next())
			{
				this.feed_selector = this.feed_selector.next();		
			}
			
			else
			{
				this.feed_selector = this.feed_list.select('li').first();
			}
			
			this.feed_scroller.scrollTop = this.feed_selector.positionedOffset()[1] - this.feed_selector.getHeight();
			this.feed_selector.addClassName('focus');
		}
	},

	// Hides the feed list and resets it's contents
	hideFeed: function()
	{
		this.feed_scroller.hide();
		this.feed_list.update(' ');
		this.feed_selector = null;
	},

	// Show the feed list and its contents
	showFeed: function()
	{
		this.feed_scroller.show();
	},

	// Update the contents of the list of AJAX feed items
	updateFeed: function(list)
	{
		this.feed_scroller.setStyle({height:'', width:''});
		this.feed_list.update(list);
		
		var items = this.feed_list.select('li');
		
		if (this.feed_size > 0)
		{
			this.showFeed();
			
			var unit_width = this.feed_list.getWidth() + 25;

			if (items.length > this.feed_size)
			{	
				var unit_height = items[0].getHeight() * this.feed_size;	
				this.feed_scroller.setStyle({height:unit_height + 'px', width:unit_width + 'px'});
			}
			
			else
			{
				var unit_height = this.feed_list.getHeight();	
				this.feed_scroller.setStyle({height:unit_height + 'px', width:unit_width + 'px'});
			}
		}		
	},

	// Add a list item to the list of selected items
	addSelection: function(item)
	{
		item = $(item);
		
		var temp_value = item.value;

		if (temp_value != null)
		{
			var new_item = new Element('li');
			new_item.insert(item.innerHTML);
			
			var del_link = new Element('a', {href:'#', title:'Remove', 'class':'remove'});
			del_link.insert('<span>Remove</span>');

			del_link.observe('click', this.removeSelectionLink.bindAsEventListener(this));

			new_item.insert(del_link);
			
			new_item.observe('click', this.focusSelection.bindAsEventListener(this));
			new_item.observe('mouseover', this.hoverSelection.bindAsEventListener(this));
			new_item.observe('mouseout', this.unhoverSelection.bindAsEventListener(this));
			
			item.removeClassName('focus');
			
			// Add the selected item to the list
			this.select_list.insert(new_item);
			
			new_item.writeAttribute('value', item.value);
			
			this.focusSelectionElement(null);
			
			if (this.max_items > 0 && this.select_list.select('li').length > this.max_items)
			{
				this.select_list.select('li').first().remove();
			}
			
			this.updateSelectionValues();
		}
	},
	
	focusSelectionElement: function(elt)
	{
		if (this.selection_selector)
		{
			this.selection_selector.removeClassName('focus');
			this.selection_selector.removeClassName('over');
		}
		
		if (elt)
		{
			elt = this.grabParentLI(elt);
			elt.addClassName('focus');
		}
		
		this.selection_selector = elt;		
	},
	
	focusSelection: function(e)
	{
		var elt = Event.element(e);
		
		this.focusSelectionElement(elt);
	},
	
	hoverSelection: function(e)
	{
		var elt = this.grabParentLI(Event.element(e));
			
		elt.addClassName('over');
	},
	
	unhoverSelection: function(e)
	{	
		var elt = this.grabParentLI(Event.element(e));
			
		elt.removeClassName('over');
	},

	// Called from the remove 'x' in each of the select list items
	// removes the item from the list.
	removeSelectionLink: function(e)
	{
		e.stop();
		this.removeSelection(Event.element(e).up());
		return false;
	},

	// Removes the selected item from the list of selected values
	removeSelection: function(item)
	{
		item.remove();
		this.updateSelectionValues();
	},

	// Update the list of selected values in the field and the local variable 'selections'
	updateSelectionValues: function()
	{
		this.selections = [];

		var temp_value = '';
		var temp_list = this.select_list.select('li');

		if (temp_list.length > 0)
		{
			for (var index in temp_list)
			{
				temp_value = temp_list[index].value;

				if (temp_value != null)
				{
					this.selections.push(temp_value);
				}
			}
		}
		
		if (this.selections.length > 0)
		{
			this.select_list.show();
		}
			
		else
		{
			this.select_list.hide();	
		}

		this.field.setValue(this.selections.join(this.seperator));
		this.debug('Input: ' + this.field.getValue());
	},

	// Add the list of default values to the list of selected values
	addDefaultValues: function()
	{
		var temp_list = $(this.defaults);

		if (temp_list)
		{
			var temp_values = temp_list.select('li');

			for (var i = 0; i < temp_values.length; i++)
			{
				this.addSelection($(temp_values[i]));
			}

			temp_list.remove();
		}
	},

	// Adds the provided info to the debug window
	debug: function(info)
	{
		if (this.debug_mode)
		{
			this.debug_info.down().next(1).insert('<li>' + info + '</li>');
		}
	},

	// Toggle the debug window from appearing and hiding
	debugOpenClose: function(e)
	{
		e.stop();
		
		if (this.debug_info.down().next(1).visible())
		{
			Event.element(e).update('Show');
		}
		
		else
		{
			Event.element(e).update('Hide');
		}
			
		this.debug_info.down().next(1).toggle();
	}
});