// =========================================================
// AssetAPI class
// 22-04-2009
// Versie 1.3
//
// LMS interface for assets as described in ' ASSET API, Tehcnical description' in \\wngrn-fp01.wolters.nl\users\pawi\home\mijn documenten\assetapi.doc
// Version 1.1

var assetAPI = {
  initializedGUIDs : new Array(),
  intializedSources : new Array(),
  assetData : new Array(),   //Stub
  assetScore : new Array(),   //Stub
  userID: new String(),

   /*
   Before calling any other API functions, SetInitialized should be called to indicate that the smartAsset that wants to
   communicate is loaded and initialized. Unless successful Initialized, all other requests will be ignored by the API.
   GUID is the unique identification string that is assigned to the asset at startup by passing it as a parameter.
   If initialization fails, the “Initialized” parameter should be set to False. Subsequent calls from this GUID will be ignored,
   except for the SetException function. This allows the Asset to pass through what exactly goes wrong.
   Default assetAPI.initializedGUIDs[GUID]  == false;
   */
  SetInitialized : function(GUID, Initialized) {
    var constWeight = 1;

    assetAPI.initializedGUIDs[GUID] = false;
    if (Initialized)
    {
      assetAPI.initializedGUIDs[GUID] = Initialized;
      assetAPI.initializedGUIDs[GUID] = Initialized;
      assetAPI.intializedSources[GUID] = '';

      if(!assetAPI.initializedGUIDs[GUID])
      {
        assetAPI.SetException(GUID, true, -1, "Asset could not be loaded by the content. \n Identifier unknown!");
      }
    }
  },

 /*
 This call wil handle exceptions such as warings and errors that occur in the asset, and act on them in a centralized way.
 Depending on the product, configuration and type of error this generates a centralized log mechanism or a error handling.
 Two types of exceptions are possible:
 Warning  (fatal is set to False)
  GUID, ErrorNum and Details are passed to the log (if logging is enabled in the receiving application).
  If there is no logging capability in the receiving application, warnings are discarded.
  GUID is used to identify which asset has caused this warning. it is recommended to resolve this GUID in the prodcuct configuration to a human readable format as well.
 Fatal  (fatal is set to true)
  ErrorNum and Details are used to present an error message. The underlying application should translate the error into a meaningfull message in the correct language.
 A meaningfull message not only contains the message, but also as much as possible hints how to resolve it. This can be done based on the errnum, but is essentially the responsibility of the underlying application.
 */
 SetException : function(GUID, Fatal, ErrNum, Details) {
  if (Fatal) {
   assetAPI.initializedGUIDs[GUID] = false;
   alert("GUID " + GUID + "\nFatal Exception (ErrNum=" + ErrNum + "):\n" + Details);
  }
  else {
   alert("GUID " + GUID + "\nWarning (ErrNum=" + ErrNum + "):\n" + Details);
  }
 },

 /*
 This returns the userID for the user currently running this content. The user ID is retrieved from the LMS when the underlying
 product runs from within an LMS, otherwise it will use the applications own implementation of user Identification if present. If no userID is known  this will return the value “anonymous”.
 Note: the userID is a technical ID. In many cases this differs from the user name or display name.
 Currently there is no implementation for retrieving the user name or display name.
 */
 GetUserId : function() {
  // geen controle op initialized voor backwarcompatibility (zo kan de e-book er ook in)
  if (!assetAPI.userID || assetAPI.userID == "") {
   assetAPI.userID = page.getUserId();
  }
  return assetAPI.userID;
 },


 /*
 This call returns previously stored data related to this asset (identified with the GUID) and the current user (identified by LMS or product).
 If no user data has been stored earlier, this call will return an empty string.
 */
 GetAssetData : function(GUID) {
  if (assetAPI.initializedGUIDs[GUID]) {
    var storedValue = requestStatus(GUID.replace(/-/g, "_"));
    if (storedValue != "") {
      return unescape(storedValue);
    }
  }
  return "";
 },
  
 /*
 Use this call to store data that is important for the asset. This can include answers, user input, configuration data etc.
 In some cases (i.e. when the product is used from within an LMS), this data might become visible in some sort of view. Also, the storage limit is 4Kb per Asset.
 */
 SetAssetData : function(GUID, Value) {
  if (assetAPI.initializedGUIDs[GUID]) {

   var storedData = Value;
   storeStatus(GUID.replace(/-/g, "_"), storedData);
  }
 },

 /*
 This call will return the scaled score related to this asset (identified with the GUID) and the current user (identified by LMS).
 The score is a numeric value between 0 and 1. if  the score can not be retrieved, a value of 0 will be returned.
 */
 GetScore : function(GUID) {
  if (assetAPI.initializedGUIDs[GUID]) {
  var storedValue = requestStatus(GUID.replace(/-/g, "_"));
  var doc = assetAPI.loadXMLString(storedValue);
  var iScoreChilds = doc.getElementsByTagName("iscore");
  if (iScoreChilds.length > 0) {
    return unescape(iScoreChilds[0].firstChild.nodeValue);
  }
  }
  return 0;
 },


 /*
 Use this function to store a score value. This is  a numeric value between 0 and 1. it is up to the asset to determine what value should be assigned.
 However, since the asset is part of a product, the score will be scaled to a final score for a complete SCO. If the SCO contains 6 scorable elements,
  the score will influence 1/6th of the total score for that SCO. Some products allows to influence the importance of a score for an individual asset
 by setting a score-factor at design-time.
 */
  SetScore : function(GUID, Score) {
    //var constWeight = 1;

    if (assetAPI.initializedGUIDs[GUID] && assetAPI.IsNumericAndPositive(Score)) {
      //update assetData iScore element:
      storeStatus(GUID.replace(/-/g, "_"), Score);
    }
  },


 /*
 Besides access to runtime data, some smartAssets require some additional external resources as a data-soure.
 This is rather static data and is classically retrieved by applying a relative path to the asset itself. During production,
 this requires that both asset and the external resources stay together.
 This call enables the asset to request external resources that reside on a different (fixed or shared) location. This gains efficiency
 in both the production process and data-transfer. (using shared resources)
 GUID is the unique identification string that is assigned to the asset at startup by passing it as a parameter.
 The ‘Resource’ value contains the resource filename it requests. The API will resolve the name  and return the full URI to
 the resource including the filename. The final resource filename might been changed from the original in order to keep hares asset names
 unique, so the Asset has to call GetResource before every resource use, or cache the results internally.
 */
 GetResource : function(GUID, Resource) {
  if (assetAPI.initializedGUIDs[GUID]) {
    var fileName = '';
    var baseSource = assetAPI.intializedSources[GUID];
    var itemResources = baseSource.getElementsByTagName("resources")[0];
    if (itemResources) {
      var resources = itemResources.getElementsByTagName("file");
      for(i = 0; i< resources.length; i++)
      {
        if( resources[i].getAttribute("name") == Resource)
        {
          fileName = resources[i].getAttribute("src");
          break;
        }
      }
    }
    return fileName;
  }
 },


 //-----  Helper functions

 loadXMLString : function(txt)
 {
  try //Internet Explorer
  {
   xmlDoc=new ActiveXObject("Microsoft.XMLDOM");
   xmlDoc.async="false";
   xmlDoc.loadXML(txt);
   return(xmlDoc);
  }
  catch(e)
  {
   try //Firefox, Mozilla, Opera, etc.
   {
    parser=new DOMParser();
    xmlDoc=parser.parseFromString(txt,"text/xml");
    return(xmlDoc);
   }
   catch(e) {
    alert(e.message)
   }
  }
  return(null);
 },

 //check valid numeric number
 // check if number is positive
 IsNumericAndPositive : function(strString)
 {
  var strValidChars = "0123456789.";
  var strChar;
  var blnResult = true;

  if (strString.length == 0) {
   return false;
  }

  for (i = 0; i < strString.length && blnResult == true; i++)
  {
   strChar = strString.charAt(i);
   if (strValidChars.indexOf(strChar) == -1)
   {
    blnResult = false;
   }
  }
  return blnResult;
 }

}


