/**
 * Constructor class PHAutocomplete():
 * Changes ordinary 'input text' field into an autocomplete field like Google-Suggest
 *
 * Code example to setup autocomplete-box see example.html
 * 
 * Instance variables to setup PHAutocomplete:
 *
 *    search_field_id:      ID of the HTML textfield to enhance with autocomplete
 *                          features (must be <input type="text" />).
 *    search_result_div_id: ID of the DIV to write search result items to, that is
 *                          only visible when search result exists.
 *    search_button_id:     ID of the search button tag (could be type="button"
 *                          or type="image").
 *    search_button_on:     Image for activated search button when type="image"
 *                          (when type is different assign empty value).
 *    search_button_off:    Image for not activated search button when type="image"
 *                          (when type is different assign empty value).
 *
 *    search_result_count_key:      Array key with the number of search results
 *                                  in the JSON-Array of the respondent search 
 *                                  script.
 *    search_result_all_values_key: Array key with the search result items in the
 *                                  JSON-Array of the respondent search script.
 *    search_result_item_value_key: Array key in the search result item of the 
 *                                  value to be shown in the autocomplete field.
 *                                  Accordingly the result JSON-Array shold look
 *                                  like this: 
 *                                     Array( 'count' => X,
 *                                            'values' => Array( 
 *                                                               Array( 'id' => 1,
 *                                                                'name' => 'foo' ),
 *                                                               Array( 'id' => 2,
 *                                                                'name' => 'bar' )
 *                                                              )
 *                                     );
 *
 *    item_element_class_name:  CSS class used by the items when written into the
 *                              search result div.
 *    item_id_prefix:           Prefix used for the <a id=""... field of the items
 *                              when written into the search result div
 *    item_bg_color_selected:   Color of an search result item item when selected
 *    item_bg_color_unselected: Color of an search result item item when selected
 *    
 *    search_delay = 800;
 *
 *    search_request_method:  HTTP-Request Method (POST or GET)
 *    page_up_and_down_count: Number of lines skipped when pressing page up and 
 *                            page down to move selection of search item
 *
 * Methods to overwrite to use PHAutocomplete:
 *
 *    getSearchRequestUrl():       
 *       Has to return the URL to the script returning the search result
 *    getSearchRequestParameter(search_field_value):
 *       Must return the parameter string that is necessary to call the search script
 *    processSelectedItem( item_id, all_items ):
 *       Method that is called after a search result item was selected
 *    disableSearchButton():
 *       Should be overwritten with an empty method if no search Button is wanted
 *    enableSearchButton():
 *       Should be overwritten with an empty method if no search Button is wanted
 *
 *
 * @author Nikolas Plewa <np@pixelhouse.de>
 * @example example.html
 * @version 0.1 (2006-09.29)
 * @link https://sim.office.pixelhouse.de/mediawiki/index.php/PH_Wiki:PHP:AJAX:PH_Widgets#PHAutocomplete
 * @return PHAutocomplete Object
 *
 * (C) 2006 pixelhouse GmbH
 */
