﻿// Copyright 1998,1999 Macromedia, Inc. All rights reserved.

// Localization strings
var TRACK_system_not_found = "Tracking system not found.";

//Constructs an Interaction
function MM_interaction(theSelf,
               theJudgeOnSel, theAllowMultiSel, theAllThatApply, theUnknownIsCorrect,
               theDisabled, theTriesLimit, theTimeLimit,
               theTrackIntId, theTrackObjectiveId,
               theTrackQType, theTrackWeight, theKTrack)
{
	// properties
	this.judgeOnSel = theJudgeOnSel;
	this.allowMultiSel = theAllowMultiSel;
	this.allThatApply = theAllThatApply;
	this.unknownIsCorrect = theUnknownIsCorrect;
	this.disabled = theDisabled;
	this.tries = 0;
	this.triesLimit = (theTriesLimit) ? theTriesLimit : 0;
	this.triesAtLimit = false;
	this.time = 0;
	this.timeLeft = 0;
	this.timeLimit = (theTimeLimit) ? theTimeLimit : 0;
	this.timeAtLimit = false;
	this.trackIntId = theTrackIntId;
	this.trackObjectiveId = theTrackObjectiveId;
	this.trackQType = theTrackQType;
	this.trackWeight = theTrackWeight;
	this.knowledgeTrack = theKTrack;
	this.totalElems = 0;
	this.possCorrect = 0;
	this.possIncorrect = 0;
	this.knownResponse = false;
	this.totalCorrect = 0;
	this.totalIncorrect = 0;
	this.correct = false;
	this.score = 0;

	this._self = theSelf;
	this._timeStart = 0;
	this._timerID = 0;

	this.browserIsNS = (navigator.appName.indexOf('Netscape') != -1);
	this.browserIsIE = (navigator.appName.indexOf('Microsoft') != -1);
	this.osIsWindows = (navigator.appVersion.indexOf('Win') != -1);
	this.osIsMac = (navigator.appVersion.indexOf('Mac') != -1); // ????
	this.browserVersion = parseFloat(navigator.appVersion);

	this.e = new Array();
	this.b = new Array();

	// methods
	this.init = MM_intInit;
	this.reset = MM_intReset;
	this.resetElems = MM_intResetElems;
	this.enable = MM_intEnable;
	this.disable = MM_intDisable;
	this.setDisabled = MM_intSetDisabled;
	this.update = MM_intUpdate;
	this.add = MM_intAdd;
	this.setTries = MM_intSetTries;
	this.setTriesLimit = MM_intSetTriesLimit;
	this.setTime = MM_intSetTime;
	this.setTimeLimit = MM_intSetTimeLimit;
	this.track = MM_intTrack
	this.getTime = MM_intGetTime;

	this.am = MM_intAm;
	this.judge = MM_intJudge;
	this.resetActionMgr = MM_intResetActionMgr;
	this.setSegmNode = MM_intSetSegmNode;
	this.getSegmNode = MM_intGetSegmNode;
	this.setSegmDisabled = MM_intSetSegmDisabled;
	this.getSegmDisabled = MM_intGetSegmDisabled;
}

//Calls the element init funtions if they exist, and then does a reset
function MM_intInit()
{
	var i, j, localPC;

	with (this)
	{
		// init elems, and set totalElems, possCorrect, possIncorrect;
		totalElems = 0;
		possCorrect = 0;
		possIncorrect = 0;
		for (i in e) if (i != 'length')
		{
			if (e[i].init != null) e[i].init();
			totalElems++;
			localPC = 0;
			for (j in e[i].c) if (j != 'length' && e[i].c[j].isCorrect != null)
				(e[i].c[j].isCorrect) ? localPC++ : possIncorrect++;
			if (e[i]._singleChoice != null && localPC > 1)
				localPC = 1;
			possCorrect += localPC;
		}
		reset();
		if (knowledgeTrack)
		{
			var frm = findcmiframe(null);
			if (frm == null)
			{
				installcmi(window); //layers in NS
				if (!CMIIsPresent())
				{
					var cmi = cmiinit(window);
					if (cmi) CMIInitialize();
					else if (!window.trackwarning)
					{
						alert(TRACK_system_not_found);
						window.trackwarning = true;
					} 
				} 
			}
			else
			{
				if (window.CMIInitialize == null) frm.installcmi(window);
				if (window.CMIInitialize != null) CMIInitialize();
				if (!CMIIsPresent() && !window.trackwarnings)
				{
					alert(TRACK_system_not_found);
					window.trackwarning = true;
				} 
			} 
		} 
	}
	window["'" + this._self + "'"] = this._self; //redeclare global on window in case inserted in layer
}

