/**
 * Rm_Suggest MUST be single for each "suggested" input-element
 * @param {Object} input HTML element
 * 
 */

function Rm_Suggest(input)
{
	this.input = input;
	
	this.puntoSwitcher = new PuntoSwitcher();
};

Rm_Suggest.prototype.hidden = null; //hiden input
Rm_Suggest.prototype.input = null; // visible input
Rm_Suggest.prototype.oldValue = '';
Rm_Suggest.prototype.list = null; // Rm_Suggest_List object
Rm_Suggest.prototype.cache = null; // query cache
Rm_Suggest.prototype.timeoutID = 0; // for window.setTimeout
Rm_Suggest.prototype.minSearchLength = 3; //Default minimum length of a search string





/**
 * init input element as suggest
 */
Rm_Suggest.prototype.init = function(param)
{
	$(this.input).addClass('rm-suggest-input');
		
	if(!this.hidden)
	{
		this.hidden = this.createHiddenInput();
		
		// rename inputs
		$(this.hidden).attr('name', $(this.input).attr('name'));
		var inputName = param.fieldName ? param.fieldName :  $(this.input).attr('name')+'_suggest';
		if(param.fieldId)
			$(this.hidden).attr('id', param.fieldId);
		$(this.input).attr('name', inputName);
	}
	
	if(!this.list)
	{
		this.list = new Rm_Suggest_List(this);
	}
	
	if(!this.cache)
	{
		this.cache = new Rm_Suggest_Cache(); 
	}
	
	this.cache.clear();
	
	
	this.oldValue = $(this.input).val();
	
	this.list.alignByInput(this.input);
	
	var rmSuggestObject = this;
	$(this.input).keydown(function(k)
	{
		rmSuggestObject.keyDown(k);
	});

	$(this.input).keyup(function(k)
	{
		rmSuggestObject.keyUp(k);
	});

	$(this.input).blur(function()
	{
		rmSuggestObject.list.hide();
	});

	$(this.list.scrollDIV).mouseover(function()
	{
		rmSuggestObject.list.mouseInside = true;
	});

	$(this.list.scrollDIV).mouseout(function()
	{
		$(rmSuggestObject.input).focus();
		rmSuggestObject.list.mouseInside = false;
	});
	
	
	this.list.init(this.input);
	
	if(param.url)
		this.url(param.url);
		
	if(param.urlVar)
		this.urlVar(param.urlVar);

	if(param.data)
		this.selectedData(param.data);

	if(param.value)
		this.selectedValue(param.value);
		
	if(param.minSearchLength)
		this.minSearchLength = param.minSearchLength;
	this.processId=0;
	this.processObject = false;
	$(window).resize(function (){
		rmSuggestObject.list.alignByInput(rmSuggestObject.input);
	});
};


/**
 * Creates hidden input to stode selected Data
 * @return {Object} HTML INPUT element
 */

Rm_Suggest.prototype.createHiddenInput = function()
{
	return $(this.input).after('<input type="hidden" />').next().get();
}



/**
 * Updates selectedData value according to entered text
 */

Rm_Suggest.prototype.updateDataByEnteredValue = function()
{
	var v = this.selectedValue();
	var obj = this.list.map[v];
	if (undefined != obj) {
		this.selectedData(obj.data)
	}
	else 
	{
		this.selectedData('');
	}
}





/**
 * Text changed
 */

Rm_Suggest.prototype.change = function()
{
	this.loadListAndShowDelayed();
	this.updateDataByEnteredValue();
}


Rm_Suggest.prototype.loadListAndShowDelayed = function()
{
	if(this.timeoutID)
		window.clearTimeout(this.timeoutID);
		
	rmSuggestObject = this;
	this.timeoutID = window.setTimeout(function(){rmSuggestObject.loadListAndShow();}, 500);
}


/**
 * get/set suggest option
 * @param {Object} name
 * @param {Object} value
 */

Rm_Suggest.prototype.getSetOption = function(name, value)
{
	if(undefined != name)
	{
		return $(this.input).attr('suggest_option_'+name, value);	
	};
	
	return $(this.input).attr('suggest_option_'+name);
}


/**
 * get/set url
 * @param {Object} value
 */

Rm_Suggest.prototype.url = function(value)
{
	if(value)
		this.cache.clear();
	return this.getSetOption('url', value);
}

/**
 * get/set url variable name with entered text
 * @param {Object} value
 */

Rm_Suggest.prototype.urlVar = function(value)
{
	if(value)
		this.cache.clear();
	return this.getSetOption('url_var', value);
}


/**
 * HTML element indicating that ajax transaction is active
 */
Rm_Suggest.prototype.ajaxProgress = null;

/**
 * On ajax transaction start
 */
