
/**
 * class CmooAutoComplete
 * Az autoComoplete mező. Csak a megjelenítésért, és a felhasználói interakciókért felelős.
 * A szerverrel történő kommunikációt egy CSuggestProvider objektum közreműködésével végzi.
 * @todo
 * A szerver oldalról egy JSON objektumnak kell visszajönnie, amelynek kötelezően tartalmazina kell egy list nevű attributumot.
 * Ez egy tömb, vagy null. A tömb elemei szintén objektumok amelyek a következő attributumokkal kell, hogy rendelkezzenek: html, plain, value
 * A html a legördülő listában megjelenő elem, ami lehet egyszerű szöveg. A plain az a szöveg, amelynek az input mezőbe kell kerülnie kiválasztás esetén.
 * A value az érték, amely egy hidden mezőbe kerül amely az input mező.name + '_' megnevezéssel rendelkezik;
 * A lista elem objektumhoz, vagy  még adható plusz név-érték pár. Ha a konstruktor aPFields paraméterében a név meg volt adva, akkor megkeresi az adott id-jű input elemet , és ezt az értéket adja hozzá kiválasztáskor.
 * Ugyanez érvényes visszafelé, a javaslatkéréskor legyűjti ezen mezők aktuális értékét, és elküldi a szerver felé. Tehát ezeket a mezőket használhatjuk oda-vissza egyaránt. 
 * <code>
 * var oRManager = new CmooRequestManager();
 * var oAComplete = new CmooAutoComplete ($('from'),new CSuggestProvider('./ajax/autoairport.php','post',oManager,'auto_location'),['f_type']);
 * </code>
 */
