var calhoun; 
if (typeof(calhoun) == "undefined") calhoun={};

calhoun.selectbox = {};
var domGet = YAHOO.util.Dom.get;

MAX_FRAMES = 1000;

calhoun.selectbox.Range = function(textElementId, rowElementId) {
    this.startBox = domGet(textElementId + "Start");
    this.stopBox = domGet(textElementId + "Stop");
    this.row = domGet(rowElementId);
    this.start = null;
    this.stop = null;
    this.isInit = false;
};

(function() {
    var RangeClass = calhoun.selectbox.Range.prototype
     
    RangeClass.setStart = function(start) {
        this.start = start;
        if (this.startBox != null)
            this.startBox.value = start;
    }
    RangeClass.setStop = function(stop) {
        this.stop = stop;
        if (this.stopBox != null)
            this.stopBox.value = stop;
    }
    
    RangeClass.hide = function() {
        if (this.row.hide)
            this.row.hide();
    }
    RangeClass.show = function() {
        if (this.row.show)
            this.row.show();
    }
    RangeClass.init = function(start, stop) {
        this.setStart(start);
        this.setStop(stop);
        this.hide();
        this.isInit = true;
    }
    
    RangeClass.disable = function() {
        this.startBox.disabled = true;
        this.stopBox.disabled = true;
    }
    
    RangeClass.enable = function() {
        this.startBox.disabled = false;
        this.stopBox.disabled = false;
    }
    
})()

// SelectOption class
calhoun.selectbox.Option = function(text, value) {
	this.text = text
	this.value = value
	this.oOption = null;
	this.selector = null;
};

(function() {
	var OptionClass = calhoun.selectbox.Option.prototype	
	
	OptionClass.fromEl = function(selector, el) {
	    this.oOption = el;
	    this.text = el.text;
	    this.value = el.value;
	    this.selector = selector;
	}
	
	OptionClass.create = function(selector, text, value) {
	    this.oOption = document.createElement("OPTION");
        this.oOption.text = text;
        this.oOption.value = value;
        this.selector = selector;
	}
	
	OptionClass.select = function() {
	    this.oOption.selected = true;
	}
	
})()

// Selector class
calhoun.selectbox.Selector = function(type, id, textElementId, data, serverUrl) {
    this.type = type;
    this.selectorId = id;
    this.textElementId = "text" + id;
    this.selectElementId = "select" + id;
    this.selectedField = domGet("selected");
    this.selectionName = null;
    this.dataSource = null;
    this.lengthArray = {}; 
    this.frameData = data;
 	this.queryDelay = .5;
    this.updateCall = null;
    this.restoringPrevious = false;
    this.doExactMatch = false;
    this.currentRange = new calhoun.util.Range(null, null);
    this.fetchMore = false;
    this.fetchingMore = false;
    
    this.selectors = null;
    this.rangeObj = null;
    this.selectedValue = null;

    this.levelCount = null;
    this.asmLevels = null;
    this.targetLevel = null; // the level currently being updated by ajax call
    this.parentValue = null; // the value that last populated this selector
    this.parentId = null; // the selector ID of the parent who last populated this selector
       
    this._nKeyCode = null;
    this._nDelayID = -1;
    this._sCurQuery = null;
    this._removeNonMatching = false;

    this.textBox = domGet(this.textElementId);
    this.selectBox = domGet(this.selectElementId);
    this.tdCell = domGet("td" + this.selectorId);
    this.title = domGet("title" + this.selectorId);
    
    	    
	this.serverSource = new YAHOO.widget.DS_XHR(serverUrl, ["Results.Result","safeName","name","globalId","seqLength"]);
   	this.serverSource.scriptQueryParam = "sp";	

    YAHOO.widget.DS_XHR.prototype.doQuery = calhoun.ajax.doQuery;
	
	var eventSource2 = new calhoun.util.EventSource(this.textElementId);	
	// in order to suppress default handling, listener must be on "keypress" 
	// rather than "keydown" or "keyup"
	eventSource2.addListener("keypress", this, "handleEnter");
	eventSource2.addListener("keyup", this, "handleTyping");
	
	var selectEventSource = new calhoun.util.EventSource(this.selectElementId);
    // IE doesn't work with mouse events on a selectbox.  
	selectEventSource.addListener("change", this, "handleSelect");
	
	var arrowEventSource = new calhoun.util.EventSource(this.selectElementId);
	arrowEventSource.addListener("keyup", this, "handleSelect");
	
	this.parentChangeEvent = new YAHOO.util.CustomEvent("parentChange", this);    
   	this.parentChangeEvent.subscribe(this.updateSelection);

};

