
var meters=[null,null,null,null,null,null];	// all the progress meters.
	// a maximum of 6 new jobs, progress meters are displayed in an html table.

var jobCount=0;		// tracks the number of jobs started this session by the user.
var cgiSeq=0;		// tracks calls to cgis so we can handle out-of-sequence responses.

// start a new aggregation job (if possible).  set up a new progress meter and display it
function newJob() {

        index=getFreeProgressMeterIndex();       // at most 6 jobs allowed
	if ( index >= 0 ) {
		if ( validateParams() ) {
			var meter=new progressMeter( index );
			meters[index]=meter;

			var parameters=buildJobParams();
				jobCount++;
				meter.jobCount=jobCount;
				meter.productName=prod.names[prod.selectedIndex];
				meter.fileType=getCheckedValue(document.forms['f'].elements['FILETYPE']);

			var cgiScript="startBackgroundAggregator.cgi.sh";
			var callBack=meter.jobStarted;
			
			meter.cgiCalledAwaitingResponse();	// set the initial state
			progressMeterAjaxCall( meter, cgiScript, callBack, parameters );

		}
	} else {
		report.error( "cannot start any new jobs - maximum reached" );
	}
}

// find a free index for a new progress meter.
function getFreeProgressMeterIndex() {
	var index=-1;	// indicates failure to find a free index
	for ( i=meters.length - 1; i>=0; i-- ) {
		if ( meters[i]==null ) { index=i; }
	}
	return index;
}

// shuffle all jobs down to close up any gaps in the meters[] array
function closeGaps() {
	for ( i=0; i<meters.length; i++ ) {
		//report.say( "doing meter "+i );
		if ( meters[i]==null ) { 
			//report.say( "meter "+i+" is null ", "green" );
			for ( j=i; j<meters.length - 1; j++ ) {
				if ( meters[j+1]!=null ) {
					//report.say( " shuffling meter "+j, "purple" );
					meters[j]=meters[j+1];
					meters[j+1]=null;
					meters[j].cell=document.getElementById( "job"+index );
					meters[j].redraw();
					meters[j].index=j;
					document.getElementById( "job"+(j+1) ).innerHTML="";
				}
			}
			document.getElementById( "job"+ ( meters.length - 1 ) ).innerHTML="";
		}
	}
}

// redraw all the progress meters
function redrawAll() {
	for ( i=0; i<meters.length; i++ ) {
		if ( meters[i]==null ) {
			
		} else {
			meters[i].redraw();
		}
	}
}

// **************************************************************************************
//      Ajax calls
// **************************************************************************************
// send a call to a cgi on behalf of a progress meter, using Ajax.
// make it a raw function rather than a method to save confusion.
function progressMeterAjaxCall( meter, cgiScript, callBack, parameters ) {
	// meter is the progressMeter requesting the call
	// cgiScript is the name of the executable that the cgi will call
	// callBack is the progressMeter method to use if data is successfully returned.
	//	the document that the cgi delivers is passed to this method
	// parameters is any extra parameters to pass to the cgiScript.  Must be http safe.
        var XHR=new xhr(cgiURL);
	XHR.meter=meter;
        XHR.processData=function () {
                var data=XHR.data;
		callBack( data, meter );
        }
        XHR.httpError=function () {
		report.say("http error. status: "+XHR.status );
		XHR.report();
        }
        XHR.url+='?'+runtimeEnvironment+','+cgiScript+','+parameters;
        XHR.send();
}

// **************************************************************************************
// 	progressMeter
// **************************************************************************************
function progressMeter( index ) {
	this.index=index;		// index within the meters[] array
	this.cgiSeq=0;			// tracks progress requests
	this.maxReturnedCgiSeq=0;	// tracks progress requests answered
	this.sendToggle=false;		// toggles the send activity indicator
	this.returnToggle=false;	// toggles the return activity indicator
	this.returnFailure=false;	// requests sent, but reply not received
	this.draw();
}

// this should release the progressMeter object for garbage collection..
// it also frees up the index for a new job.
progressMeter.prototype.deleteMe=function() { 
	clearInterval(this.time);	// stop the progress timer thread.
	this.cell.innerHTML="";
	this.dead=true;		// prevents the timer callback from redrawing the cell.
	meters[this.index]=null; 
	//report.say("closing gaps","red");
	closeGaps();
	redrawAll();
}

