//
// SCO Support functions
//
// The following functions support SCO to LMS communication via the API Adapter
//
// Author: Gavin Arndt
// Date:   July 2002
// Copyright(c) 2002 Intuition Publishing
//
//  

// Constants for Assessments
var ASSESSMENT_EVALUATE = 1;
var ASSESSMENT_PRACTICE = 2;
var ASSESSMENT_EXAM = 3;
var ASSESSMENT_SGA = 4;

var QUESTION_NOT_ATTEMPTED = 1;
var QUESTION_NOT_ANSWERED = 2;
var QUESTION_CORRECT = 3;
var QUESTION_PARTIALLY_CORRECT = 4;
var QUESTION_INCORRECT = 5;

// Debug flag, set to true to display debug alerts
var DEBUG = false;
  
// Define the API handle
var LMSAPI = null;

// Define time tracking variables
var m_SCOProvidesTiming = false;
var startTime = 0; // Holds the time when the timer was started if set to 0 means not currently tracking time
var autoCommitTimerID = null; // used to store the ID returned from setInterval when we setup the auto commit timer
var timeCapTimerID = null; // used to store the ID returned from setTimeout when we setup the time cap timer
var sessionTime = 0; // Holds the cumulative session time in seconds. If time cap is off, this will be the time the SCO is open.

// Set to true when LMSInitialize() has been called
// Set to false when LMSFinish() is called
var initialised = false;

// Define assessment scoring and tracking variables
var m_marks = 0;			// current mark allocated for assessment
var m_questionAnsweredCount = 0;	// No of inputs answered to date
var m_assessment; // Set to output of CreateAssessment

// Define cmi.core.exit status
//	possible values: logout, time-out, suspend or an empty-string
var exitState = "";	//default logout state

// If there's a timecap, keep track of whether it has been called or not
var timeCapCalled = false;

// Locate the API
// FLAT_CHANGE
//LMSAPI = getAPI();

function startDebug() {
	LMSAPI.debug.push()
}

function stopDebug() {
	LMSAPI.debug.pop()
}

function debug(parameter) {
	LMSAPI.debug.dump(parameter)
}

//  
// Function: SCOGetIconForMenuCollection(pageList)
// Parameters: 
//    win - current window
//    pageList – A list of all subpage numbers that make up a menu collection, each separated by '|'
//
// Returns: URL of image to show
//
// Description: Checks if all, some, or none of the sub pages in a menu collection have been visited 
function SCOGetIconForMenuCollection(pageList) {
var sRecordedPages = "";
var sPages = "";
var a_PageList;
var i = 0;
var iCount = 0;

   if (initialised) {
	sRecordedPages = jftrim(LMSAPI.LMSGetValue("cmi.suspend_data"));
	
	// If it's blank just return 0
	if (sRecordedPages + "" == "undefined" || sRecordedPages + "" == "") {
		return "../interface_images/generic/status_green.gif";
	}

	// Get bits only by chopping off '%_Data:' part
	sPages = sRecordedPages.substr(7);
	
	if (pageList.indexOf("|") != -1) {
		a_PageList = pageList.split("|");

		// Check how many pages have been visited
		for (i=0; i<a_PageList.length; i++) {
			if (sPages.substr(a_PageList[i]-1, 1) == "1") {
				iCount++;
			}
		}
		if (iCount == a_PageList.length) {
			return "../interface_images/generic/intellim_red.gif";
		} else if (iCount == 0) {
			return "../interface_images/generic/intellim_grn.gif";
		} else {
			return "../interface_images/generic/intellim_amb.gif";
		}
	} else {
		// Only one page in list 
		if (sPages.substr(pageList-1, 1) == "0") {
			return "../interface_images/generic/intellim_grn.gif";
		} else {
			return "../interface_images/generic/intellim_red.gif";
		}
	}
    }

    return "../interface_images/generic/intellim_grn.gif";
}

//  
// Function: SCOGetIconForMenuSubPage(pageNum)
// Parameters: 
//    pageNum – The number of the page being checked
//
// Returns: URL of image to show 
//
// Description: Checks if user has visited a page and returns appropriate image
function SCOGetIconForMenuSubPage(pageNum) {
var NOT_VISITED = 0;
var VISITED = 1;
var sRecordedPages = "";
var sPages = "";
var sPage = "";

    if (initialised) {
	sRecordedPages = jftrim(LMSAPI.LMSGetValue("cmi.suspend_data"));
	
	// If it's blank just return 0
	if (sRecordedPages + "" == "undefined" || sRecordedPages + "" == "") {
		return "../interface_images/generic/intellim_page.gif";
	}

	// Get bits only by chopping off '%_Data:' part
	sPages = sRecordedPages.substr(7);
	sPage = sPages.substr(pageNum-1, 1);

	if (sPage == '0') {
		return "../interface_images/generic/intellim_page.gif";
	} else {
		return "../interface_images/generic/intellim_page_tick.gif";
	}
    }
	
    return "../interface_images/generic/intellim_page.gif";
}

