const config=new Object();

function clear(){
	let el=config.document.getElementById(config.debugId);
	if (el===null) {
		//alert("Debug section not initialized");
		return;
	}
	config.document.getElementById(config.debugId).value="";
}

function print(msg){
	let el=config.document.getElementById(config.debugId);
	if (el===null) {
		//alert("Debug section not initialized. Message: "+msg);
		return;
	}
	el.value+="\n"+msg;
}

function getCRFrefFirst(){
	//crfRef is part of html call and gets stored in the page
	return config.document.getElementById(config.crfRefId).innerHTML;
}


function getCRFref(){
	//'crfRefId'
	return	config.formConfig.crfEntry['entryId'];
}

function getCRFrefData(){
	let parentCrf=config.formConfig.crfEntry['parentCrf'];
	if (parentCrf!=undefined) return parentCrf;
	return getCRFref();
}


function onFailure(errorInfo, options, responseObj){
	
	if (errorInfo && errorInfo.exception)
		alert("Failure: " + errorInfo.exception);
	else
		alert("Failure: " + responseObj.statusText);
}

function doNothing(){
	print('doNothing called');
}

function generateDebugSection(){
	//let debug=true;
	//if (debug) print("generateDebugSection "+sectionName);

	let formName=config.debugDiv;
	let sectionName="debugSection";
	let sectionTitle="Debug Messages";
	let tb=config.document.createElement('table');
	tb.className='t2';
	let row=tb.insertRow();
	let cell=config.document.createElement('th');
	row.appendChild(cell);	
	cell.setAttribute("colspan","4");
	cell.style.fontSize="20px";
	cell.style.textAlign="center";
	let cellData=config.document.createTextNode(sectionTitle);
	cell.appendChild(cellData);
	cell=row.insertCell();
	let input=config.document.createElement("input");	
	input.type="button";
	input.id="toggle"+sectionName+"VisbilityButton";
	input.onclick=function(){toggleVisibility(sectionName,input.id)};
	cell.appendChild(input);
	config.document.getElementById(formName).appendChild(tb);
	

	let div=config.document.createElement('div');
	div.id=sectionName;
	config.document.getElementById(formName).appendChild(div);	
	
	//start open (for debug)
	
	//input.value="Hide";
	//div.style.display="block";

	//start hidden (for production)
	input.value="Show";
	div.style.display="none";

	let debugArea=config.document.createElement('textarea');
	debugArea.rows=10;
	debugArea.cols=95;
	debugArea.id=config.debugId;
	div.appendChild(debugArea);

	//print('ver: 0.10');

}




function getAdditionalData(formSetupEntry){
   //return information on additional data associated with the form
   //additionalData is a sub-list with multiple entries per patient/visit
   

   //argument is the row of the formSetup setup list
	let queryName=config.formConfig.queryMap[formSetupEntry['queryName']];
	let fName='[getAdditionalData/'+queryName+']';
	print(fName);

   //additionalData holds a reference to all queries already parsed
   //this helps in reducing number of calls to the database (I assume)
	if (queryName in config.formConfig.additionalData){
		print(fName+': Returning preset value');
		return config.formConfig.additionalData[queryName];
	}

   //first time we see this query, so we have to do the setup
	print(fName+': generating');
	config.formConfig.additionalData[queryName]=new Object();
	
   //takes address, so further changes will be to the newly created object
   //in fact, ad is just a short alias of the long variable name on the right
	let ad=config.formConfig.additionalData[queryName];

   //no additional data
	if (formSetupEntry["showFlag"]==="NONE") {
		print(fName+": empty");
		return ad;
	}

   //use showFlag to setup report section of the CRF list
	if (formSetupEntry["showFlag"]==="REVIEW") {
		//abuse additionalData to signal different segment
		print(fName+": generateReport");
		ad.isReview=true;
		return ad;
	}

   //setup the additionalData memory object
	print(fName+': setting values');
	ad.showFlag=formSetupEntry["showFlag"];
	ad.showFlagValue=formSetupEntry["showFlagValue"];
	ad.queryName=formSetupEntry["showQuery"];

   //for data queries, limit to present CRF only
	ad.filters=new Object();
	ad.filters['crfRef']=getCRFref();

   //compose a long debug message
	let msg=fName+": flag "+ad.showFlag;
	msg+=" value "+ad.showFlagValue;
	msg+=" query "+ad.queryName;
	print(msg);

	return ad;	
}

function fullAccessSetup(listName){
	//generate setup object whcih should contain fields:
	//readonlyFlag - whether the dataset is writeable
	//filters - selection fields that allow creation of LABKEY.Filter.create()
	//getInputId - formating of unique ids for html elements
	//addApply - whether a submit/Save button is generated
	//unique - whether entries in list are unique

	let debug=true;
	if (debug) print("fullAccessSetup");

	let setup=new Object();
	setup.queryName=listName;
	setup.readonlyFlag=function(vName){return false};
	setup.filters=new Object();
	setup.filters['crfRef']=getCRFref();
	setup.getInputId=function(vName){return listName+"_"+vName;}
	setup.addApply="Save";
	setup.isReview=false;
	return setup;

}

function readonlySetup(listName){
	//see definition of setup object above
	let debug=true;
	if (debug) print("readonlySetup");

	let setup=new Object();
	setup.queryName=listName;
	setup.readonlyFlag=function(vName){return true};
	setup.filters=new Object();
	setup.filters['crfRef']=getCRFref();
	setup.getInputId=function(vName){return listName+'_'+vName;}
	setup.isReview=false;
	return setup;
}

function getSetup(listName,writeAccess=true){
	//change to section granulated permission of type EDIT, COMMENT, READ
	//let formStatus=config.formConfig.formStatus;
	//equivalent to READ

	if (!writeAccess)
	//if (formStatus=="Submitted")
		return readonlySetup(listName);
	//if (formStatus=="Approved")
	//	return readonlySetup(listName);
	return fullAccessSetup(listName);
}

function generateSection(formSetupEntry){

	let listName=config.formConfig.queryMap[formSetupEntry['queryName']];
	let fName='[generateSection/'+listName+']';
	let sectionTitle=formSetupEntry['title'];	
	let accessModeColumn=config.formConfig.operator+'Mode';
	let accessMode=formSetupEntry[accessModeColumn];
	//this will fix it for later use as well
	let additionalData=getAdditionalData(formSetupEntry);
	print(fName);

	let formName=config.masterForm;//this is HTML designator of area on page
	let debug=true;
	let tb=config.document.createElement('table');
	tb.className='t2';
	let row=tb.insertRow();
	let cell=config.document.createElement('th');
	row.appendChild(cell);	
	cell.setAttribute("colspan","4");
	cell.style.fontSize="20px";
	cell.style.textAlign="center";
	let cellData=config.document.createTextNode(sectionTitle);
	cell.appendChild(cellData);
	cell=row.insertCell();
	let input=config.document.createElement("input");	
	input.type="button";
	input.value="Show";
	input.id="toggle"+listName+"VisbilityButton";
	input.onclick=function(){toggleVisibility(listName,input.id)};
	cell.appendChild(input);
	config.document.getElementById(formName).appendChild(tb);

	let div=config.document.createElement('div');
	div.id=listName;
	div.style.display="none";
	config.document.getElementById(formName).appendChild(div);	

	let divTable=config.document.createElement('div');
	divTable.id=listName+"Table";
	div.appendChild(divTable);
	
	if ("showFlag" in additionalData) {

		additionalData.divName=listName+"SubDiv";
		additionalData.divQueryName=listName+"SubDivList";

		let div1=config.document.createElement('div');
		div1.id=additionalData.divName;
		div1.style.display="none";
		div.appendChild(div1);
	
		let div2=config.document.createElement('div');
		div2.id=additionalData.divQueryName;
		div1.appendChild(div2);

	}
	if (debug) print("generate master table");

	let writeMode=accessMode=="EDIT";	
	let setup=getSetup(listName,writeMode);
	
	if ("isReview" in additionalData){
		generateReviewSection(listName,div.id,generateReviewSectionCB);
		return;	
	}
	//master table is unique per visit

	
	setup.unique=true;
	generateTable(listName,divTable.id,additionalData,setup);
	
	if (debug) print("generate master table: done");

	let generateSubTable=true;
	//generateSubTable equivalent to read/write access to section
	if (accessMode != "EDIT")
		generateSubTable=false;
	
	if (! ("showFlag" in additionalData) ) generateSubTable=false;
	
	if (generateSubTable){
		let qName=additionalData.queryName;
		let dName=additionalData.divName;
		

		let setup=fullAccessSetup(qName);
		//only set master query for additionalData
		setup.masterQuery=listName;
		//if (readonly) setup=readonlySetup(config);
		generateTable(qName,dName,additionalData,setup);
		//generateTable(formSetupEntry,qName,dName,additionalData,setup);
	}

	print("generate review");

	let divReviewList=config.document.createElement('div');
	divReviewList.id=listName+"ReviewList";
	div.appendChild(divReviewList);
	
	let divReview=config.document.createElement('div');
	divReview.id=listName+"Review";
	div.appendChild(divReview);


	//assume we already have listId (content of config.setupQueryName is listId)
	//we need listName also
	//qconfig.queryName=config.setupQueryName;
	generateReview(divReview.id,divReviewList.id,listName,accessMode);

	if (accessMode!='GENERATE') return;
	print('Adding generate button');	
	//add generateButton
	let divGenerateButton=config.document.createElement('div');
	divGenerateButton.id=listName+'GenerateButton';
	div.appendChild(divGenerateButton);
	print('Adding generate button completed to here');	
	let cb=function(){onGenerateQuery(listName);};
	generateButton(divGenerateButton.id,'Generate','Generate '+listName,'XX',cb);
	print('Adding generate button completed');	
}

//>>>>reviewSection associated routines

function parseResponseXML(){
	//print(config.config,'Status:' +this.status);
	print('Status:'+this.status);
	if (this.status!=200) return;
	config.loadFileConfig.json=JSON.parse(this.responseText);
	config.loadFileConfig.cb();
}

function loadFile(){
	print('YY: '+config.loadFileConfig.url);

	let connRequest=new XMLHttpRequest();
	connRequest.addEventListener("loadend",parseResponseXML);
		//function(e){parseResponseXML(e,config);});
	connRequest.open("GET", config.loadFileConfig.url);
	connRequest.send();
}


function getBasePath(){
	let server=LABKEY.ActionURL.getBaseURL();
	let basePath=server+"_webdav";
	basePath+=LABKEY.ActionURL.getContainer();
	return basePath;
}


function generateReviewSection(listName,id,callback){
	//callback should be generateReviewSectionCB and it takes no arguments
	print("generateReviewSection");
	//need base path


	config.loadFileConfig=new Object();
	
	
	config.loadFileConfig.cb=callback;
	config.loadFileConfig.id=id;
	config.loadFileConfig.url=getBasePath()+'/@files/reportSetup/'+listName+'.json';
	loadFile();
	//load file and continue in the next function
}


function generateErrorMessage(id,listName,msg){
	print('generateErrorMessage:');
	let eid=listName+"_errorMsg";
	let el=config.document.getElementById(eid);
	if (el===null){
		el=config.document.createElement("p");
		config.document.getElementById(id).appendChild(el);
	}
	el.innerHTML=msg;
}

function clearErrorMessage(listName){
	let eid=listName+"_errorMsg";
	let el=config.document.getElementById(eid);
	if (el===null) return;
	el.remove();
}

function getParticipantCode(pid){
	let selectRows=new Object();
	selectRows.schemaName='lists';
	//we should now which form we are in
	let mfId=config.formConfig.form['masterQuery'];
	selectRows.queryName=config.formConfig.queryMap[mfId];
	//point to data container
	selectRows.containerPath=getContainer('data');
	

	//selectRows.queryName='PET';
	pid.afterId=setParticipantCode;
	pid.participantField=config.formConfig.studyData["SubjectColumnName"];
	selectRows.success=function(data){afterRegistration(pid,data);}
	selectRows.filterArray=[LABKEY.Filter.create("crfRef",getCRFref())];
	LABKEY.Query.selectRows(selectRows);
}

function visitCodeFromVisitId(visitId){
	if (visitId<0) return "NONE";
	let project=getContainer('data');
	print('visitCodeFromVisitId: '+project.search('retro'));
	if (project.search('retro')>-1)
		visitId-=1;
	return 'VISIT_'+visitId.toString();
}