Rm_Suggest.prototype.ajaxStart = function()
{
	if(this.ajaxProgress)
		return;
	this.ajaxProgress = $('<img class="rm-suggest-ajax-indicator" src="/img/indicator.gif" style="position:absolute;" />').insertBefore(this.input).get(0);
	$(this.input).addClass('rm-suggest-busy');
}

/**
 * On ajax transaction stop
 */
Rm_Suggest.prototype.ajaxStop = function()
{
	$(this.input).prev('img.rm-suggest-ajax-indicator').remove();

	this.ajaxProgress = false;
	$(this.input).removeClass('rm-suggest-busy');
}


/**
 * Loads list from server and shows result
 */
Rm_Suggest.prototype.loadListAndShow = function()
{
	if(!this.url())
		return;
	var myProcessId = Math.random();
	this.processId=myProcessId;
		
	var parseAndShow = function(rmSuggestObject, xml)
	{
			rmSuggestObject.list.loadFromXML(xml);
			rmSuggestObject.list.rebuild();
			rmSuggestObject.list.show();
			rmSuggestObject.updateDataByEnteredValue();
	}
		
		
	var val = $(this.input).val();
	var rmSuggestObject = this;
	
	var xml = this.cache.get(val);
	if(undefined == xml) // CACHE FAIL
	{
		this.ajaxStart();
		if(this.processObject)
			this.processObject.abort();
		this.processObject = $.ajax({
			timeout: 10000,
			url: this.url(),
			data: this.urlVar()+"="+encodeURIComponent(val),
			success: function(xml){
				if(rmSuggestObject.isLastProcess(myProcessId))
				{
					parseAndShow(rmSuggestObject, xml);
					rmSuggestObject.cache.put(val, xml);
				}
			},
			complete: function(){
				if(rmSuggestObject.isLastProcess(myProcessId))
					rmSuggestObject.ajaxStop();
				rmSuggestObject.processObject = false;
			}
		});
	}
	else // CACHE HIT!
	{
		parseAndShow(this, xml);
	};
}

 
Rm_Suggest.prototype.isLastProcess = function(checkProcessId)
{
	return this.processId==checkProcessId;
}


/**
 * get/set suggest value (typed text)
 * @param {Object} val
 */
Rm_Suggest.prototype.selectedValue = function(val)
{
	if(undefined != val)
	{
		this.oldValue = val;
	};
	return $(this.input).val(val);	
}

/**
 * get/set suggest data (value for hidden input)
 * @param {Object} val
 */
Rm_Suggest.prototype.selectedData = function(val)
{
	return $(this.hidden).val(val);	
}




/**
 * What to do on key pressed
 * @param {Object} k key object
 */

Rm_Suggest.prototype.keyDown = function(k)
{
	switch(k.keyCode)
	{
		case 38: // UP
			this.list.selectPrev();
			break;

		case 40: // DN
			if(this.list.visible())
				this.list.selectNext()
			else
				this.loadListAndShowDelayed();
			break;

		case 27: // ESC
			this.list.hide(true);
			break;

		case 13: // ENTER
			if(this.list.visible())
			{
				$("form").bind("submit", function() { return false; });
				if(!this.list.getSelectedItem())
					this.list.hide(true);
				else
					this.list.itemClick(this.list.getSelectedItem());
				window.setTimeout(function () {$("form").unbind("submit");},100);
			}
			break;
		case 9: // BS
			break;

		case 8: // TAB
			break;
			
		default:
			//this.list.show();

	}
	
	this.list.scrollToSelected();


}

/**
 * on keyUp event.
 * User to determine text change
 * @param {Object} key
 */


Rm_Suggest.prototype.keyUp = function(key)
{
	//Punto Switcher
	this.puntoSwitcher.convertToRus( key, this.input );
	
	if(this.oldValue != $(this.input).val())
	{
		
		var oldLength = this.oldValue.length;
		var newLength = $(this.input).val().length;
		this.oldValue = $(this.input).val();
		if(oldLength > newLength || newLength >= this.minSearchLength)
		{
			this.change();
		}
	}
}




/**
 * get/set input value
 * @param {Object} v
 */

Rm_Suggest.prototype.value = function(v)
{
	if(undefined != v) 
		$(this.input).val(v);
	
	return $(this.input).val();
}

/**
 * get selected data (NOT value!)
 * @return selected data("data" property of selected list item) or FALSE
 */

Rm_Suggest.prototype.data = function()
{
	
}





/************************
 * 
 * Rm_Suggest_List
 * 
 ************************/