//Called to reset the interaction
function MM_intReset()
{
	with (this)
	{
		tries = 0;
		triesAtLimit = false;
		_timeStart = Math.floor((new Date()).getTime() / 1000);
		if (_timerID) clearTimeout(_timerID);
		if (!disabled && timeLimit) _timerID = setTimeout(_self + ".judge()", (timeLimit + 1) * 1000);
		time = 0;
		timeLeft = timeLimit;
		timeAtLimit = false;

		resetActionMgr();
		resetElems();
		update(true);
	}
}

//Calls the reset for the individual elements
function MM_intResetElems()
{
	with (this)
	{
		for (var i in e) if (i != 'length')
			if (e[i].reset != null) e[i].reset();
		update(true);
	}
}

//Enables the interaction
function MM_intEnable()
{
	if (this.disabled) with (this)
	{
		_timeStart = Math.floor((new Date()).getTime() / 1000) - time;
		if (timeLimit)
			_timerID = setTimeout(_self + ".judge()", Math.max(0, (timeLimit - time) + 1) * 1000);
		disabled = false;
		update(true);
		for (var i in e) if (i != 'length')
			if (e[i].enable != null) e[i].enable();
	}
}

//Disables the interaction
function MM_intDisable()
{
	if (!this.disabled) with (this)
	{
		update(true);  // update 'time'
		if (_timerID) clearTimeout(_timerID);  // clear the timer
		disabled = true;
		for (var i in e) if (i != 'length')
			if (e[i].disable != null) e[i].disable();
	}
}

//Calls the approppriate disable or enable function
function MM_intSetDisabled(theDisabled)
{
	if (theDisabled) this.disable();
	else this.enable();
}

//Update the interaction state
// Note: tries will be updated by the judge method, and time will
//       be updated by both this method and the judge method.
function MM_intUpdate(noJudge)
{
	if (!this.disabled) with (this)
	{
		knownResponse = false;
		totalCorrect = 0;
		totalIncorrect = 0;
		correct = false;
		score = 0;
		for (var i in e) if (i != 'length')
		{
			for (var j in e[i].c) if (j != 'length')
			{
				if (e[i].c[j].selected)
				{
					knownResponse = true;
					score += e[i].c[j].score;
					if (e[i].c[j].isCorrect != null)
						(e[i].c[j].isCorrect) ? totalCorrect++ : totalIncorrect++;
				} 
			} 
		}
		if (!knownResponse) correct = unknownIsCorrect;
		else if (totalIncorrect != 0) correct = false;
		else if (totalCorrect == 0) correct = null; // not judged
		else correct = (!allThatApply || totalCorrect >= possCorrect);

		time = Math.floor((new Date()).getTime() / 1000) - _timeStart;
		if (timeLimit && !timeAtLimit)
		{
			timeLeft = Math.max(0, timeLimit - time);
			timeAtLimit = (time > timeLimit);
		}

		if (judgeOnSel && !noJudge) judge();
	}
}

function MM_intSetTries(theTries)
{
	with (this)
	{
		tries = theTries;
		triesAtLimit = (triesLimit) ? (tries >= triesLimit) : false;
	}
}

function MM_intSetTriesLimit(theTriesLimit)
{
	with (this)
	{
		triesLimit = theTriesLimit;
		triesAtLimit = (triesLimit) ? (tries >= triesLimit) : false;
	}
}

function MM_intSetTime(theTime)
{
	with (this)
	{
		time = Math.max(0, theTime);
		_timeStart = Math.floor((new Date()).getTime() / 1000) - time;
		timeLeft = (timeLimit) ? Math.max(0, timeLimit - time) : timeLimit;
		timeAtLimit = (timeLimit) ? (time > timeLimit) : false;
		if (_timerID) clearTimeout(_timerID);  // clear the timer
		if (!disabled && timeLimit)
			_timerID = setTimeout(_self + ".judge()", Math.max(0, (timeLimit - time) + 1) * 1000);
	}
}

function MM_intSetTimeLimit(theTimeLimit)
{
	with (this)
	{
		if (!disabled)
			time = Math.floor((new Date()).getTime() / 1000) - _timeStart;
		timeLimit = theTimeLimit;
		timeLeft = (timeLimit) ? Math.max(0, timeLimit - time) : timeLimit;
		timeAtLimit = (timeLimit) ? (time > timeLimit) : false;
		if (_timerID) clearTimeout(_timerID);  // clear the timer
		if (!disabled && timeLimit)
			_timerID = setTimeout(_self + ".judge()", Math.max(0, (timeLimit - time) + 1) * 1000);
	}
}