// set the meter to indicate that the cgi has been called (awaiting response)
progressMeter.prototype.cgiCalledAwaitingResponse=function() {
	this.message="Awaiting Response";
	this.redraw();
}

// set the meter to indicate that the cgi has started the aggregation job
// data is JSON, provided by the cgi script.  It contains jobId and a message
// meter is provided as an argument because `this' no longer points to the meter
//	(I think it points to the XHR, but who knows!)
progressMeter.prototype.jobStarted=function( dataJson, meter ) {
	var data;
	eval ( "data="+dataJson+";" );
	meter.jobId=data.jobId;
	meter.message="Aggregation has begun";
	meter.message=data.message;

	meter.redraw();
        meter.time=setInterval( function () { meter.checkProgress();}, 2000 ); // ~ 2.0 sec tic.
}
progressMeter.cancelJob=function( index ) {
	if ( meters[ index ] ) {
		meters[ index ].cancelJob();
	} else {
		cell=document.getElementById( "job"+index );
		cell.innerHTML="";
	}
}

progressMeter.downloadFile=function( index ) {
	meters[ index ].downloadFile();
}

progressMeter.viewData=function( index ) {
	meters[ index ].viewData();
}

progressMeter.viewErrors=function( index ) {
	meters[ index ].viewErrors();
}

// check progress
progressMeter.prototype.checkProgress=function() {
	var cgiScript="reportProgress.cgi.sh";
	var callBack=this.reportProgress;
	this.cgiSeq=cgiSeq++; // ensures that each progress request has a unique sequence number.
	var parameters=this.jobId+","+this.cgiSeq;
	this.sendToggle=!(this.sendToggle);		// toggle the activity indicator
	if ( this.maxReturnedCgiSeq < (this.cgiSeq)-4 ) {
		this.returnFailure=true;	// nothing coming back from the server.
	} else {
		this.returnFailure=false;
	}
	progressMeterAjaxCall( this, cgiScript, callBack, parameters );
	this.redraw();
}
// report progress
// meter is provided as an argument because `this' no longer points to the meter
progressMeter.prototype.reportProgress=function( dataJson, meter ) {
	// report.say( "reportProgress(): "+dataJson , "blue" );
	meter.returnToggle=!(meter.returnToggle);	// toggle the activity indicator

	if ( meter.dead ) { return; }	// the progress meter has already been cancelled.
	var data;
	eval ( "data="+dataJson+";" );
	// report.say( reportObject( data ) );

	if ( data.cgiSeq < meter.maxReturnedCgiSeq ) { return; }
		// .. ignore it, we already have more up to date information
	meter.maxReturnedCgiSeq = data.cgiSeq;

	meter.fromServer=dataJson;
	meter.message="";
	if ( data.urlCount > 0 ) {
		var progress=data.urlsComplete * 100 / data.urlCount ;
		meter.message+=progress.toFixed()+"% complete";
		if ( progress.toFixed() == 100 ) {
			meter.message=" Ready ";
			meter.ended=true;
			meter.dataReady=true;
		}
		if ( meter.fileType== "urls" && data.returnCode != -1 ) {
			meter.message=" Ready ";
			meter.dataReady=true;
			meter.ended=true;
		}
	} else {
		meter.message+="Initialising";
	}
	if ( data.returnCode == 0 && data.datafileReady == false ) {
		// no data
		meter.message=" No data was found ";
		meter.ended=true;
	}
	// lots of different ways errors might be reported...
	if ( data.hasErrors ) meter.hasErrors=true;
	if ( data.returnCode > 0 ) meter.hasErrors=true;
	if ( data.errors ) meter.hasErrors=true;
	if ( meter.hasErrors ) {
		//report.say( "error encountered on server for job# "+meter.jobCount
		//		+" - "+meter.jobId, "red");
		meter.ended=true;
		if ( data.errors ) { 
			meter.message=data.errors;
		} else {
			meter.message=" an error occured ";
		}
		//report.say( reportObject( data ) );
	} 
	if ( meter.ended ) {
		clearInterval( meter.time ) ;
	}
	meter.redraw();
}

// create the DOM object corresponding to this meter.
progressMeter.prototype.draw=function() {
	this.message="Searching...";
	this.element=document.createElement("div");
	this.element.innerHTML=this.makeHTML();
	this.redraw();
}

