//  Copyright © Gopas, a.s.
//  Všechna práva, zejména autorské právo, licenční právo a průmyslová ochranná práva jsou výhradním vlastnictvím GOPAS, a.s.
//  Všechna práva vyhrazena. Rozmnožování kódu nebo jeho části, jeho rozšiřování, pronájem, půjčování, vystavování,
//  sdělování veřejnosti či jiné užití je dovoleno jen na základě licenční smlouvy uzavřené se společností
//  GOPAS, a.s.
////////////////////////////

///////////////////////////////////////////////////////////////////////////////////////////////////
//  Global Variables
///////////////////////////////////////////////////////////////////////////////////////////////////
var b_LMSInitialized        = false;
var gc_strLMSDataModelPath  = "viewer/LMS/LMSDataModel.xml";
var gc_strLMSErrorsPath     = "viewer/LMS/LMS_Errors.xml";
var gc_strPreloaderPath     = "viewer/preload.htm";

///////////////////////////////////////////////////////////////////////////////////////////////////
// Used for userData
///////////////////////////////////////////////////////////////////////////////////////////////////
var gc_strUserDataPrefix    = "per";
var gc_strNeedSaveAttribute = "NeedSave";
var gc_strEmptyPersistXML   = "<ROOT></ROOT>";

///////////////////////////////////////////////////////////////////////////////////////////////////
// Base Pers XML
///////////////////////////////////////////////////////////////////////////////////////////////////
var g_oBasePersXML          = LoadXML(gc_strEmptyPersistXML);

///////////////////////////////////////////////////////////////////////////////////////////////////
// Error details types: these strings are used to construct LRNError objects
///////////////////////////////////////////////////////////////////////////////////////////////////
var gc_strParseError        = "XML_Parse_Error";
var gc_strJScriptError      = "JScript_Error";
var gc_strMultiStatusError  = "Multi-Status_Error";
var gc_strString            = "String";
var gc_strNoDetails         = "Null";

///////////////////////////////////////////////////////////////////////////////////////////////////
// For working with Pers XML
///////////////////////////////////////////////////////////////////////////////////////////////////
var gc_strPersElementName   = "lrnpers";
var gc_strPropertyPropName  = "Property";
var gc_strValuePropName     = "Value";
var gc_strOldValuePropName  = "Old_Value";
var gc_strScopePropName     = "Scope";
var gc_strTimePropName      = "Time";
var gc_strLRNStartLocation  = "lrn.startlocation";
var gc_strLRNPrefix         = "lrn.";

///////////////////////////////////////////////////////////////////////////////////////////////////
// Special case properties
///////////////////////////////////////////////////////////////////////////////////////////////////
var gc_strCMIStudentName    = "cmi.core.student_name";
var gc_strCMIStudentID      = "cmi.core.student_id";
var gc_strCMILessonStatus   = "cmi.core.lesson_status";
var gc_strCMIExitStatus     = "cmi.core.exit";

///////////////////////////////////////////////////////////////////////////////////////////////////
// Used for CVEO events
///////////////////////////////////////////////////////////////////////////////////////////////////
var gc_LMSAPI_LESSONSTATUS  = 200;
var gc_LMSAPI_EXITSTATUS    = 300;
var gc_CPO_SELECT           = 2;
var myPreloader             = null;  // PRELOADER HWND

var splt_number             = 1;     // number files to split
var splt                    = 40000; // max persist storing per files (Bytes/3)
var compr_limit             = 200000;// max storing without comprimation (Bytes/3)

var conv=new Array(
            new Array('#t',' Property="cmi.core.total_time"'),
            new Array('#l',' Property="cmi.core.lesson_location"'),
            new Array('#s',' Property="cmi.core.lesson_status"'),
            new Array('#o',' Property="cmi.core.score"'),
            new Array('#e',' Property="cmi.core.entry"'),
            new Array('#a',' Property="lrn.startlocation"'),
            new Array('#b',' Value="browsed"'),
            new Array('#i',' Value="incomplete"'),
            new Array('#c',' Value="complete"'),
            new Array('#d',' Value="completed"'),
            new Array('#p',' Value="passed"'),
            new Array('#r',' Value="resume"'),
            new Array('#f',' Value="failed"'),
            new Array('#m',' Scope="item-'),
            new Array('#G','<lrnpers'),
            new Array('#P',' Property="'),
            new Array('#V',' Value="'),
            new Array('#T',' Time="'),
            new Array('#S',' Scope="'),
            new Array('#q','"'),
            new Array('#g','>')
          );

function IsIE()
{
  return (typeof(window.ActiveXObject) != "undefined");
}

function IsOpera()
{
   return (typeof(window.opera) != "undefined");
}

function IsFF()
{
  return ((document.attachEvent)? false : true);
}

function IsFF2()
{

  return (typeof globalStorage != "undefined");
}

var isChrome = false; var chromeXMLRes = null;
function IsChrome() { return isChrome; }

function mf_CreateDOM()
{
  var xmlDoc = null;
  if (IsIE()) xmlDoc = new ActiveXObject("Msxml2.DOMDocument.3.0"); // ActiveX pro XML DOM model
  else xmlDoc = document.implementation.createDocument("","",null); // při načítání z XML souboru je nutné založit XML document /nestaci DOMPARSER /

  return xmlDoc;
}


function mf_LoadXMLDataAsync(xmlDoc, xmlPath, func)
{
  if (document.implementation && document.implementation.createDocument)
  {
    if (typeof(xmlDoc.load) != "undefined")
    { // FF, OP
      xmlDoc.onload = eval(func);
      xmlDoc.async = true;
      xmlDoc.load(xmlPath);
    } else {
      // Chrome - nema podporu XML musim pres XMLHttpRequest
      if (window.XMLHttpRequest)
      {
        isChrome = true;  // nastaveni pro CHROME
        xmlDoc = new window.XMLHttpRequest();
        xmlDoc.open("GET", xmlPath, false); // synchronni nacteni
        xmlDoc.send("");
        chromeXMLRes = xmlDoc.responseXML;
        eval(func());
      }
    }
  }
  else if (window.ActiveXObject)
  {
    xmlDoc.async = false;
    xmlDoc.load(xmlPath);
    eval(func());
  }
}


