/**
 * Make instance like;
 *  oHints = new Hints('#result', '#query');
 */

function Hints(sQuery) 
	{
	// set id's of html elements to use
	this.sResult = '';
	this.sQuery = sQuery;

	// search mode
	//this.searchMode = '';

	// last selected hint id
	this.iLastSel = -1;

	// hint data
	this.dataCache = '';

	// cache last value so we know when its changed
	this.lastValue = '';

	this.lastTimeout = 0;

	// the timeout before actually doing the ajax request
	this.ajaxTimeout = 350;

	// stores whether the cursor is hovering over the hints screen or not
	this.bMouseOnHints = false;

	// URL that returns json hints
	this.hintSource = '';

	// this is a callback to render the hint lines
	this.renderHintCallback = '';

	// callback when item is chosen
	this.itemPickedCallback = '';

	this.addResultBox();

	$(this.sResult).mouseover($.proxy(this, 'setMouseOnHints'));
	$(this.sResult).mouseout($.proxy(this, 'unsetMouseOnHints'));

	$(this.sResult + ' div').live('mouseenter', $.proxy(this, 'mouseSelect'));
	$(this.sResult + ' div').live('mouseleave', $.proxy(this, 'mouseUnselect'));

	// hide hint box
	$(this.sQuery).focusout($.proxy(this, 'hideHints'));

	// update hint screen
	$(this.sQuery).keydown($.proxy(this, 'handleKeyPressDown'));
	$(this.sQuery).keyup($.proxy(this, 'handleKeyPressUp'));
	}

/**
 * Add hint resultbox to the dom tree. This is what gets filled with hints
 */
Hints.prototype.addResultBox = function() 
	{
	// turn off autocomplete and outline for query box
	$(this.sQuery)
		.attr('autocomplete', 'off')
		.css('outline', 'none !important')
		.css('outline-style', 'none !important');

	// id for result div
	this.sResult = this.sQuery + '_result';

	// calc result div position
	offset = $(this.sQuery).position();
	// Add query box height+topmargin to top coords. substract 1 pixel to get it even.
	offset.top = offset.top + $(this.sQuery).outerHeight() + parseInt($(this.sQuery).css('margin-top')) - 1;

	// add result div to dom tree
	$(this.sQuery).after('<div id="' + this.sResult.replace('#','') + '" style="top: ' + offset.top + 'px; left: ' + offset.left + 'px; display: none; position: absolute; margin: 0px; background-color: white; border: 1px solid #BBB; min-width: 300px; max-height: 300px; overflow: auto; z-index: 100;">hello world!</div>');
	};

Hints.prototype.setMouseOnHints = function() 
	{ 
	this.bMouseOnHints = true; 
	};
Hints.prototype.unsetMouseOnHints = function() { this.bMouseOnHints = false; };

Hints.prototype.hideHints = function() 
	{ 
	if (!this.bMouseOnHints) $(this.sResult).css('display', 'none');
	};

Hints.prototype.mouseSelect = function(event) 
	{
	$(event.currentTarget).addClass('selected');
	};

Hints.prototype.mouseUnselect = function(event) 
	{
	$(event.currentTarget).removeClass('selected');
	};

/**
 * This function should be called using setTimeout with the timeout set to the delay
 * you want to have in the ajax calls.
 */
Hints.prototype.getHints = function(s)
	{
	s = $(this.sQuery).val();
	if (s == '' || s.length < 3) 
		{
		$(this.sResult).css('display', 'none');
		return;
		}

	url = this.hintSource.replace('%s', s);

	$.getJSON(url, $.proxy(this, 'updatediv'));
	};

/**
 * fills result field with suggestions.
 * this gets called when the ajax request returns
 */
Hints.prototype.updatediv = function(data)
	{
	// cache data so we know what id to add later
	this.dataCache = data;

	if (data.length == 0) 
		{
		$(this.sResult).css('display', 'none');
		return;
		}

	// always empty first, then fill with data
	$(this.sResult).empty();

	// reset selected item
	this.iLastSel = -1;

	// always display hints block when we receive data
	$(this.sResult).css('display', 'block');

	// build hint list
	$.each(data, $.proxy(function(i, item){
		s = this.renderHintCallback(item);
		$(this.sResult).append('<div>' + s + '</div>');
		$(this.sResult).find('div').last().bind('click', {id: i}, $.proxy(this, 'itemPickedHandler'));
		}, this));
	};

/**
 * handle control keys
 */
Hints.prototype.handleKeyPressDown = function(event)
	{
	if (event.keyCode == 38) // up arrow
		{ 
		this.iLastSel--; 
		if (this.iLastSel < 0) this.iLastSel = this.dataCache.length-1;
		this.markItem(); 
		}
	else if (event.keyCode == 40) // down arrow
		{ 
		this.iLastSel++; 
		if (this.iLastSel > this.dataCache.length-1) this.iLastSel = 0;
		this.markItem();
		}
	else if (event.keyCode == 27) // escape
		{ 
		$(this.sResult).css('display', 'none');
		}

	else if (event.keyCode == 13) // enter
		{
		// item was clicked
		event.preventDefault();
		this.itemPicked(this.iLastSel);
		}
	};

/**
 * handle value changes to reload hints. cannot be in the keydown event
 * because the value hasn't changed yet, so it can't be compared
 */
Hints.prototype.handleKeyPressUp = function(event)
	{
	// reload hints if value changed
	if (event.currentTarget.value != this.lastValue)
		{
		this.lastValue = event.currentTarget.value;
		if (this.lastTimeout != 0) clearTimeout(this.lastTimeout);
		this.lastTimeout = setTimeout($.proxy(this, 'getHints'), this.ajaxTimeout);
		}
	};

/**
 * marks an item when moving up and down with cursor keys
 */
Hints.prototype.markItem = function() 
	{
	
	$(this.sResult).find('div').removeClass('selected');

	o = $(this.sResult).find('div').eq(this.iLastSel);
	$(this.sResult).find('div').eq(this.iLastSel).addClass('selected');

	// autoscroll
	/* work in progress...
	if (o.position().top > $(this.sResult).height())
		{
		$(this.sResult).scrollTop(o.position().top-o.height());
		}
	else if (o.position().top < 10)
		$(this.sResult).scrollTop($(this.sResult).scrollTop()-o.height());
	*/
	};

/**
 * Handles the itemPick click
 * this is a seperate handler 'cause the id gets passed as event.data.id
 */
Hints.prototype.itemPickedHandler = function(event) { this.itemPicked(event.data.id); };

/**
 * hide hint box and fire callback function
 */
Hints.prototype.itemPicked = function(id)
	{
	$(this.sResult).css('display', 'none');
	this.itemPickedCallback(this.dataCache[id]);
	};



