
var HUNDREDTHS_PER_SECOND = 100;
var HUNDREDTHS_PER_MINUTE = HUNDREDTHS_PER_SECOND * 60;
var HUNDREDTHS_PER_HOUR   = HUNDREDTHS_PER_MINUTE * 60;
var HUNDREDTHS_PER_DAY    = HUNDREDTHS_PER_HOUR * 24;
var HUNDREDTHS_PER_MONTH  = HUNDREDTHS_PER_DAY * (((365 * 4) + 1) / 48);	// assumed to be an "average" month (figures a leap year every 4 years) = ((365*4) + 1) / 48 days - 30.4375 days per month
var HUNDREDTHS_PER_YEAR   = HUNDREDTHS_PER_MONTH * 12;

var REG_EX_DIGITS = /\d+/g;
var REG_EX_CHILDREN = /._children$/;
var REG_EX_COUNT = /._count$/;


function ConvertIso8601TimeToUtcAnsiSql(iso8601Time){

	var dateParts = GetParsedIso8601Time(iso8601Time);
	var date = new Date();

	if (dateParts["timezoneoffsetchar"] === "" || 
		dateParts["timezoneoffsetchar"] === null || 
		dateParts["timezoneoffsetchar"] === undefined) 
	{
		date.setFullYear(dateParts["year"]);
		// Months happen to be zero-based in JavaScript
		date.setMonth(dateParts["month"] - 1); 
		date.setDate(dateParts["day"]);
		date.setHours(dateParts["hours"]);
		date.setMinutes(dateParts["minutes"]);
		date.setSeconds(dateParts["seconds"]);
		date.setMilliseconds(dateParts["milliseconds"]);
	}
	else
	{
		date.setUTCFullYear(dateParts["year"]);
		// Months happen to be zero-based in JavaScript
		date.setUTCMonth(dateParts["month"] - 1); 
		date.setUTCDate(dateParts["day"]);
		date.setUTCHours(dateParts["hours"]);
		date.setUTCMinutes(dateParts["minutes"]);
		date.setUTCSeconds(dateParts["seconds"]);
		date.setUTCMilliseconds(dateParts["milliseconds"]);
		if (dateParts["timezoneoffsetchar"] == "-") 
		{
			date.setUTCHours(date.getUTCHours() + new Number(dateParts["offsethours"]) );
			date.setUTCMinutes(date.getUTCMinutes() + new Number(dateParts["offsetminutes"]) );
		} 
		else if (dateParts["timezoneoffsetchar"] == "+") 
		{
			date.setUTCHours(date.getUTCHours() - new Number(dateParts["offsethours"]));
			date.setUTCMinutes(date.getUTCMinutes() - new Number(dateParts["offsetminutes"]));
		}
	}
	
	var dbDateTime = date.getUTCFullYear() + "-" +	
					 // Add 1 to month because months are zero-based in JavaScript
					 ZeroPad(date.getUTCMonth() + 1, 2) + "-" +  
					 ZeroPad(date.getUTCDate(), 2) + "T" +
					 ZeroPad(date.getUTCHours(), 2) + ":" +
					 ZeroPad(date.getUTCMinutes(), 2) + ":" + 
					 ZeroPad(date.getUTCSeconds(), 2);
	
	return dbDateTime;
}

function ConvertDateToIso8601String(date) {

    var zeropad = function (num) { return ((num < 10) ? '0' : '') + num; }

    var str = "";
    str += date.getUTCFullYear();
    str += "-" + zeropad(date.getUTCMonth() + 1);
    str += "-" + zeropad(date.getUTCDate());
    str += "T" + zeropad(date.getUTCHours()) + ":" + zeropad(date.getUTCMinutes());
    var secs = Number(date.getUTCSeconds() + "." + zeropad(date.getUTCMilliseconds()).substr(0,2));
    str += ":" + zeropad(secs);  
    str += "Z";  

    return str;
}

