// Copyright © 2002 Microsoft Corporation.  All rights reserved.

function CViewerObj() 
{
 		//////////////////////////////////////////////////////////////////////
		//PRIVATE VARIABLES.
		//
		//These vars are not exposed to public references of this object.
		//////////////////////////////////////////////////////////////////////
		
		var m_oXMLDOM = null;				//DOM representation of manifest
		var m_strCurrentNodeID = "";
		var m_oTimer = null;
		var m_strCurrentTOC = "";			//current toc index.
		var m_strCurrentManifest = "";
		var m_strXSLPath = "";				//path to resources.
		var m_oLMSAPI = null;

		var enum_SELECT_SINGLEITEM = 0;
		var enum_SELECT_ITEMCHILDREN = 1;
		var enum_SELECT_TOCS = 2;
		var enum_SELECT_TOC = 3;
	
		var enum_CPO_PREVIOUS = 0;
		var enum_CPO_NEXT = 1;
		var enum_CPO_SELECT = 2;
		var enum_CPO_INIT = 3;
		var enum_CPO_LESSONSTATUS = 4;
		var enum_CPO_EXITSTATUS = 5;
		
		var LMSAPI_LESSONSTATUS = 200;
		var LMSAPI_EXITSTATUS = 300;
		
		var m_bIsReady = false;
		
		//////////////////////////////////////////////////////////////////////
		//PUBLIC METHODS.
		//
		//These methods are exposed to public references of this object.
		//////////////////////////////////////////////////////////////////////
	
		
		function CViewerObj.prototype.DoNext()
		{
			var strNodeID = m_strCurrentNodeID;
			var oNewCurrentNode = this.GetNext(strNodeID);

			if (oNewCurrentNode != null)
			{
				m_strCurrentNodeID = oNewCurrentNode.documentElement.childNodes(0).getAttribute("ID");
				mf_CreateEvent(enum_CPO_NEXT, m_strCurrentNodeID);
			}

			return ( oNewCurrentNode );
		}
		
		function CViewerObj.prototype.DoPrevious()
		{
			var strNodeID = m_strCurrentNodeID;
			var oNewCurrentNode = this.GetPrevious(strNodeID);

			if (oNewCurrentNode != null)
			{
				m_strCurrentNodeID = oNewCurrentNode.documentElement.childNodes(0).getAttribute("ID");
				mf_CreateEvent(enum_CPO_PREVIOUS, m_strCurrentNodeID)
			}
			
			return ( oNewCurrentNode );
		}
		
		function CViewerObj.prototype.DoSelect(id)
		{
			if ( (id == "") || (id == null) || ("string" != typeof(id)) )
				return null;

			var oNewCurrentNode = this.GetItem(id);
			if (oNewCurrentNode != null)
			{
				m_strCurrentNodeID = id;
				if (m_oLMSAPI)
				{
					m_oLMSAPI.LRNSetScopedValue("cmi.core.entry", "resume", m_strCurrentNodeID, true);

					if (m_oTimer)
					{
						var strTimeDelta = m_oLMSAPI.LRNGetScopedValue("cmi.core.total_time", m_strCurrentNodeID, true);
						strTimeDelta = m_oTimer.CalculateNewTimeDelta(strTimeDelta);
						m_oLMSAPI.LRNSetScopedValue("cmi.core.total_time", strTimeDelta, m_strCurrentNodeID, true);
					}
				}
	
				//will need to fire event.
				mf_CreateEvent(enum_CPO_SELECT, m_strCurrentNodeID);
			}							
			return ( oNewCurrentNode );
		}
		
		function  CViewerObj.prototype.DoSelectFirst()
		{
			if (!m_oXMLDOM)	return null;
				
			var oNewCurrentNode = null;
			var oXMLCurrentNode = m_oXMLDOM.documentElement.selectSingleNode("//organization[@identifier='" + m_strCurrentTOC + "']//item[0]");
			
			if (oXMLCurrentNode)			
				oNewCurrentNode = this.DoSelect(oXMLCurrentNode.getAttribute("identifier"))
				
			return ( oNewCurrentNode );			
		}
		
			
		function CViewerObj.prototype.GetCurrent()
		{
			return ( this.GetItem(m_strCurrentNodeID) );
		}
		
	
		//node retrieval methods.
		function CViewerObj.prototype.GetNext(id)  
		{
			if (!m_oXMLDOM)	return null;
			
			var oNextNode = null;
			var oNewNode = null;
			
			//get the relative node.
			
			if(id != null)
			{
				var oNode = mf_FindItem(id);
					
				if (oNode)
					oNextNode = mf_FindNext(oNode,true);
			}
			else //id is null. return the first node.
				oNextNode = m_oXMLDOM.selectSingleNode("//organization[@identifier='" + m_strCurrentTOC + "']//item[0]");
			
			if (oNextNode)
			{
				var strID = oNextNode.getAttribute("identifier");
			
				if (strID != null)
					oNewNode = mf_TransformFromIMStoMLT(strID, enum_SELECT_SINGLEITEM);
			}
			
			return (oNewNode);
		}

		function CViewerObj.prototype.GetPrevious(id)  
		{
			if ( (id == "") || (id == null) || ("string" != typeof(id)) )
				return (null);
			
			var oPrevNode = null;
			var oNewNode = null;
			
			//get the relative node.
			var oNode = mf_FindItem(id);
			
			if (oNode)
				oPrevNode = mf_FindPrevious(oNode);
				
			if (oPrevNode)
			{
				var strID = oPrevNode.getAttribute("identifier");
				
				if (strID != null)
					oNewNode = mf_TransformFromIMStoMLT(strID, enum_SELECT_SINGLEITEM) ;
			}
			
			return (oNewNode);
		}


		function CViewerObj.prototype.GetParent(id)
		{
			if ( (id == "") || (id == null) || ("string" != typeof(id)) )
				return null;
			
			var oNewNode = null;
			
			var oParent = mf_FindParent(id);
				
			if (oParent)
			{
				var strID = oParent.getAttribute("identifier");
				
				if (strID)
					oNewNode = mf_TransformFromIMStoMLT(strID , enum_SELECT_SINGLEITEM);
			}	
			
			return (oNewNode) ;
		}


		function CViewerObj.prototype.GetItemAncestors(id)
		{
			if ( (id == "") || (id == null) || ("string" != typeof(id)) )
				return null;
			
			var strItemID = id;
			var oParent = null;
			var arrAncestors = new Array();
			var nIndex = 0;
			
			while (true)
			{
				oParent = mf_FindParent(strItemID);

				if (oParent)
				{
					switch (oParent.nodeName)
					{
						case "item":
							arrAncestors[nIndex] = oParent.getAttribute("identifier");
							break;
						case "organization":
						case "organizations":
						case "manifest":
							break;
					}

					strItemID = arrAncestors[nIndex];
				}
				else
					break;

				nIndex++;
			}
			
			// return the array in reverse order, so that the eldest ancestor is first
			return arrAncestors.reverse();
		}


		function CViewerObj.prototype.Init(strLRNXML, strXSLPath, oLMSAPI)
		{
			if ( (strLRNXML == null) || (strLRNXML == "") || "string" != typeof(strLRNXML) ) return;

			if ( (strXSLPath == null) || (strXSLPath == "") || "string" != typeof(strXSLPath) ) return;

			if ( (oLMSAPI == null) || "object" != typeof(oLMSAPI) ) return;			

			m_oXMLDOM = new ActiveXObject("Msxml2.DOMDocument.3.0");
			mf_LoadXMLData(m_oXMLDOM,strLRNXML);

			m_strXSLPath = strXSLPath;
				
			m_oLMSAPI = oLMSAPI;
				
			//get the default table of contents.
			m_strCurrentTOC = m_oXMLDOM.selectSingleNode("manifest/organizations").getAttribute("default");
				
			if (!m_strCurrentTOC)
				m_strCurrentTOC = m_oXMLDOM.selectSingleNode("manifest/organizations/organization[0]").getAttribute("identifier");

			//create timer object.
			m_oTimer = new Timer();

			m_oLMSAPI.LRNListenForEvents(mf_EventHandler);

			m_bIsReady = true;

			mf_CreateEvent(enum_CPO_INIT, null);			
		}

		function CViewerObj.prototype.SetCurrentTOC(strCurrentTOCID)
		{
			if (strCurrentTOCID)
				m_strCurrentTOC = strCurrentTOCID;
		}
		
		function CViewerObj.prototype.GetCurrentTOC()
		{
			if (!m_strCurrentTOC) return null;

			return ( mf_TransformFromIMStoMLT(m_strCurrentTOC, enum_SELECT_TOC));
		}

		function CViewerObj.prototype.GetTOCS()
		{
			return ( mf_TransformFromIMStoMLT(null, enum_SELECT_TOCS));
		}
						
		function CViewerObj.prototype.GetChildren(id)
		{
			return ( mf_TransformFromIMStoMLT(id, enum_SELECT_ITEMCHILDREN)) ;
		}
		
		function CViewerObj.prototype.GetItem(id)
		{
			if ( (id == "") || (id == null) || ("string" != typeof(id)) )
				return null;

			return ( mf_TransformFromIMStoMLT(id, enum_SELECT_SINGLEITEM));
		}

		function CViewerObj.prototype.IsReady()
		{
			return m_bIsReady;
		}
		
		function CViewerObj.prototype.GetCustomView()
		{
			if (!m_oXMLDOM)	return null;
				
			var strURL = "";
				
			//check for the existance of the node that indicates a custom UI
			var node = m_oXMLDOM.selectSingleNode("//userinterface/customviewer/@href");
			
			if (node)
				strURL =  node.text;
			
			return strURL;
		}

		function CViewerObj.prototype.GetSubmanifestCount(id)
		{
			if (m_oXMLDOM)
			{	
				var nodes = null;

				// Get the children for the root manifest if no id was specified, 
				// otherwise get the children for the specified submanifest (direct descendants only).
				if (null == id)
				{
					nodes = m_oXMLDOM.selectNodes("/manifest/manifest");
				}
				else if ( (id != "") && ("string" == typeof(id)) )
				{
					nodes = m_oXMLDOM.selectNodes("//manifest[@identifier='" + id + "']/manifest");
				}

				// return the number of nodes found
				if (nodes)
					return nodes.length;
			}
			
			return 0;
		}

		function CViewerObj.prototype.GetManifestXMLDOM(id)
		{
			if (m_oXMLDOM)
			{
				// Get the DOM for the root manifest if no id was specified, 
				// otherwise get the DOM for the specified submanifest
				if (null == id)
				{
					return m_oXMLDOM;
				}
				else if ( (id != "") && ("string" == typeof(id)) )
				{
					return m_oXMLDOM.selectSingleNode("//manifest[@identifier='" + id + "']");
				}
			}
			return null;
		}

		function CViewerObj.prototype.GetCurrentManifestXMLDOM()
		{
			if (m_oXMLDOM)
			{
				// Get current node's manifest; this may be a submanifest.
				return m_oXMLDOM.selectSingleNode("//manifest[organizations//organization//item[@identifier='" + m_strCurrentNodeID +  "']]");
			}
			return null;
		}

		function CViewerObj.prototype.GetSubmanifestIDs(id)
		{
			if (!m_oXMLDOM)	return null;

			var oXMLNodeList = null;
			var arrManifestID = null;

			// Get child manifests
			if (null == id)
			{
				oXMLNodeList = m_oXMLDOM.selectNodes("/manifest/manifest/@identifier");
			}
			else if ( (id != "") && ("string" == typeof(id)) )
			{
				oXMLNodeList = m_oXMLDOM.selectNodes("//manifest[@identifier='" + id + "']/manifest/@identifier");
			}
				
			if (oXMLNodeList)
			{
				arrManifestID = new Array();

				for (var i = 0; i < oXMLNodeList.length; i++)
					arrManifestID[i] = oXMLNodeList.item(i).text;
			}

			return arrManifestID;
		}

		function CViewerObj.prototype.GetRootManifestID()
		{
			if (!m_oXMLDOM)	return null;

			// Get the root manifest's identifier
			return m_oXMLDOM.selectSingleNode("/manifest/@identifier").text;
		}

		function CViewerObj.prototype.GetCurrentManifestID()
		{
			if (!m_oXMLDOM)	return null;

			// Get the current node's manifest identifier
			return m_oXMLDOM.selectSingleNode("//manifest[organizations//organization//item[@identifier='" + m_strCurrentNodeID +  "']]/@identifier").text;
		}

		function CViewerObj.prototype.GetManifestResourceHrefs(id)
		{
			if (!m_oXMLDOM) return null;

			var oManifestXMLBase = null;
			var oResourcesXMLBase = null;
			var oResourceNodeList = null;
			var arrManifestHrefs = null;
			
			if (null == id)
			{	
				// get the xml:base attribute for the manifest and resources
				oManifestXMLBase = m_oXMLDOM.selectSingleNode("/manifest/@xml:base");
				oResourcesXMLBase = m_oXMLDOM.selectSingleNode("/manifest/resources/@xml:base");

				// get the manifest resource nodes 
				oResourceNodeList = m_oXMLDOM.selectNodes("/manifest/resources/resource");
			}
			else if ( (id != "") && ("string" == typeof(id)) )
			{
				// get the xml:base attribute for the manifest and resources
				oManifestXMLBase = m_oXMLDOM.selectSingleNode("//manifest[@identifier='" + id + "']/@xml:base");
				oResourcesXMLBase = m_oXMLDOM.selectSingleNode("//manifest[@identifier='" + id + "']/resources/@xml:base");

				// get the manifest resource nodes 
				oResourceNodeList = m_oXMLDOM.selectNodes("//manifest[@identifier='" + id + "']/resources/resource");
			}
			
			if (oResourceNodeList)
			{
				arrManifestHrefs = new Array();

				for (var i = 0; i < oResourceNodeList.length; i++)
				{
					arrManifestHrefs[i] = "";

					// prepend the manifest xml:base value
					if (oManifestXMLBase)
						arrManifestHrefs[i] += oManifestXMLBase.text + "/";

					// prepend the resources xml:base value
					if (oResourcesXMLBase)
						arrManifestHrefs[i] += oResourcesXMLBase.text + "/";

					// get the resource xml:base value and prepend to the href
					var oResourceXMLBase = oResourceNodeList.item(i).selectSingleNode("@xml:base")
					if (oResourceXMLBase)
						arrManifestHrefs[i] += oResourceXMLBase.text + "/";

					arrManifestHrefs[i] += oResourceNodeList.item(i).getAttribute("href");
				}
			}

			return arrManifestHrefs;
		}

		function CViewerObj.prototype.GetManifestXmlBaseAttribute(id)
		{
			var strXmlBase = ""

			if (m_oXMLDOM)
			{
				var oNode = null;

				// Get the xml:base attribute for the root manifest if no id was specified, 
				// otherwise get the attribute for the specified submanifest
				if (null == id)
				{
					oNode = m_oXMLDOM.selectSingleNode("/manifest/@xml:base");
				}
				else if ( (id != "") && ("string" == typeof(id)) )
				{
					oNode = m_oXMLDOM.selectSingleNode("//manifest[@identifier='" + id + "']/@xml:base");
				}

				if (oNode)
					strXmlBase = oNode.text;
			}
			return strXmlBase;
		}

		function CViewerObj.prototype.GetManifestResourcesXmlBaseAttribute(id)
		{
			var strXmlBase = ""

			if ( (id == null) || (id == "") || ("string" != typeof(id)) ) 
				return strXmlBase;

			if (m_oXMLDOM)
			{
				var oNode = m_oXMLDOM.selectSingleNode("//manifest[@identifier='" + id + "']/resources/@xml:base");
				
				if (oNode)
					strXmlBase = oNode.text;
			}
			return strXmlBase;
		}

		function CViewerObj.prototype.GetManifestTitle(id)
		{
			var strTitle = "";
			var oNode = null;

			if (!m_oXMLDOM) return strTitle;
			
			try
			{
				if (id == null)
				{
					// try to obtain the title from the metadata
					oNode = m_oXMLDOM.selectSingleNode("manifest/metadata/imsmd:lom/imsmd:general/imsmd:title/imsmd:langstring");
					
					// if the title wasn't found in the metadata, try to get the title from the first item
					if (!oNode)
						oNode = m_oXMLDOM.selectSingleNode("manifest/organizations/organization/item[0]/title");
				}
				else
				{
					if ( (id != "") || ("string" == typeof(id)) )
					{
						// try to obtain the title from the submanifest's metadata
						oNode = m_oXMLDOM.selectSingleNode("//manifest[@identifier='" + id + "']/metadata/imsmd:lom/imsmd:general/imsmd:title/imsmd:langstring");

						// if the title wasn't found in the submanifest's metadata, try to get the title from the first item
						if (!oNode)
							oNode = m_oXMLDOM.selectSingleNode("//manifest[@identifier='" + id + "']/organizations/organization/item[0]/title");
					}
				}

				// if we found something, prepare to return it
				if (oNode)
					strTitle = oNode.text;
			}
			catch(e)
			{
			}
			return strTitle;
		}

		function CViewerObj.prototype.GetFrameByName(sName)
		{
			if ( (sName == null) || (sName == "") || ("string" != typeof(sName)) ) 
				return null;

			return ( mf_GetFrameByName(window.frames[0],sName) );
		}
		
		//////////////////////////////////////////////////////////////////////
		//PRIVATE METHODS.
		//
		//These methods are not exposed to public references of this object.
		//////////////////////////////////////////////////////////////////////

		function mf_GetFrameByName(oFrame, sName)
		{
			if (!oFrame) return null;
			
			// We found a match, so return
			if (oFrame.name && oFrame.name == sName) 
				return oFrame;
			
			var oFrameObj = null;

			// Search children
			for (var i = 0; i < oFrame.frames.length; i++)
			{
				oFrameObj = mf_GetFrameByName(oFrame.frames[i], sName);
		
				if (oFrameObj) break;
			}

			if (!oFrameObj) 
			{
				var nFrameIdx = -1;

				// Get our index within the parent frameset
				for (var i = 0; i < oFrame.parent.frames.length; i++)
				{
					if (oFrame.parent.frames[i].name == oFrame.name)
					{
						// increment the index if we aren't the last child
						if (i < oFrame.parent.frames.length)
							nFrameIdx = i + 1;
						break;
					}
				}

				// Search next sibling
				if (nFrameIdx != -1)
					oFrameObj = mf_GetFrameByName(oFrame.parent.frames[nFrameIdx], sName);
			}

			return oFrameObj;
		}
		
		function mf_CreateEvent(nType,sID)
		{
			if ( ("number" != typeof(nType)) || (null == nType) )
				return;

			if ( (sID != null) && ("string" != typeof(sID)) )
				return;
	
			try
			{
				var obj = new Object();
				obj.type = nType;
				obj.id = sID;
			
				if (m_oLMSAPI)
					m_oLMSAPI.LRNRaiseEvent(obj);		
			}
			catch(e)
			{
			}
		}
		
		function mf_FindParent(id)
		{
			if ( (id == "") || (id == null) || ("string" != typeof(id)) )
				return null;

			var oParent = null;
			var oNode = mf_FindItem(id);

			if (oNode)
			{ 
				if (oNode.parentNode.nodeName == "item")
					oParent = oNode.parentNode;

				else if (oNode.parentNode.nodeName == "organization")
				{
					var oParentItem = mf_GetItemInParentManifest(oNode.parentNode);
					if (oParentItem)
						oParent = mf_FindPrevious(oParentItem);
				}
			}
			return oParent;
		}
				
		function mf_FindItem(id)
		{
			if (! (m_oXMLDOM || ("" == id) || (null == id) || ("string" == typeof(id))) )
				return null;
				
			var oNode = m_oXMLDOM.selectSingleNode("//item[@identifier='" + id + "']");
			
			return (oNode);
		}
		
		function mf_GetItemInParentManifest(oNode)
		{
			if (!oNode) return null;

			var oParent = null;
			var strBase = oNode.getAttribute("identifier");
		
			while(true)
			{
				var oNode = oNode.selectSingleNode("ancestor(manifest)");
				if (oNode == null)
					break;
				
				oParent = oNode.selectSingleNode("/manifest/organizations//item[@identifierref='" + strBase + "']");
				if (oParent != null)
					break;
			}
			return oParent;
		}

		function mf_FindNext(oNode, bDescend)
		{
			if (!oNode || ("boolean" != typeof(bDescend)) ) return null;

			var oNext = null;

			if (bDescend && (mf_ResolveTOCNodeForNext(oNode) != oNode))
				oNext = mf_ResolveTOCNodeForNext(oNode);
			else if (bDescend && oNode.firstChild != null)
				oNext =  mf_ResolveTOCNodeForNext(oNode.firstChild);
			else if (oNode.nextSibling != null)
				oNext =  mf_ResolveTOCNodeForNext(oNode.nextSibling);
			else if (oNode.parentNode.nodeName == "item")
				oNext =  mf_FindNext(oNode.parentNode, false);
			else if (oNode.parentNode.nodeName == "organization")
			{
				var oParentItem = mf_GetItemInParentManifest(oNode.parentNode);
				if (oParentItem)
					oNext = mf_FindNext(oParentItem, false);
			}

			if (oNext)
			{
				if (oNext.nodeName != "item")
					oNext = mf_FindNext(oNext,false);
			}

			return oNext;
		}

		function mf_ResolveTOCNodeForNext(oNode)
		{
			if (!oNode) return null;

			if (oNode.nodeName != "item") return oNode;
				
			var oResource = mf_ResourceFromItem(oNode);
		
			if (oResource == null)
				return oNode;
			else if (oResource.nodeName == "organization")
				return (oResource.childNodes[0]); // return the root. always an item.
			else
				return oNode;
		}
		
		function mf_FindPrevious(oNode)	
		{
			if (!oNode) return null;
			
			var oPrev = null;

			if (oNode.previousSibling)
			{
				oPrev = oNode.previousSibling;

				while (true)
				{
					oPrev = mf_ResolveTOCNodeForPrev(oPrev);

					if ((oPrev.nodeName == "item" || oPrev.nodeName == "organization") && oPrev.lastChild != null)
						oPrev = oPrev.lastChild;
					else
						break;
				}
			}
			else if (oNode.parentNode.nodeName == "item")
				oPrev = oNode.parentNode;

			else if (oNode.parentNode.nodeName == "organization")
			{
				var oParentItem = mf_GetItemInParentManifest(oNode.parentNode);
				if (oParentItem)
					oPrev = mf_FindPrevious(oParentItem);
			}

			if (oPrev)
			{
				if (oPrev.nodeName != "item")
					oPrev = mf_FindPrevious(oPrev);
			}

			return oPrev;
		}

		function mf_ResolveTOCNodeForPrev(oNode)
    		{
    			if (!oNode) return null;
    				
    			if (oNode.nodeName != "item") return oNode;
    				
    			var oResource = mf_ResourceFromItem(oNode);
    		
    			if (!oResource || oResource.nodeName != "organization")
    				return oNode;		
    			else
    				return oResource;
    		}
		
								
		//this method is for transforming the returned node into our xml format.	
		//strID is the item identification which will establish context for the transformation.		
		function mf_TransformFromIMStoMLT(strID, enumValueType)
		{
			if ( !m_oXMLDOM || (enumValueType == null) || ("number" != typeof(enumValueType)) )
				return null;
			
			try
			{
				//the XSL stylesheet.
				var oXSLNode =  new ActiveXObject("Msxml2.DOMDocument.3.0");
				mf_LoadXMLData(oXSLNode, m_strXSLPath);
			
				//the resulting transformation XML.
				var oXMLResult  =  new ActiveXObject("Msxml2.DOMDocument.3.0");
				oXMLResult.async = false;
				oXMLResult.validateOnParse = false;
					
				var strQuery = "//xsl:stylesheet//xsl:template//xsl:apply-templates"
				var oAppTemp = oXSLNode.selectSingleNode(strQuery);
				var strValue = "";
				
				switch (enumValueType)
				{
					case enum_SELECT_SINGLEITEM:
						strValue = "//item[@identifier='" + strID + "']";
						break;
					case enum_SELECT_ITEMCHILDREN:
					{
						//the children could be in another manifest.
						if (strID == null)
						{
							strValue = "//manifest[0]/organizations/organization[@identifier='" + m_strCurrentTOC + "']/item";
							break;
						}

						var oNode = mf_FindItem(strID);
						var oResource = mf_ResourceFromItem(oNode);
						
						if (oResource == null)
							strValue = "//item[@identifier='" + strID + "']/item";
						else
						{						
							if (oResource.nodeName == "organization")
								strValue = "//organization[@identifier='" + oResource.getAttribute("identifier") + "']/item";
							else
								strValue = "//item[@identifier='" + strID + "']/item";
						}
						break;
					}	
					case  enum_SELECT_TOCS:
						strValue = "/manifest[0]/organizations//organization";
						break;
						
					case  enum_SELECT_TOC:
						strValue ="/manifest[0]/organizations/organization[@identifier='" + strID + "']"	
						break;
				}
				
				oAppTemp.setAttribute("select", strValue);

				m_oXMLDOM.transformNodeToObject(oXSLNode, oXMLResult);

				if( enumValueType == enum_SELECT_ITEMCHILDREN || enumValueType == enum_SELECT_SINGLEITEM)
				{
					mf_SetProperties(oXMLResult);
				}

				return ( oXMLResult );
			}
			catch (e)
			{
				return null;
			}
		}	

		//helper function to load xml data.
		function mf_LoadXMLData(oXMLDOM, sXML)
		{
			if (oXMLDOM)
			{
				oXMLDOM.async = false;
				oXMLDOM.validateOnParse = false;
					
				if (false == oXMLDOM.load(sXML))
				{
					alert(oXMLDOM.parseError.reason + " " + oXMLDOM.parseError.line);
				}
			}
		}

		function mf_SetLMSProps(node)
		{
			if (!node) return;

			var strStatus = "";
			
			var strPrerequisites = node.getAttribute("Prerequisites");
			if (("string" == typeof(strPrerequisites)) && ("" != strPrerequisites))
			{
				// Currently we only support simple prerequisites
				if (m_oLMSAPI)
				{
					var strPrereqStatus = m_oLMSAPI.LRNGetScopedValue("cmi.core.lesson_status", strPrerequisites);
					if (("completed" == strPrereqStatus) || ("passed" == strPrereqStatus))
					{
						node.setAttribute("PrerequisitesMet", "true");
					}
					else
					{
						node.setAttribute("PrerequisitesMet", "false");
					}
				}				
			}
			else
			{
				node.setAttribute("PrerequisitesMet", "true");
			}

			//set datafromlms.
			if (m_oLMSAPI)
			{
				if (node.getAttribute("DataFromLMS"))
				{
					m_oLMSAPI.LRNSetScopedValue("cmi.launch_data", node.getAttribute("DataFromLMS"), node.getAttribute("ID"), true);
				}
			}
		}

		function mf_ResourceFromItem(item)
		{
			if (!item) return null;

			var sIDRef = item.getAttribute("identifierref");
				
			if (sIDRef == null || sIDRef == "")
				return null;

			// split the identifier into XPath-style notation
			var arr = sIDRef.split("/");

			// start navigating from the ancestor manifest
			var strQueryBase = "ancestor(manifest)";

			// Look into all submanifests, if any. Note that the last argument is not part of this loop.
			for (var i = 0; i < arr.length-1; i++)
			{
				strQueryBase = strQueryBase + "/manifest[@identifier='" + arr[i] + "']";
			}

			// Now, append the last argument as if it's a resource. Note that i==arr.length-1.
			var rsc = item.selectSingleNode(strQueryBase + "/resources/resource[@identifier='" + arr[i] + "']");
			if (rsc != null)
			{
				return rsc;
			}

			// No? Must be a TOC or junk. Treat it as a TOC; if it's junk, the null will pass through.
			var rsc = item.selectSingleNode(strQueryBase + "/organizations/organization[@identifier='" + arr[i] + "']");
			return rsc;
		}
		
		function mf_SetHref(node)
		{
			if (node)
			{
				var strHref = node.getAttribute("Href");
				
				if (strHref && strHref !== "")
				{
					// get resource 'parameters' attribute if present
					var sID = node.getAttribute("ResourceRef");
					var strQuery = "//resource[@identifier = '" + sID + "']";
					var rsc = m_oXMLDOM.selectSingleNode(strQuery);

					// add the parameters to the existing URL
					if (rsc && rsc.getAttribute("parameters"))
						strHref += "?" + rsc.getAttribute("parameters");
				}
				else
					strHref = "viewer\\blank.htm";
				
				node.setAttribute("Href", strHref);
			}
		}	
			
		function mf_SetProperties(oXML)
		{
			if (!oXML) return;
			
			var nLen = oXML.documentElement.childNodes.length;
			var node = null;
			
			for (var i = 0; i < nLen; i++)
			{
				node = oXML.documentElement.childNodes(i);
				
				mf_SetLMSProps(node);
				mf_SetHref(node);	//temporary until new storage model.
			}	
		}

		function mf_EventHandler(obj)
		{
			if (!obj) return;

			switch (obj.type)
			{
				case LMSAPI_LESSONSTATUS:
					mf_CreateEvent(enum_CPO_LESSONSTATUS, obj.id)
					break;
				case LMSAPI_EXITSTATUS:
					mf_CreateEvent(enum_CPO_EXITSTATUS, obj.id)
					break;
			}
		}
}