//  
// Function: SCORecordPercentComplete()
// Parameters: 
//    pageNum – The number of the page being recorded
//    totalPages – The total number of pages to be recorded
//
// Returns: True or false
//
// Description: Records a page that a user visits 
function SCORecordPercentComplete(pageNum, totalPages) {
var sRecordedPages = "";
var sPages = "";
var i = 0;
var sNewString = "";
var result;


    if (initialised) {
	sRecordedPages = jftrim(LMSAPI.LMSGetValue("cmi.suspend_data"));

	// Check to see if we have %_Data:
	var re = /%_Data:([01]*)/im
	var res = re.exec(sRecordedPages)	
	// If it's blank then create a string filled with '0's, to the number of totalPages
	if (res == null) {
		for (i=0; i<totalPages; i++) {
			sPages += "0";
		}
		sRecordedPages = "%_Data:" + sPages;
	} else {
		sPages = RegExp.$1;
	}
	
	sNewString = setChar(sPages, "1", pageNum);
	sNewString = "%_Data:" + sNewString;

	// Set the SCO status
	result = SCOSetValue( "cmi.suspend_data", sNewString );
	
	return result + "::" + sRecordedPages + "::" + sPages + "::" + sNewString;
    }
}

//
// Function: setChar()
// Parameters:	str - string to modify
//				char - character used to update
//				pos - position to update (1 based, not 0 based)
//
// Returns: updated string
//
// Description: 
//				
//

function setChar(str, chr, pos) {	
var preStr	
var postStr	

	preStr = str.slice(0, pos - 1);	
	postStr = str.slice(pos);	
	return preStr + chr + postStr;
}

//
// Function: SCOInitialize(scoProvidesTiming)
// Parameters: useAutoTimer - boolean value indicating if the
//		SCO should automatically time the session (normal SCOs)
//		or the SCO will provide a time itself (Assessment SCOs)
// Returns: "true" - successful initialise
//			"false" - initialise failed.
//
// Description: Initialise the SCO
//				Update the lesson status
//				Start a timer
//
function SCOInitialize(scoProvidesTiming)
{
	if (DEBUG) { startDebug(); debug("LMSInitialise()"); }
	var result = "true";

	return result;

	m_SCOProvidesTiming = scoProvidesTiming;
	
	if (LMSAPI != null)  {
		result = LMSAPI.LMSInitialize("");
	
		if (result == "false") {
			SCOError();
		} else {
			// Reset tracking variables
			marks = 0;
			maxMarks = 0;
			totalQuestions = 0;
			questionCount = 0;
			
			initialised = true;

			// Retrieve previous lesson mode
			var mode = LMSAPI.LMSGetValue("cmi.core.lesson_mode");
			    
			// Only set the lesson status if the current status is not passed,failed or completed
			var lesson_status = LMSAPI.LMSGetValue("cmi.core.lesson_status");
	
			// Set lesson status to incomplete if lesson can be scored
			if (SCOisScored("incomplete", 0)) {
				LMSAPI.LMSSetValue("cmi.core.lesson_status", "incomplete");
				}
				
			// Start the timer
			if (m_SCOProvidesTiming == false) {
				SCOStartTimer();
			}
		}
	} else {
		//Remove below as this is now taken care of by versioning pages - IR 1816
		//alert("LMS API could not be found. SCORM communication is disabled.");
		result = "false";
	}
		
	return result;
}


//
// Function: SCOFinish()
// Parameters: None
// Returns: "true" - successful finalise
//			"false" - Finalise failed.
//
// Description: Finalise the SCO
//				Stop any  timers
//				Commit any data to the LMS
//
// 
function SCOFinish()
{
	var result = "true";
	var exitState = "";

	if (initialised) {
	
		if (DEBUG) { debug("LMSFinish()"); }	
	
		// Stop the SCO timer
		if (m_SCOProvidesTiming == false) {
			SCOStopTimer();
		}

		// Set the SCO status
		result = LMSAPI.LMSSetValue("cmi.core.exit", exitState);
  
		// LMSFinish()
		if (result == "true") {
			result = LMSAPI.LMSFinish("");
		
			if (result == "true") {
				LMSAPI = null;
			} else {
				// Check for errors
// ***** getting a false on LMSFinish() from ApiAdapter, LMSCommit() has the error
				SCOError();
			}
		} else { 
			// Check for errors
			SCOError();
		}
		initialised = false;
	
	}
	
  	return result;
}