function LoadXML(strXML)
{
  try
  {
    if((typeof(strXML) != "string") || (strXML == "")) return null;

    var oXML = null;
    if (window.ActiveXObject)
    {
      oXML = new ActiveXObject("Msxml2.DOMDocument.3.0");
      oXML.async = false;
      oXML.loadXML(strXML);
      if(oXML.parseError.errorCode != 0) return null;
    }
    else
    {
      oXML = new DOMParser();
      oXML = oXML.parseFromString(strXML,"text/xml");
    }

    return oXML;
  }
  catch(err)
  {
    return null;
  }
}

function 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;
  }
}

function FormatTimeDelta(nSeconds)
{
  try
  {
    if ((typeof(nSeconds) != "number") || (nSeconds < 0)) return "0:00:00";

    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) strMinutes = "0" + nMinutes.toString();
    else  strMinutes = nMinutes.toString();

    var strSeconds;
    if(10 > nSeconds) strSeconds = "0" + nSeconds.toString();
    else strSeconds = nSeconds.toString();

    return strHours + ":" + strMinutes + ":" + strSeconds;
  }
  catch(err)
  {
    return "0:00:00";
  }
}

function AddTime(strTime1, strTime2)
{
  try
  {
    var nTime1 = UnFormatTimeDelta(strTime1);
    var nTime2 = UnFormatTimeDelta(strTime2);
    var nNewTime = nTime1 + nTime2;
    return this.FormatTimeDelta(nNewTime);
  }
  catch(e)
  {
    return strTime1;
  }
}

///////////////////////////////////////////////////////////////////////////////////////////////////
//
// LMSAPI class & implemenatation
//
///////////////////////////////////////////////////////////////////////////////////////////////////
function LMSAPI(strIdentifier, bPreview)
{
  try
  {
    if("string" != typeof(strIdentifier)) strIdentifier = "";

    if("boolean" != typeof(bPreview)) return null;
    this.m_bInitialized   = false;
    this.m_bNeedSave      = false;
    this.m_bPreview       = bPreview;
    this.m_strIdentifier  = strIdentifier;
    this.m_oLastError     = null;
    this.m_oStorage       = null;
    this.m_oPersXML       = g_oBasePersXML.documentElement.cloneNode(true).ownerDocument;


    this.m_oLMSDataModel  = new LMSDataModel();

    this.m_oLMSErrorsXML = mf_CreateDOM();
    mf_LoadXMLDataAsync(this.m_oLMSErrorsXML, gc_strLMSErrorsPath, this.Loaded);
    // synchronni nacteni v pripade CHROME
    if (IsChrome()) this.m_oLMSErrorsXML = chromeXMLRes;

    try {
      this.m_oUserData      = g_oDefaultUserData;
    } catch(e) {}

    try
    {
      this.m_oContentViewerEvts = new ContentViewerEvtsObj();
      this.m_oContentViewer   = new CViewerObj();
    }
    catch(e)
    {
      this.m_oContentViewerEvts = null;
      this.m_oContentViewer   = null;
    }

    // IIS DB
    // stored imsmanifest and CMI data
    this.isStoredInIIS=false;
    this.isLoggedInIIS=false;
    this.loggedName='';
    this.fullName='';

    return this;
  }
  catch(e) { return null; }
}

LMSAPI.prototype.Loaded = function Loaded() {}


LMSAPI.prototype.getmyCurrentID = function getmyCurrentID()
{
  return this.m_oContentViewer.m_myCurrentNodeID;
}

LMSAPI.prototype.LRNDoNext = function LRNDoNext()
{
  return this.m_oContentViewer.DoNext();
}

LMSAPI.prototype.LRNDoPrevious = function LRNDoPrevious()
{
  return this.m_oContentViewer.DoPrevious();
}

LMSAPI.prototype.LRNDoSelect = function LRNDoSelect(id)
{
  return this.m_oContentViewer.DoSelect(id);
}

LMSAPI.prototype.LRNDoSelectFirst = function LRNDoSelectFirst()
{
  return this.m_oContentViewer.DoSelectFirst();
}

LMSAPI.prototype.LRNGetCurrent = function LRNGetCurrent()
{
  return this.m_oContentViewer.GetCurrent();
}

LMSAPI.prototype.LRNGetNext = function LRNGetNext(id)
{
  return this.m_oContentViewer.GetNext(id);
}

LMSAPI.prototype.LRNGetPrevious = function LRNGetPrevious(id)
{
  return this.m_oContentViewer.GetPrevious(id);
}

LMSAPI.prototype.LRNGetParent = function LRNGetParent(id)
{
  return this.m_oContentViewer.GetParent(id);
}

LMSAPI.prototype.LRNSetCurrentTOC = function LRNSetCurrentTOC(strCurrentTOCID)
{
  return this.m_oContentViewer.SetCurrentTOC(strCurrentTOCID);
}

LMSAPI.prototype.LRNGetCurrentTOC = function LRNGetCurrentTOC()
{
  return this.m_oContentViewer.GetCurrentTOC();
}

LMSAPI.prototype.LRNGetTOCS = function LRNGetTOCS()
{
  return this.m_oContentViewer.GetTOCS();
}

LMSAPI.prototype.LRNGetChildren = function LRNGetChildren(id)
{
  return this.m_oContentViewer.GetChildren(id);
}

LMSAPI.prototype.LRNGetItem = function LRNGetItem(id)
{
  return this.m_oContentViewer.GetItem(id);
}

LMSAPI.prototype.LRNGetCustomView = function LRNGetCustomView()
{
  return this.m_oContentViewer.GetCustomView();
}

LMSAPI.prototype.LRNIsReady = function LRNIsReady()
{
  return this.m_oContentViewer.IsReady();
}

LMSAPI.prototype.LRNListenForEvents = function LRNListenForEvents(fnHandler)
{
  return this.m_oContentViewerEvts.ListenForEvents(fnHandler);
}

LMSAPI.prototype.LRNUnListenForEvents = function LRNUnListenForEvents(fnHandler)
{
  return this.m_oContentViewerEvts.UnListenForEvents(fnHandler);
}