//private
function GetParsedIso8601Time(dateStr) {

	var dateParts = new Object();
	var end;
	
	// year
	dateParts["year"] = dateStr.slice(0, 4);
	var pos = 5;
	
	// month
	end = dateStr.indexOf("-", pos);
	if (end > -1) {
		dateParts["month"] = dateStr.slice(pos, end);
		pos = end + 1;
	} else {
		dateParts["month"] = dateStr.slice(pos, dateStr.length);
		pos = dateStr.length;
	}
	
	// day
	end = dateStr.indexOf("T", pos);
	if (end > -1) {
		dateParts["day"] = dateStr.slice(pos, end);
		pos = end + 1;
	} else {
		dateParts["day"] = dateStr.slice(pos, dateStr.length);
		pos = dateStr.length;
	}
	
	// hour
	end = dateStr.indexOf(":", pos);
	if (end > -1) {
		dateParts["hours"] = dateStr.slice(pos, end);
		pos = end + 1;
	} else {
		dateParts["hours"] = dateStr.slice(pos, dateStr.length);
		pos = dateStr.length;
	}	
	
	// minute
	end = dateStr.indexOf(":", pos);
	if (end > -1) {
		dateParts["minutes"] = dateStr.slice(pos, end);
		pos = end + 1;
	} else {
		dateParts["minutes"] = dateStr.slice(pos, dateStr.length);
		pos = dateStr.length;
	}	
	
	// seconds
	end = dateStr.indexOf(".", pos);
	if (end > -1) {
		dateParts["seconds"] = dateStr.slice(pos, end);
		pos = end + 1;
	} else {
		dateParts["seconds"] = dateStr.slice(pos, dateStr.length);
		pos = dateStr.length;
	}		
	
	// millseconds
	end = dateStr.indexOf("-", pos);
	if (end == -1) {
		end = dateStr.indexOf("+", pos);
	}
	if (end == -1) {
		end = dateStr.indexOf("Z", pos);
	}
	if (end > -1) {
		dateParts["milliseconds"] = dateStr.slice(pos, end);
		pos = end;
	} else {
		dateParts["milliseconds"] = dateStr.slice(pos, dateStr.length);
		pos = dateStr.length;
	}		
	
	// Get the UTC offset, expecting null if local time, Z for UTC, or +/- for offset
	if (pos != dateStr.length) {
		dateParts["timezoneoffsetchar"] = dateStr.charAt(pos);
		pos = pos + 1;
	}
	
	// hour offset
	end = dateStr.indexOf(":", pos);
	if (end > -1) {
		dateParts["offsethours"] = dateStr.slice(pos, end);
		pos = end + 1;
	} else {
		dateParts["offsethours"] = dateStr.slice(pos, dateStr.length);
		pos = dateStr.length;
	}			
	
	// minute offset
	end = dateStr.indexOf(":", pos);
	if (end > -1) {
		dateParts["offsetminutes"] = dateStr.slice(pos, end);
		pos = end + 1;
	} else {
		dateParts["offsetminutes"] = dateStr.slice(pos, dateStr.length);
		pos = dateStr.length;
	}	
		
	return dateParts;	
	
}



function ConvertIso8601TimeSpanToCmiTimeSpan(iso8601TimeSpan){
	
	var hundredths = ConvertIso8601TimeSpanToHundredths(iso8601TimeSpan);
	var cmiRepresentation = ConvertHundredthsIntoSCORMTime(hundredths);
	
	return cmiRepresentation;
}

function ConvertCmiTimeSpanToIso8601TimeSpan(cmiTimeSpan){
	
	var hundredths = ConvertCmiTimeSpanToHundredths(cmiTimeSpan);
	var isoRepresentation = ConvertHundredthsToIso8601TimeSpan(hundredths);
	
	return isoRepresentation;
}