// display the progress meter on the page.
progressMeter.prototype.redraw=function() {
	if ( ! this.drawing ) {
		this.drawing=true;
		this.cell=document.getElementById( "job"+this.index );
		// yes, this is an ugly hack, and it should be fixed ...
		this.element.innerHTML=this.makeHTML();
		this.cell.innerHTML=this.element.innerHTML;
		this.drawing=false;
	}
}

// create the html for this element, and return it as a string.
progressMeter.prototype.makeHTML=function() {
	var html;
	if ( this.dataReady ) {
                html="<div class='progressMeterReady'>";
        } else if ( this.hasErrors ) {
		html="<div class='progressMeterProblem'>";
	} else {
		html="<div class='progressMeter'>";
	}
	html+="<div class='jobCount'>"+this.jobCount+"</div>";
	if ( ! this.cancelled ) {
		//html+=this.makeCloseButton();
		//html+=this.makeImageCloseButton( "IMOS_X-1.png" );
		html+=this.makeButton( "IMOS_X-", "progressMeter.cancelJob", 
					"closeButton" , "progressMeterImageCloseButton"  );
	}
	if ( this.jobId ) {
		html+="<div class='PMjobId'>"+this.jobId+"</div>";
	} else {
		html+="<div class='PMjobId'> ------ </div>";
	}
	html+=this.productName;
	//html+="<div class='fileType'> "+this.fileType+"</div>";
	html+="<span class='fileType'> "+this.fileType+"</span>";
	html+=" <br>";
	if ( this.dataReady ) {
		if ( this.done ) { // user has already requested download.
			//html+=this.makeButtonHtmlGrey( "Download", "progressMeter.downloadFile("+this.index+");" );
			//html+=this.makeImageButtonHtml( "IMOS_download_Grey-2.png", "progressMeter.downloadFile("+this.index+");" );
			html+=this.makeButton( "Download_button_grey-", 
					"progressMeter.downloadFile", "downloadButton"  );
		} else {
			//html+=this.makeButtonHtml( "Download", "progressMeter.downloadFile("+this.index+");" );
			//html+=this.makeImageButtonHtml( "IMOS_download-2.png", "progressMeter.downloadFile("+this.index+");" );
			html+=this.makeButton( "Download_button_blue-", 
					"progressMeter.downloadFile", "downloadButton"  );
		}
		//html+=this.makeButtonHtml( "View", "progressMeter.viewData("+this.index+");" );
		//html+=this.makeImageButtonHtml( "IMOS_View-2.png", "progressMeter.viewData("+this.index+");" );
		html+=this.makeButton( "View_button_blue-", 
					"progressMeter.viewData", "viewButton"  );
	}
	if ( this.hasErrors ) {
		html+=this.makeButton( "ViewErrors_button_red-",
					"progressMeter.viewErrors", "viewErrorsButton"  );
	}
	if ( ! this.dataReady ) {
		html += "<div class='toggles'>";
		if ( this.sendToggle ) {
			// html += "<img src='images/activity_green.png'>";
			html += this.toggleActivity( "green" );
		} else {
			// html += "<img src='images/activity_black.png'>";
			html += this.toggleActivity( "black" );
		}
		if ( this.returnFailure ) {
			// html += "<img src='images/activity_red.png'>";
			html += this.toggleActivity( "red" );
		} else {
			if ( this.returnToggle ) {
				// html += "<img src='images/activity_green.png'>";
				html += this.toggleActivity( "green" );
			} else {
				// html += "<img src='images/activity_black.png'>";
				html += this.toggleActivity( "black" );
			}
		}
		html += "</div>";
	}
	// message needs to come AFTER the buttons so they have something to float against.
	html+=this.message;
	html+="<br>";
//	html+="<hr>"+this.fromServer;
	html+="</div>";
	return html;
}

// start the timer, to do regular progress updates
progressMeter.prototype.startTimer=function() {
	this.message="making progress!";
	this.draw();
}

// cancel the job
// ie, kill the job if it's running.  delete data files.  remove progress meter.
progressMeter.prototype.cancelJob=function() {
	clearInterval( this.time ) ;	// stop the progress reports.
	var cgiScript="cancelJob.cgi.sh";
	var callBack=this.removeMeter;
	var parameters=this.jobId;
	progressMeterAjaxCall( this, cgiScript, callBack, parameters );
	this.message="Cancelling Job";
	this.cancelled=true;
	this.redraw();
	report.clear();	// may have a message about too many jobs
}

