function Communications(){

	//internal state data
	this.IntervalFunctionID = "";
	this.FinalExitCalls = 0;
	this.FailedSubmissions = 0;
	this.Disabled = false;				//when disabled, this class does not communicate with the server (used when there is an AICC course launched)
	
	//functions
	this.StartPostDataProcess = Communications_StartPostDataProcess;
	this.KillPostDataProcess = Communications_KillPostDataProcess;
	
	this.SaveData = Communications_SaveData;
	this.SaveDataNow = Communications_SaveDataNow;
	this.SaveDataOnExit = Communications_SaveDataOnExit;
	this.SaveDebugLog = Communications_SaveDebugLog;
	
	this.SendDataToServer = Communications_SendDataToServer;
	this.ProcessReadyStateChange = Communications_ProcessReadyStateChange;
	this.CheckServerResponse = Communications_CheckServerResponse;
	
	this.CallFailed = Communications_CallFailed;
	
	this.Disable = Communications_Disable;
}


function Communications_StartPostDataProcess(){
	
	if (this.Disabled) {return;}
	
	Control.WriteDetailedLog("Communications_StartPostDataProcess");
	this.IntervalFunctionID = window.setInterval("Control.Comm.SaveData(false);", RegistrationToDeliver.Package.Properties.CommCommitFrequency);
}

function Communications_KillPostDataProcess(){
	
	if (this.Disabled) {return;}

	Control.WriteDetailedLog("Communications_KillPostDataProcess");
	if (this.IntervalFunctionID !== ""){
		window.clearInterval(this.IntervalFunctionID);
		this.IntervalFunctionID = "";
	}
}

function Communications_SaveData(synchronous, isFinalExit){
	
	if (this.Disabled) {return;}
	
	Control.WriteDetailedLog("Communications Save Data, synchronous=" + synchronous + ", isExit=" + isFinalExit);
	
	if (isFinalExit !== true){
	
		if (Control.IsThereDirtyData()) {
		
			Control.WriteDetailedLog("Found Dirty Data to Save");
			
			Control.MarkDirtyDataPosted();
			var stateXml = Control.GetXmlForDirtyData();
			
			this.SendDataToServer(synchronous, stateXml);
		}
	} else {
	
		this.FinalExitCalls++;

		// Always post data back to server if this is the first time we've done a "final" exit.  In most cases this will be
		// it.  There are a small number of cases where the SCO will call terminate afterwards and trigger a second final exit.
		// In that case, only post data back if there is dirty data	
		if (this.FinalExitCalls == 1 || Control.IsThereDirtyData()) {
		
			Control.WriteDetailedLog("About to save final data upon player exit, final exit calls = " + this.FinalExitCalls);
		
			Control.MarkDirtyDataPosted();
			
			var stateXml = Control.GetXmlForDirtyData();
			
			this.SendDataToServer(synchronous, stateXml, true);
		}
	}
}

function Communications_SaveDataNow(endPeriodicCommit){
	
	if (this.Disabled) {return;}
	
	//kill the interval call so we don't get into timing issues with this function being called simultaneously
	
	Control.WriteDetailedLog("Communications_SaveDataNow");
	
	this.KillPostDataProcess();

	this.SaveData(true);

	if (!endPeriodicCommit){
		this.StartPostDataProcess();
	}
}

function Communications_SaveDataOnExit(){
	
	if (this.Disabled) {return;}
	
	//kill the interval call so we don't get into timing issues with this function being called simultaneously
	
	Control.WriteDetailedLog("Communications_SaveDataOnExit");
	
	this.KillPostDataProcess();

	this.SaveData(true, true);
}

function Communications_SaveDebugLog(debugData, isTextMap){
   
	if (!debugData && Debug.log.root.childNodes.length == 0) {
	    return;
	}
   
    var successMethod = function() {
        //alert("save succeeded");
    }
    
    var failureMethod = function() {
       // alert("ERROR: save failed");
       //Control.WriteDetailedLog("In SaveDebugLog, save FAILED");
    }
    
    if (!debugData) {
    	debugData = Debug.log.dom;
	    Debug.Clear();
    }
	
	var postUrl = DEBUG_LOG_PERSIST_PAGE;
	if (isTextMap) {
	    postUrl += "&isTextMap=true";   
	}
	
	// Use the ContentLoader utility class so we can have overlapping calls that utlize xmlhttp.
    new net.ContentLoader(postUrl, successMethod, failureMethod, "POST", debugData);
}