function ConvertCmiTimeToIso8601Time(cmiTime){
	
	//TOTEST: Unit Test ConvertCmiTimeToIso8601Time
	
	//assumes that cmiTime is a valid CmiTime (hh:mm:ss.s)
	//since CmiTime doesn't have any concept of a date, we'll assume that the current date is the best selection
	//this assumption should be ok since in SCORM 1.2, the only time CmiTime is used is for interactions which are only written once
	
	dtmNow = new Date();
	
	var year = dtmNow.getFullYear();
	var month = dtmNow.getMonth();
	var day = dtmNow.getDate();
	
	year = ZeroPad(year, 4);
	month = ZeroPad((month + 1), 2);
	day = ZeroPad(day, 2);
	
	var IsoTime = year + "-" + month + "-" + day + "T" + cmiTime;
	
	return IsoTime;
}

function ConvertCmiTimeSpanToHundredths(cmiTimeSpan){
	
	//TOTEST: unit test ConvertCmiTimeSpanToHundredths
	
	if (cmiTimeSpan === ""){
		return 0;
	}
	
	var aryParts;
	var intHours;
	var intMinutes;
	var intSeconds;
	
	var intTotalMilliSeconds;
	
	//split the string into its parts
	aryParts = cmiTimeSpan.split(":");
	
	//seperate the parts and multiply by the appropriate constant (360000 = num hundredths in an hour, etc)
	intHours   = aryParts[0];
	intMinutes = aryParts[1];
	intSeconds = aryParts[2];	//don't need to worry about hundredths b/c they are expressed as fractions of a second
		
	intTotalHundredths = (intHours * 360000) + (intMinutes * 6000) + (intSeconds * 100);
	
	//necessary because in JavaScript, some values for intSeconds (such as 2.01) will have a lot of decimal
	//places when multiplied by 1000. For instance, 2.01 turns into 2009.999999999999997.
	intTotalHundredths = Math.round(intTotalHundredths);
	
	return intTotalHundredths;
}

function ConvertIso8601TimeSpanToHundredths(strIso8601Time){
	
	//Debug.Write("In ConvertIso8601TimeSpanToHundredths, strIso8601Time=" + strIso8601Time);
	
	if (strIso8601Time === ""){
		return 0;
	}
	
	var intTotalHundredths = 0;
	
	var strNumberBuilder;
	var strCurrentCharacter;
	var blnInTimeSection;
	
	var Seconds = 0;	// 100 hundreths of a seconds
	var Minutes = 0;	// 60 seconds
	var Hours = 0;		// 60 minutes
	var Days = 0;		// 24 hours
	var Months = 0;		// assumed to be an "average" month (figures a leap year every 4 years) = ((365*4) + 1) / 48 days - 30.4375 days per month
	var Years = 0;		// assumed to be 12 "average" months
	
	strIso8601Time = new String(strIso8601Time);
	
	strNumberBuilder = "";
	strCurrentCharacter = "";
	blnInTimeSection = false;

	//start at 1 to get past the "P"
	for (var i=1; i < strIso8601Time.length; i++){
		
		strCurrentCharacter = strIso8601Time.charAt(i);
		
		if (IsIso8601SectionDelimiter(strCurrentCharacter)){
			
			switch (strCurrentCharacter.toUpperCase()){
				
				case "Y":
					Years = parseInt(strNumberBuilder, 10);
				break;
				
				case "M":
					if (blnInTimeSection){
						Minutes = parseInt(strNumberBuilder, 10);
					}
					else{
						Months = parseInt(strNumberBuilder, 10);
					}
				break;
				
				case "D":
					Days = parseInt(strNumberBuilder, 10);
				break;
				
				case "H":
					Hours = parseInt(strNumberBuilder, 10);
				break;
				
				case "S":
					Seconds = parseFloat(strNumberBuilder);
				break;
				
				case "T":
					blnInTimeSection = true;
				break;
				
			}

			strNumberBuilder = "";
		}
		else{
			strNumberBuilder += "" + strCurrentCharacter;		//use "" to keep the number as string concats instead of numeric additions
		}
		
	}
	
	intTotalHundredths = (Years * HUNDREDTHS_PER_YEAR) +
						(Months * HUNDREDTHS_PER_MONTH) + 
						(Days * HUNDREDTHS_PER_DAY) + 
						(Hours * HUNDREDTHS_PER_HOUR) + 
						(Minutes * HUNDREDTHS_PER_MINUTE) + 
						(Seconds * HUNDREDTHS_PER_SECOND);
	
	//necessary because in JavaScript, some values (such as 2.01) will have a lot of decimal
	//places when multiplied by a larger number. For instance, 2.01 turns into 2009.999999999999997.
	intTotalHundredths = Math.round(intTotalHundredths);
	
	//Debug.Write ("returning-" + intTotalHundredths);
		 
	return intTotalHundredths;
}