(function() {
    var SelectorClass = calhoun.selectbox.Selector.prototype;
       
    SelectorClass.init = function() {         
        if (this.frameData != null) {
            this.createDataSource();
        }
        if (this.dataSource != null) {		// populate the dropdown from the data source
            this._filterOptions("", false); 
            if (this.getText() != null && this.getText() != "") {
     	        this.selectOption(this.getText(), null, true);
           } 
        }
    }
    
    SelectorClass.createDataSource = function() {
        this.processLengthArray();       
        this.dataSource = new YAHOO.widget.DS_JSArray(this.parseFrameData(this.frameData));
        this.dataSource.queryMatchContains = true;
    }
    
    SelectorClass.parseFrameData = function() {
        var returnArray = [];
        var i=0;
        for (var id in this.frameData) {
            returnArray[i++] = [this.frameData[id].safeName,this.frameData[id].name,this.frameData[id].globalId ];
        }
        return returnArray;
    }
    
    SelectorClass.buildFrameData = function(data) {
        if (data.length == 0)
            return null;
        var returnMap = {};
        for (var i=0; i < data.length; i++) {
            returnMap[data[i][2]] = {"safeName": data[i][0], "name": data[i][1], "globalId": data[i][2]};
        }
        return returnMap;
    }
        
	// YUI does a substring search on URI-encoded versions of the data
    // so we have to translate the frame names and the input strings to strings that don't need encoding
    // otherwise a search for "2" will match "%20"
    
    SelectorClass.makeSafeText = function(text) {
        return text.replace(/[^A-Za-z0-9$_.+!*'()-]/g, "");
    }

    // prevent form from submitting when enter is pressed in a form field    
    SelectorClass.handleEnter = function(event) {
    		var nKeyCode = event.keyCode;
		if (this._isEnter(nKeyCode)) {
    		    YAHOO.util.Event.preventDefault(event);
		}
    }
     
	SelectorClass.handleTyping = function(event) {
		var nKeyCode = event.keyCode;
		this._nKeyCode = nKeyCode;
		var sText = this.getText(); //string in textbox
		
        // Filter out chars that don't trigger queries
	    if (this._isIgnoreKey(nKeyCode) || 
	        (sText.toLowerCase() == this._sCurQuery)) {
	        return;
	    }
	    
	    if (this._isEnter(nKeyCode))
	       this.doExactMatch = true;
	    
	    var oSelf = this;
		// wait for a decent length pause in typing
		// Set timeout on the request
	    if (this.queryDelay > 0) {
	        var nDelayID =
	            setTimeout(function(){oSelf._filterOptions(sText);},
	               (this.queryDelay * 1000));
	        if (this._nDelayID != -1) {
	            clearTimeout(this._nDelayID);
	        }
	        this._nDelayID = nDelayID;
	    } else {
	        // No delay so send request immediately
	        this._filterOptions(sText);
	    }
		
	}
    
    SelectorClass.setText = function(input) {
    	if (this.textBox != null)
    		this.textBox.value = input;
    }
    SelectorClass.getText = function() {
    	if (this.textBox != null)
    		return this.textBox.value;
    	else
    		return "";
    }
    
    SelectorClass.addOption = function(option) {
        if (document.all) 
           this.selectBox.add(option)
        else
           this.selectBox.appendChild(option);   
    }
    
    SelectorClass.getRealOptions = function() {
        if (this.selectBox.options == null || this.selectBox.options.length == 0)
            return this.selectBox.options;
        if (this.selectBox.options[this.selectBox.options.length - 1].value == "MORE") {
            var returnOptions = [];
            for (var i=0; i < this.selectBox.options.length - 1; i++) {
                returnOptions[i] = this.selectBox.options[i];
            }
            return returnOptions;
        }
        return this.selectBox.options;        
            
    }
    
    SelectorClass.getTargetRange = function() {
        if (this.currentRange != null && this.currentRange.isSet()) {
            if (this.fetchingMore)
                return new calhoun.util.Range(this.currentRange.start,
                             this.currentRange.stop + MAX_FRAMES);
            else
                return this.currentRange;
        }       
        return new calhoun.util.Range(0, MAX_FRAMES);
    }

    SelectorClass.processLengthArray = function() {
        for (var id in this.frameData) {
            if (this.frameData[id].seqLength != null) {
                this.lengthArray[this.frameData[id].globalId] = this.frameData[id].seqLength;
                // this.frameData[i].splice(2,1);
            }
         }
    }
 	
	SelectorClass._isIgnoreKey = function(nKeyCode) {
    	if ((nKeyCode == 9) ||  // tab
	        (nKeyCode == 16) || (nKeyCode == 17) || // shift, ctl
	        (nKeyCode >= 18 && nKeyCode <= 20) || // alt,pause/break,caps lock
	        (nKeyCode == 27) || // esc
	        (nKeyCode >= 33 && nKeyCode <= 35) || // page up,page down,end
	        (nKeyCode >= 36 && nKeyCode <= 38) || // home,left,up
	        (nKeyCode == 40) || // down
	        (nKeyCode >= 44 && nKeyCode <= 45) || // print screen,insert
	        (nKeyCode == 78)) {  // open-apple-n
	           return true;
	   }
       return false;
	}
	
	SelectorClass._isEnter = function(nKeyCode) {
	    return (nKeyCode == 13);
	}
	
	SelectorClass.sendServerQuery = function(inputText) {
	    this.serverSource.scriptQueryAppend = "sp=S" +  this.parentValue + "&sp=S" + this.selectorId + 
	       "&sp=" + (this.doExactMatch ? "T" : "F");
        this.serverSource.getResults(calhoun.util.bindMethod(this, this._updateFromServer), inputText);
	}
	
	SelectorClass._updateFromServer = function(inputText, results) {
	    for (var i=0; i < results.length; i++) {
            var obj = {};
            obj.safeName = results[i][0];
	        obj.name = results[i][1];
	        obj.globalId = results[i][2];
	        obj.seqLength = results[i][3];
            this.frameData[obj.globalId] = obj;
	    }
        this.createDataSource();
        this._updateList(inputText, results);	    
	}
	
	SelectorClass._filterOptions = function(inputText, removeNonMatching) {
	    if (this.getValue())
        		this.deselect();
    	    this._sCurQuery = inputText;
    	    	    
	    // populate if necessary
	    if (this.dataSource == null && this.selectorId > 0) {
            this.populateSelf(inputText);
	    }
	    
	    // make a server-side text query if not all data is populated on the client (in the case of large assemblies)
    	if (inputText != "" && this.fetchMore) {
            // log("makign server query for " + inputText);
    	    this.sendServerQuery(inputText);
    	    
    	} else if (this.dataSource != null) {
    	    // log("checking client for " + inputText);
	        this._removeNonMatching = removeNonMatching;
	        this._nDelayID = -1;    // Reset timeout ID because request has been made
       		this.dataSource.getResults(calhoun.util.bindMethod(this, this._updateList), 
       		   encodeURIComponent(this.makeSafeText(inputText)));
        } 
	}

	
	// - Sync the options in selectbox list with the current contents of this.frameData,
	//   plus any results returned from a query
	// - Highlight options matching the query results
  	SelectorClass._updateList = function(inputText, results) {
	    var selectedIndex = -1;
	    var multipleMatches = false;
	    var matchedLength = -1;
	    var matchedOpt;
	    var input = decodeURIComponent(inputText);
	    var doExactMatch = this.doExactMatch;
	    
	    if (this.getText() != input && !this.restoringPrevious) {
        	    this.setText(input);
	    }
        	
		// log(results);
        var resultsMap = this.buildFrameData(results);

        var range = this.getTargetRange(); 	
        
       // build allData hash of existing frame data in selectbox + new results	
       var allData = this.frameData;         	
       if (resultsMap != null) {
            for (var id in resultsMap) {
                allData[id] = resultsMap[id];
            }
        }

    	var options = this.getRealOptions();      	

    	// create an option for each allData entry (where necessary)
    	if (options.length != allData.length ||
    	       options[options.length - 1].value != allData[allData.length - 1].globalId) {
            var optionsToAdd = {};
            for (var id in allData) {
                optionsToAdd[id] = allData[id];
            }
            var lastOption = 0;
            
            // go through existing dropdown options.  if they are already among results,
            // remove them from optionsToAdd
            for (var i=0; i < options.length; i++) {
                if (allData[options[i].value] != null)
                    delete optionsToAdd[options[i].value];                
            }
            
        	for (var id in optionsToAdd) {     // add remaining matched options to end of selectbox
        	       matchedOpt = optionsToAdd[id];
                   oOpt = new calhoun.selectbox.Option();          
                   oOpt.create(this, matchedOpt.name,matchedOpt.globalId);
                   this.addOption(oOpt.oOption);   
        	}            
    	}	
        	
        // select returned results
        if (resultsMap != null) {
        	for (var i=0; i < this.selectBox.options.length; i++) {
    
    		    var curOption = new calhoun.selectbox.Option();
    		    curOption.fromEl(this, this.selectBox.options[i]);
               
    		   if (resultsMap[curOption.value] != null && 
        		      (!this.doExactMatch || this.makeSafeText(inputText).toLowerCase() == this.makeSafeText(curOption.text).toLowerCase())) {    // there's a match in results hash
                    if (selectedIndex != -1) {
                        curOption.select();
                        multipleMatches = true;
                    } else {
                        selectedIndex = i;
                    }
    		   } else {
    		       	if (this.selectBox.options[i].selected)
    	        	  this.selectBox.options[i].selected = false;
            		if (this._removeNonMatching || this.selectBox.options[i].value == "MORE") {
                        this.selectBox.remove(i);
                        i--;
            		}
    		   }
 		    }
        }
                	
      	// add an option for 'fetch more' if there are too many results to print
     	if (this.fetchMore && 
     	      this.selectBox.options[this.selectBox.options.length - 1].value != "MORE") {
        	   oOpt = new calhoun.selectbox.Option();          
               oOpt.create(this, "Fetch more...", "MORE");
        	   this.addOption(oOpt.oOption);      	   
        }
		
       	this.doExactMatch = false;
		
		// log("input is ", input, " selected index is ", selectedIndex, "resultsMap is ", resultsMap);
		
		// highlight and drilldown (only if the search was for actual text)
        if (input != "" && selectedIndex != -1 && resultsMap != null) {		
        	// re-select the first selection to keep textbox from scrollin past it
      		this.selectBox.options[selectedIndex].selected = true;
        
    	    // drilldown if there's a unique match
    	    if (!multipleMatches) {
                this.selectOption(null, selectedIndex);
    	    }
        }	    
	}
	
	SelectorClass.numSelected = function() {
	    var num = 0;
    		for (var i=0; i < this.selectBox.options.length; i++) {
    		    if (this.selectBox.options[i].selected == true)
    		      num++;
    		}
    		return num;
	}
	
	SelectorClass.getSelected = function() {
	    var selected = new Array();
	    var num = 0;
    		for (var i=0; i < this.selectBox.options.length; i++) {
    		    if (this.selectBox.options[i].selected)
    		      selected[num++] = i;
    		}
    		if (selected.length == 1)
    		  return this.selectBox.options[selected[0]];
    		return null; 
	}
	
	SelectorClass.handleSelect = function(event) {
	    var target = YAHOO.util.Event.getTarget(event);
        this.selectOption(null, target.index);
	}
	
	SelectorClass.updateSelection = function() {
	    	    
	   var selectedValue = this.getValue();
       if (selectedValue != null) {        	                  
    	       if (this.selectorId == 0) {  // Genome
                showHideSelectors(selectedValue);
            }
                 	        
            if (!this.fetchingMore) {
                // set this as the current selection
                this.selectedField.value = selectedValue;                  
    	                              
                // populate selected frame type
                this.selectionName.innerHTML = this.asmLevels[this.selectorId];
            }
             
            // populate range fields
            var selectionLength = -1;
            if (this.lengthArray != null && this.lengthArray[selectedValue])
                selectionLength = this.lengthArray[selectedValue]; 

            this.rangeObj.show();
            if (selectionLength != -1 && !this.restoringPrevious) {
                this.rangeObj.setStart(1);
                this.rangeObj.setStop(selectionLength); 
            } 
	    }
        this.refreshData(this.restoringPrevious ? 1 : 0);
        if (this.selectedField.value == selectedValue)
            this.restoringPrevious = false;

	}
    
    SelectorClass.refreshData = function(maxLevels) {
        var levelCount = 0;
        for (var i=this.selectorId; i < this.levelCount - 1; i++) {
            if (maxLevels > 0) {
                if (levelCount >= maxLevels)
                    break;
                levelCount++;
            }
    	        var child = this.selectors[i+1];
    	        var value = this.getValue();
    	        if (!child.isVisible())
    	           continue;
    	        if (!child.consistentWith(value, child.getTargetRange()) || child.fetchingMore) {
        	        child.parentId = this.selectorId;
        	        child.parentValue = value;

                var targetRange = child.getTargetRange();
                
                child.empty();
                  if (!child.dataCache.hasData(child.parentValue, child.selectorId, targetRange.start, targetRange.stop)) {
                       child.dataCache.retrieveData(child.parentValue, child.selectorId, targetRange.start, targetRange.stop);
                 } 

    	        } 
        }      
	    this.populateChildren();
    }    
    
    SelectorClass.consistentWith = function(value, range) {
        return (this.parentValue == value && 
            range.equals(this.currentRange));
    }
    
    // called by the parent that changed, causing an update of children
    SelectorClass.populateChildren = function() {
         for (var level=this.selectorId + 1; level < this.levelCount; level++) {
            var childSelector = this.selectors[level];
            if (!childSelector.consistentWith(childSelector.parentValue, childSelector.getTargetRange()))
                childSelector.populateSelf();
         }        
    }
    
    // called by an updated child
    SelectorClass.populateSelf = function(inputText) {
        var level = this.selectorId;
        var targetRange = this.getTargetRange();
        var resp = this.dataCache.getData(this.parentValue, level, targetRange.start, targetRange.stop);
         var fetchMore;
         if (resp != null) {
                try {
                    // update the child selector
                    var targetRange = this.getTargetRange();   
                    var responseMap = eval(resp);       
                    var type = responseMap["type"];
                    this.fetchMore = responseMap["more"];
                    var childId = this.asmLevels.indexOf(type);
                    this.frameData = responseMap["frames"];
         			this.createDataSource();  
        			this.currentRange = targetRange;
           			this.fetchingMore = false;
    			}
    			catch(e) {
    				log("error evaluating: " + resp)
    				log(e)
    			}
                this.filterData();
        }
    }
    
    SelectorClass.filterData = function() {   
        var savedValue = null;
        if (this.restoringPrevious) {
            savedValue = this.getText();
            // log("restoring value " + savedValue);
        }
        if (this.selectBox.options.length == 0) {
            // log("filtering on nothing");
            this._filterOptions("", false);
        }
        if (savedValue != null) {
            this.doExactMatch = true;
            this.setText(savedValue);
            // log("filtering on " + savedValue);
            this._filterOptions(savedValue, false);
        } 

    }
    
    SelectorClass.setTitle = function(title) {
        this.title.innerHTML = title;
    }
 
    SelectorClass.isVisible = function() {
        return YAHOO.util.Dom.getStyle(this.tdCell, 'display') != 'none';
    }
 
    SelectorClass.show = function() {
        YAHOO.util.Dom.setStyle(this.tdCell, 'display', '');
    }
    SelectorClass.hide = function() {
        YAHOO.util.Dom.setStyle(this.tdCell, 'display', 'none');
    }
    
    SelectorClass.empty = function() {
        if (!this.restoringPrevious) {
            this.setText("");
        }
        this.dataSource = null;
        this.emptyOptions();
        if (!this.fetchingMore) {
            this.frameData = null;
            this.currentRange = new calhoun.util.Range(null, null);
        }
    }
    
    SelectorClass.emptyOptions = function() {
        for (var i=this.selectBox.options.length - 1; i >=0; i--) {
            this.selectBox.options[i] = null;
        }
    }
    
    SelectorClass.getValue = function() {
          var sel = this.getSelected();
          if (sel != null)
            return sel.value;
          return null;
    }
    
    SelectorClass.removeSelection = function() {
    	    this.setText("");
    	    this.selectBox.selectedIndex = -1;
    	    this.selectedField.value = "";
    }
    
    SelectorClass.deselect = function() {        
        this.removeSelection();
        var parentSel = null;
        if (this.selectorId > 0) {
            var parentId = this.selectorId - 1;
            parentSel = this.selectors[parentId];
            // find the closest parent with an option selected
    	       // while (parentSel.selectBox.selectedIndex == -1) {
    	       while (parentSel.getSelected() == null) {
    	        if (parentId == 0)
    	            break;
    	            parentId--;
                parentSel = this.selectors[parentId];
            }
        }
        // repopulate on basis of first parent with a selection
        	if (parentSel != null)  { 
             parentSel.parentChangeEvent.fire();
        	} else {  // or just clear all selections
        		for (var i=1; i < this.levelCount; i++) {
        		    this.selectors[i].empty();
        		}
        		this.rangeObj.hide();
        	}	
    }
    
    SelectorClass.selectOption = function(optionStr, optionIndex) {
        var selectedVal = null;
        var selectIndex = -1;
        for (var i= 0; i < this.selectBox.options.length; i++) {
             if (optionIndex == i || this.selectBox.options[i].text == optionStr || 
                 this.selectBox.options[i].selected) {
            		selectIndex = i;
    				selectedVal  = this.selectBox.options[i].value;
    				this.selectBox.options[i].selected = true;
    				break;
            	} else {
            	    this.selectBox.options[i].selected = false;
            	}
        }
        if (selectIndex > -1) {
            if (this.selectBox.options[selectIndex].value == "MORE") {
	           this.fetchingMore = true;
                this.deselect();
            } else {
                this.setText(this.selectBox.options[selectIndex].text);
         	   this.parentChangeEvent.fire();
            }
        }
    }
    

})()