function Communications_SendDataToServer(synchronous, strXmlPostData, isExit){
	
	if (this.Disabled) {return true;}
	
	if (isExit) {
		var resultPostbackPage= MergeQueryStringParameters(SCORM_RESULTS_PAGE, "isExitScormPlayer=true");
	} else {
		var resultPostbackPage = SCORM_RESULTS_PAGE;
	}
	
	var serverResponse;
	
	//make sure synchronous is a valid boolean (use the NOT operator twice)
	synchronous = !!synchronous
	
	Control.WriteDetailedLog("In SendDataToServer, synchronous=" + synchronous + ", strPostData=" + strXmlPostData);
	
	// Deliberately moved open() above onreadystatechange assignment due to IE bug which otherwise stops handler from working on subsequent calls
	objXmlHttp.open("POST", resultPostbackPage, (! synchronous));
	if ( ! synchronous) {
		objXmlHttp.onreadystatechange =  function() {Control.Comm.ProcessReadyStateChange();};		//works in ie and firefox
	}	
	objXmlHttp.setRequestHeader('Content-Type','text/xml');
	objXmlHttp.send(strXmlPostData);
	
	if (synchronous === true){ 

		if (objXmlHttp.readyState != 4){
			Control.WriteDetailedLogError("ERROR - Server side error occurred when saving state data, readyState not completed. Ready state=" + objXmlHttp.readyState);
			
			var canTryAgain = this.CallFailed();
			if (canTryAgain){
				this.SaveData(true);
			}
			return false;
		}
		if (objXmlHttp.status != 200) {
			Control.WriteDetailedLogError("ERROR - Server side error occurred when saving state data, HTTP response not 200 (OK). " + objXmlHttp.responseText);
			var canTryAgain = this.CallFailed();
			if (canTryAgain){
				this.SaveData(true);
			}
			return false;
		}

		serverResponse = objXmlHttp.responseText;
			
		Control.WriteDetailedLog("Server Responded Synchronously With:" + serverResponse);

		return this.CheckServerResponse(serverResponse, true);
	}
	
	return true;
}


function Communications_ProcessReadyStateChange(){
	if (this.Disabled) {return;}
	
	Control.WriteDetailedLog("Communications_ProcessReadyStateChange - " + objXmlHttp.readyState);
	
	if (objXmlHttp.readyState != 4){
		return;
	}
	
	if (objXmlHttp.status != 200) {
		Control.WriteDetailedLogError("ERROR - Server side error occurred when saving state data, HTTP response not 200 (OK). " + objXmlHttp.responseText);
		this.CallFailed();
		return;
	}

	serverResponse = objXmlHttp.responseText;
		
	Control.WriteDetailedLog("Server Responded Asynchronously With:" + serverResponse);

	this.CheckServerResponse(serverResponse, false);	
}

function Communications_CheckServerResponse(serverResponse, retryFailuresImmediately){
	
	if (this.Disabled) {return true;}
	
	/* 
	Expected response format
	<?xml version="1.0"?>
	<lmsresponse>
	<error present="true/false">
	<source>error source</source>
	<description>error description</description>
	</error>
	</lmsresponse>
	*/
		
	var success;
	
	serverResponse = String(serverResponse);
	
	var REG_EX_ERROR_NODE = /\<error present\=\"(true|false)\"\>/;
	
	var matchParts = serverResponse.match(REG_EX_ERROR_NODE);
	
	if (matchParts === null || matchParts.length != 2){
		Control.WriteDetailedLogError("ERROR - Invalid server response received from the LMS.");
		success = false;
	}
	else{
		var callSucceeded = (matchParts[1] == "false");
		if (callSucceeded === false){
			Control.WriteDetailedLogError("ERROR - LMS was unable to successfully save state date, see the LMS response for specific error information. Server Response=" + serverResponse);
			success = false;
		}
		else{
			success = true;
		}
	}
	
	if (success === false){
		var canTryAgain = this.CallFailed();
		
		if (retryFailuresImmediately && canTryAgain){
			this.SaveData(true);
		}
	}
	else{
		this.FailedSubmissions = 0;
		Control.MarkPostedDataClean();
	}
	
	return success;
}

function Communications_CallFailed(){
	
	if (this.Disabled) {return false;}
	
	this.FailedSubmissions++;
	Control.MarkPostedDataDirty();
	
	Control.WriteDetailedLog("Communications Call Failed, Failed Submissions=" + this.FailedSubmissions);
	
	if (this.FailedSubmissions > RegistrationToDeliver.Package.Properties.CommMaxFailedSubmissions){
		this.KillPostDataProcess();
		Control.DisplayError("A fatal error has occurred, communication with the server has been lost.");
		return false;
	}
	
	return true;
}

function Communications_Disable(){
	this.Disabled = true;	
}