//
// Function: SCOCommit(restart_timer)
// Returns: "true" - successful finalise
//			"false" - Finalise failed.
//
// Description: Calculate session time
//				Commit data to LMS
//
function SCOCommit(restart_timer)
{
   	if (DEBUG) { debug("SCOCommit() restart_timer = " + restart_timer); }
	var result = "true";
 
	if (initialised){
		// Write the current time to the API Adapter and reset the current counter if required
		if (m_SCOProvidesTiming == false) {
			SCOWriteTimer(restart_timer);
		}

		// Commit the currently stored values to the LMS
		result = LMSAPI.LMSCommit("");
	}	
	
	return result;
}

//
// Function: SCOGetValue()
// Parameters:  element - CMI data model element
// Returns: the return from LMSGetValue()
//
// Description: Get a value from the API Adapter. 
//				This is a wrapper for LMSGetValue()
//
function SCOGetValue(element) {

	var result = ""; // empty string is the default return for a success

	if (element == "cmi.launch_data"){
		return("##SERIAL_NUMBER##");
	}
 
	if (initialised)	{
	
		result = LMSAPI.LMSGetValue(element);
	
		// Check for errors
		SCOError();
	
	}
	return result;
}


//
// Function: SCOSetValue()
// Parameters:  element - CMI data model element
//				value - value to set
// Returns: "true" - set was successful
//			"false" - set failed
//
// Description: Set a value to the API Adapter. 
//				This is a wrapper for LMSSetValue()
//
function SCOSetValue(element, value) {

	var result = "true";
 
	if (initialised)	{
	
		result = LMSAPI.LMSSetValue(element, value);
		
		if (result == "false") {
			// Check for errors
			SCOError();
			}
		}
	
	return result;
}


// Function: SCOSetLessonLocation()
// Parameters:  location - browser location object for SCO page
// Returns: "true" - success
//			"false" - fail
//
// Description: Set the lesson location
//				This is a wrapper for LMSSetValue("cmi.core.lesson_location")
//
function SCOSetLessonLocation(location) {

	var result = "false";
	var url = new String(location.href)	

	if (initialised) {
		if (url + "" != "" && url + "" != "undefined"){
			urlArray = url.split("/");

			// Get the page name from the location
			if (urlArray.length > 0)
			{
				url = new String(urlArray[urlArray.length - 1]);

				urlArray = url.split("?");
				url = new String(urlArray[0]);
			}
			else{
				url = new String(urlArray[0]);
			}
		}

		result = SCOSetValue("cmi.core.lesson_location", url+ "");
		if (DEBUG) { debug("Set lesson location: " + url); }
	}
	
	return result;

}

//  
// Function: SCODisplayResumeStudy()
// Parameters: none 
// Returns: URL for the resume link 
//
// Description: Creates resume link (Book Marking)
//
function SCODisplayResumeStudy() {
var nOffset;
var nDirStart;
var nThrowaway;
var nDirEnd;
var nUrlLen;
var sPage;
var url = "";

	if (initialised) {
		url = new String(LMSAPI.LMSGetValue("cmi.core.lesson_location"));

		if (url + "" != "" && url + "" != "undefined"){
			urlArray = url.split("/");

			// Get the page name from the location
			if (urlArray.length > 0)
			{
				url = new String(urlArray[urlArray.length - 1]);
			}
			else{
				url = new String(urlArray[0]);
			}

			// If the URL is empty or is the menu page then return nothing
			if (jftrim(url) == ""){
				url = "";
			}
			// otherwise create the framset URL
			else{
				url = url.replace("?", "&");
				url = "frameset.html?phase=communicate&page=" + url;
			}
		}
		else if (url + "" == "" || url + "" == "undefined"){
			url = "";
		}
	}
	else{
		url = "";
	}

	// Send it back...
	return url;
}