function replaceSlash(x){
	return x.replace(/\//,'_');
}

function setParticipantCode(pid){
	let fName='[setParticipantCode]';
	let rows=pid.registration.rows;
	//pick from study
	let participantField=config.formConfig.studyData["SubjectColumnName"];
	if (rows.length==1){
		print(fName+': '+rows[0][participantField]+'/'+rows[0].visitId);
		let visitCode=visitCodeFromVisitId(rows[0].visitId);
		print('setParticipantCode: '+pid.participantId+'/'+visitCode);
		pid.participantCode=replaceSlash(pid.participantId);
		pid.visitCode=visitCode;
	}
	generateReviewSection2(pid);
}


function generateReviewSectionCB(){

	let listName=config.loadFileConfig.listName;
	let id=config.loadFileConfig.id;

	clearErrorMessage(listName);

	let pid=new Object();
	pid.participantCode="NONE";
	pid.visitCode="NONE";
	getParticipantCode(pid);
	print('Get participant code sent');
	//involves database search, continue after callback
}

function getValueFromElement(id,defaultValue){
	let e=config.document.getElementById(id);
	if (e!=null){
		defaultValue=e.innerHTML;
	}
	return defaultValue;
}

function pickParticipantCodeFromPage(){
	let pid=new Object();
	pid.participantCode=getValueFromElement("participantCode","NIX-LJU-D2002-IRAE-A000");
	pid.visitCode=getValueFromElement("visitCode","VISIT_1");
	generateReviewSection2(pid);
}


function patternReplace(src,replacements,values){

	for (rep in replacements){
		let txt1=src.replace(new RegExp(rep),values[replacements[rep]]);
		src=txt1;
	}
	return src;

}

function plotImage(cell,k,row,rowVariable,obj,pid){
	let baseDir=patternReplace(obj.imageDir,obj.replacements,pid);
	print('Base dir: '+pid.basePath);
	pid[obj.variable]=obj.values[k];
	cell.id=pid[obj.variable]+"_"+rowVariable+pid[rowVariable];
	let img=null;
	let imgId=cell.id+'_img_';
	img=config.document.getElementById(imgId);
	if (img===null){
		img=config.document.createElement('img');
		img.id=imgId;
		cell.appendChild(img);
	}
	let imgSrc=patternReplace(obj.file,obj.replacements,pid);
	print('Image: '+imgSrc);
	let imagePath=pid.basePath+'/'+baseDir+'/'+imgSrc;
			
	img.src=imagePath;
	img.width="300";

	
}

function showReport(cell,k,row,rowVariable,obj,pid){

	cell.width="300px";
	cell.id='report_'+obj.values[k]+"_"+rowVariable+pid[rowVariable];
	let reportConfig=new Object();
	reportConfig.partName="Report";
	reportConfig.renderTo=cell.id;
	//reportConfig.showFrame=false;
	//reportConfig.width="300";
	reportConfig.frame="none";
	reportConfig.partConfig=new Object();
	reportConfig.partConfig.width="300";
	reportConfig.partConfig.title="R Report";
	reportConfig.partConfig.reportName=obj.values[k];
	for (f in obj.parameters){
		reportConfig.partConfig[f]=pid[f];
	}
	reportConfig.partConfig.showSection="myscatterplot";
	let reportWebPartRenderer = new LABKEY.WebPart(reportConfig);
	print('Render to: '+reportConfig.renderTo);
	reportWebPartRenderer.render();
}


function showProbability(cell,k,row,rowSetup,j,obj,pid){
	print('showProbability: '+rowSetup);
	let rowVariable=rowSetup.variable;	
	cell.id='prob_'+obj.values[k]+"_"+rowVariable+pid[rowVariable];

	let probDensity=new Object();
	probDensity.mean=rowSetup.mean[j];
	probDensity.sigma=rowSetup.sigma[j];

	print('showProbability: mean '+probDensity.mean+' sigma '+probDensity.sigma);


	probDensity.func=obj.values[k];
	probDensity.organCode=pid.organCode;
	pid[obj.variable]=rowSetup[obj.variable][j];
	probDensity.percentile=pid.percentile;
	let selectRows=new Object();
	selectRows.queryName=obj.queryName;
	selectRows.schemaName="study";
	selectRows.filterArray=[];
	selectRows.containerPath=getContainer('data');
	for (let f in obj.filters){
		selectRows.filterArray.push(
				LABKEY.Filter.create(f,pid[obj.filters[f]]));
		print('Filter ['+f+']: '+pid[obj.filters[f]]);
	}
	selectRows.success=function(data){
		drawProbability(data,cell,obj,pid,probDensity);}
	LABKEY.Query.selectRows(selectRows);
}

function erf(x){
	let fx=[0,0.02,0.04,0.06,0.08,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,
		1,1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,1.9,2,
		2.1,2.2,2.3,2.4,2.5,3,3.5];
	let fy=[0,0.222702589,0.328626759,0.428392355,0.520499878,
		0.603856091,0.677801194,0.742100965,0.796908212,
	 	0.842700793,0.880205070,0.910313978,0.934007945,
		0.952285120,0.966105146,0.976348383,
		0.983790459,0.989090502,0.992790429,0.995322265,
		0.997020533,0.998137154,0.998856823,0.999311486,
		0.999593048,0.999977910,0.999999257];
	let n=32;
	let i0=n-1;

	for (let i=1;i<n;i++){
		if (Math.abs(x)>fx[i]) continue;
		i0=i-1;
		break;
	}
	let fval=1;
	if (i0<n-1){
		//interpolate
		let y1=fy[i0+1];
		let y0=fy[i0];
		let x1=fx[i0+1];
		let x0=fx[i0];
		fval=y0+(y1-y0)/(x1-x0)*(Math.abs(x)-x0);
	}
	print('Erf: '+fval);
	if (x<0) return -fval;
	return fval;
}


function setLine(fbox,name,value,fontSize){
	let fpId=fbox.id+name;
	let fp=config.document.getElementById(fpId);
	if (fp===null){
		fp=config.document.createElement("p");
		fp.id=fpId;
		fbox.appendChild(fp);
	}
	fp.classList.add("center");
	fp.style.textAlign="center";
	fp.style.fontSize=fontSize;
	fp.innerText=value;
}	

function drawProbability(data,cell,obj,pid,probDensity){
	print('drawProbability');
	if (data.rows.length!=1){
		print("drawProbability row length mismatch: "+data.rows.length);
		return;
	}
	//possible mismatch; I assume the dataset will have a field called value
	let val=data.rows[0].value;

	let prob=0;
	let fz=-100;

	if (probDensity.func=="gaus"){
		fz=(val-probDensity.mean)/probDensity.sigma/Math.sqrt(2);
		prob=0.5+0.5*erf(fz);
	}
	let color="red";
	let fzx=fz*Math.sqrt(2);
	print('drawProbability '+fzx);

	for (let i=1;i<obj.intervals.n;i++){
		if (fzx>obj.intervals.zlimits[i]) continue;
		color=obj.intervals.colors[i-1];
		break;
	}
	
	let fboxId=cell.id+'_fbox_';
	let fbox=config.document.getElementById(fboxId);
	if (fbox===null){
		fbox=config.document.createElement("div");
		fbox.id=fboxId;
		cell.appendChild(fbox);
	}
	fbox.style.backgroundColor=color;
	fbox.style.width="180px";
	fbox.style.height="180px";
	

	print('organCode '+probDensity.organCode);
	let organName="Lung";

	if (probDensity.organCode==4){
		organName="Thyroid";
	}
	if (probDensity.organCode==5){
		organName="Bowel";
	}

	setLine(fbox,'_fp4_',organName,"16px");
	setLine(fbox,'_fp_',val.toPrecision(3),"25px");
	setLine(fbox,'_fp1_',"SUV("+probDensity.percentile+"%)","16px");
	setLine(fbox,'_fp2_',fzx.toPrecision(3),"25px");
	setLine(fbox,'_fp3_',"z-value","16px");


}

function generateReviewSection2(pid){ 
	
	let listName=config.loadFileConfig.listName;
	let id=config.loadFileConfig.id;
	
	print('generateReviewSection2: '+pid.participantCode+'/'+
		pid.visitCode);
	if (pid.participantCode=="NONE" || pid.visitCode=="NONE"){
		generateErrorMessage(id,listName,
			"ParticipantId/visitId not set");
		return;
	}
	

	print('JSON: '+config.loadFileConfig.json);

	let json=config.loadFileConfig.json;
	let nrows=json.rows.values.length;
	let ncol=json.columns.length;

	pid.basePath=getBasePath()+"/@files";
	
	
	let el=config.document.getElementById(id);
	let tableId=id+'_Table';
	let table=config.document.getElementById(tableId);
	if (table==null){
		table=config.document.createElement('table');
		table.id=tableId;
		el.appendChild(table);
	}
	table.style.tableLayout="fixed";
	table.style.columnWidth="300px";
	
	for (let i=0;i<nrows;i++){
		pid[json.rows.variable]=json.rows.values[i];
		//let organ=organs[i];
		let row=null;
		if (i<table.rows.length)
			row=table.rows[i];
		else
			row=table.insertRow();

		let ic=0;
		for (let j=0;j<ncol;j++){
			let obj=json.columns[j];
			let nv=obj.values.length;
			for (let k=0;k<nv;k++){
				let cell=null;
				if (ic<row.cells.length)
					cell=row.cells[ic];
				else
					cell=row.insertCell();
				if (obj.display=="image") 
					plotImage(cell,k,row,json.rows.variable,obj,pid);
				if (obj.display=="report") 
					showReport(cell,k,row,json.rows.variable,obj,pid);
				if (obj.display=="probability"){ 
					showProbability(cell,k,row,json.rows,i,obj,pid);
				}	
				ic++;
			}


		}
		

	}
}

///>>>>>>>>>>>>>>end of reviewSection(REPORT)

function generateReview(divReviewId,divReviewListId, listName, accessMode){
	let listId=config.formConfig.fields[listName].queryId;

	//listId is a number->should it be queryName?
	

	let debug=true;
	if (debug) print("Generate review for: "+listId+'/'+listName);
	
	let reviewSetup=new Object();
	reviewSetup.readonlyFlag=function(vName){
		if (vName=="queryName") return true; 
		if (vName=="queryname") return true; 
		if (vName=="ModifiedBy") return true;
		return false;};
	reviewSetup.addApply="Add Review";

	let generateTableFlag=true;
	let formStatus=config.formConfig.formStatus;
	//COMMENTS allowed or not
	//three levels of access: EDIT, COMMENT, READ
	if (accessMode == "READ"){
	//if (formStatus == "Approved" ){
		delete reviewSetup.addApply;
		reviewSetup.readonlyFlag=function(vName){return false;}
		generateTableFlag=false;
	}
	
	reviewSetup.filters=new Object();
	reviewSetup.filters["crfRef"]=getCRFref();
	
 	reviewSetup.filters["queryName"]=listId;//entry in reviewComments list is queryname, all in small caps
	//needs listName, in argument
	
	reviewSetup.getInputId=function(vName){return listName+"_add"+vName};
	reviewSetup.divReviewListId=divReviewListId;
	reviewSetup.isReview=true;	

	if (debug) {
		let msg="Review: divId: "+divReviewId;
		msg+=" inputId: "+reviewSetup.getInputId;
		print(msg);
	}
	updateListDisplay(divReviewListId,"reviewComments",reviewSetup.filters,true);

	if (! generateTableFlag) return;

	
	generateTable("reviewComments",divReviewId,new Object(),reviewSetup);
}	

//>>>>>>>>>>trigger visibility of additional lists

function setListVisibility(input,setup,readonlyFlag){
	let debug=true;
	let fName="[setListVisibility/"+setup.queryName+"]";
	print(fName);
	let additionalData=config.formConfig.additionalData[setup.queryName];
	
	let x = config.document.getElementById(additionalData.divName);
	if (debug) print(fName+": Div: "+x);
	x.style.display="none";

	let sText;
	if (readonlyFlag) sText=input.innerText;
	else sText=input.options[input.selectedIndex].text;
			
	if (debug) print(fName+": Selected option text: "+sText);

	if (sText == additionalData.showFlagValue){
		let filters=new Object();
		if ("filters" in additionalData) filters=additionalData.filters;
		x.style.display = "block";
		updateListDisplay(additionalData.divQueryName,
			additionalData.queryName,filters,readonlyFlag);
	}
}

//>>have list refresh when data is added (not optimal yet)

function updateListDisplay(divName,queryName,filters,readonlyFlag){
	//use Labkey.QueryWebPart to show list

	let debug=true;
	let fName="[updateListDisplay]";

	if (debug) 
		print(fName+": UpdateListDisplay: Query - "+queryName
			+" div - "+divName);

	if (divName=="NONE") return;

	let crfRef=getCRFref();
	let div=config.document.getElementById(divName);

	if (debug)
		print(fName+": generating WebPart: "+queryName);
	
	var qconfig=new Object();
	qconfig.renderTo=divName;
	//point to data container
	qconfig.containerPath=getContainer('data');
	qconfig.schemaName='lists'; 
	qconfig.queryName=queryName;
	qconfig.buttonBarPosition='top';
	qconfig.filters=[];
	for (f in filters){
		qconfig.filters.push(LABKEY.Filter.create(f, filters[f]));
	}
	qconfig.success=updateSuccess;
	qconfig.failure=updateFailure;
	//show only print button
	if (readonlyFlag){
		qconfig.buttonBar=new Object();
		qconfig.buttonBar.items=["print"];
	}

	LABKEY.QueryWebPart(qconfig);
	
}

function updateSuccess(data){
	print("Update success");
}

function updateFailure(data){
	print("Update failed");
}


//TODO: this should trigger a data refresh on section, ie populateData(field)
function toggleVisibility(divName,buttonName){
	let fName='[toggleVisibility/'+divName+']';
	print(fName);

	let x = config.document.getElementById(divName);
	if (x.style.display === "none") {
		//exclude non data sections (like debug)...
		print(fName+': issuing setData(populateSection)');
    		x.style.display = "block";
		config.document.getElementById(buttonName).value="Hide";
		let cb=function(){populateSection(divName);};
		setData(cb);

  	} else {
    		x.style.display = "none";
		config.document.getElementById(buttonName).value="Show";

  	}
}

function generateButtonBU(divName,title,buttonName,callback,
	callbackParameters){
	let debug=true;
	if (debug) print("generateButtonBU");
	
	let tb=config.document.createElement('table');
	tb.className="t2";
	
	let r1=tb.insertRow();
	th=config.document.createElement('th');
	r1.appendChild(th);
	th.innerHTML=title;
	//*!*
	let c2=r1.insertCell();
	let i1=config.document.createElement("input");	
	i1.type="button";
	i1.value=buttonName;
	i1.style.fontSize="20px";
	i1.onclick=function(){callback(callbackParameters);}
	c2.appendChild(i1);	

	let c1=r1.insertCell();
	c1.setAttribute("colspan","1");
	c1.id=callbackParameters.submitReportId;

	let el=config.document.getElementById(divName);
	if (debug) print("generateButton: element["+divName+"]: "+el);
	
	
	el.appendChild(tb);
	
	
}

function generateButton(divName,caption,label,callbackLabel,callback){
	let debug=true;
	if (debug) print("generateButtonX");
	
	let tb=config.document.createElement('table');
	tb.className="t2";
	
	let r1=tb.insertRow();
	th=config.document.createElement('th');
	r1.appendChild(th);
	th.innerHTML=caption;
	//*!*
	let c2=r1.insertCell();
	let i1=config.document.createElement("input");	
	i1.type="button";
	i1.value=label;
	i1.style.fontSize="20px";
	i1.onclick=callback;
	i1.id='button_'+callbackLabel;
	c2.appendChild(i1);	

	let c1=r1.insertCell();
	c1.setAttribute("colspan","1");
	//this is only for saveReview?
	c1.id=divName+'_reportField';
	//c1.id=config.submitReportId;

	let el=config.document.getElementById(divName);
	if (debug) print("generateButton: element["+divName+"]: "+el);
	
	
	el.appendChild(tb);
	
	
}

function generateSubQuery(input, setup, readonlyFlag){
	let fName="[generateSubQuery]";
	if (setup.isReview) return;

	if (!(setup.queryName in config.formConfig.additionalData)){
		print(fName+': no additionalData entry (probably a subquery)');
		return;
	}

	let additionalData=config.formConfig.additionalData[setup.queryName];
	if (!("showFlag" in additionalData))
		return;

	print(fName);
		
	let expId=setup.getInputId(additionalData.showFlag);
	if (expId!=input.id) {
		print(fName+": ignoring field "+input.id+"/"+expId);
		return;
	}

	print(fName+": Setting onChange to "+input.id);
	if (!readonlyFlag)
		input.onchange=function(){setListVisibility(input,setup,readonlyFlag)};
}


//>>populate fields
//
//
//split to field generation and field population
//
function addFieldRow(tb,field,setup,additionalData){

	let fName="[addFieldRow/"+setup.queryName+':'+field.name+']';

	let vName=field.name;
	let vType=field.type;
	let isLookup=("lookup" in field);
	print(fName+": ["+vName+"/"+vType+'/'+isLookup+"]");

	let row=tb.insertRow();
	let cell=config.document.createElement('th');
   cell.style.width='300px';
	row.appendChild(cell);
	
	let text = config.document.createTextNode(field.shortCaption);
	cell.appendChild(text);
		
	
	let input=null;
   let colSpan="3";
	let cell1=row.insertCell();
	cell1.colSpan=colSpan;
	let readonlyFlag=setup.readonlyFlag(vName);


	//set the html input object
	while (1){

		if (readonlyFlag){
			input=config.document.createElement('label');
			input.innerText='Loading';
			break;
		}
	

		//lookup
		if (isLookup){
			input = config.document.createElement("select");
			break;
		}

		//date
		if (vType=="date"){ 
			input = config.document.createElement("input");
			input.type="date";
			break;
		}

		//string
		if (vType=="string"){
			//we have to make sure UNDEF is carried to below
			//since we are adapting file to either show
			//current file or allow user to select a file
			//
			//TODO change this so one can always select file
			//but also show the selected file

			if(vName.search("reviewComment")>-1){
				input = config.document.createElement("textarea");
				input.cols="65";
				input.rows="5";
				break;
			}

			input=config.document.createElement('input');
			input.type="text";
			
			if (vName.search('_file_')<0) break;
			cell1.setAttribute('colspan',"1");
			let cell2=row.insertCell();
			cell2.setAttribute('colspan',"2");
			let input1=config.document.createElement('input');
			input1.type="file";
			input1.id=setup.getInputId(vName)+'_file_';
			cell2.appendChild(input1);
			break;
				
		}


		if (vType=="float"){
			input = config.document.createElement("input");
			input.type="text";
			break;
		}	
		
		
		if (vType=="boolean"){
			input = config.document.createElement("input");
			input.type="checkbox";
			print("Creating checkbox");
			break;
		}
		break;
	}
	
	input.id=setup.getInputId(vName);
	cell1.appendChild(input);
	print(fName+': adding element '+input.id);
	print(fName+': listing element '+config.document.getElementById(input.id));
	

	//connect associated list
	generateSubQuery(input,setup,readonlyFlag);	

	if (readonlyFlag) {
		print(fName+': exiting(readonlyFlag)');
		return;
	}
	
	if (!isLookup) 	{
		print(fName+': exiting (not lookup)');
		return;
	}

	let lookup=field["lookup"];

	//get all values from config.formConfig.lookup[X]
	let lObject=config.formConfig.lookup[lookup.queryName];
	
	//debug
	print(fName+": query: "+lookup.queryName);
	print(fName+": ElementId: "+input.id);
	print(fName+": No of options: " +lObject.LUT.length);
	print(fName+": Element: "+input);

	//set the lut value (input is text label) for readonly

   	//clear existing fields from input	
	for(let i = input.options.length; i >= 0; i--) {
		input.remove(i);
   	}
	
	//create option -1
	let opt = config.document.createElement("option");
	opt.text = "<Select>";
	opt.value = -1;
	input.options[0] = opt;
	print(fName+": Adding <Select>");
	

	//add other, label them with LUT
	for (let v in lObject.LUT) {
		print(fName+': populating '+v+': '+lObject.LUT[v]);

		let opt = config.document.createElement("option");
		opt.text = lObject.LUT[v];
		opt.value = v;
		input.options[input.options.length] = opt;
		
	}
	input.selectedIndex=0;	

}

function parseVariables(pars){
   let pA=pars.split(";");
   let q=new Object();
   for (let i=0;i<pA.length;i++){
      let vA=pA[i].split('=');
      q[vA[0]]=vA[1];
   }
   return q;
}



function addSpecialFieldRow(tb,specFieldSetup,setup){
   //tb is the table, specFieldSetup is a row from the table where special fields are being setup
   //the first column is fieldUID, which is a colon joined amalgation of queryName:fieldName
   let fieldUID=specFieldSetup["fieldUID"];
   let x=fieldUID.split(':');
   let fieldName=x[1];
   let fName="[addSpecialFieldRow/"+fieldUID+"]";
   let q=parseVariables(specFieldSetup['actionParameters']);
   print(fName);
   if (specFieldSetup['actionType']=='textArea'){
      let row=tb.insertRow();
      let cell1=row.insertCell();
      cell1.colSpan="4";
      cell1.style.textAlign="justify";
      cell1.style.padding="10px";
      cell1.style.backgroundColor="#e0e0e0";
      cell1.innerText=q['description'];
      return;
   }
   if (specFieldSetup['actionType']=='generationObject'){
      //only in EDIT mode!!
      let ro=setup.readonlyFlag(fieldName);
      if (ro) return;
      config.print=print;
      let gc=getGenerationObject(config,q,setup.getInputId(fieldName));
      let row=tb.insertRow();
      let cell=config.document.createElement('th');
      row.appendChild(cell);
      let text = config.document.createTextNode("Automatic ID generator");
      cell.appendChild(text);
      let cell1=row.insertCell();
      cell1.colSpan="3";
      let b=config.document.createElement("input");
      b.type="button";
      b.id="generateIdButton";
      b.onclick=function(){gc.execute();};
      b.value="Generate ID";
      cell1.appendChild(b);

   }
}

		
function populateFieldRow(entry,field,setup){
	populateField(entry,field,setup);
	populateSubQuery(entry,field,setup);
}

function populateSubQuery(entry,field,setup){
	let fName='[populateSubQuery/'+setup.queryName+':'+field.name+']';
	if (setup.isReview) return;
	
	if (!(setup.queryName in config.formConfig.additionalData)){
		let msg=fName+': no additionalData entry for '+setup.queryName;
		msg+=' (probably a subquery)';
		print(msg);
		return;
	}
	//find if field is connected to a sub array
	//find queryName
	//
	let additionalData=config.formConfig.additionalData[setup.queryName];	
	print(fName);
	//let flag=additionalData.showFlag;
	
	if (!("showFlag" in additionalData)) return;
	let eId=setup.getInputId(additionalData.showFlag);
	let id=setup.getInputId(field.name);
	
	if (eId!=id) {
		print(fName+": ignoring field "+id+"/"+eId);
		return;
	}
	
	print(fName+': id '+id);
	//hard to estimate readonlyFlag
	//
	let input=config.document.getElementById(id);
	let eType=input.nodeName.toLowerCase();
	let readonlyFlag=eType!="select";
	setListVisibility(input,setup,readonlyFlag);

}

function populateField(entry,field,setup){

	let vName=field.name;
	let fName='[populateFieldName/'+vName+']';

	let varValue="UNDEF";

	//if (vName in setup.filters) varValue=setup.filters[vName];
	if (vName in entry) varValue=entry[vName];
	//if part of the filter, set it to value
	if (vName in setup.filters) varValue=setup.filters[vName];
	
	let isLookup=("lookup" in field);
	
	print(fName+' v='+varValue+'/'+isLookup+' ['+
		setup.getInputId(field.name)+']');
	
	let vType=field.type;
	let id=setup.getInputId(vName);
	let input=config.document.getElementById(id);

		
	//date
	if (vType=="date"){
		if (varValue=="UNDEF") varValue=new Date();
		else varValue=new Date(varValue);
	}
	
	//lookup for readonly
	if (isLookup && varValue!="UNDEF"){
		let lookup=field["lookup"];
		//get all values from config.formConfig.lookup[X]
		let lObject=config.formConfig.lookup[lookup.queryName];
		varValue=lObject.LUT[varValue];
	}

	print('Element: '+input);
	//figure out the element type
	let eType=input.nodeName.toLowerCase();
	print('Element type: '+eType);

	//change varValue for printing
	if (varValue=="UNDEF") varValue="";
	//HTMLTextArea, createElement(textArea)
	if (eType==="textarea"){
		input.value=varValue;
		return;
	}
	//Text, createTextNode
	if (eType==="#text"){
		input.nodeValue=varValue;
		return;
	}
	//HTMLLabelElement, createElement('label')
	if (eType==="label"){
		input.innerText=varValue;
		return;
	}

	//HTMLSelectElement, createElement('select')
	if (eType==="select"){
		input.selectedIndex=0;
		for (let i=0;i<input.options.length;i++){
			let v=input.options[i].text;
			if (v!=varValue) continue;
			input.selectedIndex=i;
			break;
		}
		return;
	}

	if (eType!="input"){
		print('Unknown type: '+eType+' encountered, igonring');
		return;
	}
	
	//HTMLInputElement
	let type=input.type;

	if (type=="date"){
		input.valueAsDate=varValue;
		return;
	}
	//string,float
	if (type=="text"){
		input.value=varValue;
		return;
	}
	//boolean
	if (type=="checkbox"){
		input.checked=varValue;
		return;
	}
	print('Unknown input type: '+type+'. Ignoring.');
}


function populateTable(listName,writeMode){
//function populateTable(formSetupEntry){
	//let listName=config.formConfig.queryMap[formSetupEntry['queryName']];
	//let accessMode=config.formConfig.operator+'Mode';
	//let writeMode=formSetupEntry[accessMode]=='EDIT';

	let fName='[populateTable/'+listName+']';

	let setup=getSetup(listName,writeMode);
	let entry=new Object();
	
	//data snapshot
	let fQuery=config.formConfig.dataQueries[listName];
	
	//here I assume that listName was parsed during setDataLayout and setData 
	//so that rows was set (even if they are empty)
	print(fName+"]: nrows "+fQuery.rows.length);
	
	if (fQuery.rows.length>0)
		entry=fQuery.rows[0];
	
	let fields=fQuery.fields;
		
	for (f in fields){	
		let field=fields[f];
		//each field is a new row
		print(fName+": Adding field: "+f+'/'+field.name+' hidden: '+field.hidden);
		if (field.hidden) continue;
		if (field.name=="crfRef") continue;
		populateFieldRow(entry,field,setup);
		
	}

}

function generateTable(listName,divName,additionalData,setup){
	let debug=true;
	let fName="[generateTable/"+listName+"]";	
	if (debug) print(fName);

	//is listName and setup.queryName a duplicate of the same value
	print(fName+': setup.queryName '+setup.queryName);	
	//assume data is set in config.formConfig.dataQueries[data.queryName].rows;
	

	let entry=new Object();

	//data snapshot
	let fQuery=config.formConfig.dataQueries[listName];

	//here I assume that listName was parsed during setDataLayout and setData 
	//so that rows was set (even if they are empty)
	print(fName+": Nrows "+fQuery.rows.length);
	
	if (fQuery.rows.length>0)
		entry=fQuery.rows[0];
	
	let tb=config.document.createElement('table');
	tb.className="t2";
	config.document.getElementById(divName).appendChild(tb);

	//this are the fields (probably constant)
	let fields=fQuery.fields;
		
	for (f in fields){
		let field=fields[f];
      let fieldUID=listName+":"+field.name;
		//each field is a new row
		print(fName+": Adding field: "+f+'/'+field.name+' ('+fieldUID+').');
      //unique name
		if (field.hidden) continue;
		if (field.name=="crfRef") continue;
		addFieldRow(tb,field,setup,additionalData);
		populateFieldRow(entry,field,setup);
      if (fieldUID in config.formConfig["specialFields"]){
         let specFieldSetup=config.formConfig["specialFields"][fieldUID];
         addSpecialFieldRow(tb,specFieldSetup,setup);
      }

		
	}
   //finish of if apply button is not required
	if (!("addApply" in setup)) {
		print(fName+"populateTable: done");
		return;
	}
	
	let row=tb.insertRow();

	let th=config.document.createElement('th');
	row.appendChild(th);
	th.innerHTML=setup.addApply; 
	let cell=row.insertCell();
	//cell.setAttribute("colspan","2");
	let input=config.document.createElement("input");	
	input.type="button";
	input.value=setup.addApply;
	cell.appendChild(input);	
	let cell1=row.insertCell();
	cell1.setAttribute("colspan","2");
	cell1.id=setup.getInputId("rerviewLastSave");
	cell1.innerHTML="No recent update";
	//saveReview is a generic name for saving content of the html page to a list entry
	input.onclick=function(){saveReview(listName,cell1.id,setup)};
}	
	
function setEntryFromElement(entry,elementId, field){
   //set value to entry from element using representation (field) from labkey
   //
   //
   
   let fName='setEntryFromElement';

   let el=config.document.getElementById(elementId);
				
   if (!el) {
      print(fName+" element: "+elementId+" not found");
      return;
   }
   print(fName+" element: "+elementId);
      

   let vName=field.name;
   let vType=field.type;

   let eType=el.nodeName.toLowerCase();

   if (eType==="select"){
      entry[vName]=el.options[el.selectedIndex].value;
      return;
   }

   if (eType==="td"){
      entry[vName]=el.innerText;
      return;
   }
   
   if (vType=="date"){
      let date=el.valueAsDate;
      if (date==="null") return;

      date.setUTCHours(12);
      entry[vName]=date.toString();
      print(fName+" setting date to "+entry[vName]);
      return;
   }

   if (vType=="string"){
      entry[vName]=el.value;
      
      if (vName.search('_file_')<0) 
         return;
      
      //upload file
      let id1=elementId+'_file_';
      let input1=config.document.getElementById(id1);
      print(fName+' attachment field: '+input1.value);
      //entry[vName]=el.files[0].stream();
      let ctx=new Object();
      ctx['dirName']='consent';
      ctx['ID']=entry['crfRef'];
      //should point to data container
      ctx['project']=getContainer('data');
      //need ID->crf!
      //assume crfRef will get set before this
      //element is encountered
      uploadFile(input1,ctx);
      let fv=el.value;
      let suf=fv.split('.').pop();
      entry[vName]=entry['crfRef']+'.'+suf;
      return;
      
   }	
   if (vType=="float" || vType=="int"){
      entry[vName]=el.value;
      
      if (vName=="queryName") {
         print(fName+' parsing queryName: '+el.innerText);
         entry[vName]=config.formConfig.fields[el.innerText].queryId;
         //use queryMap lookup
      }
      return;
   }	
   if (vType=="boolean"){
      entry[vName]=el.checked;
      return;
	}
   return;
}

function saveReview(queryName,elementId,setup){
	//loads any queryName

	let debug=true;
	if (debug) print("saveReview: elementId "+elementId+" queryName "+queryName);
	let useInsert=false;

   let unique=("unique" in setup);
	
	if (!unique) useInsert=true;

	let fQuery=config.formConfig.dataQueries[queryName];

	if (fQuery.rows.length==0) useInsert=true;
	let entry=new Object();
	
	if (!useInsert){
		entry=fQuery.rows[0];
 	}
	entry.crfRef=getCRFrefData();

	if (debug) print("Set crfRef="+entry.crfRef);

	//if ("queryName" in setup.filters) {
	//	entry.queryName=setup.filters["queryName"];
	//	if (debug) print("Setting queryName: "+entry.queryName);
	//}

	let fields=fQuery.fields;
	for (f in fields){

		let field=fields[f];
		if (debug) print("saveReview field: "+field.name);
		if (field.hidden) continue;
		
		let vName=field.name;
		let vType=field.type;

		if (debug) print("vType: "+vType);
		
		if (vName=="crfRef") continue;
		//need to save queryName for reviewComments
		
		let eId=setup.getInputId(vName);
      //copy values from form to entry
      setEntryFromElement(entry,eId,field);
      if (!unique){
         let foo=Object();
         //reset the field value
         populateField(foo,field,setup);
      }
	}
	
	let qconfig=new Object();
	qconfig.rows=[entry];
	//should point to data container
	qconfig.containerPath=getContainer('data');
	qconfig.schemaName='lists';
	qconfig.queryName=queryName;

	//only update comments
	print("modifyRows: useInsert "+useInsert);
	qconfig.success=function(data){updateLastSavedFlag(data,setup,elementId)};
	if (!useInsert){
		LABKEY.Query.updateRows(qconfig);
	}
	else{
		LABKEY.Query.insertRows(qconfig);
	}
}

function updateLastSavedFlag(data,setup,elementId){
	let debug=true;
	if (debug) print("Update last saved flag to "+elementId);
	let el=config.document.getElementById(elementId);
	let dt=new Date();
	el.innerHTML="Last saved "+dt.toString();
	if (data.queryName=="reviewComments"){
		updateListDisplay(setup.divReviewListId,"reviewComments",setup.filters,true);
	}	
	//refresh stored data!
	let writeMode=!setup.readonlyFlag();
	if ("unique" in setup)
		setData(function (){populateTable(data.queryName,writeMode);});
	if ("masterQuery" in setup){
		let ad=config.formConfig.additionalData[setup.masterQuery];
		print('Updating list display: '+setup.queryName+'/'+ad.queryName);
		updateListDisplay(ad.divQueryName,ad.queryName,ad.filters,false);
	}
}

//******************************************upload to database *********************

function onDatabaseUpload(){
	let fName='[onDatabaseUpload]';
	print(fName);
	config.upload=new Object();
   let fc=new Object();
   let pM=getParticipantManagerObject(config);
   fc.participantId=pM.getParticipantIdFromCrfEntry();
	print(fName+' id '+fc.participantId);
   afterParticipantId(fc);
}


function onDatabaseUpload1(){
	let fName='[onDatabaseUpload]';
	print(fName);
	config.upload=new Object();
	//figure out the participantId
	
	let masterQueryId=config.formConfig.form["masterQuery"];
	print(fName+': master query: '+masterQueryId);
	let pidClass=new Object();
	pidClass.afterId=afterParticipantId;
	//use stored name of participantField 
	//for migrating data from lists to study
	//this is to avoid conflicts in column assignments
	//since datasets contain all fields of a list plus
	//default fields of which participantId is one
	pidClass.participantField=config.registrationParticipantIdField;
	let qconfig=new Object();
	qconfig.queryName=config.formConfig.queryMap[masterQueryId];
	//queryMap holds mapping for queries in visit; 
	//masterQuery should be one of them, so this is safe.
	qconfig.schemaName='lists';
	qconfig.containerPath=getContainer('data');
	qconfig.filterArray=[LABKEY.Filter.create('crfRef',getCRFref())];
	qconfig.success=function(data){afterRegistration(data,pidClass);};
	LABKEY.Query.selectRows(qconfig);
	//waitForCompleteUpload(config);//

}

function afterRegistration(data,fc){
	let fName='[afterRegistration/'+data.queryName+']';
	print(fName+": rows:"+data.rows.length);
	fc.registration=data;
	let registrationData=fc.registration;
	clearErr();
	if (registrationData.rows.length!=1){
		let msg=fName+": ERROR: Found "+registrationData.rows.length;
		msg+=" registration entries for crfrefid "+getCRFref();
		print(msg);
		fc.afterId(fc);
		return;
	}
	print(fName+'registration participant field: '+fc.participantField);
	fc.participantId=registrationData.rows[0][fc.participantField];
	//could be a lookup field (particularly for studies)
	print('ID: '+fc.participantId);	
	let fields=registrationData.metaData.fields;
	let field="NONE";
	for (f in fields){
		if (fields[f]["name"]==fc.participantField)
			field=fields[f];
	}
	if ("lookup" in field){
		let pid=fc.participantId;
		print("Using lookup for participantId: "+pid);
		let lookup=field["lookup"];
		print("Lookup: ["+lookup.schemaName+','+lookup.queryName+']');
		let qconfig=new Object();
		//should point to data container
		qconfig.containerPath=getContainer('data');
		qconfig.schemaName=lookup.schemaName;
		qconfig.queryName=lookup.queryName;
		qconfig.filterArray=
			[LABKEY.Filter.create(lookup.keyColumn,pid)];
		qconfig.success=function(data){
			afterRegistrationLookup(data,lookup.displayColumn,fc)};
		LABKEY.Query.selectRows(qconfig);
	}
	else{
		//afterParticipantId(configUpload);
		fc.afterId(fc);
	}
}

function afterRegistrationLookup(data,displayColumn,fc){
	print("afterRegistrationLookup");
	let entry=data.rows[0];
	fc.participantId=entry[displayColumn];
	print('Setting to '+fc.participantId);
	fc.afterId(fc);
	//afterParticipantId(configUpload);
}


function afterParticipantId(fc){
	print("Setting participantId to "+fc.participantId);
	config.upload.participantId=fc.participantId;
	//another select rows to update all queries from setup
	//just use registration for test
	let formSetupRows=config.formConfig.formSetupRows;
	config.upload.queries=new Array();
	print("Form rows: "+formSetupRows.length);
	for (let i=0;i<formSetupRows.length;i++){
		let entry=formSetupRows[i];
		//skip reviews
		if (entry.showFlag=="REVIEW") continue;
		//use lookup table to convert from id to name
		let queryName=config.formConfig.queryMap[entry.queryName];
		config.upload.queries.push({queryName:queryName,queryStatus:"QUEUED"});
		print('form ['+i+']='+queryName+' '+entry.showFlag+'/'+entry.showQuery);
		if (entry.showQuery=="NONE")
			continue;
		config.upload.queries.push({queryName:entry.showQuery,queryStatus:"QUEUED"});
	}
	//add reviews
	config.upload.queries.push({queryName:"reviewComments",queryStatus:"QUEUED"});
	config.upload.queryId=0;
	copyToDataset();

}



function copyToDataset(){
	let fName='[copyToDataset]: ';
	print(fName+'['+config.upload.queryId+'/'+config.upload.queries.length+']');
	//watch dog + scheduler
	//

	//watchdog part
	if (config.upload.queryId==config.upload.queries.length) {
		print(fName+'completing');
		let targetStatus=config.formConfig.targetStatus['onDatabaseUpload'];
		let targetRecipient=config.formConfig.targetRecipient['onDatabaseUpload'];
		let action=new Object();
		action.name='onDatabaseUpload';
		action.cb=function(data){sendEmail(data,targetRecipient,redirect,'Form uploaded');}
		updateFlag(targetStatus,action);//Approved
		return;
	}

	//scheduler
	let queryName=config.upload.queries[config.upload.queryId].queryName;
	print("copyToDataset["+config.upload.queryId+"/"+
			config.upload.queries.length+"]: "+queryName);
	let qconfig=new Object();
	qconfig.queryName=queryName;
	qconfig.schemaName="lists";
	qconfig.containerPath=getContainer('data');
	qconfig.filterArray=[LABKEY.Filter.create('crfRef',getCRFref())];
	qconfig.success=afterListData;
	LABKEY.Query.selectRows(qconfig);
}

function afterListData(data){
	let fName='[afterListData]: ';

	let queryName=config.upload.queries[config.upload.queryId].queryName;
	print(fName+" ["+queryName+"/list]: "+data.rows.length+" entries");
	config.upload.queries[config.upload.queryId].listData=data;
	let id=config.upload.participantId;

	let qconfig=new Object();
	qconfig.queryName=queryName;
	qconfig.schemaName="study";
	qconfig.containerPath=getContainer('data');
	qconfig.filterArray=[LABKEY.Filter.create('crfRef',getCRFref())];
	qconfig.filterArray.push(LABKEY.Filter.create('ParticipantId',id));
	qconfig.success=afterStudyData;
	LABKEY.Query.selectRows(qconfig);
}


function afterStudyData(data){

	let fName='[afterStudyData]: ';
	let queryObj=config.upload.queries[config.upload.queryId];
	queryObj.studyData=data;

	let msg=fName+"["+queryObj.queryName+"/study]: "+data.rows.length+" entries";
	print(msg);
	

	let listRows=queryObj.listData.rows;
	//skip uploading an empty set
	if (listRows.length==0){
		printErr("List "+queryObj.queryName+" empty.");
		queryObj.queryStatus="DONE";
		config.upload.queryId+=1;
		//back to watchdog
		copyToDataset();
		return;
	}
	

	let studyRows=queryObj.studyData.rows;

	for (let i=0;i<studyRows.length;i++){
		let entry=studyRows[i];
		//
		if (! (i<listRows.length) ) continue;
		let entryList=listRows[i];
		//keeps study only variables (ParticipantId, SequenceNum)
		for (let f in entryList) {
			entry[f]=entryList[f];
			print(fName+"Copying ["+f+"]: "+entry[f]+"/"+entryList[f]);
		}
	}
	print(fName+' copying completed');

	if (studyRows.length>0) {
		let qconfig=new Object();
		qconfig.queryName=queryObj.queryName;
		qconfig.schemaName="study";
		qconfig.rows=studyRows;
		qconfig.containerPath=getContainer('data');
		qconfig.success=afterStudyUpload;
		LABKEY.Query.updateRows(qconfig);
		print(fName+'updateRows sent');

	}
	else{
		let data=new Object();
		data.rows=new Array();
		afterStudyUpload(data);
	}
}

function afterStudyUpload(data){
	let fName='[afterStudyUpload] ';
	print(fName);
	//let participantField=config.participantField;
	let participantField=config.formConfig.studyData["SubjectColumnName"];
	print(fName+' participantField: '+participantField);

	let queryObj=config.upload.queries[config.upload.queryId];
	let queryName=queryObj.queryName;
	printErr("Updated "+data.rows.length+" rows to "+queryName);
	
	let studyRows=queryObj.studyData.rows;
	let listRows=queryObj.listData.rows;
	
	let rows=new Array();
	//also updating existing rows, if they exist
	for (let i=studyRows.length;i<listRows.length;i++){
		let entry=listRows[i];
		//make sure you have the participantField right
		//
		entry[participantField]=config.upload.participantId;
		entry.crfRef=getCRFref();
		entry.SequenceNum=getCRFref();
		entry.SequenceNum=entry.SequenceNum % 1000000000;
		
		if (listRows.length>1){
			entry.SequenceNum+=i/100;
		}
		print( "Adding sequence number "+entry.SequenceNum);
		rows.push(entry);
	}
	if (rows.length>0){

		let qconfig=new Object();
		qconfig.queryName=queryName;
		qconfig.schemaName="study";
		qconfig.containerPath=getContainer('data');
		qconfig.success=afterListUpload;
		qconfig.rows=rows;
		LABKEY.Query.insertRows(qconfig);
	}
	else{
		let data=new Object();
		data.rows=rows;
		afterListUpload(data);
	}
			
}

function afterListUpload(data){
	
	let queryObj=config.upload.queries[config.upload.queryId];
	let queryName=queryObj.queryName;
	printErr("Inserted "+data.rows.length+" rows to "+queryName);
	queryObj.queryStatus="DONE";
	config.upload.queryId+=1;
	copyToDataset();

}




//*************************update for further review *************************

function onUpdateForReview(){
	let targetStatus=config.formConfig.targetStatus['onUpdateForReview'];
	let targetRecipient=config.formConfig.targetRecipient['onUpdateForReview'];
	let action=new Object();
	action.name='onUpdateForReview';
	action.cb=function(data){sendEmail(data,targetRecipient,redirect,'Form updated for review');};

	updateFlag(targetStatus,action);
}

function updateFlag(flag,action){
	let fName='[updateFlag 1]';
	let debug=true;

	let entry=config.formConfig.crfEntry;
	entry.FormStatus=flag;
	let uId=config.formConfig.currentUser.UserId;
	entry[config.formConfig.operator]=uId;

	print(fName+': Form: '+entry.Form);
	print(fName+": set form status to "+entry.FormStatus);
	
	let qconfig=new Object();
	qconfig.schemaName='lists';
	qconfig.queryName='crfEntry';
	qconfig.containerPath=getContainer('data');
	qconfig.rows=[entry];
	//qconfig.success=function(data){completeWithFlag(data,flag);}
	qconfig.success=function(data){completeWithFlag(data,action);};
	LABKEY.Query.updateRows(qconfig);
	
}


function completeWithFlag(data,action){
	let fName='[completeWithFlag]';
	print(fName+': nrows '+data.rows.length);

	let fentry=data.rows[0];
	print(fName+': form status '+fentry.FormStatus);
	print(fName+': form '+fentry.Form);

	let entry=new Object();
	entry.entryId=getCRFref();
	entry.submissionDate=new Date();
	entry.FormStatus=fentry.FormStatus;
	entry.User=config.formConfig.currentUser.UserId;
	entry.Form=fentry.Form;
	entry.operator=config.formConfig.operator;
	entry.action=action.name;

	let qconfig=new Object();
	qconfig.schemaName='lists';
	qconfig.queryName='crfStatus';
	qconfig.containerPath=getContainer('data');
	qconfig.rows=[entry];
	//qconfig.success=function(data){completeWithFlag(data,flag);}
	qconfig.success=action.cb;
	LABKEY.Query.insertRows(qconfig);

}

//************************************************ submit *******************************************

function onSubmit(){
	//update list storage and change status

	let debug=true;
	hideErr();
	clearErr();
	printErr("onSubmit");

	setData(verifyData);
	


}

function verifyData(){
	let queries=config.formConfig.dataQueries;
	for (q in queries){
		let qData=queries[q];
		if (q=="reviewComments") continue;
		//if it doesn't have additionalData, it is a sub query
		if (!(q in config.formConfig.additionalData))
			continue;
		if (qData.rows.length<1){
			printErr('Missing entry for query '+q);
			return false;
		}
	}
	let targetStatus=config.formConfig.targetStatus['onSubmit'];	
	let targetRecipient=config.formConfig.targetRecipient['onSubmit'];
	print('verifyStatus: targetStatus: '+targetStatus);
	let fName='verifyStatus';
	//useful for debug
	//let finalStep=doNothing;
	//production mode
	let finalStep=redirect;
	
	let action=new Object();
	action.name='onSubmit';
	action.cb=function(data){sendEmail(data,targetRecipient,finalStep,'Form sumbitted');};
	updateFlag(targetStatus,action);
}

function getEmail(recipientCode){

	print('getEmail w/'+recipientCode);
	let recipients=new Array();
	let typeTo=LABKEY.Message.recipientType.to;
	let create=LABKEY.Message.createRecipient;
	let currentUser=config.formConfig.currentUser;
	let formCreator=config.formConfig.formCreator;
	let currentSite=config.formConfig.currentSite;
	let userRows=config.formConfig.userRows;
	let parentUser=undefined;
	if ("parentCrfData" in config.formConfig){
		let parentCrf=config.formConfig.parentCrfData;
		parentUser=getUser(parentCrf.rows[0].UserId,'parentUser');
	}
	

	let recipientCategories=recipientCode.split(',');
	for (let i=0;i<recipientCategories.length;i++){

		let recipient=recipientCategories[i];
		print('Checking '+recipient);
		if (recipient=='crfEditor'){
			print('Adding :'+formCreator.Email);
			recipients.push(create(typeTo,formCreator.Email));
			if (parentUser==undefined) continue;
			print('Adding :'+parentUser.Email);
			recipients.push(create(typeTo,parentUser.Email));
			continue;
		}
		//Monitor or Sponsor
		let fList=recipient+'s';
		let fRows=config.formConfig[fList];
		for (let i=0;i<fRows.length;i++){
			print('Checking '+fRows[i].User+'/'+fRows[i].Site);
			if (fRows[i].Site!=currentSite.siteNumber) continue;
			for (let j=0;j<userRows.length;j++){
				if (userRows[j].UserId!=fRows[i].User) continue;
				print('Adding :'+userRows[j].Email);
				recipients.push(create(typeTo,userRows[j].Email));
				break;
			}
		}
	}

	return recipients;
}

function sendEmail(data,recipient='crfEditor',cb=redirect,subj='Form submitted'){

	print('sendEmail; recipient: '+recipient);

	let st=config.formConfig.settings;
	let cvar='sendEmail';
	if (cvar in st){
		print(cvar+' set to '+st[cvar]);
		if (st[cvar]=='FALSE'){
			print('Skipping sending emails');
			cb();
			return;
		}
	}
	if (recipient==null){
      print('Skipping sending emails w/ no recipients');
      cb();
      return;
   }


	print('send email '+data.rows.length);
	let crf=data.rows[0]['entryId'];
	let formId=data.rows[0]['Form'];
	let link=LABKEY.ActionURL.getBaseURL();
	link+=LABKEY.ActionURL.getContainer();
	link+='/crf_tecant-visit.view?';
	link+='entryId='+crf;
	link+='&formId='+formId;
	link+='&role='+recipient;

	//debug
	let recipients=getEmail(recipient);
	//from crfManagers list
	
	let typeHtml=LABKEY.Message.msgType.html;
	let typePlain=LABKEY.Message.msgType.plain;
	let msg1=LABKEY.Message.createMsgContent(typePlain,link);

	//let cb=doNothing;
	//let cb=redirect;
	LABKEY.Message.sendMessage({
		msgFrom:'labkey@fmf.uni-lj.si',
		msgSubject:subj,
		msgRecipients:recipients,
		msgContent:[msg1],
		success: cb
	});

}

function hideErr(){
	let el=config.document.getElementById("errorDiv");
	el.style.display="none";
}

function clearErr(){
	let el=config.document.getElementById("errorTxt");
	el.value="";
}

function showErr(){
	let el=config.document.getElementById("errorDiv");
	el.style.display="block";
}

function printErr(msg){
	showErr();
	el=config.document.getElementById("errorTxt");
	el.style.color="red";
	el.value+="\n"+msg;
}


//**************************************************
//
function onRemoveCRF(){
	let debug=true;

	if (debug){
		print("Removing CRF");
	}

	let selectRows=new Object();
	//points to data container
	selectRows.containerPath=getContainer('config');
	selectRows.schemaName="lists";
	selectRows.queryName="inputLists";
	selectRows.success=afterInputLists;
	LABKEY.Query.selectRows(selectRows);
}

function afterInputLists(data){
	let debug=true;
	if (debug)
		print("After input lists");
	config.inputLists=data;
	config.inputListsIterator=0;

	removeCRFLoop();
}

function removeCRFLoop(){
	let debug=true;

	let i=config.inputListsIterator;
	let iMax=config.inputLists.rows.length;
	//in fact, we are adding two additional passages of the loop, one for 
	//crfEntry, the second for the same query, but using parentCrf as the 
	//selection variable
	let iTotal=iMax+1;
	//let iTotal=iMax+1;
	
	if (debug)
		print("removeCRFLoop ["+i+"/"+iMax+"]");

	if (i>iTotal){
		if (0) return;
		redirect();
	}
	let queryName="crfEntry";
	let idVar="entryId";
	let idValue=getCRFref();
	
	if (i<iMax){
		//in all but crfEntry, variable is called crfRef
		queryName=config.inputLists.rows[i].queryName;
		idVar="crfRef";
	}
	//for i=iMax and i=iMax+1, query is crfEntry.

	//delete also crfEntries where parentCrf is set to crf that we are deleting
	if (i==iTotal){
		idVar='parentCrf';
	}
	
	if (debug)
		print("["+i+"/"+iMax+"] "+queryName+":"+idVar+'/'+idValue);

	let selectRows=new Object();
	//points to data container
	selectRows.containerPath=getContainer('data');
	selectRows.schemaName="lists";
	selectRows.queryName=queryName;
	selectRows.filterArray=[LABKEY.Filter.create(idVar,idValue)];
	selectRows.success=removeListCRF;
	selectRows.failure=skipListCRF;
	LABKEY.Query.selectRows(selectRows);
}

function removeListCRF(data){
	let debug=true;

	if (debug)
		print(data.queryName+": "+data.rows.length);

	config.inputListsIterator+=1;
	
	if (data.rows.length==0){
		removeCRFLoop();
		return;
	}

	let deleteRows=new Object();
	//points to data container
	deleteRows.containerPath=getContainer('data');
	deleteRows.schemaName=data.schemaName;
	deleteRows.queryName=data.queryName;
	deleteRows.success=function(data){removeCRFLoop()};
	deleteRows.rows=data.rows;
	LABKEY.Query.deleteRows(deleteRows);
	
}

function skipListCRF(errorInfo){
	let debug=true;

	if (debug)
		print("Error in removeCRF: "+errorInfo.exception);

	config.inputListsIterator+=1;
	
	removeCRFLoop();
	
}



function redirect(){

	let debug=false;
	let formUrl="begin";
	let params=new Object();
	params.name=formUrl;
	params.pageId="CRF";

	//points to crf container
	let containerPath=getContainer('CRF');
        
	// This changes the page after building the URL. 
	//Note that the wiki page destination name is set in params.
        
	var homeURL = LABKEY.ActionURL.buildURL(
			"project", formUrl , containerPath, params);
        print("Redirecting to "+homeURL);
	if (debug) return;	 
	window.location = homeURL;

	

}

//printing section

function checkBlob(){
	print("checkBlob: "+config.blob);
	if (config.blob) {
		clearInterval(config.blobInterval);
		config.a.href = config.window.URL.createObjectURL(config.blob);
		print("HREF: "+config.a.href);
		config.a.download = 'test.pdf';
		config.a.click();
		config.window.URL.revokeObjectURL(config.a.href);
	}
	config.count=config.count+1;
	print("Eval: "+config.count);
	if (config.count>100){
		clearInterval(config.blobInterval);
	}

}

function printForm(){

	config.doc=new PDFDocument();
	//config.doc.end();
	let stream = config.doc.pipe(blobStream()).on("finish",function(){
			config.blob=stream.toBlob("application/pdf");});
	
	print("BLob: "+config.blob);
	config.a = config.document.createElement("a");
	config.document.body.appendChild(config.a);
	config.a.innerHTML="Download PDF";
	config.a.style = "display: none";
	config.count=0;
	//run until blob is set
	config.blobInterval=setInterval(checkBlob,1000);

	//pick data from crfForm list
        print("Printing form");
	printHeader();
	setData(formatPrintData);
}

function printHeader(){
	config.doc.fontSize(25).text(config.formConfig.form['formName']);
	config.doc.moveDown();
	let crfEntry=config.formConfig.crfEntry;
	let site=config.formConfig.currentSite;
	let val=new Object();
	let user=config.formConfig.user;
	val['A']={o:crfEntry,f:'EudraCTNumber',t:'Eudra CT Number'};
	val['B']={o:crfEntry,f:'StudyCoordinator',t:'Study Coordinator'};
	val['C']={o:crfEntry,f:'StudySponsor',t:'Study Sponsor'};
	val['D']={o:site,f:'siteName',t:'Site'};
	val['E']={o:site,f:'sitePhone',t:'Phone'};
	val['F']={o:user,f:'DisplayName',t:'Investigator'};

	for (let f in val){
		print('Printing for '+f);
		let e=val[f];
		let entry=new Object();
		entry[f]=e.o[e.f];
		printPDF(entry,
			{name:f,caption:e.t,type:'string'},null);
	}
	config.doc.moveDown();
}

function formatPrintData(){
	qS=config.formConfig.dataQueries;
	for (let q in qS){
		print('Setting up '+q);
		let qData=qS[q];
		print('Number of rows: '+qData.rows.length);
		if (qData.rows.length>0){
			config.doc.fontSize(20).text(qData.title);
		}
		for (let i=0;i<qData.rows.length;i++){
			let entry=qData.rows[i];
		       	for (let f in qData.fields){
				let field=qData.fields[f];
				let lookup=null;
				if (field.lookup){
					lookup=config.formConfig.lookup[field.lookup.queryName];
				}
				if (field.hidden) continue;
				printPDF(entry,field,lookup);
			}
		}
		config.doc.moveDown();
	}
	print("All done");
	config.doc.end();
}

function printPDF(entry,field,lookup){
	//object field should have a name, type, caption
	//entry should have field.name
	//lookup is null or has a lookup table LUT 
	//for value v of entry[field.name]
	//
	//the total width of a A4 page is 598 px, 
	//left margin is 72. With a right margin of 50,
	//the total available with is 476 px.
	
	let w=476;
	let spacing=25;
	let w1=(w-spacing)*0.5;
	let fontSize=14;	
	
	print('printPDF: entry['+field.name+']='+entry[field.name]);
	let v=entry[field.name];
	if (lookup!=null){
		v=lookup.LUT[v];
	}
	print('printPDF: field type:'+field.type);
	if (field.type=="date"){
		let d=new Date(v);
		v=d.getDate()+'/'+(d.getMonth()+1)+'/'+d.getFullYear();
	}	
	if (v===null) v=' / ';
	if (v===undefined) v=' / ';

	//measure text
	let label=field.caption;
	let opt={width:w1};
	config.doc.fontSize(fontSize);
	
	//for more eloquent display the height of the text
	//can be measured prior to output
	//use currentLineHeight to scale height
	//let lineH=config.doc.currentLineHeight(1);
	//let h=config.doc.heightOfString(label,opt)/lineH;


	//print label
	config.doc.font('Courier').text(label,opt);
	
	//align last row of description w/ first row of value
	config.doc.moveUp();

	//store x value for later use
	let tx=config.doc.x;
	let ty=config.doc.y;

	//shift for value output
	config.doc.x+=w1+spacing;
	
	config.doc.font('Courier-Bold').text(v,opt);

	//restore x value
	config.doc.x=tx;
	
}


//master section, entry point from html files

function generateMasterForm(){

	generateDebugSection();
	//read enviroment from lists disperesed on labkey
	setFormConfig();
}


//helper function to set basic parameters on web page 
//(fields defined in html file)
function populateBasicData(){

   let staticData=new Object();
   let titles=new Object();
   staticData['version']=config.formConfig.softwareVersion;	
   titles['version']='Software version';
   let varRows=config.formConfig['crfStaticVariables'].rows;
   for (let i=0;i<varRows.length;i++){
      let vName=varRows[i].staticVariable;
      let val=config.formConfig.crfEntry[vName];
      if (val==undefined) continue;
      staticData[vName]=val;
      titles[vName]=varRows[i].Title;
   }
	staticData['investigatorName']=config.formConfig.user['DisplayName'];
   titles['investigatorName']='Investigator';
   staticData['email']=config.formConfig.user['Email'];
   titles['email']='Email';
   staticData['siteName']=config.formConfig.currentSite['siteName'];
   titles['siteName']='Site';
   staticData['sitePhone']=config.formConfig.currentSite['sitePhone'];
   titles['sitePhone']='Telephone(site)';

   for (f in staticData){
      addStaticData(f,titles[f],staticData[f]);
   }
}

function addStaticData(f,title,value){
   let el=config.document.getElementById(f);

   //populate only
   if (el!=undefined){
      el.innerText=value;
      return;
   }
   
   //add row to table if element cannot be found
   let table=config.document.getElementById('staticTable');
   let row=table.insertRow();
   let cell=row.insertCell();
   cell.innerText=title;
   let cell1=row.insertCell();
   cell1.id=f;
   cell1.style.fontWeight='bold';

   //populate
   cell1.innerText=value;
}

//come here after the layout is read from labkey page
//
function generateErrorMsg(msg){
	let txt=config.document.createElement('p');
	txt.innerText=msg;
	config.document.getElementById(config.masterForm).appendChild(txt);
	generateButton("submitDiv",'Exit','Exit','redirect',redirect);
}

function getUser(id,field){
	if (field in config.formConfig) return config.formConfig[field];
	let uRows=config.formConfig.userRows;
	for (let i=0;i<uRows.length;i++){
		let userId=uRows[i].UserId;
		if (userId!=id) continue;
		config.formConfig[field]=uRows[i];
		return config.formConfig[field];
	}
	return null;
}	


function afterConfig(){

	let debug=true;

	if (debug)
		print("afterConfig");	

	populateBasicData();
	
	//check if user has permission on the form
	let currentUser=getUser(LABKEY.Security.currentUser.id,'currentUser');
	let currentSite=config.formConfig.currentSite;
	let formCreator=getUser(config.formConfig.crfEntry.UserId,'formCreator');
	let formCreatorId=formCreator.UserId;



	//let formSite=config.formConfig.crfEntry.Site;
	let fList=config.formConfig.operator+'s';
	let fRows=config.formConfig[fList];
	//let currentSiteId=-1;
	
	//depending on operator mode, we should decide what is right
	let operator=config.formConfig.operator;
	if (operator=='crfEditor'){
		//editor can only edit its own forms
		if (currentUser.UserId!=formCreatorId){
			let msg='User '+currentUser.DisplayName;
			msg+=' has no permission on this form';
			generateErrorMsg(msg);
			return;
		}
	}
	if (operator=='crfMonitor' || operator=='crfSponsor'){
		//monitor can look at forms based on his site
		//find monitor line
		let operatorSites=new Array();
		for (let i=0;i<fRows.length;i++){
			if (fRows[i].User!=currentUser.UserId) continue;
			operatorSites.push(fRows[i].Site);
		}
		print('operator Site: '+operatorSites.length);
		if (operatorSites.length==0){
			let msg='User '+currentUser.DisplayName;
			msg+=' is not a '+operator;
			generateErrorMsg(msg);
			return;
		}

		let selectedSite=-1;
		let siteCandidates="[";
		for (let i=0;i<operatorSites.length;i++){
			if (i>0) siteCandidates+=", ";
			siteCandidates+=operatorSites[i];
			if (operatorSites[i]!=currentSite.siteNumber) continue;
			selectedSite=currentSite.siteNumber;
			break;
		}
		siteCandidates+="]";
		if (selectedSite==-1){
			let msg='User '+currentUser.DisplayName;
			msg+=' is not a '+operator+' for site ';
			msg+=currentSite.siteName+'('+currentSite.siteNumber+')';
			msg+='/'+siteCandidates;
			generateErrorMsg(msg);
			return;
		}
	}


	print('User '+currentUser.DisplayName+'/'+
		config.formConfig.currentSite['siteName']+
		' acting as '+config.formConfig.operator);	


	let rows=config.formConfig.crfButtons.rows;
	config.formConfig.targetStatus=new Array();
	config.formConfig.targetRecipient=new Array();

	for (let i=0; i<rows.length; i++){
		let action=rows[i].action;//String
		let tstatus=rows[i].targetFormStatus;
		let trecip=rows[i].targetRecipient;
		config.formConfig.targetStatus[action]=tstatus;
		config.formConfig.targetRecipient[action]=trecip;
	}
	let formStatus=config.formConfig.formStatus;

	//let functionArray=new Array();

	print("Generating buttons for formStatus \""+ formStatus+"\"");

	let buttonRows=config.formConfig.crfButtons.rows;
	for (let i=0;i<buttonRows.length;i++){
		let bt=buttonRows[i];
		if (typeof window[bt.action]==="function"){
			generateButton("submitDiv",bt.caption,bt.label,bt.action,
				window[bt.action]);
		}
		else{
			print('No match for function :'+bt.action+
				' obj: '+window[bt.action]);
		}
	}

	print('Here');


	//here we should get data. For now, just initialize objects that will hold data
	setDataLayout(afterDataLayout);//callback is afterDataLayout
}
	
function afterDataLayout(){

	setData(afterData);//callback is afterData
}

function verifyCrfStudyId(pM){

   //is studyId already set for the crf
   let studyId=pM.getParticipantIdFromCrfEntry('STUDY');
   if (!studyId) return;
   pM.mode="STUDY";
   pM.readOnly="TRUE";
}

function verifyRegistration(pM){
   //if registration is in, 
   //then local id should not be changed any longer
   let idFieldName=pM.getCrfEntryFieldName("STUDY");
   //let registrationQuery=config.formConfig.settings['registrationQuery'];
   //if (!registrationQuery) return; //LOCAL is OK
   //let fQuery=config.formConfig.dataQueries[registrationQuery];
   //if (!fQuery) return; //no registration query, then it should be ignored
   let fQuery=config.formConfig.registrationData;
   if (fQuery.rows.length==0) return; //registration is empty

   let studyId=fQuery.rows[0][idFieldName];
   if (!studyId) return; //study id not set
   //set 
   pM.mode="STUDY";
   pM.readOnly="TRUE";
   //set crf (this happens later, but probably before the form will be corrected)
   pM.setParticipantIdToCrfEntry(studyId,"STUDY");
   pM.updateCrfEntry();
}

function afterData(){
	let fName='afterData';
	//operatorBasedAccessMode	
	let accessMode=config.formConfig.operator+'Mode';
	let rowsSetup=config.formConfig.formSetupRows;

   let idMode=config.formConfig.form['idMode'];
   //set default value if no value is in the list (read value is null)
   if (!idMode) idMode="STUDY:EDIT";

   print(fName+': idMode '+idMode);
   //add print to config so participantManager can use it
   config.print=print;
   let pM=getParticipantManagerObject(config);
   pM.updateCrfEntry=function(){updateFlag(config.formConfig.crfEntry['FormStatus'],doNothing);};   

   let idModeArray=idMode.split(':');
   pM.mode="STUDY";
   if (idModeArray.includes("LOCAL")) {
      pM.mode="LOCAL";
      //OK, but check if CRF or registration indicate that study id is already set
      verifyCrfStudyId(pM);
      verifyRegistration(pM);
   }
   if (idModeArray.includes("READONLY")){
      pM.readOnly="TRUE";
   }
   
   let pId=pM.getParticipantIdFromCrfEntry();
   if (!pId){
      pM.setEditMode();
   }
   else{
      let label=pId;
      if (pM.mode=="STUDY"){
         let loc=pM.getParticipantIdFromCrfEntry('LOCAL');
         label=pId+':'+loc;
         pM.readOnly="true";
      }
      pM.setLabelMode(label);
      //in STUDY mode also change LOCAL ID from crfEntry
      
   }

	for (let i=0;i<rowsSetup.length;i++){
		let entry=rowsSetup[i];
		let queryName=config.formConfig.queryMap[entry['queryName']];
		
		print(fName+" ["+queryName+"]: showFlag: "+entry["showFlag"]);
		print(fName+" ["+queryName+"]: accessMode: "+entry[accessMode]);

		const nData=config.formConfig.dataQueries[queryName].rows.length;
		
		print(fName+" ["+queryName+"]: nData: "+nData);


		//skip sections
		//also from fields
		if (entry[accessMode]=="NONE") continue;
		//skip readonly empty records
		//if (entry[accessMode]=="READ" && nData==0) continue;
			
		//let additionalData=new Object();
		//setAdditionalData(additionalData,entry);
		//section fits one dataset/list
      
		generateSection(entry);
		//generateSection(queryName,entry["title"],entry[accessMode], 
		//		additionalData);
	}

}

function findSetupRow(queryName){
	let rowsSetup=config.formConfig.formSetupRows;
	for (let i=0;i<rowsSetup.length;i++){
		let e=rowsSetup[i];
		let queryName1=config.formConfig.queryMap[e['queryName']];
		if (e.formName!=config.formId) continue;
		if (queryName1!=queryName) continue;
		return e;
	}
	return null;
}

function populateSection(queryName){
	let fName='[populateSection/'+queryName+']';
	print(fName);

	let entry=findSetupRow(queryName);
	//ignore names without associated entry in formSetup
	if (entry==undefined){
		print(fName+': no matching FormSetup entry found');
		return;
	}
	//populate comes after generate, we should be pretty safe in taking
	//already generated additionalData
	
	if (!(queryName in config.formConfig.additionalData)){
		print(fName+': no additionalData generated for '+queryName);
		return;
	}
	
	let additionalData=config.formConfig.additionalData[queryName];
	print(fName+': using additionalData '+additionalData);
	if ("isReview" in additionalData){
		generateReviewSection(queryName,queryName,generateReviewSectionCB);
		return;	
	}

	let accessMode=config.formConfig.operator+'Mode';
	let aM=entry[accessMode];
	print(fName+': accessMode '+aM);

	if (aM!='GENERATE'){
		let writeMode=entry[accessMode]=='EDIT';
		print(fName+': mode='+writeMode);
		populateTable(queryName,writeMode);
		return;
	}

	//deal with generate
	//
	//already available -> shift to READ mode
	let divTable=queryName+'Table';
	let divObj=config.document.getElementById(divTable);
	let divRev=config.document.getElementById(queryName+'Review');
	let divRLi=config.document.getElementById(queryName+'ReviewList');
	let divGBu=config.document.getElementById(queryName+'GenerateButton');

	print('div GBU: '+divGBu);
	divObj.style.display="block";
	divRev.style.display="block";
	divRLi.style.display="block";
	if (divGBu!=undefined) divGBu.style.display="none";

	let nData=config.formConfig.dataQueries[queryName].rows.length;
	print('['+queryName+']: nrows '+nData);
	if (nData>0){
		populateTable(queryName,0);
		return;
	}
	//hide table
	divObj.style.display="none";
	divRev.style.display="none";
	divRLi.style.display="none";
	if (divGBu!=undefined) divGBu.style.display="block";
	//add buttons?
	//is button already generated?
	
	//populateTable(entry);
	
}		

//*******    generateQuery infrastructure *********************

function onGenerateQuery(queryName){
   let fName='[onGenerateQuery]';
	print(fName+' '+queryName);
//
	let cfgRows=config.formConfig.generateConfigData.rows;
//	//queryName to queryId?
	let queryId=config.formConfig.fields[queryName].queryId;
	let cfgRow=undefined;

	for (let i=0;i<cfgRows.length;i++){
		if (cfgRows[i].queryId!=queryId) continue;
		cfgRow=cfgRows[i];
		break;
	}
	if (cfgRow==undefined){
		print('generateConfig for queryName['+queryId+']='+queryName+' not found');
		return;
	}
	//add config to the list
	if (!("generateConfig" in config.formConfig)){
		config.formConfig.generateConfig=new Object();
	}
	config.formConfig.generateConfig[queryName]=cfgRow;

	if (!("generateForm" in config.formConfig)){
		config.formConfig.generateForm=new Object();
	}
	
//
	let formRows=config.formConfig.formRows;	
	let formId=cfgRow.formId;
	for (let i=0;i<formRows.length;i++){
		if (formRows[i].Key==formId) {
			config.formConfig.generateForm[queryName]=formRows[i];
			break;
		}
	}
	//print('XcfgRow '+config.formConfig.generateForm[queryName]);
//
//	//check if all required datasets were at least saved
	checkGenerationFields(queryName);
}

function checkGenerationFields(queryName){
   let fName='[checkGenerationFields]';
	let genForm=config.formConfig.generateForm[queryName];
	let genCfg=config.formConfig.generateConfig[queryName];
	let mailRecipient=genCfg.emailRecipient;
	
	//list of queries that are part of Registration form
	print(fName);	
	print(fName+' setRecipient: '+mailRecipient);
	let formId=genForm.Key;
	print(fName+" Checking form w/id "+formId);
	let selectGenerationRows=selectFormSetupRows(formId);
	//registration rows
	for (let i=0;i<selectGenerationRows.length;i++){
		let row=selectGenerationRows[i];
		let queryId=row.queryName;
		let fQueryName=config.formConfig.queryMap[queryId];
		if (fQueryName==queryName) continue;
		let fQuery=config.formConfig.dataQueries[fQueryName];
		print('Checking '+fQueryName+' nrows: '+fQuery.rows.length);
		if (fQuery.rows.length==0){ 
			generateError(queryName,fQueryName);
			return;
		}
	}
	generateMessage(queryName,'Vailidation OK');
	print('callback: set recipient: '+mailRecipient);
	let cb=function(){prepareForm(queryName,formId,mailRecipient);};
	generateListEntry(formId,queryName,cb);
}



function prepareForm(queryName,formId,mailRecipient){

	print('prepareForm: recipient '+mailRecipient);
//
//	return;
	//look for existing registration entry
	let selectRows=new Object();
	//inputLists should be in configuration container
	selectRows.containerPath=getContainer('data');
	selectRows.schemaName='lists';
	selectRows.queryName='crfEntry';
	selectRows.success=function(data){generateForm(data,queryName,mailRecipient);};
	let formFilter=LABKEY.Filter.create('Form',formId);
	let parentCrfFilter=LABKEY.Filter.create('parentCrf',getCRFref());

	selectRows.filterArray=[formFilter,parentCrfFilter];
	LABKEY.Query.selectRows(selectRows);
}

function generateError(queryName,fQueryName){
	let elName=queryName+'GenerateButton'+'_reportField';
	let el=config.document.getElementById(elName);
	el.innerText='Error: '+fQueryName+' was not set';
	el.style.color='red';
}
function generateMessage(queryName,msg){
	let elName=queryName+'GenerateButton'+'_reportField';
	let el=config.document.getElementById(elName);
	el.innerText=msg;
	el.style.color='green';
}

function generateForm(data,queryName,mailRecipient){

	print('generateForm, recipient: '+mailRecipient);
//	
	const nData=data.rows.length;
	print('Registration: '+nData+' rows');

	let formRow=config.formConfig.generateForm[queryName];

	//we have to generate masterQuery with parentCrf and crfRef 
	//and crfEntry with new entryId and parentCrf equal to crfRef
	if (nData>0) {
		generateMessage(queryName,'Registration already generated.');
		return;
	}
	let formId=formRow.Key;
	let formName=formRow.formName;
	let crfBase=config.formConfig.crfEntry;
	let crfEntry=new Object();
	//add new reference
	crfEntry.entryId=Date.now();
	crfEntry.parentCrf=getCRFref();
	crfEntry["Date"]=new Date();
	crfEntry["View"]="[VIEW]";
	crfEntry.formStatus=1;//In progress
   //get local Id
   let pM=getParticipantManagerObject(config);
   
   crfEntry[pM.getCrfEntryFieldName()]=pM.getParticipantIdFromCrfEntry();
//	//set other variables
	//requires studyData as part of formConfig
//	let studyData=config.formConfig.studyData;
	print('Adding study: '+crfBase.EudraCTNumber);
	crfEntry.EudraCTNumber=crfBase.EudraCTNumber;
	crfEntry.StudyCoordinator=crfBase.StudyCoordinator;
	crfEntry.StudySponsor=crfBase.StudySponsor;
	crfEntry.RegulatoryNumber=crfBase.RegulatoryNumber;
//
//	//find sponsor for site
	let site=crfBase.Site;
	let crfSponsors=config.formConfig.crfSponsors;
	let users=config.formConfig.userRows;
	for (let i=0;i<crfSponsors.length;i++){
		//print('Checking for site '+crfSponsors[i].Site);
		if (crfSponsors[i].Site!=site) continue;
		config.formConfig.sponsorId=crfSponsors[i].User;
		//print('Setting id '+config.formConfig.sponsorId);
		//finds first
		break;
	}
	for (let j=0;j<users.length;j++){
		if (config.formConfig.sponsorId!=users[j].UserId) continue;
		config.formConfig.sponsor=users[j];
		//finds first (should be unique)
		break;
	}
	print('Selecting '+config.formConfig.sponsor.DisplayName+' as sponsor');
	//different user than the original form...
	//should be set to the study sponsor
	crfEntry.UserId=config.formConfig.sponsor.UserId;
	crfEntry.Site=site;
//	//set formId to one found through registration search
	crfEntry.Form=formId;
////
	let qconfig=new Object();
	qconfig.schemaName='lists';
	qconfig.queryName='crfEntry';
	qconfig.success=function(data){sendEmail(data,mailRecipient,doNothing,formName+' generated');}
	qconfig.rows=[crfEntry];
	LABKEY.Query.insertRows(qconfig);

}
//
function generateListEntry(formId,queryName,cb){

	//check if registration was already generated

	let formRows=config.formConfig.formRows;
	let qForm=undefined;
	for (let i=0;i<formRows.length;i++){
		if (formRows[i].Key!=formId) continue;
		qForm=formRows[i];
	}
	let nData=config.formConfig.dataQueries[queryName].rows.length
	if (nData>0) return;
	
   let pM=getParticipantManagerObject(config);
   

	let e2=new Object();
	e2.crfRef=getCRFref();
	e2.registrationStatus=0;
	e2.submissionDate=new Date();
   e2[pM.getCrfEntryFieldName()]=pM.getParticipantIdFromCrfEntry();
	print('set values');
	
	let qconfig=new Object();	
	qconfig.containerPath=getContainer('data');
	qconfig.schemaName='lists';
	qconfig.queryName=queryName;
	qconfig.success=cb;
	qconfig.rows=[e2]
	LABKEY.Query.insertRows(qconfig);

}
		
// ******************** end form generator (Registration) ********************

//jump to populate table/generate review, etc defined at the begining of the file

function setContainer(label,container){
	if (!(config.formConfig.hasOwnProperty('container'))){
		config.formConfig.container=new Array();
	}
	config.formConfig.container[label]=container;
}

function getContainer(label){
	return config.formConfig.container[label];
}

//entry point from generateMasterForm
function setFormConfig(){

	

	//add object to store form related data
	config.formConfig=new Object();

	config.formConfig.softwareVersion='T.15.23';
	let debug=true;

	if (debug)
		print("generateMasterForm1");	
	
	//set containers for data and configuration

	//TODO: set this from a query
	//
	
	setContainer('data',LABKEY.ActionURL.getContainer());
	setContainer('config',LABKEY.ActionURL.getContainer());
	setContainer('CRF',LABKEY.ActionURL.getContainer());

	let selectRows=new Object();
	//this is local data
	selectRows.containerPath=getContainer('CRF');
	selectRows.schemaName='lists';
	selectRows.queryName='crfSettings';
	//store form related data to this object
	selectRows.success=afterSettings;
	LABKEY.Query.selectRows(selectRows);

}


function convertToDictionary(rows){
   let x=new Array();
	for (let i=0;i<rows.length;i++){
		let n=rows[i]['name'];
		let v=rows[i]['value'];
		x[n]=v;
	}
   return x;
}
   
function convertToAssociatedArray(rows,fieldName="name"){
   let fName="[convertToAssociatedArray]";
   let x=new Object();
	for (let i=0;i<rows.length;i++){
		let n=rows[i][fieldName];
		x[n]=rows[i];
	}
   return x;
}
 
function afterSettings(data){

	config.formConfig.settings=convertToDictionary(data.rows);

	let st=config.formConfig.settings;
	print('afterSettings');
	for (let k in st){
		print('\t'+k+'='+st[k]);
	}

	//if ('dataContainer' in st){
	//	setContainer('data',st['dataContainer']);
	//}
	let vname='configContainer';
	if (vname in st){
		setContainer('config',st[vname]);
	}
	print('Config: '+getContainer('config'));
	print('Data: '+getContainer('data'));

	let selectRows=new Object();
	//this is local data
	selectRows.containerPath=getContainer('data');
	selectRows.schemaName='lists';
	selectRows.queryName='crfEntry';
	//use first-> we must first establish link to the rigth crf entry
	selectRows.filterArray=[LABKEY.Filter.create('entryId',getCRFrefFirst())];
	//store form related data to this object
	selectRows.success=afterCRFEntry;
	LABKEY.Query.selectRows(selectRows);

}

function afterCRFEntry(data){
	config.formConfig.crfEntry=data.rows[0];
	print("Setting crfEntry (x) to "+config.formConfig.crfEntry["entryId"]);
	//for empty records or those with parentCrf not set, parentCrf comes up as null
	//nevertheless, with two equal signs, check against undefined also works
	print('parentCrf set to '+config.formConfig.crfEntry.parentCrf);
	
	collectData();
}

function collectData(){
	
   let targetObject=config.formConfig;
   targetObject.print=print;
   targetObject.getContainer=getContainer;
	let queryArray=new Array();
   //k
	//site
	queryArray.push(makeQuery(targetObject,'config','site','siteData',[]));
	//users
	queryArray.push(makeQuery(targetObject,'CRF','users','userData',[]));
	queryArray[queryArray.length-1].schemaName='core';
	//crfEditors
	queryArray.push(makeQuery(targetObject,'config','crfEditors','crfEditorsData',[]));
	//crfMonitors
	queryArray.push(makeQuery(targetObject,'config','crfMonitors','crfMonitorsData',[]));
	//crfSponsors
	queryArray.push(makeQuery(targetObject,'config','crfSponsors','crfSponsorsData',[]));

   //study static data
   queryArray.push(
      makeQuery(targetObject,'data','crfStaticVariables','crfStaticVariables',[]));

   queryArray.push(makeQuery(targetObject,'data','specialFields','specialFieldsQuery',[]));
	//study
	queryArray.push(makeQuery(targetObject,'data','Study','studyDataAll1',[]));
	let e=queryArray[queryArray.length-1];
	//overload schema name
	e.schemaName='study';
	//make sure variables not part of default view are loaded
	//here we should already have read crfStaticVariables table
	e.columns="SubjectColumnName,EudraCTNumber,StudySponsor";
	e.columns+=",StudyCoordinator,RegulatoryNumber";
	
	//formStatus
	let varLabel='sourceFormStatus';
	let formStatus=config.formConfig.crfEntry['FormStatus'];
	let formFilter=LABKEY.Filter.create('Key',formStatus);
	queryArray.push(
		makeQuery(targetObject,'config','FormStatus','formStatusData',[formFilter]));
	//crfButtons
	let statusFilter=LABKEY.Filter.create(varLabel,formStatus);
	queryArray.push(
		makeQuery(targetObject,'config','crfButtons','crfButtons',[statusFilter]));
	//Forms	
	queryArray.push(makeQuery(targetObject,'config','Forms','formData',[]));
	//FormSetup	
	queryArray.push(makeQuery(targetObject,'config','FormSetup','formSetup',[]));
	//generateConfig
	queryArray.push(
		makeQuery(targetObject,'config','generateConfig','generateConfigData',[]));	
	//parentCrf
	let parentCrf=config.formConfig.crfEntry['parentCrf'];
	if (parentCrf!=undefined){
		let crfFilter=LABKEY.Filter.create('entryId',parentCrf);
		queryArray.push(makeQuery(targetObject,'data','crfEntry','parentCrfData',[crfFilter]));	
	}	

	print('running getDataFromQueries');
	getDataFromQueries(queryArray,addStudyData);
}

function addStudyData(){
   let fName='addStudyData';

   //convert specialFields to array
   let q=config.formConfig["specialFieldsQuery"].rows;
   config.formConfig.specialFields=convertToAssociatedArray(q,"fieldUID");
   print(fName);
	let queryArray=new Array();
	
   let targetObject=config.formConfig;
   targetObject.print=print;
   targetObject.getContainer=getContainer;
	//study
	queryArray.push(makeQuery(targetObject,'data','Study','studyDataAll',[]));
	//queryArray.push(makeQuery('data','Study','studyDataAll',[]));
	let e=queryArray[queryArray.length-1];
	//overload schema name
	e.schemaName='study';
	//make sure variables not part of default view are loaded
	//here we should already have read crfStaticVariables table
   let staticVarRows=config.formConfig['crfStaticVariables'].rows;
   let columnModel=""
   for (let i=0;i<staticVarRows.length;i++){
      if (i>0) columnModel+=',';
      columnModel+=staticVarRows[i]['staticVariable'];
   }
	e.columns=columnModel;

   //also collect ids already in study
   //registrationQuery should be a dataset
   //since monitors can review late, it might be profitable to use lists
   //rather than study
   let regQueryPars=parseVariables(config.formConfig.settings['registrationQuery']);
   let regQuery=regQueryPars['query'];
   let regSchema='study';
   if ('schema' in regQueryPars){
      regSchema=regQueryPars['schema'];
   }
   let crfFilter=LABKEY.Filter.create('crfRef',getCRFref());
   queryArray.push(makeQuery(targetObject,'data',regQuery,'registrationData',[crfFilter]));
   queryArray[queryArray.length-1].schemaName=regSchema;
      
	
	getDataFromQueries(queryArray,fcontinue);

}

function selectFormSetupRows(formId){
	let formSetupRows=new Array();
	let allRows=config.formConfig.formSetup.rows;
	for (let i=0;i<allRows.length;i++){
		let formEntry=allRows[i];
		if (formEntry.formName==formId)
			formSetupRows.push(formEntry);
	}
	return formSetupRows;
}

function fcontinue(){

   //debug
   let fName='[fcontinue]';
   let varRows=config.formConfig['crfStaticVariables'].rows;
   let studyVars=config.formConfig['studyDataAll'].rows[0];
   for (let i=0;i<varRows.length;i++){
      let vName=varRows[i].staticVariable;
      print(fName+' '+vName+': '+studyVars[vName]);
   }

	//parse site
	config.formConfig.siteRows=config.formConfig.siteData.rows;
	let sRows=config.formConfig.siteRows;
	for (let i=0;i<sRows.length;i++){
		let siteId=sRows[i].siteNumber;
		print('site '+siteId);
		if (siteId==config.formConfig.crfEntry.Site){
			config.formConfig.currentSite=sRows[i];
			break;
		}
	}
	//config.formConfig.site=data.rows[0];
	print("Setting site name to "+config.formConfig.currentSite.siteName);
	//study
	config.formConfig.studyData=config.formConfig.studyDataAll.rows[0];
	print("XSetting participantField to "+
			config.formConfig.studyData["SubjectColumnName"]);
	
	config.formConfig.crfEditors=config.formConfig.crfEditorsData.rows;
	config.formConfig.crfMonitors=config.formConfig.crfMonitorsData.rows;
	config.formConfig.crfSponsors=config.formConfig.crfSponsorsData.rows;

	config.formConfig.userRows=config.formConfig.userData.rows;
	let uRows=config.formConfig.userRows;
	for (let i=0;i<uRows.length;i++){
		let userId=uRows[i].UserId;
		if (userId==config.formConfig.crfEntry.UserId){
			config.formConfig.user=uRows[i];
			break;
		}
	}
	//config.formConfig.user=data.rows[0];
	print("Setting user to "+config.formConfig.user["DisplayName"]);



	let fsRows=config.formConfig.formStatusData.rows;
	config.formConfig.formStatus=fsRows[0].formStatus;
	config.formConfig.operator=config.role;
	//config.formConfig.operator=fsRows[0].operator;

	print('Setting operator to: '+config.formConfig.operator);
	
	config.formConfig.formRows=config.formConfig.formData.rows;
	
	let formRows=config.formConfig.formRows;
	//filter out the current form
	for (let i=0;i<formRows.length;i++){
		if (formRows[i].Key==config.formId){
			config.formConfig.form=formRows[i];
			break;
		}
	}
	
	config.formConfig.formSetupRows=selectFormSetupRows(config.formId);

	print("Number of datasets for form ["+config.formId+"]: "+
			config.formConfig.formSetupRows.length);



	let fields=config.formConfig.formSetup.metaData.fields;

	//get the lookup for queryName column
	let formQueryName='queryName';
	let field="NONE";
	for (f in fields){
		if (fields[f]['name']!=formQueryName) continue;
		field=fields[f];
		break;
	}
	let lookup=field.lookup;

	print("Getting dataset names from "+lookup.queryName);
	let selectRows=new Object();
	//inputLists should be in configuration container
	selectRows.containerPath=getContainer('config');
	selectRows.schemaName=lookup.schemaName;
	selectRows.queryName=lookup.queryName;
	selectRows.success=afterFormDatasets;
	LABKEY.Query.selectRows(selectRows);

}

function afterFormDatasets(data){
	print('afterFormDatasets: '+data.rows.length);
	config.formConfig.formDatasets=data;//inputLists
	config.formConfig.fields=new Object();
	config.formConfig.queryMap=new Object();
	config.formConfig.additionalData=new Object();

	let rows=config.formConfig.formSetupRows;

	//should skip report only rows
	for (let i=0;i<rows.length;i++){
		let entry=rows[i];
		let reviewField=(entry['showFlag']=='REVIEW');
		//is the operator set yet?
		let accessMode=config.formConfig.operator+'Mode';
		let skipField=(entry[accessMode]=="NONE");
		let queryId=entry['queryName'];
		let lookupRows=config.formConfig.formDatasets.rows;
		print('QueryID['+i+']='+queryId);
		let dentry;

		for (let j=0;j<lookupRows.length;j++){
	
			if (queryId!=lookupRows[j]['Key']) continue;
			dentry=lookupRows[j];
			break;
		}
		let qName=dentry['queryName'];

		//update list of dataset formConfig is observing (fields/queryMap)
		while (1){
			//review contains no data
			if (reviewField) break;
			if (skipField) break;
			//already in fields
			if (qName in config.formConfig.fields) break;
			config.formConfig.fields[qName]=new Object();
			break;
		}

		while(1){
			//already done
			if (queryId in config.formConfig.queryMap) break;
			config.formConfig.queryMap[queryId]=qName;
			break;
		}
		if (reviewField) continue;
		if (skipField) continue;
		//only do this for real lists
		let field=config.formConfig.fields[qName];
		field.title=entry['title'];
		field.queryId=queryId;

	}
	print("List of datasets in form : ");
	for (f in config.formConfig.fields){
		let field=config.formConfig.fields[f];
		print("\t"+f+" ID: "+field.queryId+' title '+field.title);
	}
	afterConfig();

}

//>>>>>>>>>>>>>>>>>new>>>>>>>>>>>>

function setDataLayout(cb){
	let rowsSetup=config.formConfig.formSetupRows;
	config.formConfig.dataQueries=new Object();
	let dS=config.formConfig.dataQueries;//reference only
	let qMap=config.formConfig.queryMap;
	config.formConfig.lookup=new Object();
	for (let i=0;i<rowsSetup.length;i++){
		let entry=rowsSetup[i];
		//skip review rows
		if (entry['showFlag']=='REVIEW')
			continue;
		let queryId=entry['queryName'];
		let queryName=qMap[entry['queryName']];
		dS[queryName]=new Object();
		dS[queryName].title=entry['title'];
		if (entry['showQuery']!="NONE"){
			let sqName=entry['showQuery'];
			dS[sqName]=new Object();
			dS[sqName].title=findTitle(sqName);
			
		}
	}
	//always add reviews
	//

	config.formConfig.dataQueries['reviewComments']=new Object();

	//perhaps we will need queryId, but this is stuff for later
	for (q in config.formConfig.dataQueries){
		//callback will be a watchdog and will complete only 
		//when all data will be gathered
		let dq=config.formConfig.dataQueries[q];
		dq.collectingLayout="INITIALIZED";

		let selectRows=new Object();
		selectRows.queryName=q;
		selectRows.schemaName='lists';
		selectRows.containerPath=getContainer('data');
		//we are only interested in metadata
		selectRows.success=function(data){afterDatasets(data,cb);}
		LABKEY.Query.selectRows(selectRows);
	}
}

function findTitle(queryName){
//find by name from formDatasets 
//and set associated title as title
	let rows=config.formConfig.formDatasets.rows;
	for (let i=0;i<rows.length;i++){
		let entry=rows[i];
		if (entry['queryName']!=queryName) continue;
		return entry['title'];
	}
	return "NONE";
}

//this happens after the for loop, so all dataQueries objects are set
function afterDatasets(data,cb){
	let qobject=config.formConfig.dataQueries[data.queryName];
	print("Inspecting layout for "+data.queryName+" "+qobject);
	qobject.fields=data.metaData.fields;
	qobject.collectingLayout="STARTED";
	//qobject.started="TRUE";
	qobject.lookup=new Object();//keep track of objects

	let i=0;	
	for (let f in qobject.fields){
		//anything else is simple but lookup
		let field=qobject.fields[f];
		if (!("lookup" in field)) continue;
		qobject.lookup[f]="PENDING";
		print("Adding pending lookup for field "+f);
		if (field.lookup.queryName in config.formConfig.lookup){
		       qobject.lookup[f]="DONE";
	       	       continue;
		}
		print("Setting up query for field "+f);
		config.formConfig.lookup[field.lookup.queryName]=new Object();
		let lObject=config.formConfig.lookup[field.lookup.queryName];
		lObject.keyColumn=field.lookup.keyColumn;
		lObject.displayColumn=field.lookup.displayColumn;
		let selectRows=new Object();
		selectRows.schemaName=field.lookup.schemaName;
		selectRows.queryName=field.lookup.queryName;
		selectRows.containerPath=getContainer('data');
		if ("containerPath" in field.lookup){
			selectRows.containerPath=field.lookup.containerPath;
		}
		selectRows.columns=field.lookup.keyColumn+","+
			field.lookup.displayColumn;
		//wait for all lookups to return
		selectRows.success=function(data){addLookup(data,qobject,f,cb);};
		print("Sending query: ["+
				field.lookup.queryName+"] "+
				field.lookup.keyColumn+'/'+
				field.lookup.displayColumn);
		LABKEY.Query.selectRows(selectRows);
		i+=1;
	}
	if (i==0){
		print("No lookups for "+data.queryName);
		qobject.collectingLayout="FINISHED";
	}
	//check if done (both here and in lookup
	//this only passes if no lookups are anywhere in the dataset assembly
	if (!dataLayoutSet()) return;
	cb();
}

function addLookup(data,qobject,f,cb){
	print("Adding lookup "+data.queryName+' '+qobject+' '+f);
	let lObject=config.formConfig.lookup[data.queryName];
	lObject.LUT=new Array();//key to value
	lObject.ValToKey=new Array();//value to key
	let key=lObject.keyColumn;
	let val=lObject.displayColumn;

	for (let i=0;i<data.rows.length;i++){
		lObject.LUT[data.rows[i][key]]=data.rows[i][val];
		lObject.ValToKey[data.rows[i][val]]=data.rows[i][key];
	}
	qobject.lookup[f]="DONE";
	if (!dataLayoutSet()) return;
	cb();
}

function dataLayoutSet(){
	print("Checking layout completeness");
	let dq=config.formConfig.dataQueries;
	for (f in dq){
		print("["+f+"]: "+dq[f].collectingLayout);
		if (dq[f].collectingLayout=="INITIALIZED")
			return 0;
		if (dq[f].collectingLayout=="FINISHED")
			continue; //OK
		let qobject=dq[f];
		for (q in qobject.lookup){
			if (qobject.lookup[q]=="DONE") {
				print("["+f+"/"+q+"]: DONE");
				continue;//OK
			}
			print("["+f+"/"+q+"]: "+qobject.lookup[q]);
			return 0;
		}
		dq[f].collectingLayout="FINISHED";
	}
	print("Success");
	return 1;	
}

function setData(cb){
	fName='[setData]';
	//print(fName+': cb '+cb);	
	let crfMatch=getCRFref();
	let parentCrf=config.formConfig.crfEntry['parentCrf'];
	if (parentCrf!=undefined) crfMatch=parentCrf;

	print(fName+' form crf ['+getCRFref()+'] matching for crfRef='+crfMatch);
	//collect data and execute callback cb for queries in cb.queryList
	for (q in config.formConfig.dataQueries){
		let fQuery=config.formConfig.dataQueries[q];
		fQuery.collectingData="STARTED";
		let selectRows=new Object();
		selectRows.containerPath=getContainer('data');
		selectRows.queryName=q;
		selectRows.schemaName='lists';
		selectRows.filterArray=[
			LABKEY.Filter.create("crfRef",crfMatch)
		];
		selectRows.success=function(data){assembleData(data,cb);};
		LABKEY.Query.selectRows(selectRows);
	}
}

function assembleData(data,cb){
	let fName='[assembleData/'+data.queryName+']';
	let fQuery=config.formConfig.dataQueries[data.queryName];
	fQuery.rows=data.rows;
	fQuery.collectingData="FINISHED";
	print(fName+': adding data');
	for (q in config.formConfig.dataQueries){
		let dq=config.formConfig.dataQueries[q];
		//print("assembleData ["+q+"]: "+dq.collectingData);
		if (dq.collectingData=="STARTED") return;
	}
	print(fName+': completing');
	cb();
}


function uploadFile(inputElement,context){
	//context should have ID and dirName attributes; 
	//path will be dirName/ID/fieldName_ID.suf
	//where suf is identical to localPath content picked from
	//inputElement
	print('uploadFile: '+inputElement.value+'/');
	if (inputElement.type=="text") return;
	print('uploadFile: '+inputElement.files+'/');
	print('uploadFile: '+inputElement.files.length+'/');
	if (inputElement.files.length>0){
		let file=inputElement.files[0];
		print('uploadFile: '+inputElement.value+'/'+file.size);
	}

	let url=LABKEY.ActionURL.getBaseURL();
	url+='_webdav';
	url+=LABKEY.ActionURL.getContainer();
	url+='/@files';
	url+='/'+context['dirName'];

	print('uploadFile url: '+url);
	let uploadConfig=new Object();
	uploadConfig.inputElement=inputElement;
	uploadConfig.context=context;
	uploadConfig.url=url;
	uploadConfig.success=afterBaseDir;
	uploadConfig.failure=tryMakeDir;
	webdavCheck(uploadConfig);
}

function afterBaseDir(cfg){
	print('afterBaseDir');
	cfg.url+='/'+cfg.context['ID'];
	cfg.success=afterIDDir;
	cfg.failure=tryMakeDir;
	webdavCheck(cfg);
}

function afterIDDir(cfg){
	print('afterIDDir');
	if (cfg.inputElement.files.length==0){
	       	print('No files found');	
		return;
	}
	let file=cfg.inputElement.files[0];
	print('Uploading '+file.name);
	let suf=file.name.split('.').pop();
	cfg.url+='/'+cfg.context['ID']+'.'+suf;
	cfg.success=afterUpload;
	cfg.failure=onFailure;
	cfg.data=file;
	webdavPut(cfg);
}

function afterUpload(cfg){
	print('afterUpload');
}

function tryMakeDir(cfg){
	print('tryMakeDir '+cfg.url);
	cfg.failure=onFailure;
	webdavMakeDir(cfg);
}





function request(cfg,verb,data){
	print('request['+verb+'] '+cfg.url);
	let connRequest=new XMLHttpRequest();
	connRequest.addEventListener("loadend",
		function(){checkResponse(connRequest,cfg);});
	connRequest.open(verb, cfg.url);
	connRequest.send(data);
	//print('request['+verb+'] sent');
}

function checkResponse(xrq,cfg){
	//print('checkResponse: readyState '+xrq.readyState);
	//print('checkResponse: status '+xrq.status);
	if (xrq.status<400) {
		//client errors 400-499
		//server errors 500-599
		cfg.success(cfg);
		return;
	}
	cfg.status=xrq.status;
	cfg.failure(cfg);
}

function webdavMakeDir(cfg){ request(cfg,'MKCOL',null);}
function webdavCheck(cfg) { request(cfg,'GET',null);}
function webdavPut(cfg) { request(cfg,'PUT',cfg.data);}


function onFailure(cfg){
	print('request failed with status='+cfg.status);
}