let projectExtensions={
	
	/**
	 * Insert tabs before (to the left of) the default tabs in the project view page.
	 * This function is called automatically from the base project view page. You just need to add your tabs.
	 */
	tabsBefore:function(tabSet){
		/* Example of a tab. See the tab function in ui.js for more details.
		tabSet.tab({ 
			id:'before', 
			label:'Before', 
			url:'/api/project/'+data['id']+'/permission', 
			successHandler:function(){ document.getElementById("before").nextElementSibling().innerHTML="Tab before default project tabs"; }
		 });
		*/
		tabSet.tab({ 
			id:'plates', 
			label:'Plates', 
			url:'/api/project/'+data['id']+'/plate?sortby=id&sortdescending=yes', 
			headers:['Barcode','Owner','Description','Best score'],
			sortOrders:['name','user.fullname','','crystalscore.scoreindex'],
			cellTemplates:['<a href="/plate/{{id}}">{{name}}</a>','<a href="/user/{{ownerid}}">{{ownername}}</a>',[projectExtensions.trimDescription,'description'],[Plate.renderPlateBestScoreCell,'bestscorelabel'] ]
		});
		tabSet.tab({ 
			id:'proteins', 
			label:'Proteins', 
			url:'/api/project/'+data['id']+'/protein', 
			successHandler:function(transport){ projectExtensions.writeProteins(transport.responseJSON.rows);  },
			failureHandler:function(){ document.getElementById("proteins_body").innerHTML=""; projectExtensions.writeProteinCreateForm();  }
		});
		tabSet.tab({ 
			id:'crystals', 
			label:'Crystals',
			url:'/api/crystal/projectid/'+data['id'],
			sortBy:'id',
			sortDescending:true,
			sortOrders: ['-starrating','','',''],
			headers:['Rating','Image','Plate','Drop and number'],
			cellTemplates:[ [ui.starRatingCellContents,'starrating'], [Crystal.getThumbnailLink,'id'], '<a href="/plate/{{plateid}}">{{platename}}</a>',  [Crystal.getTextLink,'id'] ],
		});
		Dataset.listTab(tabSet, 'projectid', data['id']);
	},
	
	/**
	 * Insert tabs after (to the right of) the default tabs in the project view page.
	 * This function is called automatically from the base project view page. You just need to add your tabs.
	 */
	tabsAfter:function(tabSet){
		/* Example of a tab. See the tab function in ui.js for more details.
		tabSet.tab({ 
			id:'after', 
			label:'After', 
			url:'/api/project/'+data['id']+'/permission', 
			successHandler:function(){ document.getElementById("after").nextElementSibling().innerHTML="Tab after default project tabs"; }
		 });
		*/
	},
	
	trimDescription:function(item){
		let maxLength=50;
		let desc=item.description;
		if(desc.length<=maxLength){ return desc; }
		desc=desc.substring(0,maxLength-3);
		let lastSpace=desc.lastIndexOf(" ");
		if(0<lastSpace){
			desc=desc.substring(0,lastSpace);
		}
		return desc+'...';
	},

	
	writeProteins:function(rows, proteinIdToOpen, constructIdToOpen){
		let tab=document.getElementById("proteins_body");
		tab.innerHTML='';
		rows.forEach(function(r){
			let ti=tab.treeItem({
				record:r,
				id:"protein"+r.id,
				header:projectExtensions.proteinHeader(r.name, r.proteinacronym),
				updater:function(){ projectExtensions.renderProtein(ti, constructIdToOpen); }
			});
			if(1*r.id===1*proteinIdToOpen){
				ui.doToggleTreeItem(document.getElementById('protein'+r.id));
			}
		});
		projectExtensions.writeProteinCreateForm();
		window.setInterval(projectExtensions.setProteinTabWarning, 250);
	},

	setProteinTabWarning:function (){
		let tb=document.getElementById("proteins_body");
		let warnings=tb.querySelectorAll("img.proteinAcronymWarning");
		numWarnings=warnings.length;
		if(numWarnings>0){
			let msg="Protein acronym is not set, for "+numWarnings+" protein";
			if(1!==numWarnings){ msg+="s"; }
			tb.warn(msg);
		} else {
			tb.unwarn();
		}
	},

	proteinHeader:function(name, acronym, treeHeaderOrItem){
		let content=name+" (Acronym: ";
		if(acronym) {
			content+=acronym;
		} else {
			content+='<img class="proteinAcronymWarning" alt="" src="/'+'images/icons/darkicons/warning.png" style="height:1.75em;vertical-align: middle;position: relative;top: -.15em;">NOT SET';
		}
		content+=")";
		if(treeHeaderOrItem){
			if(!treeHeaderOrItem.classList.contains("treehead")){
				treeHeaderOrItem=treeHeaderOrItem.querySelector(".treehead");
			}
			content=content='<span class="toggleitem"></span>'+content;
			treeHeaderOrItem.innerHTML=content;
		}
		return content;
	},

	renderProtein:function(treeHeader,constructIdToOpen){
		let ti=treeHeader.closest(".treeitem");
		let tb=ti.querySelector(".treebody");
		tb.innerHTML='';
		let protein=ti.record;
		let f=ui.form({
			action:"/api/protein/"+protein['id'],
			method:"patch"
		}, tb);
		f.hiddenField('projectid',data['id']);
		if(canEdit){
			let nameField=f.textField({
				label:'Name',
				name:'name',
				value:protein['name'],
				helpText:'Protein name must be unique within this project',
			});
			let acronymField=f.textField({
				label:'Protein acronym (safety identifier)',
				name:'proteinacronym',
				value:protein['proteinacronym'],
				helpText:'Short code, 3-5 characters. Identifies the protein for synchrotron safety purposes'
			});
			nameField.addEventListener("keyup",function(){
				projectExtensions.proteinHeader(nameField.querySelector("input").value, acronymField.querySelector("input").value, ti);
//				ti.querySelector("h3").innerHTML='<span class="toggleitem"></span>'+nameField.querySelector("input").value+' (Acronym: '+acronymField.querySelector("input").value+')';
			});
			acronymField.addEventListener("keyup",function(){
				projectExtensions.proteinHeader(nameField.querySelector("input").value, acronymField.querySelector("input").value, ti);
//				ti.querySelector("h3").innerHTML='<span class="toggleitem"></span>'+nameField.querySelector("input").value+' (Acronym: '+acronymField.querySelector("input").value+')';
			});

		}
		f.textField({
			readonly:!canEdit,
			label:'Description',
			name:'description',
			value:protein['description'],
			helpText:'A short description of the protein',
			readOnly:!canEdit
		});
		
		new AjaxUtils.Request('/api/protein/'+protein['id']+'/construct',{
			method:"get",
			onSuccess:function(transport){ projectExtensions.writeConstructs(transport,constructIdToOpen); },
			onFailure:function(){ projectExtensions.writeConstructCreateForm(tb); },
		});
	},
	writeProteinCreateForm:function(){
		let tab=document.getElementById("proteins_body");
		if(canEdit){
			let createItem=tab.treeItem({
				header:'Create a new protein'
			});
			let f=createItem.form({
				action:"/api/protein",
				method:"post"
			});
			f.hiddenField('projectid',data['id']);
			f.textField({
				label:'Name',
				name:'name',
				helpText:'Protein name must be unique within this project'
			});
			f.textField({
				label:'Protein acronym (safety identifier)',
				name:'proteinacronym',
				helpText:'Short code, 3-5 characters. Identifies the protein for synchrotron safety purposes'
			});
			f.textField({
				label:'Description',
				name:'description',
				helpText:'A short description of the protein'
			});
			f.createButton({
				afterSuccess:function(createResponse){
					new AjaxUtils.Request('/api/project/'+data.id+'/protein',{
						method:'get',
						onSuccess:function(transport){
							projectExtensions.writeProteins(transport.responseJSON.rows, createResponse.created.id);
						}
					})
				}
			});
			f.formField({
				label:'<img alt="" style="vertical-align: bottom" s'+'rc="/images/icons/darkicons/warning.png"> Please note!',
				name:'',
				content:'<div style="width:80%;float:right;text-align:left;line-height:1.5em">The protein acronym is used when shipping your crystals to the synchrotron, to identify the ' +
					'protein for safety purposes. It should be <b>very short</b> (ideally 3-5 characters), contain <b>no ' +
					'spaces</b> or special characters, and <b>must match exactly</b> the protein acronym in the ' +
					'synchrotron\'s safety system at time of shipping. Mismatched acronyms will prevent your crystals from being shipped.</div>'
			});
			f.closest(".treeitem").classList.add("noprint");
		}
	},
	
	
	writeConstructs:function(transport,constructIdToOpen){
		let wrapper;
		transport.responseJSON.rows.forEach(function(r){
			wrapper=document.getElementById("protein"+r['proteinid']+"_body");
			wrapper.treeItem({
				record:r,
				id:"construct"+r.id,
				header:"Construct: "+r.name,
				updater:projectExtensions.renderConstruct
			});
			if(1*r.id===1*constructIdToOpen){
				ui.doToggleTreeItem(document.getElementById("construct"+r.id));
			}
		});
		projectExtensions.writeConstructCreateForm(wrapper);
	},
	renderConstruct:function(treeHeader){
		let ti=treeHeader.closest(".treeitem");
		let tb=ti.querySelector(".treebody");
		tb.innerHTML='';
		let con=ti.record;
		let f=ui.form({
			action:"/api/construct/"+con['id'],
			method:"patch"
		}, tb);
		f.hiddenField('constructid',con['id']);
		if(canEdit){
			let nameField=f.textField({
				label:'Name',
				name:'name',
				value:con['name'],
				helpText:'Construct name must be unique within this protein',
			});
			nameField.addEventListener("keyup",function(){
				ti.querySelector("h3").innerHTML='<span class="toggleitem"></span>Construct: '+nameField.querySelector("input").value;
			});
		}
		f.textField({
			readonly:!canEdit,
			label:'Description',
			name:'description',
			value:con['description'],
			helpText:'A short description of the construct'
		});
		projectExtensions.getSequences(con.id);
	},
	writeConstructCreateForm:function(wrapper){
		if(canEdit){
			let createItem=wrapper.treeItem({
				header:'Create a new construct'
			});
			let f=createItem.form({
				action:"/api/construct",
				method:"post"
			});
			let protein=wrapper.closest(".treeitem").record;
			f.hiddenField('projectid',data['id']); 
			f.hiddenField('proteinid',protein['id']);
			f.textField({
				label:'Name',
				name:'name',
				helpText:'Construct name must be unique within this protein'
			});
			f.textField({
				label:'Description',
				name:'description',
				helpText:'A short description of the construct'
			});
			f.createButton({
				afterSuccess:function(createResponse){
					new AjaxUtils.Request('/api/project/'+data["id"]+'/protein',{
						method:'get',
						onSuccess:function(transport){
							projectExtensions.writeProteins(transport.responseJSON.rows, protein['id'], createResponse.created.id);
						}
					})
				}
			});
		}
	},
	
	getSequences:function(constructId){
		new AjaxUtils.Request('/api/construct/'+constructId+'/sequence',{
			method:'get',
			onSuccess:function(transport){ projectExtensions.getSequences_onSuccess(transport, constructId); },
			onFailure:function(transport){ projectExtensions.getSequences_onFailure(transport, constructId); },
		});
	},
	getSequences_onSuccess:function(transport, constructId){
		if(!AjaxUtils.checkResponse(transport)){ return false; }
		projectExtensions.renderSequences(transport.responseJSON.rows,constructId);
	},
	getSequences_onFailure:function(transport, constructId){
		if(404===1*transport.status){
			projectExtensions.renderSequences([],constructId);
		} else {
			return AjaxUtils.checkResponse(transport);
		}
	},
	renderSequences:function(sequences, constructId){
		sequences.forEach(function(seq){
			let f=projectExtensions.generateSequenceBlock(seq);
			document.getElementById("construct"+seq.constructid+"_body").appendChild(f);
		});
		if(canEdit){
			let f=document.getElementById("construct"+constructId+"_body").form({
				action:'/api/sequence/',
				method:'post',
				id:"construct"+constructId+"addseqform"
			});
			let ff=f.formField({
				label:'<input type="button" onclick="projectExtensions.showSequenceWindow(this)" id="construct'+constructId+'addseqbutton" value="Add sequence..." />',
				name:'',
				value:''
			});
			ff.innerHTML+='&nbsp;';
			ff.sequence={};
			ff.querySelector("input").dataset.constructid=constructId;
		}
	},

	generateSequenceBlock:function(seq){
		let f=ui.form({
			action:'/api/sequence/'+seq.id,
			method:'patch',
			id:'seq'+seq.id
		});
		let seqToShow=projectExtensions.generateSequencePreviewString(seq);
		let ff=f.formField({
			label:'Sequence: '+seq.name,
			name:'seq'+seq.id,
			value:seq.name
		});
		ff.sequence=seq;
		let viewButton='<input type="button" onclick="projectExtensions.showSequenceWindow(this)" value="View..." /> ';
		let deleteButton="";
		if(canEdit){
			viewButton=viewButton.replace("View","View/Edit");
			deleteButton='<input type="button" onclick="projectExtensions.deleteSequence(this)" value="Delete" />';
		}
		ff.innerHTML+=('<span class="seqpreview">'+seqToShow+"</span><br/>"+viewButton+deleteButton);
		ff.querySelector("input").dataset.constructid=seq.constructid;
		return f;
	},
	
	sequencePreviewLength:36,

	generateSequencePreviewString:function(seq){
		let seqToShow=seq.dnasequence;
		if(""!==seq.proteinsequence){ seqToShow=seq.proteinsequence; }
		if(""===seqToShow){ return "(no DNA or protein sequence)"; }
		if(seqToShow.length>projectExtensions.sequencePreviewLength){
			return seqToShow.substring(0,projectExtensions.sequencePreviewLength).toUpperCase()+"...";
		}
		return seqToShow.toUpperCase();
	},
	
	showSequenceWindow:function(btn){
		let seq=btn.closest("label").sequence;
		let seqName=seq.name||"";
		let dnaSeq=seq.dnasequence||"";
		let proteinSequence=seq.proteinsequence||"";
		let formAction=seq.id ? '/api/sequence/'+seq.id : '/api/sequence/';
		let formMethod=seq.id ? 'patch' : 'post';
		let boxTitle=seq.name ? 'Sequence: '+seq.name : 'Add a sequence';
		let buttonLabel=seq.id ? 'Save changes' : 'Add sequence';
		let buttonAction=projectExtensions.submitSequenceForm;
		let mb=ui.modalBox({ title:boxTitle, content:'', confirmClose:true });
		let f=mb.form({
			id:'sequenceform',
			action:formAction,
			method:formMethod,
		});
		f.hiddenField("seqconstructid",btn.dataset.constructid);
		f.textField({
			label:'Name',
			name:'seqname',
			helpText:'Sequence name must be unique for this construct',
			value:seqName,
			readonly:!canEdit
		});
		if(canEdit || (!canEdit && ""!==dnaSeq)){
			f.textArea({
				label:'DNA sequence, if known',
				name:'dnasequence',
				helpText:'The DNA sequence',
				value:dnaSeq,
				readonly:!canEdit
			});
		}
		if(canEdit || (!canEdit && ""!==proteinSequence)){
			f.textArea({
				label:'Protein sequence',
				name:'proteinsequence',
				helpText:'The protein sequence',
				value:proteinSequence,
				readonly:!canEdit
			});
		}
		if(canEdit){
			f.buttonField({
				id:"seqsubmit",
				label:buttonLabel,
				onclick:buttonAction
			});
			fieldValidations['seqname']="required";
			fieldValidations['dnasequence']="dnaSequence";
			fieldValidations['proteinsequence']="proteinSequence";
			document.querySelectorAll("#modalBox textarea, #modalBox input[type=text]").forEach(function(elem){
				elem.onkeyup=function(){ document.getElementById("modalBox").tainted=true; };
				elem.style.width="70%"; 
			});
			document.querySelectorAll("#modalBox textarea").forEach(function(elem){
				elem.style.height="10em"; 
				elem.onclick=function(){ document.getElementById("modalBox").tainted=true; };
			});
			let psBox=document.getElementById("proteinsequence");
			let dsBox=document.getElementById("dnasequence");
			psBox.addEventListener("keyup",projectExtensions.onEditProteinSequence);
			psBox.addEventListener("click",projectExtensions.onEditProteinSequence);
			dsBox.addEventListener("keyup",projectExtensions.onEditDnaSequence);
			dsBox.addEventListener("click",projectExtensions.onEditDnaSequence);
		}
	},

	onEditProteinSequence:function(){
		let psBox=document.getElementById("proteinsequence");
		let seq=psBox.value+"";
		seq=seq.replace(/[^*ACDEFGHIKLMNPQRSTVWY]/gi, "").toUpperCase();
		if(""===seq){ return; }
		psBox.value=Protein.formatProteinSequence(seq);
	},
		
	onEditDnaSequence:function(){
		let dsBox=document.getElementById("dnasequence");
		let seq=dsBox.value+"";
		seq=seq.replace(/[^*ACGT]/gi, "").toUpperCase();
		if(""===seq){ return; }
		dsBox.value=Protein.formatDnaSequence(seq);
	},

	dnaMatchesProtein:function(){
		let dnaSequence=document.getElementById("dnasequence").value.replace(/\s/g,"");
		let proteinSequence=document.getElementById("proteinsequence").value.replace(/\s/g,"");
		if(""===proteinSequence || ""===dnaSequence){ return true; }
		if(proteinSequence.length*3 !== dnaSequence.length){ return false; }
		let convertedDna=Protein.dnaToProtein(dnaSequence);
		return convertedDna === proteinSequence;
	},
	
	submitSequenceForm:function(){
		let sequenceForm=document.getElementById('sequenceform');
		let proteinSequenceBox=document.getElementById('proteinsequence');
		let dnaSequenceBox=document.getElementById('dnasequence');
		let isValid=validator.validate(document.getElementById('seqname'));
		isValid=validator.validate(proteinSequenceBox) && isValid;
		isValid=validator.validate(dnaSequenceBox) && isValid;
		if(!isValid){ return false;	}
		if(!projectExtensions.dnaMatchesProtein()){
			if(!confirm("DNA and protein sequences do not match.\n\nClick OK to generate the protein sequence from the DNA sequence.\nClick Cancel to edit by hand.")){
				return false;
			}
			document.getElementById("proteinsequence").value=Protein.dnaToProtein(document.getElementById("dnasequence").value);
		} else if(""===proteinSequenceBox.value && ""!==dnaSequenceBox.value){
			if(confirm("The protein sequence is empty.\n\nClick OK to generate the protein sequence from the DNA sequence.\nClick Cancel to leave it empty.")){
				document.getElementById("proteinsequence").value=Protein.formatProteinSequence(Protein.dnaToProtein(document.getElementById("dnasequence").value));
			}
		}
		document.getElementById("seqsubmit").closest("label").classList.add("updating");
		let method="post";
		if("post"!==sequenceForm.dataset.ajaxmethod){ method="patch"; }
		new AjaxUtils.Request(sequenceForm.action,{
			method:method,
			parameters:{
				csrfToken:csrfToken,
				name:document.getElementById("seqname").value,
				dnasequence:dnaSequenceBox.value,
				proteinsequence:proteinSequenceBox.value,
				constructid:document.getElementById("seqconstructid").value,
			},
			onSuccess:projectExtensions.submitSequenceForm_onSuccess,
			onFailure:projectExtensions.submitSequenceForm_onFailure,
		})
		
	},
	submitSequenceForm_onSuccess:function(transport){
		document.getElementById("seqsubmit").closest("label").classList.remove("updating");
		if(!AjaxUtils.checkResponse(transport)){ return false; }
		let updated=transport.responseJSON["updated"];
		let created=transport.responseJSON["created"];
		if(updated){
			let seqBox=document.getElementById("seq"+updated.id);
			seqBox.querySelector(".label").innerHTML="Sequence: "+updated.name;
			seqBox.querySelector(".seqpreview").innerHTML=projectExtensions.generateSequencePreviewString(updated);
			seqBox.querySelector("label")["sequence"]=updated;
		} else if(created){
			let f=projectExtensions.generateSequenceBlock(created);
			document.getElementById("construct"+created.constructid+"addseqform").prepend(f);
		}
		document.getElementById("modalBox").tainted=false;
		ui.closeModalBox();
	},
	submitSequenceForm_onFailure:function(transport){
		document.getElementById("seqsubmit").closest("label").classList.remove("updating");
		AjaxUtils.checkResponse(transport);
	},

	deleteSequence:function(elem){
		if(!canEdit){ 
			alert("You don't have permission to delete sequences");
			return false;
		}
		let frm=document.getElementById(elem).closest("form");
		elem.closest("label").classList.add("updating");
		window.setTimeout(function(){
			new AjaxUtils.Request(frm.action, {
				method:'delete',
				parameters:{ csrfToken:csrfToken },
				onSuccess:projectExtensions.deleteSequence_onSuccess,
				onFailure:projectExtensions.deleteSequence_onFailure
			});
		},250); //give the "updating" time to show - deletion is very quick, may be jarring
	},
	deleteSequence_onSuccess:function(transport){
		if(!AjaxUtils.checkResponse(transport)){ return false; }
		let deletedId=transport.responseJSON['deleted'];
		document.getElementById("seq"+deletedId).remove();
	},
	deleteSequence_onFailure:function(transport){
		document.getElementById("seqsubmit").closest("label").classList.remove("updating");
		AjaxUtils.checkResponse(transport);
		
	},
	
};