// view the data in a new window
progressMeter.prototype.viewData=function() {
	var cgiScript="webvisualiser.cgi.sh";
	var parameters=this.jobId;
	var url=cgiURL+'?'+runtimeEnvironment+','+cgiScript+','+parameters;
	window.open( url );
}
// view the error file in a new window
progressMeter.prototype.viewErrors=function() {
	var cgiScript="viewerrors.cgi.sh";
	var parameters=this.jobId;
	var url=cgiURL+'?'+runtimeEnvironment+','+cgiScript+','+parameters;
	window.open( url );
}
// download the data file
progressMeter.prototype.downloadFile=function() {
	this.message="Ready";
	this.done=true;	// download requested
	this.redraw();
	var cgiScript="downloadFile.cgi.sh";
	var parameters=this.jobId;
	var url=cgiURL+'?'+runtimeEnvironment+','+cgiScript+','+parameters;
	window.open( url );
}


// remove the meter (in response to a 'cancel job' cgi call)
progressMeter.prototype.removeMeter=function( dataJson, meter ) {
        var data;
        //eval ( "data="+dataJson+";" );
		// .. might want to do some error checking?
	meter.deleteMe();
}

// remove the datafiles
progressMeter.prototype.removeData=function() {
	this.cancelJob();	// the effect is the same.
	// ie, kill the job if it's running.  delete data files.  remove progress meter.
}


// return html for a clickable button
// text is the test for the button to display.  callback is the text for the onclick property.
// be careful not to include any quotes inside either piece of text.
progressMeter.prototype.makeButtonHtml=function( text, callback ) {
	var html='<span onclick="'+callback+'" class="progressMeterButton">'+text+'</span>';
	return html;
}
progressMeter.prototype.makeButtonHtmlGrey=function( text, callback ) {
	var html='<span onclick="'+callback+'" class="progressMeterButtonGrey">'+text+'</span>';
	return html;
}
progressMeter.prototype.makeCloseButton=function( ) {
	var callback="progressMeter.cancelJob("+this.index+");";
	var html='<span onclick="'+callback+'" class="progressMeterCloseButton"> X </span>';
	return html;
}
progressMeter.prototype.makeImageButtonHtml=function( imageName, callback ) {
	var html='<span onclick="'+callback+'" >'+
			'<img class="progressMeterImageButton" src="images/'+imageName+'">'+
			'</span>';
	return html;
}
progressMeter.prototype.makeImageCloseButton=function( imageName ) {
	var callback="progressMeter.cancelJob("+this.index+");";
	var html='<span onclick="'+callback+'" >'+
			'<img class="progressMeterImageCloseButton" src="images/'+imageName+'">'+
			'</span>';
	return html;
}


// this function replaces all the various `make*Button*Html' functions:
// returns a string of html.
// the callback function must take a meter index as its only parameter.
// buttonType is text used to create the image element id (for mouseover)
// elementClass is the image element css class. (optional)
progressMeter.prototype.makeButton=function( imgprefix, callbackFn, buttonType, elementClass ) {
	var buttonId=buttonType+this.index;

	var html='<a href="#" onclick="'+callbackFn+'('+this.index+');return false;">';
	html += '<img id="'+buttonId+'" src="images/'+imgprefix+'1.png" ';
        html += '  onmouseout="makeMyImg('+"'"+buttonId+"','images/"+imgprefix+"'"+',1)" ';
        html += '  onmouseup="makeMyImg('+"'"+buttonId+"','images/"+imgprefix+"'"+',1)" ';
        html += '  onmouseover="makeMyImg('+"'"+buttonId+"','images/"+imgprefix+"'"+',2)" ';
        html += '  onmousedown="makeMyImg('+"'"+buttonId+"','images/"+imgprefix+"'"+',3)" ';
	if ( elementClass ) {
		html += '  class="'+elementClass+'" ';
	} else {
		html += '  class="progressMeterImageButton" ';
	}
  	html += '></a> ';

	return html;
}


// make visible a toggle "image" to represent status activity.
// returns a snippet of html
progressMeter.prototype.toggleActivity=function( colourClass ) {
	return "<span class='toggle"+colourClass+"'>*</span>";
}