function ContentViewerEvtsObj()
{
    var enum_CPO_INIT = 3;
    var arrListeners = null;
    var oLMSAPI = null;
    var nNext = 0;
	
    function ContentViewerEvtsObj.prototype.Init(oAPI)
    {
	   oLMSAPI = oAPI;
	   arrListeners = new Array();
    }

    function ContentViewerEvtsObj.prototype.ListenForEvents(fnHandler)
    {
	   if ( !(fnHandler || arrListeners) ) return;

	   try
	   {
	   	var oListener = new Object();
	   	oListener.fnHandle = fnHandler;
	   	arrListeners[nNext] = oListener;
	   	nNext++;

	   	if (oLMSAPI)
	   	{
	      		if (true == oLMSAPI.LRNIsReady())
		  	{
		    		var obj = new Object();
				obj.type = enum_CPO_INIT;
				obj.id = null;
				fnHandler(obj);
		  	}
	   	}
	    }
	    catch(e)
	    {
		//FIXME: handle error
	    }  
    }
    
    function ContentViewerEvtsObj.prototype.UnListenForEvents(fnHandler)
    {
	  if ( !(fnHandler || arrListeners) ) return;

	  for (var i =0 ; i< arrListeners.length;i++)
	  {
	  	if (arrListeners[i])
			if (arrListeners[i].fnHandle == fnHandler)
					arrListeners[i] = null;
	  }
    }	
    
	//arg contains event type and data.	
    function ContentViewerEvtsObj.prototype.RaiseEvent(arg)
    {
	  if ( !(arg || arrListeners) ) return;

	  if (true == oLMSAPI.LRNIsReady())
	  {
	  	for (var i = 0 ; i < arrListeners.length;i++)
	  	{
			var obj = arrListeners[i];
				
			if (obj) obj.fnHandle(arg);
	  	}
	  }
    }	

}

