/**
 * A widget for searching through an list of friends and tags - with user as the first option.
 * @author Erick S <esatire at gmail dot com>
 * @requires common.js, prefindex.js
 * @version 1.0 24/02/2008
 */

/** Static properties */
TagSearchBox.DEFAULT_INPUT_VALUE = 'הכניסו כאן תיאור או שם';
TagSearchBox.NO_RESULTS = 'לא נמצאו תוצאות';
TagSearchBox.RESULTS_LIMIT = 100;
TagSearchBox.NUMERIC_REGEX = /^\d+$/;
TagSearchBox.SELECTED_COLOR = '#9DD6DD';
/* These variables indicate the field positions of the sent array */
/** Which field holds the user name. We sort the array by this field */
TagSearchBox.NAME_FIELD = 1;
/** The user ID field. Used as a key for the internal items array */
TagSearchBox.ID_FIELD = 0;
/** Overrides any CSS definition */
TagSearchBox.LIST_BORDER_WIDTH = 1;
/** Prefix each item id - I.E "item0","item1", and so forth */
TagSearchBox.ITEM_ID = "item";

/** Constructor 
 *
 * @param {string} containerId DOM element (Preferably a DIV) to contain this class elements
 * @param {array} items Default: Each item contains [0]->name,[1]->id. Starts with a 'u' for user, or 't' for tag.
 * @param {number} maxLength Optional: If you want to limit input length
 * @param {Object} valueInput Optional: A form element (Not name or ID) that will hold the value of search results.
 */