function IsIso8601SectionDelimiter(str){
	
	if (str.search(/[PYMDTHS]/) >=0 ){
		return true;
	}
	else{
		return false;
	}
	
}


function ConvertHundredthsToIso8601TimeSpan(totalHundredths){

	//Debug.Write("In ConvertHundredthsToIso8601 ConvertHundredthsToIso8601=" + totalHundredths);
	
	var iso8601Time = "";
	
	var HundredthsOfASecond;	//decrementing counter - work at the hundreths of a second level because that is all the precision that is required
	
	var Seconds;	// 100 hundreths of a seconds
	var Minutes;	// 60 seconds
	var Hours;		// 60 minutes
	var Days;		// 24 hours
	var Months;		// assumed to be an "average" month (figures a leap year every 4 years) = ((365*4) + 1) / 48 days - 30.4375 days per month
	var Years;		// assumed to be 12 "average" months
	
	HundredthsOfASecond = totalHundredths;
		
	Years = Math.floor(HundredthsOfASecond / HUNDREDTHS_PER_YEAR);
	HundredthsOfASecond -= (Years * HUNDREDTHS_PER_YEAR);
	
	Months = Math.floor(HundredthsOfASecond / HUNDREDTHS_PER_MONTH);
	HundredthsOfASecond -= (Months * HUNDREDTHS_PER_MONTH);
	
	Days = Math.floor(HundredthsOfASecond / HUNDREDTHS_PER_DAY);
	HundredthsOfASecond -= (Days * HUNDREDTHS_PER_DAY);
	
	Hours = Math.floor(HundredthsOfASecond / HUNDREDTHS_PER_HOUR);
	HundredthsOfASecond -= (Hours * HUNDREDTHS_PER_HOUR);
	
	Minutes = Math.floor(HundredthsOfASecond / HUNDREDTHS_PER_MINUTE);
	HundredthsOfASecond -= (Minutes * HUNDREDTHS_PER_MINUTE);
	
	Seconds = Math.floor(HundredthsOfASecond / HUNDREDTHS_PER_SECOND);
	HundredthsOfASecond -= (Seconds * HUNDREDTHS_PER_SECOND);
	
	
	if (Years > 0) {
		iso8601Time += Years + "Y";
	}
	if (iso8601Time > 0){
		ScormTime += Months + "M";
	}
	if (Days > 0){
		iso8601Time += Days + "D";
	}
	
	//check to see if we have any time before adding the "T"
	if ((HundredthsOfASecond + Seconds + Minutes + Hours) > 0 ){
		
		iso8601Time += "T";
		
		if (Hours > 0){
			iso8601Time += Hours + "H";
		}
		
		if (Minutes > 0){
			iso8601Time += Minutes + "M";
		}
		
		if ((HundredthsOfASecond + Seconds) > 0){
			iso8601Time += Seconds;
			
			if (HundredthsOfASecond > 0){
				iso8601Time += "." + HundredthsOfASecond;
			}
			
			iso8601Time += "S";
		}
		
	}
	
	
	if (iso8601Time === ""){
		iso8601Time = "T0H0M0S";
	}
	
	iso8601Time = "P" + iso8601Time;
	
	//Debug.Write("Returning-" + iso8601Time);
	
	return iso8601Time;
}