// constructor
function Timer()
{
	try
	{
		this.m_datetime = new Date();
		return this;
	}
	catch(e)
	{
		return null;
	}
}

// Return Value: none
function Timer.prototype.ResetTimer()
{
	try
	{
		this.m_datetime = new Date();
	}
	catch(e)
	{
	}
	return;
}

// Return Value: time delta in seconds
function Timer.prototype.GetTimeDelta()
{
	try
	{
		return Math.round( (new Date() - this.m_datetime) / 1000 );
	}
	catch(e)
	{
		return 0;
	}
}

// Return Value: time delta in seconds
function Timer.prototype.UnFormatTimeDelta (strTime)
{
	try
	{
		if("string" != typeof(strTime))
		{
			return 0;
		}
		
		var arrTime = strTime.split(":");
		if(3 != arrTime.length)
		{
			return 0;
		}

		var nHours = parseInt(arrTime[0]);
		var nMinutes = parseInt(arrTime[1]);
		var nSeconds = parseInt(arrTime[2]);
		if(	(true == isNaN(nHours))		||
			(true == isNaN(nMinutes))	||
			(true == isNaN(nSeconds))	)
		{
			return 0;
		}

		return (nHours * 60 * 60) + (nMinutes * 60) + nSeconds;
	}
	catch(e)
	{
		return 0;
	}
}