function SCOCreateAssessment(assessmentType, title, bankDefinitionArray, maximumTime, numberOfBanksToUse, randomiseBanks, groupQuestionsInBanks, submitPage, scorePage, clockDirection, backArrowEnabled, forwardArrowEnabled, statusDisplayEnabled, markForReviewEnabled, feedbackEnabled) {
	var assessment = new Assessment(assessmentType, title, bankDefinitionArray, maximumTime, numberOfBanksToUse, randomiseBanks, groupQuestionsInBanks, submitPage, scorePage, clockDirection, backArrowEnabled, forwardArrowEnabled, statusDisplayEnabled, markForReviewEnabled, feedbackEnabled);
	SCOSetAssessment(assessment);
}

function SCOSetAssessment(assessment) {
	m_assessment = assessment;
}


function SCOGetAssessment() {
	return m_assessment;
}

//
// Function: SCORecordMark()
// Parameters: marksAwarded - marks awarded for an input
// Returns: none
//
// Description: Record the marks given for the current input.
//
function SCORecordAssessmentMark(percentage) {

	var result = "true";
	
			if (SCOisScored("passed", percentage)) {
				result = SCOSetValue("cmi.core.score.raw", percentage);
				if (result == "false") {
					SCOError();
				}
			}

	if (DEBUG) { debug("Percentage:" + percentage); }
}


//
// Function: SCOisScored()
// Parameters: none
// Returns: "true" - SCO can be scored
//			"false" - SCO cannot be scored
//
// Description: Determine if this SCO can be scored
// Criteria is  - lesson mode must be "normal"
//				- lesson status must NOT be passed, failed or completed
//
function SCOisScored(newStatus, percentage) {

	var result = false;
	
	var lesson_status = SCOGetValue("cmi.core.lesson_status");
	var lesson_mode = SCOGetValue("cmi.core.lesson_mode");
	var score = SCOGetValue("cmi.core.score.raw")
	
	// If we have passed then we cannot rescore
	if (lesson_mode == "normal" && lesson_status != "passed"){
		// If we have completed and now have achieved either a pass or a fail then update
		if (lesson_status == "completed" && (newStatus == "passed" || newStatus == "failed")) {
			result = true;
		}
		// otherwise if we have failed and have now achieved a pass then update
		else if (lesson_status == "failed" && newStatus == "passed") {
			result = true;
		}
		// otherwise if we have passed or failed and the new status is less than that then
		// do nothing
		else if (lesson_status == "failed" || lesson_status == "completed"){
			result = false;
		}
		// otherwise update our status if we're not passed/failed/completed 
		else {
			result = true;
		}
	} else if (lesson_mode == "normal" && lesson_status == "passed") {
		// if we are passed, check to see if we've got a higher score and 
		// update 
		if (score < percentage) {
			result = true;
		} else {
			result = false;
		}
	}
	return result;

}

//
// Function: SCOComputeScore()
// Parameters: window - DOM handle to content window
// Returns: none
//
// Description: Called at the completion of the evaulate phase. Function will calculate
//	final score and percentage and decide the student pass/fail status. This function is 
//	called from the score page of the evaluate phase.
//
function SCOComputeScore(win, displayOutput){

	var score = 0;
	var status = "incomplete";
	var statusMessage;
	var result = "true";

	//
	// Score the SCO if the user attempted any questions in the Evaluate phase
	//
	// Retrieve a mastery score for this SCO
	var masteryString = SCOGetValue("cmi.student_data.mastery_score");
	var mastery = parseInt(masteryString);
	var scoError = SCOFind().SCOError();

	// Calculate the percentage
	// Continue if at least one input was attempted
	if (m_assessment.atLeastOneQuestionAttempted) {
		score = m_assessment.getPercentage();
	}

	if (isNaN(mastery)) {
			
		if (DEBUG) { win.document.writeln("No mastery score has been set. Setting status to 'completed'<br><br>"); }
			
		status="completed";
	} else {
		// Continue if at least one input was attempted
		if (m_assessment.atLeastOneQuestionAttempted) {
			var credit = SCOGetValue("cmi.core.credit");
	
			// Determine pass or fail based on the mastery score
			// Student will only receive a pass or fail if cmi.core.credit is set to "credit"
			if (credit == "credit") {
				if (score >= mastery) {
					status="passed";
				} else {
					status="failed";
				}
			} else {
				// If credit is not given for this SCO then the status is set to "completed"
				status="completed";
			}
			if (DEBUG) { debug("<br><br>Debug: Total questions:"+totalQuestions+" Correct:"+marks+"  Attempted:"+questionCount+" Score:"+score+"%  Mastery:"+mastery+" Status:"+status+" Credit:"+credit+"<br><br>"); }
			
		} else {
			// If no questions were attempted, so mark the SCO as "incomplete".
			// Individual pages are not tracked at this stage so we cannot tell 
			// if all the pages in the SCO were read by the student.
			status="incomplete";	
		}
	}
	
	// Only change the score and the status if the SCO can be scored.
	if (SCOisScored(status, score)) {
		statusMessage = "Status of this course is "+status+".";

		// Record the score
		result = SCOSetValue("cmi.core.score.raw", score);
	
		// Record the status 
		result = SCOSetValue( "cmi.core.lesson_status", status);
		
	} else {
		status = SCOGetValue("cmi.core.lesson_status");
		
		// Make sure status isn't blank - un-resolved issue that may occur, but only happens rarely
		if (jftrim(status) == null || jftrim(status) == "") {
			statusMessage = "";
		} else {
			statusMessage = "Your status remains "+status+".";
		}	
	}

	// Update the LMS with the score & current time

	result = SCOCommit(true);
	if (displayOutput) {
	
		//Checks if mastery score is NOT passed from the LMS to the Assessment
		if (scoError + "" == "401") {
			// Write the score and status to the HTML page
			win.document.writeln("You scored " + m_assessment.marks + " out of " + m_assessment.maxMarks + ". That's "+score+"%.&nbsp;");
		} else {
			// Write the score and status to the HTML page
			win.document.writeln("You scored " + m_assessment.marks + " out of " + m_assessment.maxMarks + ". That's "+score+"%.&nbsp;" + statusMessage);
		}
	
		
	}

}