function ConvertHundredthsIntoSCORMTime(intTotalHundredths){
	
	var intHours;
	var intintMinutes;
	var intSeconds;
	var intMilliseconds;
	
	//this function used to accept milliseconds, so just massage the argument here
	//TODO: improvement - clean up this function to work natively with hundredths and be a little tighter
	intTotalMilliseconds = (intTotalHundredths * 10);
	
	var strCMITimeSpan;
		
	//extract time parts
	intMilliseconds = intTotalMilliseconds % 1000;

	intSeconds = ((intTotalMilliseconds - intMilliseconds) / 1000) % 60;

	intMinutes = ((intTotalMilliseconds - intMilliseconds - (intSeconds * 1000)) / 60000) % 60;

	intHours = (intTotalMilliseconds - intMilliseconds - (intSeconds * 1000) - (intMinutes * 60000)) / 3600000;
		
	/*
	deal with exceptional case when content used a huge amount of time and interpreted CMITimstamp 
	to allow a number of intMinutes and seconds greater than 60 i.e. 9999:99:99.99 instead of 9999:60:60:99
	note - this case is permissable under SCORM, but will be exceptionally rare
	*/

	if (intHours == 10000) 
	{
		intHours = 9999;

		intMinutes = (intTotalMilliseconds - (intHours * 3600000)) / 60000;
		if (intMinutes == 100) 
		{
			intMinutes = 99;
		}
		intMinutes = Math.floor(intMinutes);
		
		intSeconds = (intTotalMilliseconds - (intHours * 3600000) - (intMinutes * 60000)) / 1000;
		if (intSeconds == 100) 
		{
			intSeconds = 99;
		}
		intSeconds = Math.floor(intSeconds);
		
		intMilliseconds = (intTotalMilliseconds - (intHours * 3600000) - (intMinutes * 60000) - (intSeconds * 1000));

	}

	//drop the extra precision from the milliseconds
	intHundredths = Math.floor(intMilliseconds / 10);

	//put in padding 0's and concatinate to get the proper format
	strCMITimeSpan = ZeroPad(intHours, 4) + ":" + ZeroPad(intMinutes, 2) + ":" + ZeroPad(intSeconds, 2) + "." + intHundredths;
		
	//check for case where total milliseconds is greater than max supported by strCMITimeSpan
	if (intHours > 9999) 
	{
		strCMITimeSpan = "9999:99:99.99";
	}
	
	return strCMITimeSpan;
	
}




function RemoveIndiciesFromCmiElement(element){
	return element.replace(REG_EX_DIGITS, "n");
}


function ExtractIndex(strElementName){
	var strIndex = "";
	var aryMatches;
	
	//get the first ".#." from the string
	aryMatches = strElementName.match(/\.\d+\./);
	
	if (aryMatches !== null && aryMatches.length > 0){
		//drop the periods
		strIndex = aryMatches[0].replace(/\./g, "");
		
		strIndex = parseInt(strIndex, 10);
	}
	return strIndex;
}

function ExtractSecondaryIndex(strElementName){
	
	var strIndex = "";
	var aryMatches;
	
	//get the first ".#." from the string
	
	aryMatches = strElementName.match(/\.\d+\./g);
	
	if (aryMatches !== null && aryMatches.length > 1){
	
		//drop the periods
		strIndex = aryMatches[1].replace(/\./g, "");
		
		strIndex = parseInt(strIndex, 10);
	}
	
	return strIndex;
}