var CmooProgramAutoComplete = new Class ({
	/**
	 * maximum ennyi elem lehet a legördülő listában
	 */
	MAX_LIST_LENGTH : 10,
	/**
	 * megadja, hogy hány kaionrakter leütés után reagáljon
	 */ 
	MIN_CHAR_COUNT : 2,
	/**
	 * A gyorsgépelés késleltetése 
	 */
	PRESS_DELAY : 280,
	/** 
	 * az aktuálisan kiválasztott lem indexe
	 */		
	curr : -1,
	/**
	 *	Az input mező
	 */
	inputField : null,
	/**
	 * A mező neve
	 */
	fieldName : null,
	/**
	 *	itt tároljuk a válasz listát
	 */
	resultList : null,
	/**
	 * plusz figyelendo mezo ha van
	 */
	plusFields : new Array,
	/**
	 * az ajánlat szolgáltató
	 */
	suggestProvider : null,
	/**
	 * az ajánlatokat tartalmazó konténer.
	 */	
	resultCnt : null,
	/**
	 * a billenytű  lenyomások késleltető objektuma
	 */
	timeoutId : null,
	/**
	 * Az onComplete utan hivodik meg
	 */
	fnCallback : null,
	/**
	 * Az aktuális adatokat tárolja. 
	 */
	temp : null,
	/**
	 * Konstruktor.
	 * @param oIField Az input mező. Lehet Element, vagy létező id. Ha mégsincs ilyen elem, akkor a body-hoz hozzáfűz egy újat.
	 * @param oSProvider A kiegészítést ajánló szolgáltató objektum.
	 * @param aPFields Azon plusz elem, vagy azok tömbje, amely(ek) adatait el akarjuk küldeni, illetve az ajánlatból fel akarjuk tölteni.
	 */
	initialize : function (oIField, oSProvider, aPFields, fOnComplete, fOnInsert) {
		if (!$chk($(oIField))) {
			new Element ('input',{'type':'text','id':oIField,'name':oIField}).inject($(document.body));
		}
		this.inputField = $(oIField);
		this.inputField.set('autocomplete','off');
		this.fieldName = this.inputField.get('name');
		this.suggestProvider = oSProvider;
		//this.plusFields = aPFields;
		// rejtett mező létrehozása
		if(!$chk($(this.fieldName +'_'))) {
			this.hiddenField = new Element ('input',{'type':'hidden','name':this.fieldName + '_','id':this.fieldName + '_','value':''}).inject(this.inputField,'after');
		}
		else {
			this.hiddenField = $(this.fieldName +'_');
		}
		// onComplete callback 
		if ($type(fOnComplete) == 'function'){
			this.fnCallback = fOnComplete;
		}
		
				if ($type(fOnInsert) == 'function'){
			this.fnInsertCallback = fOnInsert;
		}

		// a további figyelt mezők
		var oPFiled = null;
		if ($type(aPFields) == 'array') {
			for (var i = 0; i< aPFields.length; ++ i) {
				if ($chk(oPField = $(aPFields[i]))) {
					this.plusFields.push(oPField);
				}
				else {
					oPField = new Element('input',{'type':'hidden','name':aPFields[i],'id':aPFields[i],'value':''});
					oPField.inject(this.hiddenField,'after');
					this.plusFields.push(oPField);
				}
			}
		}
		else if ($chk(aPFields)) {
			if ($chk(oPField = $(aPFields))) {
				this.plusFields.push(oPField);
			}
			else {
				oPField = new Element('input',{'type':'hidden','name':aPFields,'id':aPFields,'value':''});
				oPField.inject(this.hiddenField,'after');
				this.plusFields.push(oPField);
			}
		}
		// a legördülő abalak létrehozása
		this.createDropDown();
		//
		this.inputField.addEvent('keyup', function(oEvent){this.onKeyUp(oEvent);}.bind(this));
		this.inputField.addEvent('keydown', function(oEvent){this.onKeyDown(oEvent);}.bind(this));
		this.inputField.addEvent('blur',function() {this.suggestProvider.cancel();(function (){this.hideDropDown();}).delay(100,this);}.bind(this));
		//document.addEvent('click',function() {this.suggestProvider.cancel();this.hideDropDown()}.bind(this));
		document.addEvent('scroll',function () {this.setPosition(this.resultCnt)}.bind(this));
		window.addEvent('resize',function () {this.setPosition(this.resultCnt)}.bind(this));
	},
	/**
	 * Visszatölti a mentett állapotot.
	 */
	_backup : function () {
		if($chk(this.temp)) {
			this.inputField.set('value',this.temp.iText);
			this.hiddenField.set('value',this.temp.hText);
			if($chk(this.temp.pFields)) {
				for (var i= 0; i< this.plusFields.length; i++) {
					if ($chk(this.temp.pFields[this.plusFields[i].get('name')])) {
						this.plusFields[i].set('value',this.temp.pFields[this.plusFields[i].get('name')]);
					}
				}
			}	
			// this.fnCallback meghivasa
			if ($chk(this.fnCallback)) this.fnCallback();
		}
	},
	_empty : function () {
		this.resultList = null;
		this.listCnt.empty();
	},
	/**
	 * Elmenti a autocomplet mező és a kiegészítő mezők aktuális állapotát.
	 */
	_save : function () {
		this.temp = {
			'iText' : this.inputField.get('value'),
			'hText' : this.hiddenField.get('value'),
			'pFields' : []
		};
		if ($chk(this.plusFields)) {
			for (var i = 0; i< this.plusFields.length; i++) {
				this.temp.pFields[this.plusFields[i].get('name')] = this.plusFields[i].get('value');
			}
		}	
	},
	/**
	 * Beállítja az input mező és a hidden mező értékét, valamint a plusz mezőkét, ha vannak ilyenek.
	 * @param oValues A html,plain,value, és plusz mezők adatait tartalmazó objektum.
	 */
	_setValue : function (oValues) {
		if ($type(oValues) == 'object') {
			this.inputField.set('value',oValues.plain);
			this.hiddenField.set('value',oValues.value);
			for (var i = 0; i < this.plusFields.length; i++) {
				if ($chk(oValues[this.plusFields[i].get('name')])) {
					this.plusFields[i].set('value',oValues[this.plusFields[i].get('name')]);
				}
			}
			
			// this.fnCallback meghivasa
			// alert('xxxx');fInsert
			
			if ($chk(this.fnInsertCallback)) this.fnInsertCallback();
			if ($chk(this.fnCallback)) this.fnCallback();
		}
	},
	/**
	 * Magát az autokiegészítést végzi el a providertől jött találatok alapján.
	 * @param oJSON A találatokat tartalmazó JSON objektum.
	 */
	autoComplete : function (oJSON) {
		if (!$chk(oJSON)) {
			return;
		}
		this._save();
		this._empty();
		this.hideDropDown();
		// a legördülő lista feltöltése
		if ($type(oJSON.list) == 'array' && oJSON.list.length > 0) {
			this.resultList = oJSON.list;
			this.curr = -1;
			var oEl = null;
			//this._setValue(this.resultList[0]);
			for (var i = 0; i < this.resultList.length; i++ ) {
				oEl = new Element('div',{'html':this.resultList[i].html});
				oEl.addEvent('click',this.onClick.bind(this));
				oEl.addEvent('mouseover',this.onMouseOver);
				oEl.addEvent('mouseout',this.onMouseOut);
				oEl.inject(this.listCnt);
			}
			//this.stepSuggestion(1);
			this.showDropDown();
		}
		// az egyéb mezők feltöltése
		for (var i = 0; i < this.plusFields.length; i++) {
			if ($chk(oJSON[this.plusFields[i].get('name')])) {
				this.plusFields[i].set('value',oJSON[this.plusFields[i].get('value')]);
			}
		}
		// this.fnCallback meghivasa
		if ($chk(this.fnCallback)) this.fnCallback();
	},
	/**
	 * Előállítja a legördülő listát.
	 * Csak konstruktor időben egyszer fut le.
	 */
	createDropDown : function () {
		this.resultCnt = new Element('div',{'class':'input_auto_div', 'styles':{'position':'absolute'}});
		this.resultCnt.setStyle('overflow','auto');
		this.resultCnt.setStyle('position','absolute');
		this.resultCnt.inject(this.inputField,'after');
		this.hideDropDown();
		// teteje
		new Element('div', {'class':'auto_top'}).inject(this.resultCnt);
		// kozepe: ebbe jonnek majd az eredmenyek
		this.listCnt = new Element('div', {'class':'auto_cont'}).inject(this.resultCnt);
		// alja
		new Element('div', {'class':'auto_bot'}).inject(this.resultCnt);
		
		//this.autoComplete(null);
	},
	/**
	 * Lekérdezi az input mező, és ha vannak plusz mezők akkor azok tartalmával
	 * bővített lekérdezési karakterláncot.
	 * @return Az név, érték párokat reprezentáló lekérdezési karakterlánc.
	 */
	getQueryString : function () {
		var qs = this.inputField.get('name') + '=' + this.inputField.get('value');
		for (var i = 0; i < this.plusFields.length; i++) {
			if ($type(this.plusFields[i]) == 'element') {
				qs += '&' + this.plusFields[i].get('name') + '=' + this.plusFields[i].get('value');
			}
		}
		return qs;
	},
	/**
	 * Elrejti a legördülő listát.
	 */
	hideDropDown : function () {
		this.resultCnt.setStyle('display','none');
	},
	/**
	 * A legördülő lista elemeinek click eseménykezelője.
	 * @param oEvent Az esemény objektum.
	 */
	onClick : function (oEvent) {
		oEvent.stop();
		var oDiv = oEvent.target;
		var i = 0;
		var el = this.listCnt.getElements('div');
		while (el[i] != oDiv) {
			++i;
		}
		if (i < this.resultList.length) {
			this.curr = i;
			this._setValue(this.resultList[i]);
		}

		this.hideDropDown();
	},
	/**
	 * Az input mező keydown eseménykezelője.
	 * @param oEvent Az esemény objektum.
	 */
	onKeyDown : function (oEvent) {
		switch (oEvent.code) {
			case 38 :
				this.stepSuggestion(-1);
				break;
			case 40 :
				this.stepSuggestion(1);
				break;
			case 27 : // escape
				this._backup();
			case 13 : // enter
				oEvent.stop();
				this._empty();
				this.hideDropDown();
				break;
		}
	},
	/**
	 * Az input mező keyup eseménykezelője.
	 * @param oEvent Az esemény objektum.
	 */
	onKeyUp : function (oEvent) {
		var keyCode = oEvent.code;
		// leállítjuk a várakozó kérést
		$clear(this.timeoutId);
		// a backspace és a delete
		if ((keyCode == 8 || keyCode == 46) && this.inputField.get('value').length >= this.MIN_CHAR_COUNT) {
			this.timeoutId = this.suggestProvider.requestList.delay(this.PRESS_DELAY,this.suggestProvider,this);
		}
		// amiket nem kezelünk
		else if ((keyCode != 16 && keyCode < 32) || (keyCode >= 33 && keyCode < 46) || (keyCode >=112 && keyCode <= 123)) {
			return;
		}
		else if (this.inputField.get('value').length >= this.MIN_CHAR_COUNT) {
			this.timeoutId = this.suggestProvider.requestList.delay(this.PRESS_DELAY,this.suggestProvider,this);
		}
		else {
			this.listCnt.empty();
			this.hideDropDown();
		}
	},
	/**
	 * A legördülő lista elemeinek mouseout eseménykezelője.
	 * @param oEvent Az esemény objektum.
	 */
	onMouseOut : function (oEvent) {
		this.removeClass ('auto_over');
	},
	/**
	 * A legördülő lista elemeinek mouseover eseménykezelője.
	 * @param oEvent Az esemény objektum.
	 */
	onMouseOver : function (oEvent) {
		this.addClass ('auto_over');
	},
	/**
	 * Az adott elemhez szkrollozza a legördülő listát.
	 * @param oItem Az a listaelem amelyikhez szkrollozunk.
	 */
	scrollToItem : function(oItem) {
		var y = $(oItem).getPosition(this.resultCnt).y;
			this.resultCnt.scrollTo(0,y);
	},
	/**
	 * A paraméterül kapott div elemet pozicionálja az input mező bal alsó széléhez.
	 * @param A pozicionálandó elem.
	 */
	setPosition : function (oElement) {
		if ($chk(this.inputField) && $chk(oElement)) {
			var ifCoor = this.inputField.getCoordinates();
			// pozicionálás
			oElement.setStyle('left',ifCoor.left + 'px');
			oElement.setStyle('top',ifCoor.bottom + 'px');
			// méretezés
			oElement.setStyle('width',ifCoor.width);
		}
	},
	/**
	 * Megjeleníti a legördülő listát.
	 */
	showDropDown : function () {
		this.setPosition(this.resultCnt);
		if (this.resultList.length >= this.MAX_LIST_LENGTH) {
			this.resultCnt.setStyle('height',this.inputField.getSize().y * this.MAX_LIST_LENGTH + 'px');
		}
		else {
			this.resultCnt.setStyle('height','auto');
		}
		this.resultCnt.setStyle('display','block');
	},
	/**
	 * A legördülő listában léptet az előző, vagy következő elemre.
	 * @param iStep A léptetés iránya. Ha negatív, akkor fel, ha pozitív, akkor le.
	 */
	stepSuggestion : function (iStep) {
		if ($chk(this.resultList) && $chk(el = this.listCnt.getElements('div'))) {
			this.showDropDown();
			//var temp = this.curr;
			if (iStep > 0) {
				if (this.curr < this.resultList.length-1) {
					++ this.curr;
				}
			}
			else {
				if (this.curr > 0) {
					-- this.curr;
				}
			}
			for (var i = 0; i < el.length; i++) {
				el[i].removeClass('auto_highlight');
			}
			this._setValue(this.resultList[this.curr]);
			if($chk(oDiv = el[this.curr])) {;
				oDiv.addClass('auto_highlight');
				this.scrollToItem(oDiv);
			}	
		}	
	}
});