function PHAutocomplete() {
	
	try {

		mainATCPobj.printMessage( 'ERROR: Only on instance of PHAutocomplete()'
									+ ' can be used in one page' );
		
	} catch (e) {
	
		mainATCPobj = new Object();
		
		mainATCPobj.search_field_id = 'search_field';
		mainATCPobj.search_result_div_id = 'search_result_div';
		mainATCPobj.search_button_id = 'search_button';

		mainATCPobj.search_button_on = 'go_on.gif';
		mainATCPobj.search_button_off = 'go_off.gif';

		mainATCPobj.search_result_count_key = 'count';
		mainATCPobj.search_result_all_values_key = 'values';
		mainATCPobj.search_result_item_value_key = 'name';
		
		mainATCPobj.item_element_class_name = 'search_link';
		mainATCPobj.item_id_prefix = 'search_item';
		mainATCPobj.item_bg_color_selected = '#EEEEEE';
		mainATCPobj.item_bg_color_unselected = '#FFFFFF';

		mainATCPobj.search_delay = 800;

		mainATCPobj.search_request_method = 'POST';
		mainATCPobj.page_up_and_down_count = 25;
		
		mainATCPobj.xmlHttpObj = null;
		mainATCPobj.response_string = '';
		mainATCPobj.response_count = 0;
		mainATCPobj.all_items = new Array();
		mainATCPobj.last_search_string = '';
		mainATCPobj.is_open = false;
		mainATCPobj.selected_item_id = null;
		
		mainATCPobj.init = PHAutocomplete_init;
		mainATCPobj.searchFieldFocus = PHAutocomplete_searchFieldFocus;
		mainATCPobj.handleKeyPress = PHAutocomplete_handleKeyPress;
		mainATCPobj.handleMouseClick = PHAutocomplete_handleMouseClick;
		mainATCPobj.selectPreviousItem = PHAutocomplete_selectPreviousItem;
		mainATCPobj.selectFollowingItem = PHAutocomplete_selectFollowingItem;
		mainATCPobj.selectItem = PHAutocomplete_selectItem;
		mainATCPobj.unselectItem = PHAutocomplete_unselectItem;
		mainATCPobj.searchItems = PHAutocomplete_searchItems;
		mainATCPobj.fillSearchResult = PHAutocomplete_fillSearchResult;
		mainATCPobj.enableSearchButton = PHAutocomplete_enableSearchButton;
		mainATCPobj.disableSearchButton = PHAutocomplete_disableSearchButton;
		mainATCPobj.hideSearchResult = PHAutocomplete_hideSearchResult;
		mainATCPobj.showSearchResult = PHAutocomplete_showSearchResult;
		mainATCPobj.getPressedKey = PHAutocomplete_getPressedKey;
		mainATCPobj.takeItem = PHAutocomplete_takeItem;

		// Methoden die ueberschriben werden muessen:				
		mainATCPobj.getSearchRequestUrl = PHAutocomplete_getSearchRequestUrl;
		mainATCPobj.getSearchRequestParameter = PHAutocomplete_getSearchRequestParameter;
		mainATCPobj.processSelectedItem = PHAutocomplete_processSelectedItem;
		mainATCPobj.printMessage = PHAutocomplete_printMessage;
	}

	return mainATCPobj;
}

/**
 * Method has to be called in <body onLoad="ac.init()" ...> after setup
 * is completed.
 *
 * init() binds all other eventhandlers to the necessary elements.
 *
 * @return void
 */
function PHAutocomplete_init() {

	// get references to html elements
	this.search_field = document.getElementById(this.search_field_id);
	this.search_button = document.getElementById(this.search_button_id);
	this.search_result_div = document.getElementById(this.search_result_div_id);

	// Event-HAndler setzen
	document.onclick = mainATCPobj.handleMouseClick;
	this.search_field.onkeydown = mainATCPobj.handleKeyPress;
	this.search_button.onclick = mainATCPobj.takeItem;
	
	this.disableSearchButton();
	
	this.hideSearchResult();
}

/**
 * Sets cursor to search field
 *
 * Called by init(). Please Overwrite with an empty Method if this behavior is
 * not wanted.
 *
 * @return void
 */
function PHAutocomplete_searchFieldFocus() {

	mainATCPobj.search_field.focus();
}


/**
 * Event handler for any key action in the search field.
 * 
 * Event handler is assigned to search field in init()
 *
 * @param event ev JavaScript event object
 * @return void
 */
function PHAutocomplete_handleKeyPress( ev ) {

	var pressed_key = mainATCPobj.getPressedKey( ev );
	
	if( pressed_key != null ) {
		
		switch( pressed_key ) {
			
			// page up key
			case 33:
				mainATCPobj.selectPreviousItem( mainATCPobj.page_up_and_down_count );
			break;
			
			// page down key
			case 34:
				mainATCPobj.selectFollowingItem( mainATCPobj.page_up_and_down_count );							
			break;

			// left key
			case 37:
				mainATCPobj.hideSearchResult();
			break;

			// right key
			case 39:
				mainATCPobj.hideSearchResult();
			break;
			
			// up key
			case 38:
				mainATCPobj.selectPreviousItem( 1 );
			break;
			
			// down key
			case 40:
				mainATCPobj.selectFollowingItem( 1 );
			break;
			
			// enter key
			case 13:
				mainATCPobj.takeItem();
			break;
			
			// esc
			case 27: 
				mainATCPobj.hideSearchResult();
			break;
			
			// other keys
			default:
				mainATCPobj.unselectItem();
				mainATCPobj.hideSearchResult();
				window.setTimeout( "mainATCPobj.searchItems()", mainATCPobj.search_delay);
		}
	}		
}