function TranslateDualStausToSingleStatus(completionStatus, successStatus){
	
	var returnValue = null;
	
	switch (successStatus){
			
		case (SCORM_STATUS_PASSED):
			returnValue = SCORM_STATUS_PASSED;
		break;

		case (SCORM_STATUS_FAILED):	//failed
			returnValue = SCORM_STATUS_FAILED;
		break;

		case (SCORM_STATUS_UNKNOWN):

			if (completionStatus == SCORM_STATUS_COMPLETED){
				returnValue = SCORM_STATUS_COMPLETED;
			}
			else if (completionStatus == SCORM_STATUS_INCOMPLETE){
				returnValue = SCORM_STATUS_INCOMPLETE;
			}
			else if (completionStatus == SCORM_STATUS_UNKNOWN || completionStatus == SCORM_STATUS_NOT_ATTEMPTED){
				returnValue = SCORM_STATUS_NOT_ATTEMPTED;
			}
			else if (completionStatus == SCORM_STATUS_BROWSED){
				returnValue = SCORM_STATUS_BROWSED;
			}
			
		break;
	}
	
	if (returnValue === null){
		Debug.AssertError("Invalid status combination encountered in GetValue - Success = " + successStatus + ", Completion = " + completionStatus);
		return "";	
	}
	else{
		return returnValue;
	}
}


function TranslateSingleStatusIntoSuccess(singleStatus){

	var returnValue;
	
	switch (singleStatus){
		
		case(SCORM_STATUS_PASSED):
			returnValue = SCORM_STATUS_PASSED;
		break;
		
		case(SCORM_STATUS_COMPLETED):
			returnValue = SCORM_STATUS_UNKNOWN;
		break;
		
		case(SCORM_STATUS_FAILED):
			returnValue = SCORM_STATUS_FAILED;
		break;
		
		case(SCORM_STATUS_INCOMPLETE):
			returnValue = SCORM_STATUS_UNKNOWN;
		break;
		
		case(SCORM_STATUS_BROWSED):
			returnValue = SCORM_STATUS_UNKNOWN;
		break;
		
		case(SCORM_STATUS_NOT_ATTEMPTED):
			returnValue = SCORM_STATUS_UNKNOWN;
		break;
		
		default:
			Debug.AssertError("Unrecognized single status");
	}
	
	return returnValue;
}

function TranslateSingleStatusIntoCompletion(singleStatus){
	
	var returnValue;
	
	switch (singleStatus){
		
		case(SCORM_STATUS_PASSED):
			returnValue = SCORM_STATUS_COMPLETED;
		break;
		
		case(SCORM_STATUS_COMPLETED):
			returnValue = SCORM_STATUS_COMPLETED;
		break;
		
		case(SCORM_STATUS_FAILED):
			returnValue = SCORM_STATUS_COMPLETED;
		break;
		
		case(SCORM_STATUS_INCOMPLETE):
			returnValue = SCORM_STATUS_INCOMPLETE;
		break;
		
		case(SCORM_STATUS_BROWSED):
			returnValue = SCORM_STATUS_BROWSED;
		break;
		
		case(SCORM_STATUS_NOT_ATTEMPTED):
			returnValue = SCORM_STATUS_NOT_ATTEMPTED;
		break;

		default:
			Debug.AssertError("Unrecognized single status");		
	}
	
	return returnValue;
}

function IsValidCMITimeSpan(value){
	
	//WriteToDebug("In IsValidCMITimeSpan, strValue=" + value);
		
	var regValid = /^\d?\d?\d\d:\d\d:\d\d(.\d\d?)?$/;	

	if (value.search(regValid) > -1){
		//WriteToDebug("Returning TRUE");
		return true;
	}
	else{
		//WriteToDebug("Returning FALSE");	
		return false;
	}
}