function Rm_Suggest_List(suggestObject)
{
	this.suggest = suggestObject;

	var id = $(this.suggest.input).attr('id')+'_suggest_list';
	
	var $scrollDIV = $('<div id="'+id+'" class="rm-suggest-scroller"></div>');
	var $listDIV = $('<div class="rm-suggest-list"></div>');
	
	if(isNaN($(this.suggest.input).zindex()))
		var zindex=0;
	else
		var zindex=$(this.suggest.input).zindex();
		$scrollDIV.zindex(zindex+100);
	
	
	$listDIV.appendTo($scrollDIV);
	$scrollDIV.appendTo('body');

	this.scrollDIV = $scrollDIV.get(0);
	this.listDIV = $listDIV.get(0);
	
	this.raw = [];
	this.map = {};
	

}

Rm_Suggest_List.prototype.suggest = {};
Rm_Suggest_List.prototype.scrollDIV = {};
Rm_Suggest_List.prototype.listDIV = {};
Rm_Suggest_List.prototype.raw = []; // array of {value, data}
Rm_Suggest_List.prototype.map = {}; // inverse map (value => {value, data})
Rm_Suggest_List.prototype.mouseInside = {};


/**
 * Select first item, bind click event
 */

Rm_Suggest_List.prototype.init = function()
{
	this.deselectAll();
	this.selectFirst();
	var rmSuggestListObject = this;
	
	$(this.listDIV).children().click(function()
	{
		rmSuggestListObject.itemClick(this);
	});

	$(this.listDIV).children().mouseover(function()
	{
		rmSuggestListObject.selectItem(this);
	});
}

/**
 * Aligns list div under input element
 * @param {Object} input HTML element
 */

Rm_Suggest_List.prototype.alignByInput = function()
{
	var input = this.suggest.input;
	
	$(this.scrollDIV).width($(input).width());
	var h = $(input).height();
	var o = $(input).offset();
	$(this.scrollDIV).css('left', o.left);
	$(this.scrollDIV).css('top', o.top+h+5); /* @TODO: make fine tuning options */
	 
}

/**
 * @return {Number} current list length
 */

Rm_Suggest_List.prototype.length = function()
{
	return $(this.listDIV).children('p').length;
}

/**
 * deselects all list items
 */

Rm_Suggest_List.prototype.deselectAll = function()
{
	$(this.listDIV).children('p').removeClass('rm-suggest-list-item-selected');
	$(this.listDIV).children('p').addClass('rm-suggest-list-item');
}

/**
 * get selected item
 * @return {Object} HTML element
 */
Rm_Suggest_List.prototype.getSelectedItem = function()
{
	return $(this.listDIV).children('p.rm-suggest-list-item-selected').get(0);
}

/**
 * select first item
 */
Rm_Suggest_List.prototype.selectFirst = function()
{
	$(this.listDIV).children('p').eq(0).addClass('rm-suggest-list-item-selected');
}

/**
 * move selection down
 */

Rm_Suggest_List.prototype.selectNext = function()
{
	if(!this.getSelectedItem())
		return false;

	var $current = $(this.getSelectedItem());
	if(!$current.next().get(0)) 
		return false;
		
	$current.removeClass('rm-suggest-list-item-selected');
	$current.next().addClass('rm-suggest-list-item-selected');
	return true;
}

/**
 * move selection up
 */
Rm_Suggest_List.prototype.selectPrev = function()
{
	if(!this.getSelectedItem())
		return false;

	var $current = $(this.getSelectedItem()); 

	if(!$current.prev().get(0)) 
		return false;


		
	$current.removeClass('rm-suggest-list-item-selected');
	$current.prev().addClass('rm-suggest-list-item-selected');
	return true;
}


/**
 * select particular item
 * @param {HTML element} item
 */
Rm_Suggest_List.prototype.selectItem = function(item)
{
	var $current = $(this.getSelectedItem()); 
	if(!item) 
		return false;
		
	$current.removeClass('rm-suggest-list-item-selected');
	$(item).addClass('rm-suggest-list-item-selected');
	return true;
}


/**
 * scroll list to make selected item visisble
 */
Rm_Suggest_List.prototype.scrollToSelected = function()
{
	if(!this.visible)
		return;
	if(!this.getSelectedItem())
		return false;

	var itemOffset = $(this.getSelectedItem()).offset();
	var listOffset = $(this.listDIV).offset();
	
	var relativeTop = (itemOffset.top - listOffset.top);
	this.scrollDIV.scrollTop = relativeTop - ($(this.scrollDIV).height() / 3);
}



/**
 * item clicked by user
 * @param {Object} item
 */
Rm_Suggest_List.prototype.itemClick = function(item)
{
	this.selectItem(item);

	this.suggest.selectedValue(this.item2obj(item).value);
	this.suggest.selectedData(this.item2obj(item).data);

	this.hide(true);
}


/**
 * hide list
 */