/**
 * Event handler for any mouse action in the whole document
 *
 * Event handler is assigned to document in init()
 *
 * @param event ev JavaScript event object
 * @return void
 */
function PHAutocomplete_handleMouseClick( ev ) {

	var buttonCode = 0;

	if( document.all )	{ // IE
		buttonCode = event.button;	
	} else { // others
		buttonCode = ev.button;	
	}

	// on left klick 
	if( buttonCode == 0 ) {
		mainATCPobj.hideSearchResult();
	}	
}

/**
 * Method moves selected item backward
 *
 * @param int item_count No. of items to go up
 * @return void
 */
function PHAutocomplete_selectPreviousItem( item_count ) {
	
	// wenn keins oder erstes gewaehlt war
	if( ( mainATCPobj.selected_item_id == null) || (mainATCPobj.selected_item_id - item_count) < 0 ) {
		// Suchergebnis ausblenden
		mainATCPobj.hideSearchResult();
	// sonst entsprechendes item davor auswaehlen
	} else {
		mainATCPobj.selectItem( mainATCPobj.selected_item_id - item_count );
	}
}

/**
 * Method moves selected item forward
 *
 * @param int item_count No. of items to go down
 * @return void
 */
function PHAutocomplete_selectFollowingItem( item_count ) {
		
	// nur was tun wenn schon ein Suchergebnis existiert
	if( mainATCPobj.all_items.length >= 1 ) {
		// wenn Suchergebnis nicht offen ist neu suchen
		if(! mainATCPobj.is_open ) {
			mainATCPobj.searchItems();
		// wenn Suchergebnis offen ist
		} else {
			// und kein Item gewaehlt ist erstes waehlen
			if( mainATCPobj.selected_item_id == null ) {
				mainATCPobj.selectItem( 0 );
			// wenn selected_item_id + count existeirt auswaehlen
			} else if( (mainATCPobj.selected_item_id + item_count) < mainATCPobj.all_items.length ) {
				mainATCPobj.selectItem( mainATCPobj.selected_item_id + item_count );
			// sonst letztes Item im Suchergebnis waehlen
			} else {
				mainATCPobj.selectItem( mainATCPobj.all_items.length - 1 );	
			}
		} 
	}
}

/**
 * Method marks item with given id as selected
 *
 * @param int item_id Index of item in search result to be selected
 * @return void
 */
function PHAutocomplete_selectItem( item_id ) {
	
	if ( (mainATCPobj.all_items[item_id] != null) && (mainATCPobj.all_items[item_id] != "") ) {

		// unselect old
		mainATCPobj.unselectItem();
		
		// set new
		mainATCPobj.selected_item_id = item_id;
		
		// set backgroud
		var selected_item = document.getElementById(mainATCPobj.item_id_prefix + item_id);
		selected_item.style.backgroundColor = mainATCPobj.item_bg_color_selected;

		// fill search field and mark added characters
		var start = mainATCPobj.search_field.length; 
		var laenge = mainATCPobj.all_items[item_id][mainATCPobj.search_result_item_value_key].length;
		mainATCPobj.search_field.value = mainATCPobj.all_items[item_id][mainATCPobj.search_result_item_value_key];
		
		/*
		if (mainATCPobj.search_field.createTextRange) { // IE
			
			var Selection = mainATCPobj.search_field.createTextRange();
			Selection.moveStart("character", start);
			Selection.moveEnd("character", laenge - start);
			Selection.select(); 
			
		} else if (mainATCPobj.search_field.setSelectionRange) {
		
			mainATCPobj.search_field.setSelectionRange(start, laenge);
		} 
		*/
		
		// enable search button
		mainATCPobj.enableSearchButton();
	}
}

/**
 * Method removes mark of current selected item
 *
 * @return void
 */
function PHAutocomplete_unselectItem() {

	if( mainATCPobj.selected_item_id != null ) {

		// set backgroud
		document.getElementById(mainATCPobj.item_id_prefix + mainATCPobj.selected_item_id).style.backgroundColor = mainATCPobj.item_bg_color_unselected;
		
		// set to null
		mainATCPobj.selected_item_id = null;
		
		// disable search button
		mainATCPobj.disableSearchButton();
	}
}