function MM_intAdd(theType, A, B, C, D, E, F, G, H, I, J, K, L, M)
{
	var theObj = eval("new MM_" + theType + "(" + this._self + ", A, B, C, D, E, F, G, H, I, J, K, L, M)");
	if (theObj._isChoice != null)
		this.e[A].c[B] = theObj;
	else
		this.e[A] = theObj;
}

function MM_intTrack()
{
	var aDt = new Date();
	var curHr = aDt.getHours() + '', curMin = aDt.getMinutes() + '', curSec = aDt.getSeconds() + '';
	var curDay = aDt.getDate() + '', curMonth = aDt.getMonth() + 1 + '', curYear = aDt.getYear(), dmy;
	var lat = Math.floor(aDt.getTime() / 1000) - this._timeStart;
	var x = 3600;
	var y = 60;
	var hrs = Math.round(lat / x - lat % x / x) + '';
	var min = Math.round((lat - hrs * x) / y - (lat - hrs * x) % y / y) + '';
	var sec = Math.round(lat - hrs * x - min * y) + '';
	var sRes, cRes, res, aName, bName, isC, isNC, isSel, iType;

	if (curYear < 1900) curYear += 1900;
	if (curDay.toString().length == 1) curDay = '0' + curDay;
	if (curMonth.toString().length == 1) curMonth = '0' + curMonth;
	dmy = curDay + "/" + curMonth + "/" + curYear;

	sRes = cRes = res = aName = bName = ""

	with (this)
	{
		if (trackQType.length == 0) return;
		iType = trackQType.charAt(0).toLowerCase();
		if (allThatApply && possCorrect > 1) sRes = cRes = '{';

		for (var i in e) if (i != 'length')
		{
			for (var j in e[i].c) if (j != 'length')
			{
				isC = e[i].c[j].isCorrect;
				isSel = e[i].c[j].selected;
				if (iType == 'm')
				{
					aName = e[i].c[j]._elem._name;
					bName = e[i].c[j]._target._name;
					if (isC) cRes += aName + '.' + bName + ',';
					if (isSel) sRes += aName + '.' + bName + ',';
				}
				else if (iType == 'f')
				{
					aName = e[i].value;
					bName = MM_textDeencrypt(e[i].c[j].expectedValue);
					if (isC) cRes += bName + ',';
					if (isSel) sRes += aName + ',';
				}
				else if ((iType == 'c') || (iType == 't'))
				{
					if (e[i]._trkObj != null)
					{
						aName = e[i].c[j].expectedValue;
						x = aName.indexOf(':');
						aName = aName.substring(0, x) + '-' + aName.substring(x + 1, aName.length)
						bName = e[i].value
					}
					else if (e[i]._tick != null)
					{
						aName = e[i].c[j].expectedValue;
						if (isSel) bName = aName;
					}
					else if (e[i]._stateMask != null && totalElems == 1)
					{
						aName = e[i].expectedValue;
						bName = e[i].value;
					}
					else
						aName = bName = e[i].c[j]._name;

					if (isC) cRes += aName + ',';
					if (isSel) sRes += bName + ',';
				}
				else
				{
					aName = bName = '';
					if (isC) cRes += ',';
					if (isSel) sRes += ',';
				}

				isNC = (isC == false) && isSel;
				isC = isC && isSel;
				if (possCorrect > 1) res = res + (isC ? 'c,' : isNC ? 'w,' : isSel ? 'n,' : '');
				else if (isSel) res = isC ? 'c' : isNC ? 'w' : 'n';
			}
		}

		if (sRes.charAt(sRes.length - 1) == ',') sRes = sRes.substring(0, sRes.length - 1);
		if (cRes.charAt(cRes.length - 1) == ',') cRes = cRes.substring(0, cRes.length - 1);
		if (res.charAt(res.length - 1) == ',') res = res.substring(0, res.length - 1);
		if (allThatApply && possCorrect > 1)
		{
			sRes = sRes + '}';
			cRes = cRes + '}';
		}
		if (window.CMIIsPresent && CMIIsPresent())
		{
			if (hrs.toString().length == 1) hrs = '0' + hrs;
			if (min.toString().length == 1) min = '0' + min;
			if (sec.toString().length == 1) sec = '0' + sec;
			if (curHr.toString().length == 1) curHr = '0' + curHr;
			if (curMin.toString().length == 1) curMin = '0' + curMin;
			if (curSec.toString().length == 1) curSec = '0' + curSec;
			CMIAddInteraction(dmy, curHr + ":" + curMin + ":" + curSec,
       trackIntId, trackObjectiveId, trackQType,
       cRes, sRes, res, trackWeight, hrs + ":" + min + ":" + sec);
		}
	}
}