//
// Function: DisplayCert()
// Parameters: window - DOM handle to content window
// Returns: none
//
// Description: Called at the completion of the evaulate phase. Function will calculate
//	whether the student achieved pass/fail status. If the student passed, a certificate link
//	will be displayed on the score page.
//
function DisplayCert(win, displayOutput){
	var mastery = parseInt(SCOGetValue("cmi.student_data.mastery_score"));
	if (m_assessment.getPercentage() >= mastery) {
		status = "passed";
		if (displayOutput) {
			// Write the cert link to the HTML page
			//win.document.writeln("Click here to view and print your Certificate.");
			win.document.writeln("Click <a href=\"javascript:void(0)\" onClick=\"window.open('certificate.html','zen_d','width=690,height=503,scrollbars=yes,menubar=yes')\">here</a> to view and print your Certificate.");
		}
	} else {
		status = "failed";
	}
}

//
// Function: DisplayIndexLink()
// Parameters: window - DOM handle to content window
// Returns: none
//
// Description: Called from the index page.
// 		Should bring the user to the corresponding page in the main 
//		SCO Window depending on the link chosen.
//		Should also close current window
//
function DisplayIndexLink(phasetype, pageid){
var url="";
var stringfound="";

	url = "frameset.html?phase=" + phasetype + "&page=" + pageid + ".html";
	SCOFind().frames[0].location.href = url
	window.close();
}

//
// Function: SCOStartTimer()
// Parameters: none
// Returns: none
//
// Description: Called on SCO initialise. This function will record 
//	the start time of a SCO, to be later used to calculate the total time spent 
//	using the SCO. This function will also set up a timer to trigger should the 
//	maximum time set for this SCO is reached.
//
function SCOStartTimer() {
   if (m_SCOProvidesTiming) { return }; // Shouldn't be called if SCO provides timing

   if (DEBUG) { debug("SCOStartTimer | " + AUTO_COMMIT_TIMEOUT * 60000) }
   // Get current time
   startTime = new Date().getTime();
   
   // Set maximum time allowed in SCO
   setMaxTimeAllowed();

   // Setup the AutoCommit timer
   autoCommitTimerID = setInterval("SCOCommit(true)", AUTO_COMMIT_TIMEOUT * 60000);
}


//
// Function: setMaxTimeAllowed()
// Parameters: none
// Returns: none
//
// Description: set up a timer that will call timeLimitAction() when maxTimeAllowed is exceeded
//

function setMaxTimeAllowed() {
   // Request the maximum time allowed
   var maxTime = SCOGetValue("cmi.student_data.max_time_allowed");
   
   if (maxTime != "") {
	var seconds = convertCMITimeSpanToSeconds(maxTime);

	// Only continue if the return is a number		
	if (!isNaN(seconds) && seconds != 0) { 
		setTimeout("timeLimitAction()", seconds * 1000);
		
		if (DEBUG) { debug("MaxTimeAllowed = " + seconds + " seconds. "+maxTime); }
	}
   }
}