function requestStatus(guid)
{
  if (getStatus(guid))
  {
    return getStatus(guid);
  }

  //next check the lms for results
  if (__inMemoryIndex[guid] != undefined)
  {
    return doLMSGetValue("cmi.interactions."+__inMemoryIndex[guid]+".learner_response");
  }

  //if the local array is empty restore it from the LMS if pressent
  //Loop through the interactions in the LMS to refill the the guid->cmi.interactions posistion array
  var size = getLMSLearnerResponseSize();
  if (size >= 1)
  {
    for (i = 1; i <= size;i++)
    {
      guidInLMS = doLMSGetValue("cmi.interactions."+i+".id");
      __inMemoryIndex[guidInLMS] = i;
    }
    if (__inMemoryIndex[guid])
    {
      return doLMSGetValue("cmi.interactions."+__inMemoryIndex[guid]+".learner_response");
    }
  }
  return "";
}

function storeStatus(guid, value)
{
  //check if there is an cmi.interactions.n position stored for this guid
  setStatus(guid, value);
}


/**
* Store a status in the in-memory DB
*/
function setStatus(guid, statusXml) {
 __inMemoryStatusStore[guid] = statusXml;
 return true;
}

/**
* Retrieve a Status from the in-memory DB
*/
function getStatus(guid) {
 return __inMemoryStatusStore[guid];
}

function getLMSLearnerResponse()
{
  if(typeof doLMSGetValue != 'undefined')
  {
    var size = getLMSLearnerResponseSize();
    if (size >= 1)
    {
      __inMemoryIndexCount = size;
      for (i = 1; i<=size; i++)
      {
        guidInLMS = doLMSGetValue('cmi.interactions.' + i + '.id');
        valueInLMS = doLMSGetValue('cmi.interactions.' + i + '.learner_response');
        __inMemoryIndex[guidInLMS] = i;
        storeStatus(guidInLMS, valueInLMS);
      }
    }
  }
}