function MM_intGetTime()
{
	var date = new Date();
	this.time = (date.getTime() / 1000 - this._timeStart) * 1000;
	return this.time / 1000;
}


//Finds any object in either browser using recursion.
//Only pass the first argument, the name of the object to find.
//Returns a pointer the object if found, else an empty string.
//  MM_intFindObject('bar') returns the object
//  document.layers['foo'].document.layers['bar']

function MM_intFindObject(objName, parentObj)
{
	var i, tempObj = "", found = false, curObj = "";
	var NS = (navigator.appName.indexOf("Netscape") != -1);
	if (!NS && document.all) curObj = document.all[objName]; //IE4
	if (!curObj)
	{
		parentObj = (parentObj != null) ? parentObj.document : document;
		if (parentObj[objName] != null) curObj = parentObj[objName]; //at top level
		else
		{ //if in form
			if (parentObj.forms) for (i = 0; i < parentObj.forms.length; i++)
			{  //search level for form object
				if (parentObj.forms[i][objName])
				{
					curObj = parentObj.forms[i][objName];
					found = true; break;
				} 
			}
			if (!found && NS && parentObj.layers && parentObj.layers.length > 0)
			{
				parentObj = parentObj.layers;
				for (i = 0; i < parentObj.length; i++)
				{ //else search for child layers
					tempObj = MM_intFindObject(objName, parentObj[i]); //recurse
					if (tempObj) { curObj = tempObj; break; } //if found, done
				} 
			} 
		} 
	}
	return curObj;
}

//Called from within conditions to check document properties
function MM_getDocProp(theName, theProp, theType)
{
	var theObj = MM_intFindObject(theName);
	if (theObj) return eval('theObj.' + theProp);
	else return null;
}


//*********  ACTION MGR METHODS  *********

function MM_intJudge(treeRoot, curRoot, level)
{
	var i = 0, theNode, retVal = true, firstCond = true;
	level = (curRoot == null) ? 0 : level + 1;
	if (!level)
	{
		curRoot = this; //set tree to highest level
		if (this.disabled) retVal = false;
		else
		{
			this.time = Math.floor((new Date()).getTime() / 1000) - this._timeStart;
			if (this.timeLimit && !this.timeAtLimit)
			{
				this.timeLeft = Math.max(0, this.timeLimit - this.time);
				this.timeAtLimit = (this.time > this.timeLimit);
			}
			this.tries++;
			if (this.triesLimit)
				this.triesAtLimit = (this.tries >= this.triesLimit);
		} 
	} else if (treeRoot.curNode != null && curRoot == treeRoot.curNode) //if at curNode
		i = treeRoot.curIndex;  //offset by index
	for (i; (retVal && i < curRoot.b.length); i++)
	{
		theNode = curRoot.b[i];
		if (this.disabled) retVal = false;
		else if (theNode.disabled) continue;
		else if (theNode.type == "segm")
		{ //SEGMENT
			curNode = (theNode.curNode) ? theNode.curNode : theNode;
			if (curNode != 'done')
				retVal = MM_intJudge(theNode, curNode, level);   //start from that node
			else if (!level) continue;
		} else if (theNode.type == 'cond')
		{ //CONDITION
			if (eval(theNode.data))
			{
				if (!treeRoot.data) treeRoot.curNode = 'done';  //if not "auto-reset tree", set curNode
				retVal = MM_intJudge(treeRoot, theNode, level);
				if (level) break;
			} else if (firstCond)
			{
				firstCond = false;
				if (!treeRoot.data) { treeRoot.curNode = curRoot; treeRoot.curIndex = i; }
			}
		} else if (theNode.type == 'actn')
		{ //ACTION
			if (!treeRoot.data) //not auto-reset
				if (i < (curRoot.b.length - 1))
			{ //not last node
				treeRoot.curNode = curRoot; treeRoot.curIndex = i + 1; //set curNode to next node
			} else treeRoot.curNode = 'done'; //last node, clear curNode
			if (theNode.data == 'stop') retVal = false; //STOP
			else eval(theNode.data);
		} 
	}
	if (!level && curRoot != null && curRoot.knowledgeTrack) curRoot.track();
	return retVal
}


//Disables or enables ActionMgr segments. If no segment passed,
//disables *all* segments. Returns false if segment not found.

