var doDebug = false;
var prevTime;
var profilingElement;
var profilingButton;

// flag if the page has been reloaded and the first domain should be highlighted in Jmol
var displayFirstDomain = true;

var debugDiv = null;
if ( doDebug ) {
	debugDiv = $("<div>");

	$(document).ready(function() {
		if(!profilingButton) 
		{
			profilingButton = $('<a href="" onclick="javascript: return false;">Show Debug Log</a>');			
			profilingButton.click(function() {
				$(debugDiv).hide();
				$(debugDiv).appendTo(document.body);
				$(debugDiv).fadeIn();
			});
	
			//document.body.appendChild(profilingButton);
		}
		profilingButton.appendTo(document.body);
	});
}

function debug(message)
{
	if(doDebug) 
	{						
		debugDiv.append('<br/>'+(new Date()).getTime() +': ' + message);
	}
}


// the SequencePage class manages other classes and does DOM manipulation
function SequencePage(data) { with(this)
{
	debug('instantiating url param map');
	this.params = new Params();
	debug('instantiating annotation JSON data handler');
	this.annotations = new Annotations(data);
	this.showJmol = false;
	if(params.contains('params.showJmol','true'))
	{
		debug('instantiating jmol scripter');
		this.jmolScripter = new JmolScripter(this);
		showJmol = true;
	} else {
		// AP: Jmol could be UNDEF, this happens during first loading of page
		//  this is just a workaround, but does not fix the original problem that params.showJmol is undef
		if (! params.contains('params.showJmol','false')){
			debug('backup: instantiating jmol scripter and showJmol is undef!');
			this.jmolScripter = new JmolScripter(this);
			showJmol = true;
		}
	}
	this.showParam = 'params.annotationsStr';
	this.hideParam = 'params.disabledAnnotationsStr';
	this.timesCalledMap = 0; // hack for replacing image map in safari
	this.jmolCmdMap= [];
	this.domainBiol= [];
	

	
	debug('done instantiating all of SequencePage');
}}
SequencePage.prototype.unesc = function(string)
{
	return unescape(string).replace(/\+/g, " ");
};
SequencePage.prototype.showAnnotation = function(annotation) { with(this) 
{
	debug('****USER INPUT RESPONSE STARTS**** showing ' + annotation);
	params.put(showParam, annotation);
	params.remove(hideParam, annotation);
	
	displayFirstDomain = true;
	
	params.put('returnJson', 'true');
	params.put('annotationForJson', annotation);
	var url = params.url();
	debug(url);
	$.getJSON(url, showAnnotationCallback);
	params.del('returnJson');
	params.del('annotationForJson');
	
	
//	var async = asyncManager.getThread(annotation);
//	async.callback = showAnnotationCallback;
//	async.load(url);
}};
SequencePage.prototype.showAnnotationCallback = function(data, url) { with(sp)
{
	debug('****CALLBACK RESPONSE STARTS**** showAnnotationCallback responding to url ' + url);
	annotations.load(data);
	debug('showAnnotationCallback done loading annotations');
	refreshData(true);
	debug('showAnnotationCallback done refreshing data');
}};
SequencePage.prototype.hideAnnotation = function(annotation) { with(this) 
{
	debug('****USER INPUT RESPONSE STARTS**** hiding ' + annotation);
	params.put(hideParam, annotation);
	params.remove(showParam, annotation);
	annotations.del(annotation);
	displayFirstDomain = true;
	refreshData(true);
}};
SequencePage.prototype.syncParamsAndJson = function() { with(this)
{
	var ap = new Object();
	var as = annotations.getAnnotationData();
	var a;
	for(a in as) {
		params.remove(hideParam,a);
		if(!as[a].isFallback) ap[a] = true;
	}
	params.set(showParam,ap);
}};
SequencePage.prototype.refreshData = function(andTableAndImage) { with(this)
{
	debug('refreshData called with andTableAndImage : ' + (andTableAndImage ? 'true' : 'false'));
	
	// flag jmol to display the first domain annotation that it gets.
	displayFirstDomain = true;
	// reset the cached commands...
	jmolCmdMap=[];
	
	syncParamsAndJson();
	
	var chainId;
	for(chainId in annotations.getChainData())
	{
		debug("chain id in annotation: " + chainId);
		
		
		if(andTableAndImage)
		{
			try{replaceChainTable(chainId);}catch(e){ debug('failed to regenerate table ' + e ) ;}
			try{replaceChainImage(chainId);}catch(e){ debug('failed to regenerate image ' + e ) ;}
			// todo enable summary...
			//try{replaceChainImageSummary(chainId);}catch(e){ debug('failed to regenerate summary image ' + e ) ;}
		}
		
		try{replaceChainImageMap(chainId);}catch(e){ debug('failed to regenerate image map ' + e);}
		
		
		
	}
	++timesCalledMap;
	refreshReferences();
	
	// now highlight the first annotation in Jmol:
		
	if ( displayFirstDomain ) {
		debug ('showing first domain');
		displayFirstDomain  = false;
		var jmolCmd = null;
		for (var cmd in jmolCmdMap){
			jmolCmd = cmd;
			break;
		}
		debug("executing " + jmolCmd);
		// this sends the command to Jmol
		if ( jmolCmd != null){			
			eval(jmolCmdMap[jmolCmd]);
		}
	}
	
	
	
}};
SequencePage.prototype.refreshReferences = function() { with(this)
{
	var refdiv = document.getElementById('references');
	while(refdiv.hasChildNodes()) refdiv.removeChild(refdiv.firstChild);
	var anList = annotations.getAnnotationList();
	for(var i in anList)
	{
		var an = anList[i];
		if(an.isVisible)
		{
			renderRef(an.ref, refdiv, an);
		}
	}
}};
SequencePage.prototype.renderRef = function(ref, parent, an) 
{
	if(!ref) return;
	var result = document.createElement('div');
	result.id = an.name + 'Ref';
	parent.appendChild(result);
	var f = this.renderRefEl;
	with(ref)
	{
		var anchor = document.createElement('a');
		anchor.id = an.name + 'RefAnchor';
		result.appendChild(anchor);
		var header = document.createElement('div');
		header.className = 'referenceDescription';
		header.innerHTML = '<p><b>' + an.displayName + ' ' + an.classification + ' reference</b></p>';
		result.appendChild(header);
		var journalStr = journal.concat('&nbsp;',pubYear,'&nbsp;',pubMonth);
		try{journalStr.concat('&nbsp;',pubDay);}catch(e){}
		journalStr.concat(';',volume,'(',issue,');',pages,'.');
		f('authorList',authors,result);
		f('articleTitle',title,result);
		f('journal',journalStr,result);
		var refEl = f('referenceLinks','PMID:&nbsp;' + pmid + '&nbsp;',result);
		var a = document.createElement('a');
		var img = document.createElement('img');
		refEl.appendChild(a);
		a.appendChild(img);
		a.className='se_datalarge';
		a.href='http://www.ncbi.nlm.nih.gov/entrez/query.fcgi?cmd=Retrieve&db=PubMed&dopt=Abstract&list_uids='+pmid;
		a.target='_blank';
		img.src='/pdb/explorer/images/abstract_icon.gif'
		if(img.style)img.style.border='0';
		img.alt='View NCBI Pumbed Abstract';
		img.title=img.alt;
	}
};
SequencePage.prototype.renderRefEl = function(clazz, text, parent) {
	var el = document.createElement('div');
	parent.appendChild(el);
	el.className = clazz;
	el.innerHTML = text;
	return el;
};
SequencePage.prototype.replaceChainImageSummary = function(chainId) { with(this)
{
	debug('modifying summary image for ' + chainId);
	var imageSrcTemplate = params.url().replace("remediatedSequence.do","remediatedChain.do");
	debug('image url template: ' + imageSrcTemplate);
	var imgId = imageId(chainId);
	var oldImage = document.getElementById(imgId);
	var imgParent = oldImage.parentNode;
	var image = document.createElement('img');
	var imgData = annotations.getChainData(chainId).img;
	debug('got necessary variables for image modification. now making new image object');
	image.height = imgData.height;
	image.width = imgData.width;
	debug('done making image dimensions');
	try{image.style.border = '0';}catch(e){ debug(e); }
	debug('done changing image style');
	image.id = imgId;
	image.name = imgId;
	image.src = imageSrcTemplate + "&chainId=" + chainId;
	debug('done making id, name and src; replacing image in dom');
	imgParent.removeChild(oldImage);
	debug('removed old image');
	imgParent.appendChild(image);
	debug('appended new image; adding onload');
	image.onload = function() { debug('new Image for chain ' + chainId + ' is loaded') };	
	
}};
SequencePage.prototype.replaceChainImage = function(chainId) { with(this)
{
	//alert("replace chain image for " + chainId);
	//debug('modifying image for ' + chainId);
	var imageSrcTemplate = params.url().replace("remediatedSequence.do","remediatedChain.do");
	//debug('image url template: ' + imageSrcTemplate);
	debug(" replaceChainImage for chain " + chainId)
	//var imgParent = $(oldImage).parent();
	
	var image = new Image();
	
	//debug('done making image dimensions');
	try{image.style.border = '0';}catch(e){ debug(e); }
	//debug('done changing image style');
	
	
	$(image).load( function() {
		//debug('new Image for chain ' + chainId + ' is loaded')
		
		var imgData = annotations.getChainData(chainId).img;
		//debug('got necessary variables for image modification. now making new image object');
		$(image).attr("height",imgData.height);
		$(image).attr("width",imgData.width);
		 
		//debug("looking for parent:" + "#chainImage"+chainId+'-container');
		var imgParent = $("#chainImage"+chainId+'-container');
		debug(' image Parent: ' + $(imgParent).attr("id"));
		
		var oldImage = $(imgParent).children("img");
		//debug("old image " + oldImage.attr("id"));
				
		$(this).attr("id",     oldImage.attr("id")   );
		
		var mapName = '#'+mapId(chainId);
		$(this).attr("useMap", mapName);
		
		//$(this).attr("usemap", oldImage.attr("usemap"));
		debug("new image using map "+ $(this).attr("useMap"));
		
		// do the actual replacement...		
		$(oldImage).fadeOut();		

		// delete old image from parent
		$(imgParent).empty();
							
		$(this).hide();
		$(imgParent).append(this);
		$(this).fadeIn();
		//debug("finished loading src:" + $(this).attr("src"));
	}).attr('src', imageSrcTemplate + "&chainId=" + chainId);
	//debug('appended new image; adding onload');	

	
}};
SequencePage.prototype.reloadPage = function() { with(this)
{
	window.location.href = params.url();
}};
SequencePage.prototype.mapId = function(chain) 
{
	var m = 'chain' + chain + 'map';
	for(var i = 0; i < this.timesCalledMap; i++) m += '_';
	return m;
};
SequencePage.prototype.mapEl = function(chain)
{
	var id = this.mapId(chain);
	return document.getElementById(id);
};
SequencePage.prototype.imageId = function(chain)
{
	return 'chainImage' + chain;
};
SequencePage.prototype.imageSummaryId = function(chain)
{
	return 'chainImageSummary' + chain;
};
SequencePage.prototype.imageEl = function(chain)
{
	var id = this.imageId(chain);
	return document.getElementById(id);
};
SequencePage.prototype.replaceChainImageMap = function(chainId) { with(this)
{	
	//alert("replaceChainImageMap " + chainId);
	debug('modifying image map for ' + chainId);
	var mapName = mapId(chainId);
	var imageMap = mapEl(chainId);
	
	// should display: old map name: chainAmap got image map [object HTMLMapElement]
	debug('old map name: ' + mapName + ' got image map ' + imageMap);
	var parent = imageMap.parentNode;
	//var parent = $(imageMap).parent();
	debug('map parent node id is ' + parent.id);
	var newMap = document.createElement('map');
	newMap.name = mapName + '_';
	newMap.id   = mapName + '_';
	debug('original map size: ' + imageMap.areas.length);
	var imageData = annotations.getChainData(chainId).img;
	var yOffset = 0;
	var allData = annotations.getAnnotationList();
	
	debug('found ' + imageData.fragmentCount + ' fragments for the imagedata');
	
	for(var i = 0; i < imageData.fragmentCount; i++)
	{
		for(var j in allData)
		{
			var an = allData[j].name;
			//debug( '--- creating coords for '+ name + ' ' + allData[j].displayName + ' fragment count:' + i + ' j:' + j + ' ' + allData[j].isVisible);
			if(allData[j].isVisible)
			{
				//debug('---  generating coords for map: ' + mapName + '_ name:' + an + ' fragment count:' + i   +  ' yOffset: ' + yOffset + ' ' );
				generateCoordsForMap(chainId, newMap, imageData.mapData[an], i, yOffset, showJmol ? jmolScripter.canScript(an) : false, an);
				yOffset += incrementYOffset(an, imageData);
			}
		}
		
		var rs = ['upperRuler','sequence','lowerRuler'];
		for(var j in rs)
		{
			var an = rs[j];
			generateCoordsForMap(chainId, newMap, imageData.mapData[an], i, yOffset, false);
			yOffset += incrementYOffset(an, imageData);
		}
		
		yOffset += imageData.fragBufferHeight;
	}
	debug('done generating map element; adding to dom');
	//alert($(imageMap));
	//$(imageMap).before($(newMap)).replace();
	parent.replaceChild(newMap, imageMap);
	debug('new map size: ' + newMap.areas.length + ', actual height: ' + yOffset + ', expected height: ' + imageData.height);
	
	//var iid = imageId(chainId);
	
	var imgParent = $("#chainImage"+chainId+'-container');
	debug(' image Parent: ' + $(imgParent).attr("id"));
	
	var image = $(imgParent).children("img");
	debug("got image: " + image);
	//var image = $('#' + iid);
	debug("setting image map for chain " + chainId + ":" + $(image).attr('id') + ' ' + newMap.name);
	var newMapName = '#' + newMap.name;
	$(image).attr("useMap",newMapName);
	
	debug('have set map for image ' + $(image).attr('id')+' , see: ' + $(image).attr("useMap"));
	debug('this map has ' + newMap.areas.length + ' areas');
	
	//alert("done with replaceChainImageMap " + chainId+" !");
	
}};
SequencePage.prototype.generateCoordsForMap = function(chainId, map, data, fragNum, yOffset, isClickable, annotationName) { with(this)
{
	//debug('called generateCoordsForMap ' + annotationName);
	
	if(data && map && fragNum >= 0)
	{
		
		for(var frgN in data[fragNum])
		{
			
			var areaData = data[fragNum][frgN];
			
			if(isClickable && annotationName && areaData.av && areaData.av != 'n/a')
			{
				
				//debug('create new element for ' + annotationName + ' ' + areaData.av)
				var el = document.createElement('area');
				
				// create a unique map name for each map:
				// this can be used also to store the rasmol scripts and link them back to the mouse click event
				var elid = 'chain_'+chainId +'_frgC_'+fragNum + '_frgN_' + frgN + '_annName_'+ annotationName + '_areaData.av_'+areaData.av ;
				//debug(" --- setting values for image map element:" + elid);
				el.id = elid ;
				el.shape = 'rect';
				el.coords = generateCoords(areaData, yOffset);
				el.title = areaData.t;
				map.appendChild(el);
												
				var toEval ='jmolScripter.highlight("' + chainId + '", "' + annotationName + '", "' + areaData.av + '");';
				
				// the unique name is used to store the jmol script cmds in the jmolCmdMap array
				jmolCmdMap[elid] = toEval;
				domainBiol[areaData.av] = el.title
				// debug("setting domainBiol " + areaData.av + " to " + el.title);					
				debug(toEval);
				
				// don't want to see the jmol javascript
				//el.title += ' js: ' + toEval;
				if(el.style) el.style.cursor = 'pointer';
				
				$(el).click(function(e) {
									
					// hack for IE to work around JQUery bug...
				    // Calculate pageX/Y if missing and clientX/Y available					
					// 					
					e = fixEvent(e)
					// end of work around...
					//
	                
						var x = e.pageX - this.offsetLeft;
						var y = e.pageY - this.offsetTop;
						debug(' user clicked at ' + e.target.id);
						var jmolCmd = jmolCmdMap[e.target.id];
						
						// this sends the command to Jmol
						eval(jmolCmd);
						
						//jmolScripter.highlight(chainId, annotationName, areaData.av); 
						//x + ' ' + y + ' ' + e.pageX + ' ' + e.pageY + ' ' + this.offsetLeft + ' ' + this.offsetTop
						
						var mapE =   $(e.target.id );
												
						//alert(jmolCmd + ' ' + annotationName + ' ' + areaData.av + ' coords:' + mapE.coords + ' target:' + e.target.id + ' title:' + mapE.title );
					
				});
				//debug('         preparing highlighting: ' + chainId + '", "' + annotationName + '", ' + areaData.av  + ' ' + el.coords);				
				//el.onclick = function() { alert(toEval + ' ' + el.coords + ' '); eval(toEval); };			
				//debug('created: ' + mapE.title + ' ' + mapE.coords + ' ' +el.title + ' ' + el.coords);				
				//debug(el.title);
			} else {
				
				var el = document.createElement('area');
				
				// create a unique map name for each map:
				// this can be used also to store the rasmol scripts and link them back to the mouse click event
				var elid = 'chain_'+chainId +'_frgC_'+fragNum + '_frgN_' + frgN + '_annName_'+ annotationName + '_areaData.av_'+areaData.av ;
				
				var el = document.createElement('area');
				
				// create a unique map name for each map:
				// this can be used also to store the rasmol scripts and link them back to the mouse click event
				var elid = 'chain_'+chainId +'_frgC_'+fragNum + '_frgN_' + frgN + '_annName_'+ annotationName + '_areaData.av_'+areaData.av ;
				//debug("adding mouseover map only...");
				el.id = elid ;
				el.shape = 'rect';
				el.coords = generateCoords(areaData, yOffset);
				el.title = areaData.t;
				map.appendChild(el);
			}
			
		}
	}
}};