/**
 * Method is called when search has to be accomplished
 *
 * @return void
 */
function PHAutocomplete_searchItems() {

	if( mainATCPobj.selected_item_id == null ) {
		
		/* mainATCPobj.search_field.focus(); */

		// wenn Suchbegriff sich nciht geändert hat nicht neu suchen
		if( mainATCPobj.last_search_string == mainATCPobj.search_field.value ) {

			// Ergebnis nur ausgeben, wenn es eins gibt
			if( mainATCPobj.response_count > 0 ) {
			
				// prevent from mutiple searches for the same value						
				//###TODO: Debug
				//printToJsDebugWindow( '==' );
				mainATCPobj.showSearchResult();
			}
			
		} else {
		
			mainATCPobj.last_search_string = mainATCPobj.search_field.value;
			
			if(! mainATCPobj.xmlHttpObj ) {
				if (window.ActiveXObject) {
					
					try { mainATCPobj.xmlHttpObj = new ActiveXObject("Msxml2.XMLHTTP");
					} catch (e) {
					
						try { mainATCPobj.xmlHttpObj = new ActiveXObject("Microsoft.XMLHTTP");
						} catch (e) { }
					}
				
				} else if (window.XMLHttpRequest) {
				
					try { mainATCPobj.xmlHttpObj = new XMLHttpRequest(); 
					} catch (e) { }
				}
			}

			var search_request_url = mainATCPobj.getSearchRequestUrl();
			var search_request_parameter = mainATCPobj.getSearchRequestParameter( mainATCPobj.search_field.value );
			
			if( mainATCPobj.search_request_method == 'GET' ) {
			
				search_request_url = search_request_url + '?' + search_request_parameter;
				search_request_parameter = '';
			}
			
			mainATCPobj.xmlHttpObj.open( mainATCPobj.search_request_method, search_request_url, true );
			mainATCPobj.xmlHttpObj.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
			mainATCPobj.xmlHttpObj.onreadystatechange = mainATCPobj.fillSearchResult;
			mainATCPobj.xmlHttpObj.send( search_request_parameter );
		}
	}
}

/**
 * Returns URL to search script (must be overwritten with own method)
 *
 * @see      PHAutocomplete()
 * @return   String with URL
 */
function PHAutocomplete_getSearchRequestUrl() {
	
	mainATCPobj.printMessage( 'ERROR: Please implement / overwrite .getSearchRequestUrl()' );
	
	return false;
}

/**
 * Returns request parameter to search script (must be overwritten with own method)
 *
 * @see      PHAutocomplete()
 * @param    String search_field_value Current value of the search field
 * @return   String with Parameters like 'id=1&foo=bar&schnudel=batz'
 */
function PHAutocomplete_getSearchRequestParameter( search_field_value ) {
	
	mainATCPobj.printMessage( 'ERROR: Please implement / overwrite .getSearchRequestParameter()' );
	
	return false;
}

/**
 * Callback method is called when search result arrives (called by XmlHTTPRequest
 * asynchronously)
 *
 * @return void
 */
function PHAutocomplete_fillSearchResult() {

	// reset selected item wenn neue Daten kommen
	mainATCPobj.unselectItem();
	mainATCPobj.hideSearchResult();

	var item_html = '';
	
	if (mainATCPobj.xmlHttpObj.readyState == 4) {
		
		mainATCPobj.response_string = mainATCPobj.xmlHttpObj.responseText;
		
		//###TODO: Debug
		//printToJsDebugWindow( response_string );
		
		if (mainATCPobj.response_string != "") {

			var response_object = mainATCPobj.response_string.parseJSON();

			mainATCPobj.response_count = response_object[mainATCPobj.search_result_count_key];
			mainATCPobj.all_items = response_object[mainATCPobj.search_result_all_values_key];
			
			if ( mainATCPobj.response_count > 0 ) {

				for (var item_id = 0; item_id < mainATCPobj.response_count; item_id++) {
			
					item_html += '<div style="width: 100%;" id="' + mainATCPobj.item_id_prefix + item_id + '"'
										+ ' class="' + mainATCPobj.item_element_class_name + '"'
										+ ' onclick="mainATCPobj.takeItem(' + item_id + ')"'
										+ ' onmouseover="mainATCPobj.selectItem(' + item_id + ')">'
								+ mainATCPobj.all_items[item_id][mainATCPobj.search_result_item_value_key] + '</div>';
				}

				mainATCPobj.search_result_div.innerHTML = item_html;			
				//###TODO: Debug
				//printToJsDebugWindow( 'count: ' + response_count );
				mainATCPobj.showSearchResult();
			}
		}
	}
} 