function IsValidCMIDecimal(value){
	
	//check for characters "0-9", ".", and "-" only
	if (value.search(/[^.\d-]/) > -1){
		//WriteToDebug("Returning False - character other than a digit, dash or period found");
		return false;
	}
	
	//if contains a dash, ensure it is first and that there is only 1
	if (value.search("-") > -1){
		if (value.indexOf("-", 1) > -1){
			//WriteToDebug("Returning False - dash found in the middle of the string");
			return false;
		}
	}
	
	//ensure only 1 decimal point
	if (value.indexOf(".") != value.lastIndexOf(".")){
		//WriteToDebug("Returning False - more than one decimal point found");
		return false;
	}
	
	//ensure there is at least 1 digit
	if (value.search(/\d/) < 0){
		//WriteToDebug("Returning False - no digits found");
		return false;
	}
	
	//WriteToDebug("Returning True");
	return true;
	
}

function IsValidCMIIdentifier(value){
	
	//WriteToDebug("In IsValidCMIIdentifier, value=" + value);
	
	//validate string length
	if (value.length > 255){
		//WriteToDebug("ERROR: Value is too long, length=" + value.length);
		return false;
	}
	
	//can't pass the test suite unless you prohibit empty strings from being set as well
	if (value === ""){
		//WriteToDebug("ERROR: ID cannot be empty");
		return false;
	}
	
	return true;
}

function IsValidCMISInteger(value){

	//WriteToDebug("In IsValueCMISinteger, value=" + value);

	//check for characters "0-9", and "-" only
	if (value.search(/[^\d-]/) > -1){
		//WriteToDebug("Returning False - character other than a digit or dash found");
		return false;
	}
	
	//if contains a dash, ensure it is first and that there is only 1
	if (value.search("-") > -1){
		if (value.indexOf("-", 1) > -1){
			//WriteToDebug("Returning False - dash found in the middle of the string");
			return false;
		}
	}
	
	//ensure there is at least 1 digit
	if (value.search(/\d/) < 0){
		//WriteToDebug("Returning False - no digits found");
		return false;
	}
	
	value = parseInt(value, 10);
	if (value < -32768 || value > 32768){
		//WriteToDebug("Returning False - out of range");
		return false;
	}
	
	//WriteToDebug("Returning True");
	return true;
}

function IsValidCMITime(value){
	
	//WriteToDebug("In IsValidCMITime, value=" + value);
	
	var regValid = /^\d\d:\d\d:\d\d(.\d\d?)?$/;	
	var aryParts;
	var intHours;
	var intMinutes;
	
	var arySeconds;
	var intSeconds;
	var intSecondFraction = 0;
	
	//check for proper format
	if (value.search(regValid) < 0){
		//WriteToDebug("ERROR: Incorrect format - Returning FALSE");
		return false;
	}
	
	//break the string into its parts and validate each part
	aryParts = value.split(":");
	
	intHours = aryParts[0];
	intMinutes = aryParts[1];
	
	arySeconds = aryParts[2].split(".");
	intSeconds = arySeconds[0];
	
	if (arySeconds.length > 1){
		intSecondFraction = arySeconds[1];
	}
	
	if (intHours < 0 || intHours > 23){
		//WriteToDebug("ERROR: hours out of range - " + intHours);
		return false;
	}

	if (intMinutes < 0 || intMinutes > 59){
		//WriteToDebug("ERROR: minutes out of range - " + intMinutes);
		return false;
	}
	
	if (intSeconds < 0 || intSeconds > 59){
		//WriteToDebug("ERROR: minutes out of range - " + intSeconds);
		return false;	
	}
	
	if (intSecondFraction < 0 || intSecondFraction > 99){
		//WriteToDebug("ERROR: second fraction out of range - " + intSecondFraction);
		return false;		
	}
	
	return true;
}