//Calculate pageX/Y if missing and clientX/Y available
// note: IE seems to be the only one that needs this because
//       jQuery will add it for others. Don't know why not
//       for IE.
function fixEvent(e)
{  
   if ( e.pageX == null && e.clientX != null ) {
      var e = document.documentElement, b = document.body;
      e.pageX = e.clientX + (e && e.scrollLeft || b.scrollLeft || 0);
      e.pageY = e.clientY + (e && e.scrollTop  || b.scrollTop  || 0);
   }
   return e;
}


SequencePage.prototype.incrementYOffset = function(an, imageData)
{
	if(!imageData.mapData[an]) return 0;
	var i;
	if(an == 'upperRuler') i = imageData.uRulerHeight;
	else if(an == 'lowerRuler') i = imageData.lRulerHeight;
	else if(an == 'sequence')   i = imageData.seqHeight;
	else i = imageData.anHeights[an];
	return i ? i : 0;
}
SequencePage.prototype.generateCoords = function(e, yOffset)
{
	var y1 = e.y1+yOffset;
	var y2 = e.y2+yOffset;
	return e.x1 + ',' + y1 + ',' + (e.x2-1) + ',' + y2;
};
SequencePage.prototype.replaceChainTable = function(chainId) { with(this)
{
	var data = annotations.getChainData(chainId);
	var orderArr = annotations.getAnnotationList();
	var table = document.getElementById('chainTable'+chainId);
	var rowIdx = document.getElementById('chainTableRowBeforeAnnotations'+chainId).rowIndex + 1;
	if(!rowIdx) rowIdx = table.rows.length;
	var endIdx = table.rows.length - 1;
	debug('chainTable'+chainId + ' gets ' + table + '\nrow index is ' + rowIdx + '; end index is ' + endIdx);
	for(var i = endIdx; i >= rowIdx; i--) 
	{
		table.deleteRow(i);
	}	
	var item, newRow, keyCell, valueCell, optgroup, option;
	var select = document.createElement('select');
	var appendSelectEl = false;
	option = document.createElement('option');
	option.value = '';
	option.appendChild(document.createTextNode("Select"));
	select.appendChild(option);
	
	for(var i = 0; i < orderArr.length; i++)
	{
		if(orderArr[i].isVisible)
		{
			item = data[orderArr[i].name];
			if(item)
			{				
				newRow = table.insertRow(rowIdx++);
				keyCell = newRow.insertCell(0);
				keyCell.setAttribute('scope','row');
				keyCell.className = 'reportitem';
				keyCell.innerHTML = unesc(item.keyHtml);
				valueCell = newRow.insertCell(1);
				valueCell.className = 'reportvalue';
				valueCell.innerHTML = unesc(item.valueHtml);
			}
		}
		else
		{
			if(!optgroup || optgroup.label != orderArr[i].classification)
			{
				if(optgroup) select.appendChild(optgroup);
				optgroup = document.createElement('optgroup');
				optgroup.label = orderArr[i].classification;
			}
			option = document.createElement('option');
			option.value = orderArr[i].name;
			option.appendChild(document.createTextNode(orderArr[i].displayName));
			optgroup.appendChild(option);
			appendSelectEl = true;
		}
	}
	
	if(optgroup)
	{ 
		select.appendChild(optgroup);
		appendSelectEl = true;	
	}
	if(appendSelectEl)
	{
		newRow = table.insertRow(rowIdx);
		keyCell = newRow.insertCell(0);
		keyCell.className = 'reportitem';
		var text = document.createTextNode('More annotations');
		keyCell.appendChild(text);
		var form = document.createElement('form');
		form.appendChild(select);
		form.id = 'annotationSelectForm' + chainId;
		form.appendChild(select);
		select.onchange = function() { 
			showAnnotation(select.value); 
		};
		keyCell.appendChild(form);
	}
	debug('done modifying table');
}};