Rm_Suggest_List.prototype.hide = function(force)
{
	if(!force && this.mouseInside)
		return;
		
	$('.hide_select').show();		
	$(this.scrollDIV).css('visibility', 'hidden');
}

/**
 * show list
 */
Rm_Suggest_List.prototype.show = function()
{
	this.alignByInput();
	$('.hide_select').hide();
	$(this.scrollDIV).css('visibility', 'visible');
	this.scrollToSelected();
}

/**
 * is list visible
 * @return {Boolean} visible
 */

Rm_Suggest_List.prototype.visible = function()
{
	return ('visible' == $(this.scrollDIV).css('visibility'));
}

/**
 * clear list
 */

Rm_Suggest_List.prototype.clear = function()
{
	$(this.listDIV).html('');
}

/**
 * list object to HTML P element
 * @param {Object} obj
 * @return HTML P element
 */

Rm_Suggest_List.prototype.obj2item = function(obj)
{
	return '<p suggest_data="'+obj.data+'">'+obj.value+'</p>';
	
}

/**
 * Create item object based in HTML P element
 * @param {Object} item HTML P element
 * @return {Object} list item object
 */
Rm_Suggest_List.prototype.item2obj = function(item)
{
	var o = {};
	o.data = $(item).attr('suggest_data');
	o.value = $(item).html();
	return o;
}

/**
 * 
 * @param {Array} list of objects
 * @return HTML string of P elements
 */
Rm_Suggest_List.prototype.list2html = function(list)
{
	var items = '';
	var i = 0;
	for(i = 0; i < list.length; i++)
	{
		items += this.obj2item(list[i]);
	};
	
	return items;
}

/**
 * Does item object meet pattern
 * @param {Object} pattern
 * @param {Object} obj
 * @return {Boolean} 
 */

Rm_Suggest_List.prototype.criteria = function(pattern, obj)
{
	return (-1 < obj.value.indexOf(pattern));
}


/**
 * Rebuild list, basing on current input value
 */

Rm_Suggest_List.prototype.rebuild = function()
{
	this.clear();
	$(this.listDIV).html(this.list2html(this.raw));
	this.init();
}

/**
 * Get list item by value
 * @param {Object} val
 * @return list item object
 */

Rm_Suggest_List.prototype.getItemByValue = function(val)
{
	if(!this.map[v]) return undefined;
	
	return this.raw
}


Rm_Suggest_List.prototype.xml2list = function(xml)
{
}

Rm_Suggest_List.prototype.loadFromXML = function(xml)
{
	var nodes = $('item', xml).get();
	this.map = {};
	this.raw = [];

	var i = 0;
	for(i = 0; i < nodes.length; i++)
	{
		var v = $(nodes[i]).attr('name');
		var d = $(nodes[i]).attr('id');
		
		var o = {
			data: d,
			value: v
		};
		
		this.raw.push(o);
		
		this.map[v] = o;
	};
}





/***********************
 * 
 * ajax query results cache
 * 
 **********************/

function Rm_Suggest_Cache()
{
	this.storageForKey = {};
	this.storageForEmptyKey = undefined;
}

Rm_Suggest_Cache.prototype.storageForKey = {};
Rm_Suggest_Cache.prototype.storageForEmptyKey = undefined;

Rm_Suggest_Cache.prototype.clear = function()
{
	this.storageForEmptyKey = undefined;
	this.storageForKey = {};
	return true;
}

Rm_Suggest_Cache.prototype.get = function(key)
{
	key = key.toString();
	if ('' == key) 
		return this.storageForEmptyKey
	else
		return this.storageForKey[key];
		
}

Rm_Suggest_Cache.prototype.put = function(key, val)
{
	key = key.toString();
	if('' == key)
		this.storageForEmptyKey = val
	else
		this.storageForKey[key] = val;
}








/*************************************
 * 
 * Bind jQuery plugin
 * 
 ************************************/

jQuery.fn.rmsuggest = function()
/**
 * There can be only one (c) instance of Rm_Suggest for each html input element.
 * So we will cache instances in jQuery.fn.rmsuggest_cache one per input's ID 
 */
{
	var $input = this.filter('input').eq(0); // only input elements can be "suggested"
	
	if($input.length != 1)
	{
		throw new Error('Rm_Suggest: INPUT element not found');
		return false;
	};

	var id = $input.attr('id');
	if(!id)
	{
		throw new Error('Rm_Suggest: INPUT#ID not found');
		return false;
	};
	
	if(!jQuery.fn.rmsuggest_cache[id])
	{
		jQuery.fn.rmsuggest_cache[id] = new Rm_Suggest($input.get(0));
	};
	
	return jQuery.fn.rmsuggest_cache[id];
}

jQuery.fn.rmsuggest_cache = {}; //cache itself