function IsValidCMIFeedback(interactionType, value){
	
	//be sure to check length on server
		
	//no response can be greater than 4096 characters
	if (value.length > 4096){
		//WriteToDebug("WARNING: value is greater than 4096 characters");
		return false;
	}

	if (RegistrationToDeliver.Package.Properties.ValidateInteractionResponses)
	{
		//even though things are technically supposed to be lower case, allow upper case letters to come in
		value = value.toLowerCase();
		
		switch(interactionType){
			
			case "true-false":
				//WriteToDebug("Type is true-false");
				//must start with one of these characters - 0,1,t,f
				if (value.search(/^[01tf]/) !== 0){
					return false;
				}
				break;
				
			case "choice":
			
				//WriteToDebug("Type is choice");
				//comma delimited list of single digit lower case letters or numbers surrounded by optional {}
				if (value.search(/(^(([a-z0-9])|(([a-z0-9]\,)+[a-z0-9]))$)|(^\{(([a-z0-9])|(([a-z0-9]\,)+[a-z0-9]))\}$)/) !== 0){
					return false;
				}
				break;	
				
			case "fill-in":
				//WriteToDebug("Type is fill-in");
				//any alphanumeric string up to 255 chars
				if (value.length > 255){
					return false;
				}
				break;
				
			case "numeric":
				//WriteToDebug("Type is numeric");
				if (! IsValidCMIDecimal(value)){
					return false;
				}
				break;
				
			case "likert":
				//WriteToDebug("Type is likert");
				
				//single character - can be blank
				//if ( ! ((value.length == 1 && value.search(/^[0-9a-z]$/) === 0) || (value.length === 0))){
				//	return false;
				//}			
				
				//above validation was an error, there are no restrictions on the likert format in SCORM 1.2
				
				break;
				
			case "matching":
				//WriteToDebug("Type is matching");
				if (value.search(/(^[0-9a-z]\.[0-9a-z]$)|(^([0-9a-z]\.[0-9a-z]\,)+([0-9a-z]\.[0-9a-z])$)|(^\{[0-9a-z]\.[0-9a-z]\}$)|(^\{([0-9a-z]\.[0-9a-z]\,)+([0-9a-z]\.[0-9a-z])\}$)/) !== 0){
					return false;
				}				
				break;
				
			case "performance":
				//WriteToDebug("Type is performance");
				if (value.length > 255){
					return false;
				}
				break;
				
			case "sequencing":
				//WriteToDebug("Type is sequencing");
				//comma delimited list of single digit lower case letters or numbers
				if (value.search(/(^[a-z0-9]$)|(^([a-z0-9]\,)+[a-z0-9]$)/) !== 0){
					return false;
				}			
				break;
				
			default:
				//we are unable to determine the type (possibly b/c it hasn't yet been set) so just accept anything
				//we will re-check this value when the type is set
				break;
		}
	}
	
	return true;
}


//normalizes a raw score to a scaled score between 0-1
function NormalizeRawScore(rawScore, minScore, maxScore){
	
	//raw score is required to be a valid decimal 0-100 in SCORM 1.2
	
	var normalizedScore = null;
	
	if (minScore !== null && minScore !== undefined){
		minScore = parseFloat(minScore);
	}
	
	if (maxScore !== null && maxScore !== undefined){
		maxScore = parseFloat(maxScore);
	}
	
	if (rawScore !== null && rawScore !== undefined){
		
		rawScore = parseFloat(rawScore);
		
		if (minScore !== null && minScore !== undefined &&
			maxScore !== null && maxScore !== undefined &&
			rawScore >= minScore &&
			rawScore <= maxScore){
		
			if (minScore == maxScore){
				normalizedScore = 1;
			}
			else{
				normalizedScore = ((rawScore - minScore) / (maxScore - minScore));
			}
		}
		
		else{	//no valid min and max score, so try to assume that the score is 0-100
			
			if (rawScore >= 0 && rawScore <= 100){
				normalizedScore = (rawScore / 100);
			}
		}
	}
	
	if (normalizedScore !== null){
		return RoundToPrecision(normalizedScore, 7);
	}
	else{
		return null;
	}
}