//
// Function: timeLimitAction()
// Parameters: none
// Returns: none
//
// Description: Called when timer maxTimeAllowed is exceeded
//
function timeLimitAction() {
	// Set the cmi.core.exit status
	exitState = "time-out";

	// Find out what action to take when the time limit is passed
	var action = SCOGetValue("cmi.student_data.time_limit_action");
   	if (DEBUG) { debug("timeLimitAction(), action = " + action); }
   	
	if (DEBUG) { debug("===> timeCapCalled = " + timeCapCalled); }

	// Commit the data as we've reached a significant point
	if (!timeCapCalled){
		// restart the timer after we commit the data if we haven't reached the timecap
		if (DEBUG) { debug("===> About to call SCOCommit(true)"); }
		SCOCommit(true);
	}
	else{
		// otherwise just record the time as the timer is already stopped
		if (DEBUG) { debug("===> About to call SCOCommit(false)"); }
		SCOCommit(false);
	}

	// If action includes "message" then display a message 
	if (action == "continue,message" || action == "exit,message") {
		// stop the session timer and time outs as we are displaying a message
		if (m_SCOProvidesTiming == false) {
			SCOWriteTimer(false);
			stopTimeouts();
		}


		alert("You have reached the maximum time allowed.");
	}

	// If action includes "Exit" then close the SCO window
	if (action.indexOf("exit") != -1) {
	
		// Only close the SCO window if it does not live within an LMS frame
		if (window.opener == "undefined" || window.opener == null) {
			// if we can't find the opener redirect to a blank page
			// since we're probably in a frame
			document.location.replace("blank.html");
		}
		else {
			// otherwise we can close the window
			self.close();
		}
	}
	else{
		// Start the timers again if we're not exiting and if they have already been stopped
		//  but only if the timeCap hasn't already been activated
		if ((!timeCapCalled) && (action == "continue,message")){
			if (m_SCOProvidesTiming == false) {
				SCOWriteTimer(true);
			}
			
			if (DEBUG) { debug("===> timeCapCalled = " + timeCapCalled); }			
			
			// And the AutoCommit timer
			autoCommitTimerID = setInterval("SCOCommit(true)", AUTO_COMMIT_TIMEOUT * 60000);

			// And the time cap if we're using it
			if (USE_TIME_CAP) {
				timeCapTimerID = setTimeout("HandleTimeCap()", TIME_CAP * 60000);
			}
		}
	}
}

//
// Function: SCOWriteTimer()
// Parameters: none
// Returns: none
//
// Description: Writes the session time to the API adapter. Does not reset the timer.
//
function SCOWriteTimer(restart_timer) {
   if (m_SCOProvidesTiming) { return }; // Shouldn't be called if SCO provides timing

   if (DEBUG) { debug("SCOWriteTimer() : restart_timer = " + restart_timer); }
	
   if (startTime != 0  && startTime != "") {
      var currentTime = new Date().getTime();
      var elapsedSeconds = ((currentTime - startTime) / 1000);
      sessionTime += elapsedSeconds;
   }

   var formattedTime = convertSecondsToCMITimeSpan(sessionTime);

   if (DEBUG) {	debug("Session Time:"+formattedTime); }
		
   var result = SCOSetValue( "cmi.core.session_time", formattedTime );
	
   // If required then restart timer
   if (restart_timer) {
	startTime = new Date().getTime();
   } else {
	startTime = 0;
   }
}


//
// Function: SCOStopTimer()
// Parameters: none
// Returns: none
//
// Description: Writes the session time to the API adapter then stops the timer and autocommit / time cap timers 
//
function SCOStopTimer() {
	if (m_SCOProvidesTiming) { return }; // Shouldn't be called if SCO provides timing

   	if (DEBUG) { debug("SCOStopTimer()"); }

	SCOWriteTimer(false);

	stopTimeouts();
}

//
// Function: stopTimeouts()
// Parameters: none
// Returns: none
//
// Description: Stops the autocommit and time cap timeouts 
//
function stopTimeouts() {
	if (m_SCOProvidesTiming) { return }; // Shouldn't be called if SCO provides timing

   	if (DEBUG) { debug("stopTimeouts()"); }

	// cancel the autoCommit
	if (autoCommitTimerID != null) {
	   	if (DEBUG) { debug("stopTimeouts() : Cancelling autoCommit"); }
		clearInterval(autoCommitTimerID);
		autoCommitTimerID = null;
	}

	// cancel the time cap if used
	if (USE_TIME_CAP) {
		if (timeCapTimerID != null) {
		   	if (DEBUG) { debug("stopTimeouts() : Cancelling time cap"); }
			clearTimeout(timeCapTimerID);
			timeCapTimerID = null;
		}
	}
}