LMSAPI.prototype.LRNRaiseEvent = function LRNRaiseEvent(arg)
{
  return this.m_oContentViewerEvts.RaiseEvent(arg);
}

// INITIALIZE
LMSAPI.prototype.LMSInitialize = function LMSInitialize(strProperty)
{
  var b_notfalse = "false";
  try
  {
    this.m_oLastError = null;

    // LMS bylo již jednou inicializováno
    if (b_LMSInitialized) return true;

    if (strProperty != "" && strProperty != null)
    {
      this.m_oLastError = new LMSError(this.m_oLMSErrorsXML, 201);
      throw this.m_oLastError;
    }

    // Pokud jeste nebyl inicializovan Viewer, nenicializuj ani LMS
    if(!this.m_bInitialized) throw new LMSError(this.m_oLMSErrorsXML, 301);
    else b_LMSInitialized = true;
  }
  catch(err)
  {
    if(typeof(err.strDetailsType) == "undefined") this.m_oLastError = new LMSError(this.m_oLMSErrorsXML, 101, gc_strJScriptError, err);
    else this.m_oLastError = err;

    if (b_notfalse == "true") return "false";
    else b_LMSInitialized = false;
  }

  return b_LMSInitialized;
}

LMSAPI.prototype.LMSGetValue = function LMSGetValue(strProperty)
{
  try
  {
    // Reset the API's last error so the current call starts clean.
    this.m_oLastError = null;

    if (!b_LMSInitialized && strProperty.substring(0, 3) != "lrn")
    {
      this.m_oLastError = new LMSError(this.m_oLMSErrorsXML, 301);
      throw new LMSError(this.m_oLMSErrorsXML, 301);
    }

    var strScope    = "";

    if(strProperty != gc_strLRNStartLocation) strScope = this.LMSGetValue(gc_strLRNStartLocation);

    return this.LRNGetScopedValue(strProperty, strScope);
  }
  catch(err)
  {
    if(typeof(err.strDetailsType) == "undefined") this.m_oLastError = new LMSError(this.m_oLMSErrorsXML, 101, gc_strJScriptError, err);
    else this.m_oLastError = err;

    return "";
  }
}

LMSAPI.prototype.LRNGetScopedValue = function LRNGetScopedValue(strProperty,strScope,bLMS)
{
  try
  {
    // Reset the API's last error so the current call starts clean.
    this.m_oLastError = null;

    if(typeof(strProperty) != "string" || strProperty == "") throw new LMSError(this.m_oLMSErrorsXML, 201);
    if(typeof(strScope) != "string") throw new LMSError(this.m_oLMSErrorsXML, 201);
    if(!this.m_bInitialized || this.m_oPersXML == null) throw new LMSError(this.m_oLMSErrorsXML, 301);

    // Reject any of the cmi.objectives.xxx, cmi.interactions.xxx, cmi.student_xxx
    if ("cmi.objectives" == strProperty.substring(0,14) || "cmi.interactions" == strProperty.substring(0,16) || "cmi.student" == strProperty.substring(0,11)) throw new LMSError(this.m_oLMSErrorsXML, 401);

    var bInDataModel  = this.m_oLMSDataModel.IsInDataModel(strProperty);

    // Check if property is part of the Datamodel
    if (bInDataModel)
    {
      if(!this.m_oLMSDataModel.IsImplemented(strProperty)) throw new LMSError(this.m_oLMSErrorsXML, 401);
      if(bLMS && !this.m_oLMSDataModel.IsAllowRead(strProperty)) throw new LMSError(this.m_oLMSErrorsXML, 404);

      if(!this.m_oLMSDataModel.IsItemScoped(strProperty)) strScope = "";
      var strRetVal = "";
      if(strProperty.indexOf(gc_strLRNPrefix) == 0)
      {
        strRetVal = this.GetLRNProperty(strProperty, strScope);
        if(strRetVal != "") return strRetVal;
      }
      strRetVal = this.InternalGetValue(strProperty, strScope);
      if(strRetVal != "") return strRetVal;

      strRetVal = this.m_oLMSDataModel.GetDefaultValue(strProperty);
      return strRetVal;
    }
    else throw new LMSError(this.m_oLMSErrorsXML, 201);
  }
  catch(e)
  {
    if(typeof(e.strDetailsType) == "undefined") this.m_oLastError = new LMSError(this.m_oLMSErrorsXML, 101, gc_strJScriptError, e);
    else this.m_oLastError = e;

    return "false";
  }
}

LMSAPI.prototype.LMSSetValue = function LMSSetValue(strProperty,strValue)
{
  var rc;
  try
  {
    // Reset the API's last error so the current call starts clean.
    this.m_oLastError = null;
    if (!b_LMSInitialized && strProperty.substring(0, 3) != "lrn")
    {
      this.m_oLastError = new LMSError(this.m_oLMSErrorsXML, 301);
      throw new LMSError(this.m_oLMSErrorsXML, 301);
    }
    var strScope    = "";
    // Only attach scope if not lrn.startlocation
    if(strProperty != gc_strLRNStartLocation) strScope = this.LMSGetValue(gc_strLRNStartLocation);
    rc = this.LRNSetScopedValue(strProperty, strValue, strScope);
    return rc;
  }
  catch(e)
  {
    if("undefined" == typeof(e.strDetailsType))
    {
      // Return a general error, because we don't know exactly what happened.
      this.m_oLastError = new LMSError(this.m_oLMSErrorsXML, 101, gc_strJScriptError, e);
    }
    else
    {
      this.m_oLastError = e;

    }
    return "false";
  }
}