// the Annotations class encapsulates the json data
function Annotations(data) { with(this)
{
	debug("*** annotations: new instance with data:");
	for(var domain in data.byAnnotation) {
		debug("***   data.byAnnotation[" + domain + "] = " + data.byAnnotation[domain] );
	}
	this.data = data;
}};
Annotations.prototype.changeVisibility = function(annotation, isVisible) { with(this)
{
	debug('making sure ' + annotation +(isVisible ? ' is ' : ' is not ') + 'visible');
	for(var i = 0; i < data.all.length; i++)
	{
		if(data.all[i].name == annotation)
		{
			data.all[i].isVisible = isVisible;
			debug(data.all[i].name + ' found and visibility set');
			break;
		}
	}
}};
Annotations.prototype.load = function(newData) { with(this) 
{
	debug("*** annotations: loading ")
	if(newData.failure)
	{
		alert('*** annotations: there was an error on the PDB servers while they were generating the requested data');
	}
	if(newData.isDelta)
	{
		debug('*** annotations: returned json is a delta');
		for(var domain in newData.byAnnotation)
		{
			changeVisibility(domain, true);
			data.byAnnotation[domain] = newData.byAnnotation[domain];
			for(var chain in newData.byChain)
			{
				debug('*** annotations: modifying image data for chain ' + chain);
				var chDom = newData.byChain[chain][domain];
				var newImg = newData.byChain[chain].img;
				var img = data.byChain[chain].img;
				if(chDom) data.byChain[chain][domain] = chDom;
				if(newImg && newImg.mapData[domain] && img && img.mapData)
				{
					debug('*** annotations: modifying image map and dimension data');
					img.mapData[domain] = newImg.mapData[domain];
					img.height = newImg.height;
					img.width = newImg.width;
					img.anHeights[domain] = newImg.anHeights[domain];
				}
				debug('*** annotations: done modifying image data');
			}
		}
	}
	else
	{
		debug('*** annotations: returned json is full data... overwriting existing');
		annotations = newData;
	}
}};
Annotations.prototype.del = function(annotation) { with(this)
{
	changeVisibility(annotation, false);
	delete data.byAnnotation[annotation];
	for(var chain in data.byChain)
	{
		var ch = data.byChain[chain];
		var img = ch.img;
		delete ch[annotation];
		delete img.mapData[annotation];
		img.height -= img.fragmentCount * img.anHeights[annotation];
		debug('new chain ' + chain + ' img.height = ' + img.height);
	}
}};
Annotations.prototype.getChainData = function(chain) { with(this)
{
	debug(" **** annotations: getChainData from Annotations: " + chain);
	debug("      dataByChain:" + data.byChain[chain]);
	return chain ? data.byChain[chain] : data.byChain;
}};
Annotations.prototype.getAnnotationData = function(annotation) { with(this)
{
	return annotation ? data.byAnnotation[annotation] : data.byAnnotation;
}};
Annotations.prototype.getAnnotationList = function(idx) { with(this)
{
	return idx ? data.all[idx] : data.all;
}};