/**
 * Method is called when search item is selected and sellection could be submitted
 * using the search button.
 *
 * Should be overwritten with an empty function if no search button is necessary
 *
 * @return void
 */
function PHAutocomplete_enableSearchButton() {
	
	mainATCPobj.search_button.disabled = false;
	
	if ( mainATCPobj.search_button_on != '' ) {
	
		mainATCPobj.search_button.src = mainATCPobj.search_button_on;
	}
}

/**
 * Method is called when search item is unselected and sellection could not be
 * submitted.
 *
 * Should be overwritten with an empty function if no search button is necessary
 *
 * @return void
 */
function PHAutocomplete_disableSearchButton() {
	
	mainATCPobj.search_button.disabled = true;
	
	if ( mainATCPobj.search_button_off != '' ) {
	
		mainATCPobj.search_button.src = mainATCPobj.search_button_off;
	}
}

/**
 * Method is called when search result should be shown.
 *
 * @return void
 */
function PHAutocomplete_showSearchResult() {

	if(! mainATCPobj.is_open ) {
		mainATCPobj.is_open = true;
		mainATCPobj.search_result_div.style.visibility = "visible";
		mainATCPobj.search_result_div.style.display = "block";
	}
}

/**
 * Method is called when search result should not be shown.
 *
 * @return void
 */
function PHAutocomplete_hideSearchResult() {
	
	//mainATCPobj.searchFieldFocus();

	if( mainATCPobj.is_open ) {
		mainATCPobj.is_open = false;
		mainATCPobj.search_result_div.style.visibility = "hidden";
		mainATCPobj.search_result_div.style.display = "none";
	}
}

/**
 * Method returns the key code pressed during the last event
 *
 * Implements different ways to get the code for IE and other Browsers
 * 
 * @param   event ev JavaScript event object
 * @return  int Code of Key
 */
function PHAutocomplete_getPressedKey( ev ) {

	var myKeyCode;
	
	if( document.all )	{ // IE
	
		myKeyCode = event.keyCode;	
		
	} else { // others
	
		myKeyCode = ev.which;	
	}
	
	return myKeyCode;
}

/**
 * Method is called when an selected item is taken by a mouse left click, pressing
 * enter on keyboard or klicking the search button
 *
 * This method is only necessary to collect the parameters to call the actual 
 * method that processes the selected item
 * 
 * @see     PHAutocomplete_processSelectedItem()
 * @param   int item_id Index of the selected item in search result (optional)
 * @return  void
 */
function PHAutocomplete_takeItem( item_id ) {
	
	mainATCPobj.hideSearchResult();
	
	// wenn per Tab auf der Tastatur auf den Link geklickt wurde
	if( item_id != null ) {
	
		mainATCPobj.selectItem( item_id );
	}
	
	if( mainATCPobj.selected_item_id != null ) {
	
		mainATCPobj.processSelectedItem( mainATCPobj.selected_item_id, mainATCPobj.all_items );
	}
}

/**
 * Actual method to process the selected item. Should be overwritten by a method
 * that fits to the particular context of use.
 *
 * @param   int item_id Index af selecte value in all_items array
 * @param   array all_items Array with all items returned by the search script
 * @return  void
 */
function PHAutocomplete_processSelectedItem( item_id, all_items ) {
	
	mainATCPobj.printMessage( 'took item: ' + item_id );
}

/**
 * Method that prints messages like errors etc.
 *
 * Should be overwritten with an empty method if no error messages are wanted
 * or could be overwritten with a function that handles error messages differently.
 *
 * @param   Strin text Message to be printed
 * @return  void
 */
function PHAutocomplete_printMessage( text ) {
	
	alert( text );
}