function TagSearchBox(containerId,items,maxLength,valueInput)
{
	var me = this;
	// Assign items
	this.items = new Object();
	var user = items.shift();
	this.items[user[TagSearchBox.ID_FIELD]] = user[TagSearchBox.NAME_FIELD];
	// sort the rest of them
	items.sort(TagSearchBox.compare);
	// We create a new list - grouped by key - For search by ID
	for (var i=0;i<items.length;i++)
	{
		this.items[items[i][TagSearchBox.ID_FIELD]] = items[i][TagSearchBox.NAME_FIELD];
	}
	this.index = new PrefixIndex(this.items);

	this.ids = null;
	this.names = null; // Will contain an array of DIVs
	this.selectedIndex = null;

	this.onAfterSearch = null; // Attach a function that will run when selecting a value
	// create the html
	this.container = document.getElementById(containerId);
	if (this.container == null) throw Error('אלמנט לא קיים!');
	/* The ok button */
	this.okButton = document.createElement('INPUT');
	this.okButton.type = 'button';
	this.okButton.value = 'אישור';
	this.okButton.className = 'btn TagSearchButton';
	this.container.appendChild(this.okButton);
	this.okButton.onclick = function() { me.endSearch() };
	/* The text field */
	this.input = document.createElement('INPUT');
	this.input.className = 'TagSearchInput';
	this.input.type = 'text';
	this.input.value = TagSearchBox.DEFAULT_INPUT_VALUE;
	this.lastValue = TagSearchBox.DEFAULT_INPUT_VALUE;
	if (maxLength) this.input.setAttribute('maxlength',maxLength);
	this.container.appendChild(this.input);
	/* The popup list */
	this.list = document.createElement('DIV');
	this.list.className = 'TagSearchList';
	this.list.style.borderWidth = TagSearchBox.LIST_BORDER_WIDTH + 'px';
	this.container.appendChild(this.list);

	/** A custom form element (Hidden) which will be updated on search end */
	this.valueInput = (valueInput) ? valueInput : null;
 	
	/* ### METHODS ### */

	this.getValue = function()
	{
		if (this.ids == null || this.selectedIndex == null) return null;
		return this.ids[this.selectedIndex];
	}
	/** Careful with calling this explicitly */
	this.setValue = function(value)
	{
		this.input.value = value;
		this.lastValue = value;
	}

	/** 
	 * Marks an item on the list, and sets the internal variables accordingly.
	 * Note: Does not open the list itself.
	 * @param {number} n
	 */
	this.selectItem = function(n)
	{
		if (this.ids == null || this.ids.length == 0) return;
		if (isNaN(n) || this.selectedIndex == null) n = 0;
		else if (n < 0) n = this.ids.length-1;
		else if (n >= this.ids.length) n = 0;
		// Unmark the last item
		if (this.selectedIndex != null)
			this.names[this.selectedIndex].style.backgroundColor = 'transparent';

		var selected = this.names[n];
		selected.style.backgroundColor = TagSearchBox.SELECTED_COLOR;
		// Scroll this item into
		var listHeight = this.list.offsetHeight - TagSearchBox.LIST_BORDER_WIDTH*2;
		var offset = selected.offsetTop + selected.offsetHeight + TagSearchBox.LIST_BORDER_WIDTH;

		if (offset > listHeight + this.list.scrollTop)
			this.list.scrollTop = offset - listHeight;
		else if (offset <= this.list.scrollTop)
			this.list.scrollTop = selected.offsetTop + TagSearchBox.LIST_BORDER_WIDTH;

		// Update inner vars
		this.selectedIndex = n;
		if (this.input.value != this.items[this.ids[n]]) this.setValue(this.items[this.ids[n]]);
	}
	
	this.showAll = function()
	{
		var ids = new Array();
		resultText = new Array();
		var key,count=0,content;
		for (key in this.items)
		{
			content = '<div id="'+TagSearchBox.ITEM_ID + count+'" class="TagSearchListItem">';
			if (key.charAt(0) == 'u') content += '<span style="float:left">'+key.substr(1)+'</span>';
			content += this.items[key]+'</div>';

			resultText.push(content);			
			ids.push(key);
			count++;
		}
		this.list.innerHTML = resultText.join('');
		this.ids = ids;

		this.names = this.list.getElementsByTagName('div');
	}

	this.endSearch = function()
	{
		if (this.ids != null && this.selectedIndex != null)
		{
			var selectedKey = this.ids[this.selectedIndex];
			this.setValue(this.items[selectedKey][0]);
			// Update the form field, if one was supplied
			if (this.valueInput != null) this.valueInput.value = selectedKey;
			// Custom function to run after the search
			if (this.onAfterSearch)
			{
				this.onAfterSearch(selectedKey,this.items[selectedKey]);
			}
		}
		else // If no value was chosen - acts like abortSearch
		{
			if (this.onAfterSearch) this.onAfterSearch(null,this.input.value);
			this.setValue('');
		}
		// Clean search related variables
		this.ids = this.names = this.selectedIndex = null;
	}
	/** Exists for clarity reasons */
	this.abortSearch = function()
	{
		// Clean search related variables
		this.ids = this.names = this.selectedIndex = null;
		this.setValue('');
		if (this.onAfterSearch) this.onAfterSearch(null,null);
	}
	/** Cleans the list view */
	this.resetListView = function()
	{
		this.setValue(TagSearchBox.DEFAULT_INPUT_VALUE);
		this.list.innerHTML = '';
	}

	/** Usually Called from the onkeyup event. */
	this.search = function(value)
	{
		// Reset any previous result holders
		this.ids = this.names = this.selectedIndex = null;
		// On empty value - hide box and return
		if (value.length == 0)
		{
			this.list.innerHTML = '';
			return;
		}
		// Default result
		var resultText = TagSearchBox.NO_RESULTS;
		var found = false,content = null;
		// If numeric - try searching for a user with this id
		if (value.match(TagSearchBox.NUMERIC_REGEX))
		{
			value = 'u' + value;
			if (typeof(this.items[value]) != 'undefined')
			{
				ids = [value];
				value = this.items[value]; // So it will be lit later
				found = true;
			}
		}
		// The name index
		if (!found)
		{
			var ids = this.index.search(value,TagSearchBox.RESULTS_LIMIT);
		}
		// Found gets a different purpose - If an exact match is found - highlight it
		found = null;
		if (ids.length > 0)
		{
			resultText = new Array();
			for (var i=0,count = 0;i<ids.length;i++,count++)
			{
				content = '<div id="'+TagSearchBox.ITEM_ID + count+'" class="TagSearchListItem">';
				if (ids[i].charAt(0) == 'u') content += '<span style="float:left">'+ids[i].substr(1)+'</span>';
				content += this.items[ids[i]]+'</div>';

				resultText.push(content);
				if (this.items[ids[i]] == value) found = i;
			}
			resultText = resultText.join('');
			this.ids = ids;
		}

		this.list.innerHTML = resultText;
		this.names = this.list.getElementsByTagName('div');
		if (found != null) this.selectItem(found);
	}

	/* ### DOM Event handlers ### */

	/** Pretty straightforward */
	this.input.onfocus = function() { this.select(); }

	/** Checks if a search is required */
	this.input.onkeyup = function(evt)
	{
		(window.event) ? window.event.cancelBubble=true : evt.cancelBubble=true;
		var keyCode = getEventKeyCode(evt);
		// Escape must be processed here or Firefox won't allow input value change
		if (keyCode == 27)
		{
			me.abortSearch();
			// Another Firefox hack - without blur, another press on ESC will show our last value
			me.input.blur();
			return;
		}
		if (me.lastValue != this.value)
		{
			me.lastValue = this.value;
			me.search(this.value);
		}
	}
	/** Responds to hotkeys */
	this.input.onkeydown = function(evt)
	{
		(window.event) ? window.event.cancelBubble=true : evt.cancelBubble=true;
		var keyCode = getEventKeyCode(evt);
		switch (keyCode)
		{
			case 13: // Enter - Picks the currently highlighted name
				me.endSearch();
			break;
			case 38: // Up arrow
				// No list - show all friends
				if (me.ids) me.selectItem(me.selectedIndex-1);
				else if (me.input.value == '') me.showAll();
			break;
			case 40: // Down arrow
				if (me.ids) me.selectItem(me.selectedIndex+1);
				else if (me.input.value == '') me.showAll();
			break;
			case 33: // PageUp
				if (me.ids)
				{
					var n = me.selectedIndex-Math.floor(me.list.offsetHeight/me.names[0].offsetHeight);
					if (n < 0) n = 0;
					me.selectItem(n);
				}
			break;
			case 34: // PageDown
				if (me.ids)
				{
					var n = me.selectedIndex+Math.floor(me.list.offsetHeight/me.names[0].offsetHeight);
					if (n >= me.ids.length) n = me.ids.length-1;
					me.selectItem(n);
				}
			break;
			default:
				return true;
			break;
		}
		return false;
	}
	/** Prevents clicks on input from hiding the list */
	this.input.onmousedown = function(evt)
	{
		(window.event) ? window.event.cancelBubble=true : evt.cancelBubble=true;
	}
	
	this.list.onmouseover = function(evt)
	{
		(window.event) ? window.event.cancelBubble=true : evt.cancelBubble=true;		
		var target = getEventTarget(evt);
		if (target == me.list || !target.id) return;

		var i = parseInt(target.id.substr(TagSearchBox.ITEM_ID.length));

		if (me.selectedIndex != i) // A new item is selected
		{
			if (me.selectedIndex != null)
				me.names[me.selectedIndex].style.backgroundColor = 'transparent';

			me.names[i].style.backgroundColor = TagSearchBox.SELECTED_COLOR;
			me.selectedIndex = i;
			me.setValue(me.items[me.ids[i]]);
		}
	}
	/** Any click on the list */
	this.list.onmousedown = function(evt)
	{
		(window.event) ? window.event.cancelBubble=true : evt.cancelBubble=true;
		var target = getEventTarget(evt);
		if (target.parentNode == me.list) me.endSearch();		
	}	

	/** Any click outside this object should close it and cancel the search as if we pressed ESC */
	this.docMouseDown = function(evt)
	{
		(window.event) ? window.event.cancelBubble=true : evt.cancelBubble=true;
		me.abortSearch();
	}
}
/** Compare function for the initial sorting */
TagSearchBox.compare = function(item1,item2)
{
	var a = item1[TagSearchBox.NAME_FIELD];
	var b = item2[TagSearchBox.NAME_FIELD];
	if (a < b) return -1;
	if (a > b) return 1;
	return 0;
}