// the Params class manages the GET parameters of any requests made
function Params() { with(this) 
{
	var paramPattern = /([^?=&]+)=([^?=&]+)/g;
	this.theUrl = window.location.href;
	var key, value, kvp;
	this.params = new Object();
	while( (kvp = paramPattern.exec(theUrl)) != null )
	{
		params[RegExp.$1] = toArray(RegExp.$2);
	}
}}
Params.prototype.get = function(key) { with(this) 
{
	return params[key];
}};
Params.prototype.getFirst = function(key) { with(this)
{
	var result;
	if(params[key]) for(result in params[key]) break; 
	return result;
}};
Params.prototype.set = function(key, values) { with(this)
{
	params[key] = values;
}};
Params.prototype.put = function(key, value) { with(this)
{
	if(!params[key]) params[key] = new Object();
	params[key][value] = true;
}};
Params.prototype.remove = function(key, value) { with(this)
{
	if(!params[key] || !params[key][value]) return;
	delete params[key][value];
}};
Params.prototype.del = function(key) { with(this)
{
	if(!params[key]) return;
	delete params[key];
}};
Params.prototype.contains = function(k,v) { with(this)
{
	debug('looking for k:' + k + ', v:' + v + '. for k we have ' + params[k] + (params[k] && v ? (' and for v we have ' + params[k][v] + '.') : ' and there is no v.'));
	return params[k] && (!v || params[k][v]);
}};
Params.prototype.doFormAsHyperlink = function(form) { with(this)
{
	for(var i = 0; i < form.length; i++)
	{
		with(form.elements[i])
		{
			if(value && name && name.length > 0 && value.length > 0)
			{
				del(name);
				put(name, value);
			}
		}
	}
	window.location.href = url();
}};
Params.prototype.urlNoParams = function() { with(this)
{
	var newUrl;
	return newUrl;
}};
Params.prototype.url = function() { with(this)
{
	var idx, param, csv, newUrl;
	var q = '?';
	if(theUrl && (idx = theUrl.indexOf(q)) != -1) newUrl = theUrl.substring(0, idx);
	else newUrl = theUrl;
	for(param in params)
	{
		csv = toCsv(params[param]);
		if(csv.length > 0) {
			newUrl += q + param + '=' + toCsv(params[param]);
			q = '&';
		}
		else delete params[param];	
	}
	return newUrl;
}};
Params.prototype.toCsv = function(array) { with(this)
{
	var idx;
	var result = '';
	for(idx in array) {
		result += idx + ',';
	}
	return result.substring(0, result.length - 1);
}};
Params.prototype.toArray = function(csv) { with(this)
{
	var result = new Object();
	var values = csv.split(",");
	for(idx in values) {
		if(values[idx].length > 0) result[values[idx]] = true;
	}
	return result;
}};