//
// Function: SCONotifyPageTurn()
// Parameters: none
// Returns: none
//
// Description: Set up the time cap timer if we're using it
//
function SCONotifyPageTurn() {
	if (m_SCOProvidesTiming) { return }; // Shouldn't be called if SCO provides timing

   	if (DEBUG) { debug("SCONotifyPageTurn()"); }

	// restart the Session timer if necessary
	if (startTime == 0) {
		// NB there may be a few milliseconds difference between the time reported in the debug log
		// and the time recorded in the variable
   		if (DEBUG) { debug("SCONotifyPageTurn() | Restarting Session timer @ " + new Date().getTime()); }
		startTime = new Date().getTime();
	}

	// restart the autoCommit timer if necessary
	if (autoCommitTimerID == null) {
   		if (DEBUG) { debug("SCONotifyPageTurn() | Restarting Auto Commit timer : " + AUTO_COMMIT_TIMEOUT * 60000); }
		autoCommitTimerID = setInterval("SCOCommit(true)", AUTO_COMMIT_TIMEOUT * 60000);
	}

	if (USE_TIME_CAP) {
		timeCapCalled = false;
		if (timeCapTimerID != null) {
		   	if (DEBUG) { debug("SCONotifyPageTurn() : Cancelling time cap"); }
			clearTimeout(timeCapTimerID);
		}
	   	if (DEBUG) { debug("SCONotifyPageTurn() : Setting time cap, time = " + (TIME_CAP * 60000)); }
		timeCapTimerID = setTimeout("HandleTimeCap()", TIME_CAP * 60000);
	}
}

//
// Function: HandleTimeCap()
// Parameters: none
// Returns: none
//
// Description: Perform the necessary actions to stop the timers and commit the data to the LMS.
// As the SCOCommit() will handle the session timer, just call stopTimeouts 
//
function HandleTimeCap() {
	if (m_SCOProvidesTiming) { return }; // Shouldn't be called if SCO provides timing

   	if (DEBUG) { debug("HandleTimeCap()"); }
	stopTimeouts();
	SCOCommit(false);
	timeCapCalled = true;
}


//
// Function: SCOSetStatus()
// Parameters: status - SCORM status can be "passed","failed","incomplete","completed", "not attempted", "browsed"
// Returns: true if successful, error code if failure
// Can return:	405 – Incorrect Data Type: If the element is supported (element must be supported by LMS since the element is mandatory) and a request attempts to invoke an LMSSetValue() with a value that is not of the correct data type.
//				401 - Not implemented error. If this element is not supported an error code is set to 401 by the LMS to indicate that the element is not supported.  NOTE element must be supported by LMS since the element is mandatory. 
//
// Description: Sets the SCO lesson status 
//
function SCOSetStatus(SCOstatus) {

	var status;
	
     if (initialised) {
	status = LMSAPI.LMSSetValue("cmi.core.lesson_status", SCOstatus);
	
	return(status);
     }
}

//
// Function: ()
// Parameters: 
// Returns:
//
// Description:
//



//
// Function: ()
// Parameters: 
// Returns:
//
// Description:
//

//
// Function: SCOError()
// Parameters: none
// Returns: error code from LMSGetLastError(), zero is returned if no error occured.
//
// Description: Error handler
//
function SCOError() {

	var result = 0;
 
	if (initialised)	{
	
		result = LMSAPI.LMSGetLastError();
		
		if (result != 0 && result != 401) { 
			var errorStr = LMSAPI.LMSGetErrorString(result);
			var diagnosticStr = LMSAPI.LMSGetDiagnostic();
			
			// Display the error
			alert("An error has occurred. Error code: "+result+". "+errorStr + " " + diagnosticStr);
		}
	}
	return result;
}