function setLMSLearnerResponse(id)
{
  if(typeof doLMSSetValue != 'undefined')
  {
    var value = assetAPI.GetAssetData(id);
    if(!__inMemoryIndex[id])
    {
      __inMemoryIndexCount++;
      __inMemoryIndex[id] = __inMemoryIndexCount;
    }

    setLMSLearnerResponseSize(__inMemoryIndexCount);
    doLMSSetValue('cmi.interactions.' + __inMemoryIndex[id] + '.id', id);
    doLMSSetValue('cmi.interactions.' + __inMemoryIndex[id] + '.type', 'other');
    doLMSSetValue('cmi.interactions.' + __inMemoryIndex[id] + '.learner_response', value);
  }
}

function getLMSLearnerResponseSize()
{
  var size = 0;
  if(typeof doLMSGetValue != 'undefined')
  {
    var entry = doLMSGetValue('cmi.completion_status');
    if(entry != 'unknown' && entry!='')
    {
			var responseXml = doLMSGetValue("cmi.interactions.0.learner_response");
			if(responseXml)
      {
				var xmlValue = stringToXMLDOM(responseXml);
				if(xmlValue)
        {
					var count = xmlValue.getElementsByTagName("count");
					size = getInnerHTML(count[0]);
				}
			}
    }
    if (size=='')
    {
      size = 0;
    }
  }
  return size;
}

function getLMSLearnerResponseProgress()
{
  var progress = '';
  if(typeof doLMSGetValue != 'undefined')
  {
    var entry = doLMSGetValue('cmi.completion_status');
    if(entry != 'unknown' && entry!='')
    {
			var responseXml = doLMSGetValue("cmi.interactions.0.learner_response");
			if(responseXml)
      {
				var xmlValue = stringToXMLDOM(responseXml);
				if(xmlValue)
        {
					progress = getInnerHTML(xmlValue.getElementsByTagName("progress")[0]);
				}
			}
    }
  }
  return progress;
}

function setLMSLearnerResponseSize(size)
{
  var strDOM;  
	var responseXml = doLMSGetValue("cmi.interactions.0.learner_response");
	if(responseXml)
  {
		var xmlValue = stringToXMLDOM(responseXml);
		if(xmlValue)
    {
			xmlValue.getElementsByTagName("count")[0].text = size;
		}
	  var strDOM = XMLDOMtoString(xmlValue);
	  if(strDOM.search(/\r/g))
	  {
		  strDOM = strDOM.substring(0,strDOM.search(/\r/g));
		}
	}
	else
	{
	  strDOM = "<learning-unit><count>" + size + "</count><progress /></learning-unit>";
	}
  doLMSSetValue('cmi.interactions.0.learner_response', strDOM);
}

function setLMSLearnerResponseProgress(progress)
{
  var strDOM;  
	var responseXml = doLMSGetValue("cmi.interactions.0.learner_response");
	if(responseXml)
  {
		var xmlValue = stringToXMLDOM(responseXml);
		if(xmlValue)
    {
			xmlValue.getElementsByTagName("progress")[0].text = progress;
		}
	  var strDOM = XMLDOMtoString(xmlValue);
	  if(strDOM.search(/\r/g))
	  {
		  strDOM = strDOM.substring(0,strDOM.search(/\r/g));
		}
	}
	else
	{
	  strDOM = "<learning-unit><count /><progress>" + progress + "</progress></learning-unit>";
	}
  doLMSSetValue('cmi.interactions.0.learner_response', strDOM);
}

function stringToXMLDOM(text) {
	var doc;
	if (typeof DOMParser != 'undefined') {
		var parser = new DOMParser();
		doc = parser.parseFromString(text, "text/xml");
		delete(parser);
	}
	else if (typeof ActiveXObject != 'undefined') {
		doc = new ActiveXObject("Microsoft.XMLDOM");
		doc.async = "false";
		doc.loadXML(text);
	}
	return doc;
}

function XMLDOMtoString(doc) {
	var text;
	if (typeof XMLSerializer != 'undefined') {
		var parser = new XMLSerializer();
		text = parser.serializeToString(doc);
		delete(parser);
	}
	else if (typeof ActiveXObject != 'undefined') {
		text = doc.xml;
	}
	return text;
}

function getInnerHTML(doc) {
	var html = "";
	if (doc) {
		removeWhitespaceNodes(doc);
		for (var i=0; i<doc.childNodes.length; i++) {
			html += XMLDOMtoString(doc.childNodes[i]);
		}
	}
	return html;
}

function removeWhitespaceNodes(node) {
	for (var i=0; i<node.childNodes.length; i++) {
		var cNode = node.childNodes[i];
		if (cNode.nodeType == 3 && /^\s*$/.test(cNode.nodeValue)) {
			void node.removeChild(cNode);
			i--;
		} else {
			removeWhitespaceNodes(cNode);
		}
	}
}