LMSAPI.prototype.LRNSetScopedValue = function LRNSetScopedValue(strProperty,strValue,strScope,bLMS)
{
  try
  {
    // Reset the API's last error so the current call starts clean.
    this.m_oLastError = null;

    // Parameter and Object State Check ////////////////////////////////////////////////////////
    // Make sure that we were passed a reasonable argument
    if("string" != typeof(strProperty) || "" == strProperty) throw new LMSError(this.m_oLMSErrorsXML, 201);
    if("string" != typeof(strValue)) {
      if("number" == typeof(strValue) || "boolean" == typeof(strValue)) strValue = strValue.toString();
      else if(null != strValue ) throw new LMSError(this.m_oLMSErrorsXML, 201);
    }
    if("string" != typeof(strScope)) throw new LMSError(this.m_oLMSErrorsXML, 201);

    // Make sure we are initialized
    if(!this.m_bInitialized || null == this.m_oPersXML) throw new LMSError(this.m_oLMSErrorsXML, 301);
    // Reject any of the cmi.objectives.xxx, cmi.interactions.xxx, cmi.student_xxx
    if ("cmi.objectives" == strProperty.substring(0,14) || "cmi.interactions" == strProperty.substring(0,16) || "cmi.student" == strProperty.substring(0,11)) throw new LMSError(this.m_oLMSErrorsXML, 401);
    var bInDataModel  = this.m_oLMSDataModel.IsInDataModel(strProperty);

    // Check if property is part of the Datamodel
    if(true == bInDataModel)
    {
      // If not implemented, throw an error
      if(false == this.m_oLMSDataModel.IsImplemented(strProperty)) throw new LMSError(this.m_oLMSErrorsXML, 401);

      if(true == this.m_oLMSDataModel.IsKeyword(strProperty)) throw new LMSError(this.m_oLMSErrorsXML, 402);

      // Cannot set data model keyword
      // The LMS is allowed to write to any datamodel properties
      // If not write access, throw an error
      if(true != bLMS)
        if(false == this.m_oLMSDataModel.IsAllowWrite(strProperty)) throw new LMSError(this.m_oLMSErrorsXML, 403);
      // Verify that value is a valid vocabulary item
      if(false == this.m_oLMSDataModel.IsValidVocabulary(strProperty, strValue)) throw new LMSError(this.m_oLMSErrorsXML, 201);
      // If object is not item scope, set scope to ""
      if(!this.m_oLMSDataModel.IsItemScoped(strProperty)) strScope = "";
      this.InternalSetValue(strProperty, strValue, strScope);
      if ("cmi.core.session_time" == strProperty) {
        var strSessionTime = strValue;
        var strTotalTime = this.LRNGetScopedValue("cmi.core.total_time", strScope, true);
        var strNewTime = AddTime(strTotalTime, strSessionTime);
        rc = this.LRNSetScopedValue("cmi.core.total_time", strNewTime, strScope, true);
      }

      if( (gc_strCMILessonStatus == strProperty) && (null != this.m_oContentViewerEvts))
      {
        // Top Buble-up completed,passed
        switch (strValue) {
          case "complete":
          case "completed":
          case "passed":
            if ((par=this.LRNGetParent(strScope))!=null)
              if ((par=par.selectSingleNode('/Root/ContentItem'))!=null)
                if ((par_id=par.getAttribute('ID'))!=null)
                  if ((chldrn=this.LRNGetChildren(par_id))!=null) {
                    chldrn_list=chldrn.selectNodes('/Root/ContentItem');
                    var allcompl=true;  // all are completed
                    var excompl=false; // exist complete
                    for (i=0;i<chldrn_list.length;i++) {
                      if ((chld_id=chldrn_list[i].getAttribute('ID'))!=strScope) {
                        chld_status=this.LRNGetScopedValue(gc_strCMILessonStatus,chld_id, true);
                        if ((chld_status!='completed') && (chld_status!='passed') && (chld_status!='complete')) {
                          allcompl=false;
                          break;
                        }
                        if (chld_status=='complete') excompl=true;
                      }
                    }
                    if (allcompl) {
                     if (strValue!='complete')
                        if (excompl) this.LRNSetScopedValue(gc_strCMILessonStatus,"complete",par_id)
                        else this.LRNSetScopedValue(gc_strCMILessonStatus,"completed",par_id);
                     else this.LRNSetScopedValue(gc_strCMILessonStatus,"complete",par_id);
                    }
                  }
            break;
        }
        switch(strValue)
        {
          case "completed":
          case "complete":
          case "incomplete":
          case "failed":
          case "browsed":
          case "not attempted":
          case "passed":
            var oEvent = new Object();
            oEvent.type = gc_LMSAPI_LESSONSTATUS;
            oEvent.id = strScope;
            this.LRNRaiseEvent(oEvent);
            break;
        }
      }
      else if((gc_strCMIExitStatus == strProperty) && (null != this.m_oContentViewerEvts))
      {
        var oEvent = new Object();
        oEvent.type = gc_LMSAPI_EXITSTATUS;
        oEvent.id = strScope;
        this.LRNRaiseEvent(oEvent);
      }
    } else throw new LMSError(this.m_oLMSErrorsXML, 201);
    return "true";
  } catch(e)
  {
    if("undefined" == typeof(e.strDetailsType))
    {
      // Return a general error, because we don't know exactly what happened.
      this.m_oLastError = new LMSError(this.m_oLMSErrorsXML, 101, gc_strJScriptError, e);
    }
    else this.m_oLastError = e;
    return "false";
  }
}

LMSAPI.prototype.LMSCommit = function LMSCommit(strProperty)
{
  try
  {
    // Reset the API's last error so the current call starts clean.
    this.m_oLastError = null;
    if (strProperty != "")
    {
      this.m_oLastError = new LMSError(this.m_oLMSErrorsXML, 201);
      throw this.m_oLastError;
    }
    if (!b_LMSInitialized && strProperty.substring(0, 3) != "lrn")
    {
      this.m_oLastError = new LMSError(this.m_oLMSErrorsXML, 301);
      throw new LMSError(this.m_oLMSErrorsXML, 301);
    }
    if(false == this.m_bNeedSave)
    {
      return "true";
    }

    // Saving XML and state ////////////////////////////////////////////////////////////////////
    this.SavePersXML();
  }
  catch(e)
  {
    if("undefined" == typeof(e.strDetailsType))
    {
      // Return a general error, because we don't know exactly what happened.
      this.m_oLastError = new LMSError(this.m_oLMSErrorsXML, 101, gc_strJScriptError, e);
    }
    else
    {
      this.m_oLastError = e;
    }
    return "false";
  }
  return "true";
}

