function LocationOptionsBoxProvider( target, mandatory, dataSource )
{
	var that = new OptionsBoxProvider( target, mandatory ),
		old = { init: that.init, load: that.load, filter: that.filter },
		spacify = /[^a-z0-9]+/g,
		urlify = /[^a-z0-9äöüß]+/g,
		currentRequest = null;

	that.maxOptions = 100;
		
	that.init = function( ob, source )
	{
		old.init( ob, source );
	};
	that.load = function( cb )
	{
		//console.log( 'entered load' );
		
		var q;
		if( !(q = jQuery.trim( that.source.value )) || q.length < 2 )
			return;

		var query = jQuery.trim( q.toLowerCase().replace( urlify, ' ' ) );
			
		that.status = OptionsBoxProvider.LOADING;
		
		if( !that.items[ query ] )
		{
			if( currentRequest !== null && typeof( currentRequest.abort ) == 'function' )
				currentRequest.abort();
			
			currentRequest = jQuery.getJSON(
				"/suche/ort/" + jQuery.urlencode( query ),
				function( data )
				{
					currentRequest = null;
					
					that.items[ data.query ] = [];
				
					jQuery.each( data.locations, function( i, location )
					{
						that.items[ data.query ].push( {
							i : location.i,
							t : location.t,
							s : data.states[ location.s ],
							q : location.t.toLowerCase().replace( urlify, ' ' )
						} );
					} );


					if( cb )
						cb( that.items[ data.query ] );
				} 
			);
		}
		
		if( !that.items[ query ] )
			that.items[ query ] = [];
		
		if( cb )
			cb( that.items[ query ] );
	};
	that.select = function( record )
	{
		// set correct title
		that.source.value = record.t; 
		// set record ID
		that.target.value = record.i;
	};
	
	that.clear = function()
	{
		that.source.value = '';
		that.target.value = '';
	};
	
	that.filter = function( items )
	{
			this.status = OptionsBoxProvider.FILTERING;

			// operate on lowerCase alphanumeric+space characters only
			var inputValue = jQuery.trim(that.source.value).toLowerCase(),
				sourceValue = inputValue.replace( urlify, ' ' ),
				sourceTokens = sourceValue.split(' '),
				hiliteTokens = [that.source.value].concat( sourceTokens );

			// count elements shown
			var elements = {
				count: 0,
				max: that.maxOptions,
				token: []

			};

			jQuery.each( items, function(i,record)
			{
				elements.token.push( record );
				that.addElement( record, hiliteTokens );
				elements.count++;

			} );

			if( that.status == OptionsBoxProvider.FILTERING )
			{
				jQuery.each( elements.token, function(i,record)
				{
					// limit the number of displayed options
					if( elements.count >= elements.max )
						return false; // break;

					that.addElement( record, hiliteTokens );	
				} );
			}

			//return;
			if( that.status == OptionsBoxProvider.FILTERING )
			{
				// tell user nothing was found
				that.ob.showMessage( that.notfound );
			}
			else
			{
				// select the first option
				that.ob.selectDown();
			}

			that.status = OptionsBoxProvider.INITIALIZED;
	};
	
	return that;
}


function initialize_geolocationsearches( si, source )
{
	var ident,
		prefix = 'geolocationsearch';
	
	// determine ident
	jQuery( 'input', source ).each( function(i,o)
	{
		if( o.name.indexOf( prefix ) > -1 )
		{	
			ident = o.name.split( '_', 3)[1];
			return false; // break;
		}
	} );
	

	// #geolocationsearch_usersearch_query	- replace
	var queryOld = jQuery( '#geolocationsearch_' + ident + '_query' ), 
		query = document.createElement( 'input' );

	query.value = queryOld[0].value;
	jQuery(query).insertBefore( queryOld );
	queryOld.remove();
	
	if( !query.value )
	{
		var preserveOld = jQuery('#geolocationsearch_'+ ident +'_preserve');
		if( preserveOld[0] )
		{
			preserveOld = jQuery( 'strong', preserveOld.parent() ).text();
			query.value = preserveOld.substr( 0, preserveOld.lastIndexOf( ' (' ) );
		}
	}
	
	query.previousValue = query.value;
	
	// #geolocationsearch_usersearch_query_do	- remove
	jQuery( '#geolocationsearch_' + ident + '_query_do' ).remove();
	
	// #geolocationsearch_usersearch_selection
	var resultOld = jQuery( '#geolocationsearch_' + ident + '_selection' ), 
		result = document.createElement( 'input' );
		
	jQuery(result).insertAfter( query );
	resultOld.remove();
	
	// #geolocationsearch_usersearch_location	- ignore
	var locationOld = jQuery( '#geolocationsearch_' + ident + '_location' );
	if( locationOld[0] )
		result.value = locationOld[0].value;
	
	//#geolocationsearch_usersearch_preserve	- remove on selection
	jQuery('#geolocationsearch_'+ ident +'_preserve').parent().hide();
	var removePreserve = (function(){
		return function(){
			jQuery('#geolocationsearch_'+ ident +'_preserve').parent().remove();
		};
	})();
	
	query.type = 'text';
	query.name = 'geolocationsearch_' + ident + '_query';
	query.className = 'OptionsBoxQuery';
	if( jQuery(queryOld).hasClass( 'error' ) )
		query.className += ' error';
	
	query.id = 'geolocationsearch_' + ident + '_query';
	
	// IE7 doesn't like this, I'm not sure why, though
	//result.setAttribute( 'type', 'hidden' );
	result.type = 'text';
	result.style.display = 'none';
	
	result.name = 'geolocationsearch_' + ident + '_selection';
	
	
	var provider = new LocationOptionsBoxProvider( result, false, resultOld );
	
	provider.initialized = 'Name des Ortes eingeben'; 
	provider.loading = 'Lade Daten…';
	provider.notfound = 'Keine Orte gefunden';
	provider.notselected = 'Es wurde kein Ort ausgewählt';
	
	var select = provider.select;
	provider.select = function( record ){ removePreserve(); return select( record ); };
	
	new OptionsBox( query, provider );
	
	if( locationOld[0] )
		provider.target.value = locationOld[0].value;
}

// load when DOM ready
jQuery( function()
{
	$('div.geolocationsearch').each( initialize_geolocationsearches );
} );