// Return Value: time delta as string
function Timer.prototype.FormatTimeDelta (nSeconds)
{
	try
	{
		if(("number" != typeof(nSeconds)) || (0 > nSeconds))
		{
			return "0:00:00";
		}

		// 1 hour = 60 minutes * 60 seconds 
		var nHours = Math.floor(nSeconds / (60 * 60));
		nSeconds = nSeconds - (nHours * 60 * 60);

		var nMinutes = Math.floor(nSeconds / 60);
		nSeconds = nSeconds - (nMinutes * 60);

		var strHours = nHours.toString();

		var strMinutes;
		if(10 > nMinutes)
		{
			// single digit
			strMinutes = "0" + nMinutes.toString();
		}
		else
		{
			strMinutes = nMinutes.toString();
		}

		var strSeconds;
		if(10 > nSeconds)
		{
			// single digit
			strSeconds = "0" + nSeconds.toString();
		}
		else
		{
			strSeconds = nSeconds.toString();
		}

		return strHours + ":" + strMinutes + ":" + strSeconds;
	}
	catch(e)
	{
		return "0:00:00";
	}
}

// Return Value: new time delta as string
function Timer.prototype.CalculateNewTimeDelta(strOldTimeDelta)
{
	try
	{
		var nTimeDelta = this.GetTimeDelta();
		this.ResetTimer();

		var nOldTimeDelta = this.UnFormatTimeDelta(strOldTimeDelta);
		var nNewTimeDelta = nTimeDelta + nOldTimeDelta;
		return this.FormatTimeDelta(nNewTimeDelta);
	}
	catch(e)
	{
		return strOldTimeDelta;
	}
}