LMSAPI.prototype.LMSFinish = function LMSFinish(strProperty)
{
  //  This function just resets the LastError object to null.  Data is not committed to the server
  //  until LRNTerminate is called
  try
  {
    if (!b_LMSInitialized)
    {
      this.m_oLastError = new LMSError(this.m_oLMSErrorsXML, 101);
      throw new LMSError(this.m_oLMSErrorsXML, 101);
    }
    else
      b_LMSInitialized = "false";

    if (strProperty != "")
    {
      this.m_oLastError = new LMSError(this.m_oLMSErrorsXML, 201);
      throw new LMSError(this.m_oLMSErrorsXML, 201);
    }

    // Reset the API's last error so the current call starts clean.
    this.m_oLastError = null;

    return "true";
  }
  catch(e)
  {
    if("undefined" == typeof(e.strDetailsType))
    {
      // Return a general error, because we don't know exactly what happened.
      this.m_oLastError = new LMSError(this.m_oLMSErrorsXML, 101, gc_strJScriptError, e);
    }
    else
    {
      this.m_oLastError = e;
    }
    return "false";
  }
}

LMSAPI.prototype.LMSGetLastError = function LMSGetLastError()
{
  if(null == this.m_oLastError)
  {
    return 0;
  }
  return this.m_oLastError.number;
}

LMSAPI.prototype.LMSGetLastErrorString = function LMSGetLastErrorString()
{
  if(this.m_oLastError == null) return "";

  return this.m_oLastError.description;
}

LMSAPI.prototype.LMSGetErrorString = function LMSGetErrorString(nErrorNumber)
{
  try
  {
    error = new LMSError(this.m_oLMSErrorsXML, nErrorNumber);
    return error.description;
  }
  catch(e)
  {
    return "";
  }
}

LMSAPI.prototype.LMSGetDiagnostic = function LMSGetDiagnostic(nErrorNumber)
{
  try
  {
    if(null != nErrorNumber)
    {
      return "";
    }

    if(null == this.m_oLastError || 0 == this.m_oLastError.number)
    {
      return "";
    }

    switch(this.m_oLastError.strDetailsType)
    {
    case gc_strParseError:
      return this.m_oLastError.oErrorDetails.reason;
    case gc_strJScriptError:
      return this.m_oLastError.oErrorDetails.description;
    case gc_strMultiStatusError:
      return this.m_oLastError.oErrorDetails.xml;
    case gc_strString:
      return this.m_oLastError.oErrorDetails;
    default:
      return "";
    }
  }
  catch(e)
  {
  }
  return "";
}


///////////////////////////////////////////////////////////////////////////////////////////////////
//  Private Methods of LMSAPI object
///////////////////////////////////////////////////////////////////////////////////////////////////
LMSAPI.prototype.LoadPersXML = function LoadPersXML()
{
  // If we are in Preview Mode, we do not need to R/W userData
  if(this.m_bPreview==true) return;

  /*this.startPreloader(); // preloader prozatim vypnuty
  this.preloadType("Vyčkejte prosím na načtení obsahu")
  this.preloadInfo("Načítám Vaše studijní výsledky");*/

  // Get the pers XML from USERDATA
  var strPersXML = this.m_oStorage.Load(gc_strUserDataPrefix + this.m_strIdentifier);
  if (!strPersXML) strPersXML= gc_strEmptyPersistXML;
  else
  {
    // MERGE FILES UP TO 1MB PERSIST MEMORY
    var j=1;var cntPersXML=strPersXML;
    while (cntPersXML.length>0) {
      cntPersXML = this.m_oStorage.Load(gc_strUserDataPrefix+this.m_strIdentifier+j);
      if (cntPersXML==null) cntPersXML="";j++;
      strPersXML+=cntPersXML;
    }
    splt_number=j-1;
  }

  // DECOMPRIMATION LRN data, because 128kB (MERGING - 1 MB) limit for PERSIST MEMORY
  try
  {
    for (i=0;i<conv.length;i++){
      this.tracePreloader(parseInt((i+1)*(100/conv.length)));
      while (strPersXML!=strPersXML.replace(conv[i][0],conv[i][1])) strPersXML=strPersXML.replace(conv[i][0],conv[i][1]);
    }
  } catch(err) {alert(err.message);}

  var oXML = LoadXML(strPersXML);
  if(oXML!=null) this.m_oPersXML = oXML;

  this.tracePreloader(100);
  this.stopPreloader();

  return;
}