//
// Function: convertSecondsToCMITimeSpan()
// Parameters: ts - number of seconds
// Returns: CMITimeSpan string HHHH:MM:SS.SS format
//
// Description:  this function will convert seconds into hours, minutes, and seconds in
//  CMITimespan type format - HHHH:MM:SS.SS (Hours has a max of 4 digits &
//  Min of 2 digits
//
function convertSecondsToCMITimeSpan(ts)
{
   var sec = (ts % 60);

   ts -= sec;
   var tmp = (ts % 3600);  //# of seconds in the total # of minutes
   ts -= tmp;              //# of seconds in the total # of hours

   // convert seconds to conform to CMITimespan type (e.g. SS.00)
   sec = Math.round(sec*100)/100;
   
   var strSec = new String(sec);
   var strWholeSec = strSec;
   var strFractionSec = "";

   if (strSec.indexOf(".") != -1)
   {
      strWholeSec =  strSec.substring(0, strSec.indexOf("."));
      strFractionSec = strSec.substring(strSec.indexOf(".")+1, strSec.length);
   }
   
   if (strWholeSec.length < 2)
   {
      strWholeSec = "0" + strWholeSec;
   }
   strSec = strWholeSec;
   
   if (strFractionSec.length)
   {
      strSec = strSec+ "." + strFractionSec;
   }


   if ((ts % 3600) != 0 )
      var hour = 0;
   else var hour = (ts / 3600);
   if ( (tmp % 60) != 0 )
      var min = 0;
   else var min = (tmp / 60);

   if ((new String(hour)).length < 2)
      hour = "0"+hour;
   if ((new String(min)).length < 2)
      min = "0"+min;

   var result = hour+":"+min+":"+strSec;

   return result;
}





//
// Function: convertCMITimeSpanToSeconds()
// Parameters: CMITimeSpan formated time HH:MM:SS.ss
// Returns: time in seconds
//
// Description: Converts a CMITimeSPan string into seconds
//
var splitIndex = 0;
var splitArray = new Array();

function convertCMITimeSpanToSeconds(theTime) {
	var answer; 

    splitIndex = 0;
    splits(theTime,':');
	
    for (var i=splitIndex-1, j=1, answer=0; i>=0; i=i-1, j=j*60)
        answer += splitArray[i]*j - 0;
        
    return answer;    
}

// Used to convert a HHHH:MM:SS to seconds
function splits(string,text) {
	// splits string at text
    var strLength = string.length, txtLength = text.length;
    if ((strLength == 0) || (txtLength == 0)) return;

    var i = string.indexOf(text);
    if ((!i) && (text != string.substring(0,txtLength))) return;
    if (i == -1) {
        splitArray[splitIndex++] = string;
        return;
    }

    splitArray[splitIndex++] = string.substring(0,i);
    
    if (i+txtLength < strLength)
        splits(string.substring(i+txtLength,strLength),text);

    return;
}


  
//
// ADL routines to locate the API adapter
//  
var findAPITries = 0;

function findAPI(win)
{
   // Check to see if the window (win) contains the API
   // if the window (win) does not contain the API and
   // the window (win) has a parent window and the parent window  
   // is not the same as the window (win)
   while ( (win.API == null) && 
           (win.parent != null) && 
           (win.parent != win) )
   {	
      // increment the number of findAPITries
      findAPITries++;

      // Note: 7 is an arbitrary number, but should be more than sufficient
      if (findAPITries > 7) 
      {
         alert("Error finding API -- too deeply nested.");
         return null;
      }
      
      // set the variable that represents the window being 
      // being searched to be the parent of the current window
      // then search for the API again
      win = win.parent;
   }
   return win.API;
}

//  
// Function: getAPI()
// Parameters: none 
// Returns: null if API could not be located, 
//		a handle to API adapter is returned if the API was located.
//
// Description: Locate the SCORM API Adaptor
//
function getAPI()
{
   // start by looking for the API in the current window
   var theAPI = findAPI(window);

   // if the API is null (could not be found in the current window)
   // and the current window has an opener window
   if ( (theAPI == null) && 
        (window.opener != null) && 
        (typeof(window.opener) != "undefined") )
   {
      // try to find the API in the current window's opener
      theAPI = findAPI(window.opener);
   }

   // if the API is null (could not be found in the current window,
   // or the current windows opener) see if the opener of the topmost
   // window contains the api
   // and the current window has an opener window
   if ( (theAPI == null) && 
        (window.top.opener != null) && 
        (typeof(window.top.opener) != "undefined") )
   {
      // try to find the API in the current window's opener
      theAPI = findAPI(window.top.opener);
   }

   // if the API has not been found
   if (theAPI == null)
   {
      // Alert the user that the API Adapter could not be found
      //Remove below as this is now taken care of by versioning pages - IR 1816
      //alert("Unable to find an API adapter");
   }
   return theAPI;
}

// Clears white space from the parameter passed in. 
function jftrim(s) {
    if (s == null) {
        w = new String("");
    } else {
	    w = new String(s);
		if (w.length != 0) {
			while (w.substring(w.length - 1, w.length) == " ") {
				w = w.substring(0, w.length - 1);
			}
		}
	}
    return w;
}

//
// End of ADL routines 
//  
