var IS_TagHint = Class.create();
IS_TagHint.prototype = {

	name: 'IS_TagHint', 
	_tagHintCache: {}, // the cached results of all the lookups
	_lookupHistory: {}, // a list of all the partial strings we have looked up via http
	_currentLookup: '', // the partial string we are currently looking up via HTTP
	_currentHintData: new Array(), // a reference to the cache data that build the current display
	_hintDataIndexes: {}, // map of hintIDs to the index of the current hint data associated with the link on display
	_hintLinkIndexes: {}, // map of hintIDs to the index of the link on display
	_iframeID: '', // in order to display the hint list div over form elements in IE 6, we need to display an <iframe> directly behind the hint list
	_iframeAdded: false,
    _tagHintPositioned: false,


	initialize: function(options) {

		this.options = {
			//textHintBoxID: 'TextHintBox', // a div that we will display hints in
			//textHintLoadingBoxID: 'TextHintLoadingBox', // an element that will be displayed when a lookup request is pending
			//textFieldID: 'TextField', // a text field which we will get and put string data in
			//languageFieldID: 'LanguageField', // a field which lets us know which language to use
            //textDisambiguationCallback: function(hintData){}, // a function that will be called when text disambiguation is set
			autoRequestHints: true,
			requestStaticHints: true, // us a static URL instead of a dynamic AJAX server-side class library for better caching
			defaultLanguage: 'EN_US', // default language if no language field is available
			groupByTermID: true,
			setTextDisambiguationOnSelect: true,
			defaultPartialTagLookupLength: 3 // we wont do lookups for strings shorter than this
		};
		Object.extend(this.options, options || { });

		this._iframeID = "iframe_" + this.options.textHintBoxID;
        this._targetID = this.options.textFieldID;  // the id of the element the hint box should be positioned under

	},

	failed: function(req) {
		return false;
	},

	dummy: function(){
		return false;
	},

	getEventKeynum: function(e){
		var keynum;
		if(e && e.which) {
			keynum=e.which;
		} else {
			e=event;
			keynum=e.keyCode;
		}
		return keynum;
	},

	/// When key is pushed down we do fun things, like scroll through the hint list.
	/// On this event we dont actually send a lookup to the server. This method
	/// should be attached to the onkeydown event of the text field.
	getHintsFromPartialTagOnKeydown: function(e){

		var keynum = this.getEventKeynum(e);

		if ( keynum == 40 || keynum == 38 || keynum == 13 ){ // down, up, return

			keychar = String.fromCharCode(keynum);
			//alert(keynum + " = " + keychar);

			if ( keynum == 40 ){ // down
				this.selectNextHint();
				return true;
			}

			if ( keynum == 38 ){ // up
				this.selectPreviousHint();
				return true;
			}

			if ( keynum == 13 ){ // return
				this.setTextDisambiguationFromSelectedHint()
				return true;
			}

		}

		return false;

	},

	/// When a key is let up we actually do a lookup. This method
	/// should be attached to the onkeyup event of the text field.
	getHintsFromPartialTagOnKeyup: function(e){

		var keynum = this.getEventKeynum(e);
		if ( keynum == 40 || keynum == 38 || keynum == 13 ){ // down, up, return
			return false;
		}
	
		return this.getHintsFromPartialTag();

	},

	/// Get hint data for a partial tag, either from our internal
	/// cache or from the server.
	getHintsFromPartialTag: function(partialTag, language){

		// get the partialTagLookupLength
		partialTagLookupLength = this.options.defaultPartialTagLookupLength;

		// Get the tag lookup input data
		if ( !partialTag ){
			var textField = $(this.options.textFieldID);
			partialTag = textField.value.toLowerCase();
		}

		// Figure out which language to use
		if ( !language ){
			language = this.options.defaultLanguage;
			if ( this.options.languageFieldID !== '' ){
				var languageField = $(this.options.languageFieldID);
				if ( languageField ){
					if ( languageField.value ){
						language = languageField.value;
					}
				}
			}
		}

		partialTagLength = partialTag.length;
		partialCount = Math.floor(partialTag.length/partialTagLookupLength);
		partialTagStart = partialTag.substring(0, partialCount*partialTagLookupLength);

		if ( partialTagStart.length < partialTagLookupLength ){
			return false
		}

		return this.requestHintsFromPartialTag(partialTagStart, language);

	},

	requestHintsFromPartialTag: function(partialTagStart, language){

		// if the tag hint has already been cached, then show the hints using the cache data
		if ( this._tagHintCache[language] && this._tagHintCache[language][partialTagStart] ){
			this.showHintsForPartialTag(this._tagHintCache[language][partialTagStart]);
			return true;
		}

		// The tag hint may not yet be in the cache, but if we did send a request
		// in the past, then dont bother sending it again. The goal is to never
		// send the same request across the network more than once.
		if ( this._lookupHistory[partialTagStart] ){
			this._currentLookup = partialTagStart;
			// Show the loading element
			this.showLoadingBox();
			return false;
		}
		this._lookupHistory[partialTagStart] = true;

		// Because requests happen asynchronously, we remember
		// which lookup is currently running and a duplicate 
		// lookup is in progress then we let it complete
		if ( this._currentLookup == partialTagStart ){
			return false;
		}
		this._currentLookup = partialTagStart;

		// Show the loading element
		this.showLoadingBox();

		// prepare to do a new tag hint lookup.
		// Static hints are more easily cached by the network,
		// reduce server load, and speed up response times.
		var url, params;
		if ( this.options.requestStaticHints == true ){
			var page;
			if ( this.options.groupByTermID ){
				page = 'term_hint';
			} else {
				page = 'tag_hint';
			}
			url = page + "/" + encodeURIComponent(language) + "/" + encodeURIComponent(partialTagStart) + ".txt";
			params = "";
		} else {
			// FIXME: the PHP class has not been written yet
			url = "ajax_class_creator.php";
			params = "ajax_action=getHintsFromPartialTag&ajax_class=taghint" +
				"&language=" + language +
				"&partialTag=" + partialTag +
				"&groupByTermID=" + (groupByTermID?1:0) +
				"";
		}

		// perform the request
		var myAjax = new Ajax.Request
			(
				url, 
				{ 
					method: "get", 
					parameters: params,
					onComplete: this.requestHintsFromPartialTagComplete.bind(this),
					onFailure: this.failed.bind(this)
				}
			);

		return true;

	},

	/// When hint data comes back from the server we
	/// cache it and show the hints.
	requestHintsFromPartialTagComplete: function(req){

		//alert(req.responseText);

		var data = AjaxUtil.receiveArray(req.responseText);

		if ( typeof data != 'object' ){
			return false;
		}

		// without these two parameters the response is invalid
		if ( !data.pTS || !data.l ){
			return false;
		}

		// cache the response data
		if ( typeof this._tagHintCache[data.l] != 'object' ){
			this._tagHintCache[data.l] = {};
		}
		this._tagHintCache[data.l][data.pTS] = data;

		// if the response data does not match the current lookup
		// state then abort.
		if ( data.pTS != this._currentLookup ){
			return false;
		}

		// Hide the loading element
		this.hideLoadingBox();

		// the lookup is done, so reset our lookup state
		this._currentLookup = '';

		// show the hints using the new lookup data
		return this.showHintsForPartialTag(this._tagHintCache[data.l][data.pTS]);

	},

	/// Show the hints. Each hint is a link/anchor that
	/// has on onclick event associated with it. Clicking
	/// the link sets the text and disambiguation fields
	/// with the data associated with that particualr hint.
	showHintsForPartialTag: function(hintData){

		this.hideLoadingBox();

		var textHintBox = $(this.options.textHintBoxID);
		var textField = $(this.options.textFieldID);

		var hintID, anchorElement, textNode;
        var toDisplay = 'la';


		if(hintData.tHs && hintData.tHs.length > 0){

			var hintLinks = new Array();
			var usedHints = {};
			this._currentHintData = hintData;
			this._hintDataIndexes = {};
			this._hintLinkIndexes = {};

			var synonyms = new Array();
			var label = '';
			for ( var i=0; i<hintData.tHs.length; i++ ){

				this._currentHintData.tHs[i].index = null;
				this._currentHintData.tHs[i].hintID = null;
				synonyms = new Array();

				if ( this.options.groupByTermID && hintData.tHs[i].s && hintData.tHs[i].s.length > 0 ){
					for ( var s=0; s<hintData.tHs[i].s.length; s++ ){
						if ( hintData.tHs[i].s[s].toLowerCase().indexOf(textField.value.toLowerCase()) === 0 ){
							synonyms[synonyms.length] = hintData.tHs[i].s[s];
						}
					}
					if ( synonyms.length == 0 ){
						continue;
					}
				} else {
					if ( hintData.tHs[i][toDisplay].toLowerCase().indexOf(textField.value.toLowerCase()) !== 0 ){
						continue;
					}
					synonyms[synonyms.length] = hintData.tHs[i][toDisplay];
				}

				if ( usedHints[hintData.tHs[i][toDisplay].toLowerCase()] ){
					continue;
				}

				usedHints[hintData.tHs[i][toDisplay].toLowerCase()] = true;

				this._currentHintData.tHs[i].index = i;
				this._currentHintData.tHs[i].hintID = 'SearchBarTextHintLink_' + i;

				label = '';
				if ( synonyms.length > 0 && synonyms[0].toLowerCase().replace(/ /, '').indexOf(hintData.tHs[i].txt.toLowerCase().replace(/ /, '')) !== 0 ){
					label += synonyms[0];
					label += '<i style="color:#999"> ..' + hintData.tHs[i][toDisplay] + '</i>';
				} else {
					label = hintData.tHs[i][toDisplay];
				}

				link = '<a id="' + this._currentHintData.tHs[i].hintID + '" class="TH_HintLink" ' +
					'href="javascript:void(0);"' +
					'title="' + synonyms.join(", ").replace(/"/, '%22') + '"' +
					'>' + label + '</a>';

				hintLinks[hintLinks.length] = link;

				this._hintDataIndexes[this._currentHintData.tHs[i].hintID] = i;
				this._hintLinkIndexes[this._currentHintData.tHs[i].hintID] = hintLinks.length-1;

				if ( hintLinks.length > 20 ){
					break;
				}
			}

			// if we filter down to 0 hints, hide the hint box
			if ( hintLinks.length == 0 ){
				this.resetHintBox();
				return false;
			}

			//alert(hintLinks.join("\n"));

            textHintBox.innerHTML = hintLinks.join(' ');

			// We display the box below the text field.
			// TODO: if the text field is a textarea then we should try to
			// display the box near the cursor. 
            
            
            // position the tagHint container just below the input field
            if (!this._tagHintPositioned && $(textHintBox)){
            
                var position = $(this._targetID).cumulativeOffset();
                var ht = $(this._targetID).getHeight();
                
                $(textHintBox).setStyle({
                    left:position['left']+'px',
                    top:position['top'] + ht + 1 + 'px'
                });
            
                this._tagHintPositioned = true;
            }
            $(textHintBox).show();
            
            
            // add an iFrame shim behind the hints
			if (!this._iframeAdded){
                var zIndex = (getCurrentStyle(textHintBox, "z-index") - 1);
                if (!zIndex) {
                    $(textHintBox).setStyle({'z-index':5000});
                    zIndex = 4999;
                }
                var iframeHtml = '<iframe id="'+this._iframeID+'" style="position:absolute; border:0; background:#fff; z-Index:'+zIndex+'; display:none"></iframe>';
                $('wrapper').insert({bottom: iframeHtml});
                this._iframeAdded = true;
			}

            // bind the onclick event to this object
            var tagHint;
            for ( var hintID in this._hintDataIndexes ){
                if (!$(hintID)) return;
                tagHint = {'tagHint':this, 'hintDataIndex':this._hintDataIndexes[hintID]}
                $(hintID).onclick = this._setTextDisambiguationFromHintDummy.bind(tagHint);
            }
            
            // position the iframe to match the height of the hint box
            // NOTE: do this after the tag hints have been onclick bound (above stanza) or they fail
            if ($(textHintBox) && $(this._iframeID)) {
                Element.clonePosition(this._iframeID, textHintBox.identify());
                $(this._iframeID).show();
            }

		} else {

			this.resetHintBox();

		}

		return true;
	},

	/// a private method to get around scoping and state fun
	_setTextDisambiguationFromHintDummy: function(){
		return this.tagHint.setTextDisambiguationFromHint(this.hintDataIndex);
	},

	/// given the hintDataIndex, set the text and disambiguation fields
	setTextDisambiguationFromHint: function(hintDataIndex, dontHideHints){
		var textHintBox = $(this.options.textHintBoxID);
		var textField = $(this.options.textFieldID);

        textField.value = this.quotePhrase(this._currentHintData.tHs[hintDataIndex].txt);
        SearchParams.textDisambiguation = Object.toJSON(this._currentHintData.tHs[hintDataIndex].tDa);
        if ( typeof this.options.textDisambiguationCallback == 'function' ){
            this.options.textDisambiguationCallback(this._currentHintData.tHs[hintDataIndex].tDa);
        }


		if ( !dontHideHints ){
			this.resetHintBox();
		}

		return true;
	},

	/// if text contains spaces, wrap it in double-quotes
	quotePhrase: function(text){
		if ( text.match(/ /) ){
			return '"' + text + '"';
		} else {
			return text;
		}
	},

	/// set fields based on which hint link is "selected" (if any)
	setTextDisambiguationFromSelectedHint: function(){

		var hintID = this.getSelectedHintID();
		if ( !hintID ){
			return false;
		}

		var hintDataIndex = this.getTagHintDataIndexFromHintID(hintID);
		if ( hintDataIndex == -1 ){
			return false;
		}

		return this.setTextDisambiguationFromHint(hintDataIndex);

	},

	/// map a hintID to the data index
	getTagHintDataIndexFromHintID: function(hintID){

		if ( typeof this._hintDataIndexes[hintID] == 'number' && this._hintDataIndexes[hintID] > -1 ){
			return this._hintDataIndexes[hintID];
		} else {
			return -1;
		}

	},

	selectNextHint: function(){
		return this.changeSelectedHint(1);
	},

	selectPreviousHint: function(){
		return this.changeSelectedHint(-1);
	},

	/// highlight a hint link and mark it as "selected"
	changeSelectedHint: function(indexDelta){

		var textHintBox = $(this.options.textHintBoxID);
		var linkElems = textHintBox.getElementsByClassName("TH_HintLink");

		var	selectedIndex = this.getSelectedHintIndex();
		var newSelectedIndex = -1;
		if ( selectedIndex > -1 && linkElems[selectedIndex] ){
			// if there is another hint, deselect the current one and select the next one
			if ( linkElems[selectedIndex + indexDelta] ){
				linkElems[selectedIndex].selected = false;
				linkElems[selectedIndex].style.backgroundColor = "transparent";
				newSelectedIndex = selectedIndex + indexDelta;
				linkElems[newSelectedIndex].style.backgroundColor = "#e3e3e3";
				linkElems[newSelectedIndex].selected = true;
				if ( this.options.setTextDisambiguationOnSelect ){
					this.setTextDisambiguationFromHint(this.getTagHintDataIndexFromHintID(linkElems[newSelectedIndex].id), true);
				}
			} 

		// if nothing is selected and there is at least on hint, select the first one
		} else if ( newSelectedIndex == -1 && linkElems[0] ){
			newSelectedIndex = 0
			linkElems[newSelectedIndex].style.backgroundColor = "#e3e3e3";
			linkElems[newSelectedIndex].selected = true;
			if ( this.options.setTextDisambiguationOnSelect ){
				this.setTextDisambiguationFromHint(this.getTagHintDataIndexFromHintID(linkElems[newSelectedIndex].id), true);
			}
		}

		return newSelectedIndex;

	},

	getSelectedHintIndex: function(){

		var textHintBox = $(this.options.textHintBoxID);
		var linkElems = textHintBox.getElementsByClassName("TH_HintLink");

		for ( var i=0; i<linkElems.length; i++ ){
			if ( linkElems[i].selected == true ){
				return i;
			}
		}

		return -1

	},

	getSelectedHintID: function(){

		var	selectedIndex = this.getSelectedHintIndex();
		if ( selectedIndex == -1 ){
			return false;
		}

		var textHintBox = $(this.options.textHintBoxID);
		var linkElems = textHintBox.getElementsByClassName("TH_HintLink");

		if ( linkElems[selectedIndex] ){
			return linkElems[selectedIndex].id;
		}

		return false;
		
	},

	resetHintBox: function (delay){

		var timeout = 0;
		if ( typeof delay == "number" ){
			timeout = delay;
		}
		setTimeout(this._resetHintBox.bind(this),timeout);
	},

	_resetHintBox: function(){
        var textHintBox = $(this.options.textHintBoxID);
    
        // only "reset" the box if it's visible
        if (!textHintBox.visible()) return;
        
		this._currentLookup = '';
		this._currentHintData = {};
		if ( this._iframeAdded ){
			$(this._iframeID).hide();
		}
		textHintBox.hide();
		textHintBox.innerHTML = "";
		this.hideLoadingBox();
	},

	showLoadingBox: function(){
		this.toggleLoadingBox("block");
	},

	hideLoadingBox: function(){
		this.toggleLoadingBox("none");
	},

	toggleLoadingBox: function(display){
		if ( this.options.textHintLoadingBoxID && $(this.options.textHintLoadingBoxID) ){
			$(this.options.textHintLoadingBoxID).style.display = display;
		}
	}

};


// use Prototype's method of returning values
function getCurrentStyle(oElm, strCssRule){
    return Element.getStyle(oElm, strCssRule);
}