LMSAPI.prototype.SavePersXML = function SavePersXML()
{
  if (this.m_bPreview) return;

  var strPersXML = "";
  if (IsIE()) strPersXML=this.m_oPersXML.xml;
  else
  {
    var oSerializer = new XMLSerializer();
    strPersXML = oSerializer.serializeToString(this.m_oPersXML, "text/xml");
  }

  if (this.m_oStorage == null) return; // Výsledky neukládej - problém při rychlém reloadování

  if ((strPersXML.length>splt) || (strPersXML.length>compr_limit)) {
    /*this.startPreloader(); // PRELOADER PROZATIM VYPNUTY
    this.preloadType("Vyčkejte prosím na uložení <br/> Vašich výsledků studia");*/
  }

  // COMPRIMATION LRN data, because 128kB (1 MB) limit for PERSIST MEMORY
  if (strPersXML.length>compr_limit) {
    //this.preloadInfo("Komprimuji Vaše studijní výsledky"); // PRELOADER PROZATIM VYPNUTY
    for (i=0;i<conv.length;i++) {
      //this.tracePreloader(parseInt((i+1)*(100/conv.length)));
      while (strPersXML!=strPersXML.replace(conv[i][1],conv[i][0])) strPersXML=strPersXML.replace(conv[i][1],conv[i][0]);
    }
  }

  // SPLIT FILES - UP TO 1MB PERSIST MEMORY
  /*this.preloadInfo("Ukládám Vaše studijní výsledky"); // PRELOADER PROZATIM VYPNUTY
  this.tracePreloader(0);*/
  var step=100/((parseInt((strPersXML.length)/splt))+1);
  var j=0;var cntPersXML="";
  while (strPersXML.length>0) {
    // this.tracePreloader((j+1)*step); // PRELOADER PROZATIM VYPNUTY
    if (strPersXML.length>splt) {
      cntPersXML=strPersXML.substring(0,splt);
      strPersXML=strPersXML.substring(splt,strPersXML.length);
    } else {
      cntPersXML=strPersXML;
      strPersXML="";
    }
    this.m_oStorage.Save(gc_strUserDataPrefix+this.m_strIdentifier+((j==0)?'':j), cntPersXML);
    j++;
  }

  // Vynulovat puvodni data zapasne pres ramec soucasneho ulozeni - pokud napr. komprimaci doslo ke zmenseni poctu souboru v persistentnim prostoru
  for (i=j;i<=splt_number;i++)
    this.m_oStorage.Save(gc_strUserDataPrefix+this.m_strIdentifier+((i==0)?'':i), "");

  /*this.tracePreloader(100); // PRELOADER PROZATIM VYPNUTY
  this.stopPreloader();*/

  this.m_bNeedSave = false;
  return;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
//  Private Methods of LMSAPI object (Exposed to LRN clients)
///////////////////////////////////////////////////////////////////////////////////////////////////
LMSAPI.prototype.LRNInitialize = function LRNInitialize(strUserDataObject, strURLClass, strURLLRN, strXSLPath)
{
  g_LMSInitialize = 0;
  try
  {
    this.m_oLastError = null;

    if(this.m_bInitialized) return true;    // Inicializaci proved pouze jednou
    this.m_bInitialized = true;

    var oError = null;
    try
    {
      if(("string" == typeof(strURLClass)) &&
         ("string" == typeof(strURLLRN))   &&
         ("string" == typeof(strXSLPath)))
      {
        // Inicializace Content Vieweru
        this.m_oContentViewerEvts.Init(this);
        this.m_oContentViewer.Init(strURLClass, strURLLRN, strXSLPath, this);
      }
    }
    catch(e)
    {
      oError = e;
    }

    // Nacteni dat z pezistentni pameti
    this.m_oStorage = new Storage(strUserDataObject);
    this.LoadPersXML();

    if(null != oError) throw oError;

    return true;
  }
  catch(e)
  {
    if("undefined" == typeof(e.strDetailsType))
    {
      // Return a general error, because we don't know exactly what happened.
      this.m_oLastError = new LMSError(this.m_oLMSErrorsXML, 101, gc_strJScriptError, e);
    }
    else
    {
      this.m_oLastError = e;
    }
    return false;
  }
}

LMSAPI.prototype.LRNTerminate = function LRNTerminate()
{
  try
  {
    // Reset the API's last error so the current call starts clean.
    this.m_oLastError = null;

    this.LMSCommit();

    this.m_bInitialized = false;
  }
  catch(e)
  {
    if("undefined" == typeof(e.strDetailsType))
    {
      // Return a general error, because we don't know exactly what happened.
      this.m_oLastError = new LMSError(this.m_oLMSErrorsXML, 101, gc_strJScriptError, e);
    }
    else
    {
      this.m_oLastError = e;
    }
  }
}


LMSAPI.prototype.LRNGetView = function LRNGetView(oXSL)
{
  try
  {
    // Reset the API's last error so the current call starts clean.
    this.m_oLastError = null;

    // Parameter and Object State Check ////////////////////////////////////////////////////////
    // Make sure we are initialized
    if(!this.m_bInitialized || null == this.m_oPersXML)
    {
      throw new LMSError(this.m_oLMSErrorsXML, 301);
    }

    if(null == oXSL)
    {
      return new String(this.m_oPersXML.xml);
    }

    // Make sure that we were passed reasonable inputs
    if("object" != typeof(oXSL))
    {
      throw new LMSError(this.m_oLMSErrorsXML, 201);
    }

    // Run transformation //////////////////////////////////////////////////////////////////////
    return this.m_oPersXML.transformNode(oXSL);
  }
  catch(e)
  {
    if("undefined" == typeof(e.strDetailsType))
    {
      // Return a general error, because we don't know exactly what happened.
      this.m_oLastError = new LMSError(this.m_oLMSErrorsXML, 101, gc_strJScriptError, e);
    }
    else
    {
      this.m_oLastError = e;
    }
    return "";
  }
}

LMSAPI.prototype.InternalGetValue = function InternalGetValue(strProperty,strScope)
{
  var strPattern =  "/ROOT/" + gc_strPersElementName;
  strPattern += "[@" + gc_strPropertyPropName + "=\'" + strProperty + "\']";
  strPattern += "[@" + gc_strScopePropName + "=\'" + strScope + "\']";

  if (!b_LMSInitialized) return;

  try
  {
    var oProperty = this.m_oPersXML.selectSingleNode(strPattern);
  } catch(err)
  {
    oProperty = null;
  }

  if(oProperty == null) return "";
  return oProperty.attributes.getNamedItem(gc_strValuePropName).value;
}

LMSAPI.prototype.InternalSetValue = function InternalSetValue(strProperty,strValue,strScope)
{
  // Setting a property value to xml /////////////////////////////////////////////////////////
  // Example of pattern: /ROOT/LRNpers[@property='assessment1_score' && @scope='ITEM22']
  var strPattern =  "/ROOT/" + gc_strPersElementName;
  strPattern += "[@" + gc_strPropertyPropName + "=\'" + strProperty + "\']";
  strPattern += "[@" + gc_strScopePropName + "=\'" + strScope + "\']";

  if (!b_LMSInitialized) return;

  try
  {
    var oRow    = this.m_oPersXML.documentElement.selectSingleNode(strPattern);
  } catch (err)
  {
    oRow = null;
  }
  // If a property is being added, add a row to the Pers XML
  // If a property is being deleted, remove it from the Pers XML
  // If a property is being updated, change the value in the Pers XML

  // Case 1 Adding a property
  if( (null == oRow) &&
    (! ((null == strValue) || ("" == strValue)) ) )
  {
    var oNewRow       = this.m_oPersXML.createElement(gc_strPersElementName);
    var oProperty     = this.m_oPersXML.createAttribute(gc_strPropertyPropName);
    oProperty.value     = strProperty;
    oNewRow.attributes.setNamedItem(oProperty);

    var oPropertyValue    = this.m_oPersXML.createAttribute(gc_strValuePropName);
    oPropertyValue.value  = strValue;
    oNewRow.attributes.setNamedItem(oPropertyValue);

    var oScope        = this.m_oPersXML.createAttribute(gc_strScopePropName);
    oScope.value      = strScope;
    oNewRow.attributes.setNamedItem(oScope);

    var oTime       = this.m_oPersXML.createAttribute(gc_strTimePropName);
    oTime.value       = ""; // value assigned at the COM object
    oNewRow.attributes.setNamedItem(oTime);

    // Add the new row to Pers XML
    this.m_oPersXML.documentElement.appendChild(oNewRow);

    this.m_bNeedSave = true;

  }

  // case 2 Deleting a property
  if( (null != oRow) &&
    ((null == strValue) || ("" == strValue)) )
  {
    oRow.attributes.removeNamedItem(gc_strValuePropName);

    this.m_bNeedSave = true;
  }

  // case 3 Updating a property
  if( (null != oRow) &&
    (! ((null == strValue) || ("" == strValue)) ) )
  {
    var oPropertyValue = oRow.attributes.getNamedItem(gc_strValuePropName);
    if(null == oPropertyValue)
    {
      // The property value must've been deleted in previous calls to this function
      // Create the new attribute
      oPropertyValue      = this.m_oPersXML.createAttribute(gc_strValuePropName);
      oPropertyValue.value  = strValue;
      oRow.attributes.setNamedItem(oPropertyValue);

      this.m_bNeedSave = true;
    }
    else
    {
      // The property value exists
      if(oPropertyValue.value != strValue)
      {
        // Just change the value
        oPropertyValue.value  = strValue;
        this.m_bNeedSave    = true;
      }
    }
  }

  // otherwise, the only case left is
  //  if( (null == oRow) &&
  //    ((null == strValue) || ("" == strValue)) )
  // this case is ignored since it means deleting a row that does not exist

  return;
}

LMSAPI.prototype.GetLRNProperty = function GetLRNProperty(strProperty, strScope)
{
  try
  {
    var oXML =  this.LRNGetItem(strScope);
    if("lrn.item.title" == strProperty)
    {
      return oXML.documentElement.childNodes[0].getAttribute("Name")
    }
    if("lrn.item.parentid" == strProperty)
    {
      return oXML.documentElement.childNodes[0].getAttribute("ParentID");
    }
    if("lrn.item.haschildren" == strProperty)
    {
      return oXML.documentElement.childNodes[0].getAttribute("HasChildren");
    }
    return "";
  }
  catch(e)
  {
    return "";
  }
}

// PRELOADER FUNCTIONs
LMSAPI.prototype.startPreloader = function startPreloader()
{
  try {
    if (document.all) var xMax=screen.width,yMax=screen.height;
    else
      if (document.layers) var xMax=window.outerWidth,yMax=window.outerHeight;
      else var xMax=800,yMax=600;
    var xOffset = (xMax - 400)/2, yOffset = (yMax - 200)/2;
    myPreloader=window.open(gc_strPreloaderPath,"preloader","height=200,width=400,screenX="+xOffset+",screenY="+yOffset+",top="+yOffset+",left="+xOffset+",status=no,toolbar=no,menubar=no,location=no");
    if (myPreloader.progress_ready) myPreloader.setProgress(0);
  }catch(err){
    myPreloader=null;
  }
}

LMSAPI.prototype.stopPreloader = function stopPreloader()
{
  if (myPreloader==null) return;
  try {
    if (myPreloader.progress_ready) myPreloader.setProgress(100);
    myPreloader.close();
    myPreloader=null;
  }catch(err) {
    myPreloader=null;
  }
}

LMSAPI.prototype.tracePreloader = function tracePreloader(int)
{
  if (myPreloader==null) return;
  try {
    if (myPreloader.progress_ready) myPreloader.setProgress(int);
  }catch(err) {
    myPreloader=null;
  }
}

LMSAPI.prototype.preloadInfo = function preloadInfo(textik)
{
  if (myPreloader==null) return;
  try {
    if (myPreloader.progress_ready) myPreloader.setInfo(textik);
  }catch(err) {
    myPreloader=null;
  }
}

LMSAPI.prototype.preloadType = function preloadType(textik)
{
  if (myPreloader==null) return;
  try {
    if (myPreloader.progress_ready) myPreloader.setType(textik);
  }catch(err) {
    myPreloader=null;
  }
}

LMSAPI.prototype.getAllSubItemsHTML = function getAllSubItemsHTML()
{
  xmlobj=this.m_oContentViewer.getChildrenFromActualID();
  chldrn_list=xmlobj.selectNodes('/Root/ContentItem');
  r="";
  if (chldrn_list.length>0) r+="<ul style='list-style: square; color:#FF8800; margin-left:17px;'>";
  for(i=0;i<chldrn_list.length;i++){
    str1=chldrn_list[i].getAttribute("ID");
    str2=chldrn_list[i].getAttribute("Name");
    r+='<li style="margin-top:2px;"><strong><font size="2" face="Arial"><a href="#" onClick="API.LRNDoSelect(\''+str1+'\'); return false;">'+str2+'</a></font></strong></li>';
  }
  if (chldrn_list.length>0) r+="</ul>";
  return r;
}


///////////////////////////////////////////////////////////////////////////////////////////////////
//
//  Implementation of the LMSError object
//
///////////////////////////////////////////////////////////////////////////////////////////////////
function LMSError(m_oLMSErrorsXML, nNumber, strDetailsType, oErrorDetails)
{
  if(typeof(nNumber) != "number") return null;
  if(typeof(strDetailsType) != "string") strDetailsType = gc_strNoDetails;
  if(typeof(oErrorDetails) != "undefined") oErrorDetails = null;

  this.LMSErrorsXML = m_oLMSErrorsXML
  this.number = nNumber;
  this.strDetailsType = strDetailsType;
  this.oErrorDetails  = oErrorDetails;
  this.description = this.ErrorStringFromID(nNumber);

  return this;
}

LMSError.prototype.ErrorStringFromID = function ErrorStringFromID(nNumber)
{
  try
  {
    var oError = this.LMSErrorsXML.selectSingleNode("/ROOT/LRNError[@ID=" + nNumber + "]");

    if (oError)
    {
      if (typeof(oError.text) == "undefined") return oError.textContent;
      else return oError.text;
    }
  }
  catch(err)
  {
  }
  return "";
}


///////////////////////////////////////////////////////////////////////////////////////////////////
//
//  Implementation of the LMSDataModel object
//
///////////////////////////////////////////////////////////////////////////////////////////////////
function LMSDataModel()
{
  try
  {
    this.m_oXMLInfo = mf_CreateDOM();
    mf_LoadXMLDataAsync(this.m_oXMLInfo, gc_strLMSDataModelPath, this.Loaded);
    // synchronni nacteni v pripade CHROME
    if (IsChrome()) this.m_oXMLInfo = chromeXMLRes;

    if (this.m_oXMLInfo == null) return null;
    else return this;
  }
  catch(e)
  {
    return null;
  }
}

LMSDataModel.prototype.Loaded = function Loaded() {}

LMSDataModel.prototype.IsInDataModel = function IsInDataModel(strProperty)
{
  try
  {
    var strPattern  = "/ROOT/LMSDataModel[@Property='" + strProperty + "']";
    var oNode   = this.m_oXMLInfo.selectSingleNode(strPattern);

    if(oNode) return true;
  }
  catch(e) { }

  return false;
}

LMSDataModel.prototype.IsImplemented = function IsImplemented(strProperty)
{
  try
  {
    var strPattern  = "/ROOT/LMSDataModel[@Property='";
    strPattern    +=  strProperty;
    strPattern    +=  "']";

    var oNode   = this.m_oXMLInfo.selectSingleNode(strPattern);
    if("true" == oNode.getAttribute("Implemented"))
    {
      return true;
    }
  }
  catch(e)
  {
  }
  return false;
}

LMSDataModel.prototype.IsKeyword = function IsKeyword(strProperty)
{
  try
  {
    var strPattern  = "/ROOT/LMSDataModel[@Property='";
    strPattern    +=  strProperty;
    strPattern    +=  "']";

    var oNode   = this.m_oXMLInfo.selectSingleNode(strPattern);
    if("true" == oNode.getAttribute("Keyword"))
    {
      return true;
    }
  }
  catch(e)
  {
  }
  return false;
}

LMSDataModel.prototype.GetDefaultValue = function GetDefaultValue(strProperty)
{
  try
  {
    var strPattern  = "/ROOT/LMSDataModel[@Property='";
    strPattern    +=  strProperty;
    strPattern    +=  "']";

    var oNode   = this.m_oXMLInfo.selectSingleNode(strPattern);
    if(null != oNode.getAttribute("DefaultValue"))
    {
      return oNode.getAttribute("DefaultValue");
    }
  }
  catch(e)
  {
  }
  return "";
}

LMSDataModel.prototype.IsAllowRead = function IsAllowRead(strProperty)
{
  try
  {
    var strPattern  = "/ROOT/LMSDataModel[@Property='";
    strPattern    +=  strProperty;
    strPattern    +=  "']";

    var oNode   = this.m_oXMLInfo.selectSingleNode(strPattern);
    if("true" == oNode.getAttribute("Read"))
    {
      return true;
    }
  }
  catch(e)
  {
  }
  return false;
}

LMSDataModel.prototype.IsAllowWrite = function IsAllowWrite(strProperty)
{
  try
  {
    var strPattern  = "/ROOT/LMSDataModel[@Property='";
    strPattern    +=  strProperty;
    strPattern    +=  "']";

    var oNode   = this.m_oXMLInfo.selectSingleNode(strPattern);
    if("true" == oNode.getAttribute("Write"))
    {
      return true;
    }
  }
  catch(e)
  {
  }
  return false;
}

LMSDataModel.prototype.IsValidVocabulary = function IsValidVocabulary(strProperty, strValue)
{
  try
  {
    var strPattern  = "/ROOT/LMSDataModel[@Property='";
    strPattern    +=  strProperty;
    strPattern    +=  "']";

    var oNode = this.m_oXMLInfo.selectSingleNode(strPattern);
    if("true" == oNode.getAttribute("IsControlledVocabulary"))
    {
      strPattern += "/LMSVocabulary";
      var oVocabNode = this.m_oXMLInfo.selectNodes(strPattern);
      if (oVocabNode)
      {
        for (var i=0; i<oVocabNode.length; i++)
        {
          if (typeof(oVocabNode[i].text) == "undefined") sValue=oVocabNode[i].textContent;
          else sValue=oVocabNode[i].text;
          if (strValue == sValue) return true;
        }
      }
      return false;
    }
    else
      return true;

  }
  catch(e)
  {
  }
  return false;
}

LMSDataModel.prototype.IsItemScoped = function IsItemScoped(strProperty)
{
  try
  {
    var strPattern  = "/ROOT/LMSDataModel[@Property='";
    strPattern    +=  strProperty;
    strPattern    +=  "']";

    var oNode   = this.m_oXMLInfo.selectSingleNode(strPattern);
    if("item" == oNode.getAttribute("Scope")) return true;
  }
  catch(e)
  {
  }

  return false;
}


function Storage(db)
{
  this.db = db;
  this.gid = function(id)
  {
    return document.getElementById(id);
  }
}


Storage.prototype.Load = function Load(tablename)
{
  var val="";

  if(this.db)
  {
    try
    {
      if (IsFF2())
      {
          var data_storage = globalStorage[document.domain];
          val = data_storage.getItem(tablename);
      }
      else
      if(IsIE())
      {
        var oUserData = this.gid(this.db);
        oUserData.load(tablename);
        val=oUserData.getAttribute('gopas');
      }
    } catch (err)
    {
      //alert("Došlo k chybě při načítání dat z perzistentního prostoru:"+err.message);
    }
  }
  if (val==null) return "";
  else return new String(val);
}

Storage.prototype.Save = function Save(tablename, val)
{
  if ((val) && (this.db))
  {
    try
    {
      if (IsFF2())
      {
        var data_storage = globalStorage[document.domain];
        data_storage.setItem(tablename,val);
      }
      else
      if (IsIE())
      {
        var oUserData = this.gid(this.db);
        oUserData.load(tablename);
        oUserData.setAttribute('gopas', val);
        oUserData.save(tablename);
      }
    } catch(err)
    {
      //alert("Došlo k chybě při ukládání dat do perzistentního prostoru:"+err.message);
    }
    return true;
  }
  return false;
}