// the JmolScripter class is responsible for creating all jmol scripts and managing all jmol applets
function JmolScripter(sd) { with(this) {
	this.prevAnnName;
	this.prevHighlighted;
	this.prevChain;
	this.sd = sd;
	this.jmols = [];
	this.builders = [];
	this.msg2Pattern = /\[.{1,3}\](\d+)[A-Z]?:(\S)/;
	this.seqInsCodePat = /(\d+)\D?/;
}}
JmolScripter.prototype.canScript = function(annotation) { with(this)
{
	return getBuilder(annotation) ? true : false;
}};
JmolScripter.prototype.getBuilder = function(annotation) { with(this)
{
	//debug('getting script builder for ' + annotation); 
	var msg = 'no builder for ' + annotation;
	var builder = builders[annotation];
	var data = getData(annotation);
	if(!builder)
	{
		if(data)
		{
			var builderClassname = data.script;
			if(builderClassname)
			{
				builder = eval('new ' + builderClassname + '()');
				builder.scripter = this;
				builders[annotation] = builder;
				msg = 'instantiated builder ' + builderClassname ;
			}
		}
		else msg='no script builder specified for ' + annotation;
	}
	else msg='already had ' + annotation + ' builder';
	//debug(msg);
	return builder;
}};
JmolScripter.prototype.getApplet = function(chain) { with(this)
{
//	var result = document.getElementById('jmol');
//	if(!result) debug('no jmol applet found');
//	return result;
	debug('getting jmol applet for ' + chain);
	// AP: use only one jmol from now on...
	var jmol = jmols[0];
	if(!jmol)
	{
		jmol = document['jmol'];
		jmols[0] = jmol;
	}
	debug(jmol ? 'got it' : 'couldn\'t find it');
	return jmol;
}};
JmolScripter.prototype.getData = function(annotation) { with(this)
{
	return sd.annotations.getAnnotationData(annotation);
}};
JmolScripter.prototype.getOrigChainId = function(chainId) { with(this)
{
	return sd.annotations.getChainData(chainId).origChainId;
}};
JmolScripter.prototype.highlight = function(chainId, annotationName, annotationValue) { with(this)
{
	debug('****USER INPUT RESPONSE STARTS**** in JmolScripter.highlight with ' + chainId + ' ' + annotationName + ' ' + annotationValue);
	var jmol = getApplet(chainId);
	var builder = getBuilder(annotationName);
	if(jmol && builder)
	{
		var script = builder.getScript(annotationName, annotationValue);
		jmol.script(script);
	}
	else debug('not getting script: ' + jmol + '; ' + builder);
}};
JmolScripter.prototype.highlightPicked = function(msg1, msg2, msg3) { with(this)
{
	debug('****LIVECONNECT RESPONSE STARTS**** in JmolScripter.highlightPicked with ' + msg2);
	msg2Pattern.exec(msg2);
	var chainId = RegExp.$2;
	var seqId = parseInt(RegExp.$1);
	debug('user clicked on ' + chainId + seqId + ' in Jmol');
	var startId, endId, found;
	if(chainId && seqId && prevAnnName && prevHighlighted)
	{
		var prevAnnObj = sd.annotations.getAnnotationData(prevAnnName);
		outerloop:
		for(var i in prevAnnObj.entries)
		{
			debug('looking for annotation data in ' + i);
			var rangs = prevAnnObj.entries[i].ranges;
			for(var j in rangs)
			{
				if(rangs[j].chainId == chainId)
				{
					seqInsCodePat.exec(rangs[j].startRes);
					startId = parseInt(RegExp.$1);
					seqInsCodePat.exec(rangs[j].endRes);
					endId = parseInt(RegExp.$1);
				
					debug(seqId + ', ' + startId + ', ' + endId);
					if(seqId >= startId && seqId <= endId)
					{
						found = i;
						break outerloop;
					}
				}
			}
		}
		if(found)
		{
			debug('yes: ' + chainId + ', ' +  prevAnnName + ', ' + found);
			highlight(chainId, prevAnnName, found);
		}
	}
	if(!found) debug('could not find annotation obj for ' + prevAnnName + ' at ' + chainId + seqId);
}};

