// hitsCountLabel must be defined in the calling scope

var suggestions = new Hash();

var transport;

var suggSplitter = /[,\./\-+&*\^%!@#$\(\)\s"']+/g;

var Suggestion = Class.create( {
	
	source:null,
	
	container:'',

	active:false,
	
	suggestionsURL:null,

	linkToHit:null,

	updateIdField:null,

	lastSugg:'',
	
	offset:0,

	max:10,
	
	totalCount:0,
	
	lastGaleryImg:null,
	
	additionalParams:null,

	linkObserver:null,
	
	onSelect:function( text, elem, source ){
		source.value = text; 
		this.source.focus();
		this.active = false;
		this.hideSugg();
	},
	
	handleSelect:function( e ){
		Event.stop( e );
		var elem = Event.findElement( e, 'A' );
		this.lastSugg = elem.innerHTML.stripTags().unescapeHTML().strip().replace( /"'/g, '\'' );
		if( null != this.updateIdField ){
			$( this.updateIdField ).value = elem.readAttribute( 'compositeKey' );
		}
		this.onSelect( this.lastSugg, elem, this.source );
	},
	
	initialize:function( suggField ){
		suggField = $( suggField );
		if( null == suggField ) return;
		this.source = suggField;
		this.container = 'suggestions_' + this.source.id;
		this.suggestionsURL = this.source.readAttribute( 'remoteUrl' );
		this.additionalParams = suggField.readAttribute( 'additionalParams' );

		this.updateIdField = this.source.readAttribute( 'updateIdField' );
		this.linkToHit = this.source.readAttribute( 'linkToHit' );
		
		var w = Math.max( 185, suggField.getWidth() - 2 ) + 'px'
		var div = new Element( 'div', { id:this.container, style:'width:' + w } );
	    div.addClassName( 'suggestions' );
		$$( 'body' )[ 0 ].insert( div );	
		var pos = this.source.cumulativeOffset();
		var l = pos.left;
		var t = pos.top;
		$( this.container ).setStyle( { left:l + 'px', top:t + 'px' } );
		div.hide();

		this.container = $( this.container );
		suggField.writeAttribute( 'autocomplete', 'off' );
		this.source.observe( 'keyup', this.updateSuggestions.bindAsEventListener( this ) );
		this.source.observe( 'blur', this.onBlur.bindAsEventListener( this ) );
		this.source.observe( 'focus', this.onFocus.bindAsEventListener( this ) );
	},
	
	showGalery:function( e ){
		var img = 'IMG' == e.element().tagName ? e.element() : Event.findElement( e, 'A' ).down( 'img' );
		if( img ){
			if( img != this.lastGaleryImg && null != this.lastGaleryImg ) this.lastGaleryImg.hide();
			img.show();
			this.lastGaleryImg = img;
		}else{
			if( null != this.lastGaleryImg ) this.lastGaleryImg.hide();
			this.lastGaleryImg = null;
		}
	},
	
	hideGalery:function( e ){
		var img = 'IMG' == e.element().tagName ? e.element() : e.element().down( 'img' );
		if( img ){
			img.hide();
			this.lastGaleryImg = null;
		}
	},
	
	json2html:function( e ){
	  this.container.update();
	  this.active = true;
	  var suggCommand = eval( "("+e.responseText+")" ); 
	  if( 0 == suggCommand.totalCount ){
		  $( this.container ).hide();
		  return;
	  }

	  this.totalCount = suggCommand.totalCount;
	  var suggBox = new Element( 'div' );
	  var thisObj = this;
	  var pattern = new RegExp( '([\\s\\-"!\\.,])(' + this.lastSugg.toLowerCase().replace( suggSplitter, '|' ) + ')', 'gi' );
	  suggCommand.suggestions.each( function( sugg ){
		var txt = ( ' ' + sugg.original ).replace( pattern, '$1<span class="highlited">$2</span>' );
	    var hittableLink = sugg.linkToHit || thisObj.linkToHit;
	    var line = new Element( 'a', { 'href':'/' } ).update( txt );
	    line.addClassName( 'suggestion' );
	    if( null != hittableLink ){
	    	line.href = hittableLink + sugg.compositeKey;
	    	if( null != thisObj.linkObserver ){
	    		line.observe( 'click', thisObj.linkObserver.bindAsEventListener( thisObj ) );
	    	}
	    }else{
	    	line.observe( 'click', thisObj.handleSelect.bindAsEventListener( thisObj ) );
	    }

	    line.observe( 'focus', thisObj.hilightSuggestion.bindAsEventListener( thisObj ) );
	    if( sugg.iconId && '0' != sugg.iconId ){
	    	var img = new Element( 'img', { src:imgUrl + '/' + sugg.iconId, style:'display:none;' } );
	    	img.addClassName( 'icon' );
	    	line.insert( img );
	    }
	    line.observe( 'mouseover', thisObj.showGalery.bindAsEventListener( thisObj ) );
	    line.observe( 'focus', thisObj.showGalery.bindAsEventListener( thisObj ) );
	    line.observe( 'keyup', thisObj.unhighlight.bindAsEventListener( thisObj ) );
	    line.observe( 'blur', thisObj.onBlur.bindAsEventListener( thisObj ) );

	    line.writeAttribute( 'compositeKey', sugg.compositeKey );
	    suggBox.insert( line );
	  });

	  var statusline = new Element( 'div' );
	  statusline.addClassName( 'suggestion_statusline' );
	  var left = new Element( 'span', { style:'float:left' } ).update( hitsCountLabel + ': <b>' + this.totalCount + '</b>  ' );
	  
	  var back = new Element( 'span' );
	  if( 0 < this.offset ){
	    back = new Element( 'a', { href:'/' } );
	    back.observe( 'click', function( e ){ 
	    	e.stop();
	    	thisObj.offset = Math.max( thisObj.offset - thisObj.max, 0 );
	    	thisObj.loadSuggestions();
	    });
	    back.observe( 'focus', function( e ){ thisObj.active = true; } );
	  }
	  left.insert( back.update( ' &#171; ' ) );

	  var more = new Element( 'span' );
	  if( thisObj.totalCount > thisObj.max + thisObj.offset ){
		  more = new Element( 'a', { href:'/' } );
		  more.observe( 'click', function(e){
			  Event.stop( e );
			  thisObj.offset += thisObj.max;
			  thisObj.loadSuggestions();
		  });
		  more.observe( 'focus', function( e ){ thisObj.active = true; } );
	  }
	  left.insert( more.update( ' &#187; ' ) );
	  
	  statusline.insert( left );
	  var right = new Element( 'span', { style:'float:right' } ).update( suggCommand.totalTime + ' ms ' );
	  statusline.insert( right );
	  
	  $( this.container ).insert( suggBox );
	  $( this.container ).insert( statusline );

	  Effect.Appear( this.container, {duration:0.15} );
	  this.source.focus();
	},
	
	hideSugg:function(){
		if( this.active ) return;
		this.container.update();
		this.container.hide();
	},

	onBlur:function( e ){
		this.hideGalery( e );
		this.active = false;
		setTimeout( this.hideSugg.bind( this ), 100 );
	},

	onFocus:function( e ){
		this.active = true;
	},
	
	updateSuggestions:function( e ){
	  var keyCode = e.keyCode;
	  if( 35 == keyCode || 36 == keyCode || 38 == keyCode
	   || ( 15 < keyCode && 19 > keyCode )  
	   || 37 == keyCode || 39 == keyCode ) return;
	  if( 27 == keyCode ){
		  this.active = false;
		  this.hideSugg();
		  return;
	  }			
	  if( 40 == keyCode && this.active && this.container.down( 'a' ) ){
		var links = this.container.childElements()[ 0 ].childElements();
		if( 0 < links.size() ) links.first().focus();
	    return;
	  }
	  var term = this.source.value;
	  if( 2 > term.length || this.lastSugg == term ) return;
	  var splitted = term.replace( /:/gi, '' ).split( ' ' );
	  for( var t = 0; t < splitted.length; t++ ){
	    if( 0 != splitted[ t ].length && 2 > splitted[ t ].length ) return;
	  }
	  this.lastSugg = term;
	  this.offset = 0;
	  setTimeout( this.loadSuggestions.bind( this ), 400 );
	},
	
	hilightSuggestion:function( event ){
		this.active = true;
		Effect.Appear( this.container, {duration:0.15} );
		var focused = event.element();
		focused.className = 'suggestion_focused';
		focused.siblings().without( focused ).each( function( s ){ s.className = 'suggestion'; } )
		focused.focus();
	},
	
	unhighlight:function( event ){
		var curr = event.element();
		var keyCode = event.keyCode;
		if( 27 == keyCode ){
			this.active = false;
			this.hideSugg();
			this.source.focus();
			return;
		}			
		if( 40 == keyCode ){
			if( curr.next() ) curr.next().focus();
		}else if( 38 == keyCode ){
			if( curr.previous() ) curr.previous().focus(); else this.source.focus();
		}else if( 39 == keyCode && this.totalCount > this.max + this.offset ){
			this.offset += this.max;
			this.loadSuggestions();
		}else if( 37 == keyCode && 0 < this.offset ){
			this.offset = Math.max( this.offset - this.max, 0 );
			this.loadSuggestions();
		}
	},
	
	getSuggestions:function( onLoadAction ){
		var params = new Hash( { 'value':this.lastSugg, 'offset':this.offset, 'max':this.max } );
		if( null != this.additionalParams ){
			params = params.merge( eval( '(' + this.additionalParams + ')' ) );
		}
		if( null != transport && 4 != transport.readyState ) transport.abort();
		new Ajax.Request( this.suggestionsURL, {
			onLoading:function( req ){ transport = req.transport },
			onSuccess:onLoadAction,
			parameters:params.toQueryString() 
		});
	},
	
	loadSuggestions:function(){
		this.getSuggestions( this.json2html.bindAsEventListener( this ) );
	}
} );

function extendSuggestions(){
	$$( 'input[rel=suggestable]' ).each( function( suggField ){
		suggestions.set( suggField.id, new Suggestion( suggField ) );
	});  
}

document.observe( 'dom:loaded', extendSuggestions );