function MM_intSetSegmDisabled(segmName, disable)
{
	var i, retVal = false;
	for (i = 0; i < this.b.length; i++) //search for segment
		if (!segmName || this.b[i].name == segmName)
	{
		this.b[i].disabled = disable; retVal = true;
		if (segmName) break;
	}
	return retVal
}


//Returns the disabled flag from an ActionMgr segment.
//Returns null if segment not found.

function MM_intGetSegmDisabled(segmName)
{
	var i, retVal = null;
	for (i = 0; i < this.b.length; i++) //search for segment
		if (this.b[i].name == segmName)
	{
		retVal = this.b[i].disabled; break;
	}
	return retVal
}


function MM_intResetActionMgr(segmName)
{
	var i;
	if (segmName != null) MM_intTreeSetCurNode(segmName, null, this); //clear single curNode
	else for (i = 0; i < this.b.length; i++) this.b[i].curNode = null; //clear all curNodes
}


function MM_intSetSegmNode(segmName, condName)
{
	var i, curRoot = null;
	if (!condName) this.resetActionMgr(segmName);
	else
	{
		for (i = 0; i < this.b.length; i++) //search for segment, set as curRoot
			if (this.b[i].name == segmName) { curRoot = this.b[i]; break; }
		MM_intTreeSetCurNode(condName, null, curRoot, curRoot);
	}
}


function MM_intTreeSetCurNode(nodeName, newVal, treeRoot, curRoot)
{
	var i, theNode;
	if (curRoot == null) curRoot = treeRoot; //if first time, use treeRoot
	for (i = 0; i < curRoot.b.length; i++)
	{
		theNode = curRoot.b[i];
		if (theNode.type == "segm")
		{ //SEGMENT
			if (theNode.name == nodeName) theNode.curNode = newVal;
			else MM_intTreeSetCurNode(nodeName, newVal, theNode, theNode);
		} else if (theNode.type == 'cond')
		{ //CONDITION
			if (theNode.name == nodeName)
			{  //if name found
				treeRoot.curNode = curRoot; treeRoot.curIndex = i;
				break;
			} else MM_intTreeSetCurNode(nodeName, newVal, treeRoot, theNode);
		} 
	}
}


//Returns the current node for the given Segment, returned as:
// top of segment    - "" (empty string)
// middle of segment - condition name
// end of segment    - "done"

function MM_intGetSegmNode(segmName)
{
	var i, curNode = '';
	for (i = 0; i < this.b.length; i++) //search for segment
		if (this.b[i].name == segmName)
	{
		curNode = this.b[i].curNode;
		if (curNode == null) curNode = "";
		else if (curNode != "done") curNode = curNode.b[this.b[i].curIndex].name;
		break;
	}
	return curNode
}



//*********  ACTION MGR NODE CLASS  *********

function MM_intNode(theType, theName, theData)
{
	this.type = (theType) ? theType : '';
	this.name = (theName) ? theName : '';
	this.data = (theData) ? theData : '';
	this.b = new Array();
}


//Create Action Manager (tree)

function MM_intAm(theType, theName, theData)
{
	if (theType == "segm")
	{
		if (!this.tParent)
		{ //initialize system
			this.tParent = new Array(); //tree parent nodes
			this.tIndex = new Array();  //child indexes
			this.tIndex[0] = -1;        //set index for root depth
		}
		this.tLevel = 0;            //reset level to 0
		this.tIndex[this.tLevel]++; //increment the index
		this.b[this.tIndex[this.tLevel]] = new MM_intNode("segm", theName, theData); //create tree
		this.tParent[this.tLevel] = this.b[this.tIndex[this.tLevel]]; //put this tree root at top level
		this.tIndex[this.tLevel + 1] = -1;                              //reset child level index

	} else if (theType == "actn")
	{
		var parentNode = this.tParent[this.tLevel];    //get parent
		var levelIndex = ++this.tIndex[this.tLevel + 1]; //get index
		parentNode.b[levelIndex] = new MM_intNode('actn', theName, theData); //add action

	} else if (theType == "cond")
	{
		var parentNode = this.tParent[this.tLevel];    //get parent
		var levelIndex = ++this.tIndex[this.tLevel + 1]; //get index
		parentNode.b[levelIndex] = new MM_intNode('cond', theName, theData); //add condition

		this.tLevel++; //go down a level
		this.tParent[this.tLevel] = parentNode.b[levelIndex]; //save this node as parent
		this.tIndex[this.tLevel + 1] = -1;                      //reset child level index

	} else if (theType == "end")
	{
		this.tLevel--;  //go up a level
	}
}