// the JmolScriptBuilder class is the superclass for all script generating classes
function JmolScriptBuilder() { with(this) {
	this.scripter;
}}
JmolScriptBuilder.prototype.atomExpression = function(chainId, startRes, endRes) { with(this)
{
	return startRes + "-" + endRes + ":" + scripter.getOrigChainId(chainId);
}};
JmolScriptBuilder.prototype.unhighlightAll = function()
{
	return "select all; cpk off; wireframe off; strands on; cartoons off; colour strands gray translucent 0.8;\n"
};
JmolScriptBuilder.prototype.formatColour = function(domainObj)
{
	var colour = domainObj.colour;
	return "[" + colour.r + "," + colour.g + "," + colour.b + "]";
};
JmolScriptBuilder.prototype.getData = function(annotation) { with(this)
{
	return scripter.getData(annotation);
}};
JmolScriptBuilder.prototype.defineAndColour = function(domain, domainObj) { with(this)
{
	var result = "define ~" + domain + " (";
	var range;
	var ranges = domainObj.ranges;
	for(range in ranges)
	{	
		var rangeObj = ranges[range];
		result += atomExpression(rangeObj.chainId, rangeObj.startRes, rangeObj.endRes);
		if(range < ranges.length - 1) result += ",";
	}
	result += ");\nselect ~" + domain + "; colour strands " + formatColour(domainObj) + " translucent 0.8;\n"; 

	return result;
}};
JmolScriptBuilder.prototype.buildScript = function(annotationName, annotationData, toHighlight) { with(this) 
{
	return '';
}};
JmolScriptBuilder.prototype.getScript = function(annotationName, toHighlight) { with(this) 
{
	debug('getting script to highlight ' + toHighlight + ' on annotation ' + annotationName);
	var annotationData = getData(annotationName);
	var result = buildScript(annotationName, annotationData, toHighlight);
	
	scripter.prevAnnName = annotationName;
	scripter.prevHighlighted = toHighlight;
	debug('got ' + result);
	return result;
}};

// DomainJmolScriptBuilder is responsible for scriping jmol when looking at domain assignments
function DomainJmolScriptBuilder() { }
DomainJmolScriptBuilder.prototype = new JmolScriptBuilder();
DomainJmolScriptBuilder.prototype.buildScript = function(annotationName, annotationData, toHighlight) { with(this) 
{
	var prevAnnName = scripter.prevAnnName;
	var prevHighlighted = scripter.prevHighlighted;
	var result = "";
	var domain;
	
	// colour all
	result += unhighlightAll();
	for(domain in annotationData.entries)
	{
		result += defineAndColour(domain, annotationData.entries[domain]);
	}
	
	// unhighlight highlighted
	if(prevHighlighted)
	{
		result += "select ~" + prevHighlighted + "; ";
		result += "strands on; cartoons off; colour strands translucent 0.8;\n";
	}
	
	// highlight selected
	if(toHighlight)
	{
		var domainBiol = scripter.sd.domainBiol[toHighlight];
		var domainObj = annotationData.entries[toHighlight];
		var displayLabel = domainBiol + ' ' + toHighlight;
		if ( domainBiol.toString().indexOf(toHighlight.toString()) > -1) 
			displayLabel = domainBiol;
			
		result += "select ~" + toHighlight + "; ";
		result += "strands off; cartoons on; colour cartoons " + formatColour(domainObj) + " opaque;\n";
		result += "zoomto 0.2 (~" + toHighlight + ") 100;\n"
		//result += "set echo domain {~" + toHighlight + "}; echo \"" + domainObj.label + "\";\n";
		result += "set echo BOTTOM RIGHT; echo \"" + displayLabel + "\";\n";
	}
	
	return result;
}};

// ImportantResidueScriptBuilder is responsible for scriping jmol when looking at important residues (e.g. catalytic sites)
function ImportantResidueScriptBuilder() { }
ImportantResidueScriptBuilder.prototype = new JmolScriptBuilder();
ImportantResidueScriptBuilder.prototype.buildScript = function(annotationName, annotationData, toHighlight) { with(this) 
{
	var prevAnnName = scripter.prevAnnName;
	var prevHighlighted = scripter.prevHighlighted;
	var result = unhighlightAll();
	var catalyticSite;
	
	for(catalyticSite in annotationData.entries)
	{
		result += defineAndColour(catalyticSite, annotationData.entries[catalyticSite]);
	}
	
	// do ligand
	var chainOfLigand = toHighlight.substr(4,1);
	var ligandName = toHighlight.substring(6);
	
	result += "select [" + ligandName + "]; wireframe; spacefill 0.4; ";
	
	if(prevHighlighted)
	{
		result += "select ~" + prevHighlighted + "; ";
		result += "strands on; cartoons off; colour strands translucent 0.8;\n";
	}
	
	if(toHighlight)
	{
		var domainObj = annotationData.entries[toHighlight];
		result += "select ~" + toHighlight + "; ";
		result += "strands off; cartoons on; colour cartoons " + formatColour(domainObj) + " translucent 0.5; colour translucent 0.5;\n";
		result += "zoomto 0.2 (~" + toHighlight + ") 150;\n"
		//result += "set echo domain {~" + toHighlight + "}; echo \"" + domainObj.label + "\";\n";
		result += "set echo BOTTOM RIGHT; echo \"" + ligandName + " highlighted\";\n";
		
		result += "select ~" + toHighlight + " and within(8.0, [" + ligandName + "]); ";
		result += "wireframe; colour translucent 0.5;";		
		
		result += "select ~" + toHighlight + " and within(5.0, [" + ligandName + "]); ";
		result += "colour opaque;";		
		
		result += "select ~" + toHighlight + " and within(3.0, [" + ligandName + "]); ";
		result += "spacefill 0.2;";
	}
	return result;
}};
