﻿/* ScormManager handles interactions with the SCORM 1.2 API.
 * Copyright(c) CM Group. All Rights Reserved.
 */

/* ScormManager singleton class handles
 * interactions with the SCORM 1.2 API  
 */
Luminosity.ScormManager = (function() {

    /* Luminosity.ScormManager singleton instance */
    var scormManagerInstance = null;

    SuspData_ComponentData = function() {
        this.navItemSequence = null;
        this.assesmentResults = new Array();
    }

    /* Private constructor */
    function ScormHandler() {
        this.API = null;
        this.hasAPIExtensions = false;
        this.resumeModule = false;
        this.Navigation = null;
        this.Player = null;
        this.Student = {
            ID: "",
            Name: ""
        }
        this.LessonStatus = "";
        this.Entry = "";
        //SUSPEND DATA RELATED VARS
        this.ResumePage = "";
        this.SuspData_NavItemStates = null;
        this.SuspData_ComponentDataList = null;
        this.SuspData_AsmtRetryList = null;

        this.scormCompletedPages = new Array();

    }

    /* ScormHandler prototype,only accessible to 
    * scormManagerInstance object 
    */
    ScormHandler.prototype =
    {
        /* Initializes the LMS */
        InitializeLMS: function() {
            var initialized = doLMSInitialize();
            return initialized;
        },

        /* Gets the LMS Value for the supplied parameter*/
        GetValue: function(lmsparameter) {
            return doLMSGetValue(lmsparameter);
        },

        /* Sets the LMS Value for the supplied parameter */
        SetValue: function(lmsparameter, value) {
            doLMSSetValue(lmsparameter, value);
        },

        /* Gets the Student ID and Name from the LMS */
        GetStudentInfo: function() {
            if (this.API) {
                try {

                    this.Student.ID = this.GetValue("cmi.core.student_id");
                    this.Student.Name = this.GetValue("cmi.core.student_name");

                    if (this.Student.Name.charAt(this.Student.Name.length - 1) == ",") {
                        this.Student.Name = this.Student.Name.substring(0, this.Student.Name.length - 1);
                    }
                }
                catch (e) {
                    alert("Error accessing student credentials (" + e.message + ")");
                    return null;
                }

                return this.Student;
            }
        },

        CheckComplete: function() {
            if (this.scormCompletedPages.length == navItems.length) {
                this.SetValue("cmi.core.lesson_status", "completed");
            }
            else if (this.scormCompletedPages.length > 0) {
                this.SetValue("cmi.core.lesson_status", "incomplete");
            }
        },

        /* Reads the suspend data.
        * Configures page states and assessment results from the previous session.
        * Restores the course from the saved course location in lms.        
        */
        ResumeFromSuspendData: function() {

            if (this.SuspData_NavItemStates == null ||
               this.SuspData_ComponentDataList == null) {
                self.status = "error, unable to resume from suspend data.";
            }
            else {
                this.resumeModule = true;
                if (pageMgr != null) {
                    pageMgr.DeserializeFromSuspendData(false);
                }
            }
        },

        /* Reads the suspend data from the lms. 
        * and sets the navItemStates and component data.
        * sample suspend data: 
        * [PS]2,2,2,2,2,2,2,2,2,0,3,3,3,3,3,[/PS]
        * [C]1:;2:;3:50,50,50,100;4:;5:;6:;7:;8:0,60,20,100;9:;10:;11:;12:;13:;14:;15:;[/C] 
        * PS - Page States
        * C - component data (only for assessments viz. selftest and drag and drop) 
        */
        ReadSuspendData: function() {

            var suspendData = this.GetValue("cmi.suspend_data");

            if (suspendData != null || suspendData != "") {

                var pagesStartTag = "[PS]";
                var pagesEndTag = "[/PS]";
                var componentStartTag = "[C]";
                var componentEndTag = "[/C]";
                var retryStartTag = "[R]";
                var retryEndTag = "[/R]";
                this.SuspData_NavItemStates = new Array();
                this.SuspData_ComponentDataList = new Array();
                this.SuspData_AsmtRetryList = new Array();

                //Get NavItem States

                var stpos = suspendData.indexOf(pagesStartTag) + 4;
                var endpos = suspendData.indexOf(pagesEndTag) - 4;

                var statestring = suspendData.substr(stpos, endpos);
                if (statestring !== null) {
                    var statesArr = statestring.split(",");
                    for (var i = 0; i < statesArr.length; i++) {
                        this.SuspData_NavItemStates.push(statesArr[i]);
                    }
                }

                //Get ComponentData;

                var comp_stpos = suspendData.indexOf(componentStartTag) + 3;
                var comp_enpos = suspendData.indexOf(componentEndTag) - 3;

                var comp_string = suspendData.substr(comp_stpos, comp_enpos);

                if (comp_string !== null) {
                    var comp_data = comp_string.split(';');
                    if (comp_data !== null) {
                        for (var i = 0; i < comp_data.length; i++) {

                            var pagedata = comp_data[i].split(':');

                            if (pagedata[1] == null) {
                                break;
                            }

                            var cdata = new SuspData_ComponentData();
                            cdata.navItemSequence = pagedata[0];

                            var scores = pagedata[1].split(',');

                            for (var j = 0; j < scores.length; j++) {
                                cdata.assesmentResults.push(scores[j]);
                            }

                            this.SuspData_ComponentDataList.push(cdata);
                        }
                    }
                }

                //Get Assessment Retries with score

                var r_stpos = suspendData.indexOf(retryStartTag) + 3;
                var r_endpos = suspendData.indexOf(retryEndTag) - 3;
                var asmtretrystring = suspendData.substr(r_stpos, r_endpos);
                var asmt_objects = asmtretrystring.split(";");

                if (asmt_objects !== null) {

                    for (var i = 0; i < asmt_objects.length-1; i++) {

                        var asmt_data = asmt_objects[i].split(":");
                        var asmt_navitemseq = null;
                        var asmt_score = null;
                        var asmt_retry = null;

                        if (null !== asmt_data[0]) {
                            asmt_navitemseq = asmt_data[0];
                        }

                        if (null !== asmt_data[1]) {
                            asmt_scoredata = asmt_data[1].split(",");
                            if (null !== asmt_scoredata[0]) {
                                asmt_score = asmt_scoredata[0];
                            }
                            if (null !== asmt_scoredata[1]) {
                                asmt_retry = asmt_scoredata[1];
                            }
                        }

                        if (asmt_navitemseq !== null) {
                            var asmtstate = new AssessmentState();
                            asmtstate.navitemSequence = asmt_navitemseq;
                            asmtstate.score = asmt_score;
                            asmtstate.retrycount = asmt_retry;
                            asmtstate.id = -1;
                            this.SuspData_AsmtRetryList.push(asmtstate);							
							pageMgr.asmtsretrylist = this.SuspData_AsmtRetryList;
                        }
                    }
                }
            }
        },

        /// Handles the resuming of the course from a previous session
        /// when the course is set to Completed. Configures the course from
        /// the suspend data but starts from page 1.
        ResumeCompleted: function() {
            this.ReadSuspendData();
            if (this.SuspData_NavItemStates == null ||
               this.SuspData_ComponentDataList == null) {
                self.status = "error, unable to resume from suspend data.";
            }
            else {
                this.resumeModule = true;
                if (pageMgr != null) {
                    pageMgr.DeserializeFromSuspendData(true);
                }
            }
        },

        /* Handles the resuming of the course from a previous session.     
        * Goes to the last saved page and checks suspend data.
        */
        ResumeCourse: function() {
            var LessonLocation = this.GetValue("cmi.core.lesson_location");
            if (LessonLocation != "") {
                this.ResumePage = LessonLocation;
            }

            try {
                this.ReadSuspendData();
            }
            catch (err) {
                self.status = "error reading suspend data..";
            }

            try {
                this.ResumeFromSuspendData();
            }
            catch (err) {
                self.status = "error resuming course..";
            }
        },

        /* Gets Student details and resumes the course */
        ConfigureSCO: function() {
            var Student = this.GetStudentInfo();
            if (null != Student) {
                //Update UI if needed
                //alert(Student.ID + " " + Student.Name);
            }

            this.Entry = this.GetValue("cmi.core.entry");
            this.LessonStatus = this.GetValue("cmi.core.lesson_status");

            if (this.Entry == "resume") {
                // If status is "incomplete" carry on from previous page,
                // Otherwise course is either completed, passed, failed, not attempted
                // so just continue from beginning
                if (this.LessonStatus == "incomplete") {
                    this.ResumeCourse();
                }
                else if (this.LessonStatus == "not attempted") {
                    this.SetValue("cmi.core.lesson_status", "incomplete");
                }
            }
            else {
                // Check status in case for some reason the LMS passes the entry
                // method as an empty string but the SCO has been started
                // If completed, passed, failed resume in completed state,
                // otherwise do nothing so course starts from page 1
                if (this.LessonStatus == "incomplete") {
                    this.ResumeCourse();
                }
                else if (this.LessonStatus == "not attempted") {
                    // First time user so set to incomplete and start from page 1
                    this.SetValue("cmi.core.lesson_status", "incomplete");
                }
                else if (this.LessonStatus == "completed"
                    || this.LessonStatus == "passed"
                    || this.LessonStatus == "failed") {
                    this.ResumeCompleted();
                }
            }
        },

        /* Gets the APIAdapter,
        * Initializes the LMS and Resumes the Course
        * based on student progress
        */
        InitializeSCO: function() {
            var APIInstance = GetApi();
            if (APIInstance) {
                this.API = APIInstance;
                storeAPIRef(APIInstance);

                //Initialize with LMS
                var initialized = this.InitializeLMS();
                if (initialized == "false") {
                    alert("Error: Failed to initialize session with LMS. Learner progress will not be tracked.");
                    return;
                }
                this.ConfigureSCO();
            }
        },

        /* Displays an error encountered with SCO 
        * based on the @message - error code
        */
        GetSCOError: function(message) {
            var errorCode = doLMSGetLastError();
            var errorDesc = doLMSGetDiagnostic(errorCode);
            var errorDescSpecific = doLMSGetDiagnostic("");
            var messageString = message ? message : "No Message";
            alert("Error: " + messageString + "\nLast error [" + errorCode + "] (" + errorDesc + ")\nDetails: " + errorDescSpecific);
        },


        /* Saves the current location of the student in the course  */
        SaveLocation: function(pageNo) {
            this.SetValue("cmi.core.lesson_location", pageNo);
        },

        /* Records an interaction with the LMS */
        RecordInteraction: function(id, type, correctResponses, weighting) {
            var index = 0;
            if (this.API) {
                var count = this.GetValue("cmi.interactions._count");
                if (count != null) {
                    index = count;
                }
                try {

                    //Set value of the question id
                    if (id != null) {
                        var interactions = "cmi.interactions." + index + ".id";
                        this.SetValue(interactions, id);
                    }

                    //Set the value for the type of question
                    if (type != null) {
                        var strtype = "cmi.interactions." + index + ".type";
                        this.SetValue(strtype, type);
                    }

                    //Set the value for the correct response
                    if (correctResponses != null) {
                        var strresponses = "cmi.interactions." + index + ".correct_responses.0.pattern";
                        this.SetValue(strresponses, correctResponses);
                    }

                    //Set the value for the weighting
                    var strweighting = "cmi.interactions." + index + ".weighting";
                    this.SetValue(strweighting, weighting);

                }
                catch (e) {
                    //TODO: handle error gracefully without displaying the message
                    self.status = "Error recording scorm interaction..";
                }
            }

            return index;

        },

        /* Saves the result for an interaction in the LMS */
        SaveInteractionResults: function(index, response, result, latency) {
            if (this.API) {

                if (response != null) {
                    var strstudresponse = "cmi.interactions." + index + ".student_response";
                    this.SetValue(strstudresponse, response);
                }
                if (result != null) {
                    var strresult = "cmi.interactions." + index + ".result";
                    this.SetValue(strresult, result);
                }
                if (latency != null) {
                    var strlatncy = "cmi.interactions." + index + ".latency";
                    this.SetValue(strlatncy, latency);
                }

                //Save the current time of completion
                var strtime = "cmi.interactions." + index + ".time";
                var date = new Date();
                this.SetValue(strtime, date.toLocaleTimeString());

            }
        },

        /* Exits the course. Finishes the session with the LMS,
        * and sets data for resume
        */
        ExitSCO: function() {
            try {
                var status = this.GetValue("cmi.core.lesson_status");
                if (status == "incomplete") {
                    this.SetValue("cmi.core.exit", "suspend");
                }
                else {
                    this.SetValue("cmi.core.exit", "");
                }
                //check if complete and set status accordingly..
                doQuit("incomplete");

            }
            catch (e) {
                alert('Error quitting sco..');
            }
        },

        /* Sets the suspend data for when the course is resumed.
        * Adds the state for each page and any saved assessment scores.
        */
        SetSuspendData: function(navItems, assesmentData, asmtsretrylist) {

            var pageData = new Array();
            var assesmentResults = new Array();
            var asmtretries = new Array();
            var suspendData = "";

            if (this.API) {

                //page states.
                pageData.push("[PS]");

                for (var i = 0; i < navItems.length; i++) {
                    pageData.push(navItems[i].state);
                    pageData.push(",");
                }

                pageData.push("[/PS]");

                //assement data.
                assesmentResults.push("[C]");			
				
                for (i = 0; i < navItems.length; i++) {
					if(null==navItems[i]||
					   null==navItems[i].sequence) {
					   continue;
					}
					else {
                    assesmentResults.push(navItems[i].sequence + ":");
                    var asmtScore = new Array();

					for (var j = 0; j < assesmentData.length; j++) {
							try{
	                        if (navItems[i].guid == assesmentData[j].navItem.guid) {								
	                            asmtScore.push(assesmentData[j].score);
	                        }
							}catch(err)
							{
							    //asmtScore.push(0);								
							}
	                }
					for (j = 0; j < asmtScore.length; j++) {		
						
						if(asmtScore[j]!==""){
							assesmentResults.push(asmtScore[j]);
							assesmentResults.push(",");
						}
					}
					
	                }
                    assesmentResults.push(";");
                }

                assesmentResults.push("[/C]");

                asmtretries.push("[R]");
                for (i = 0; i < asmtsretrylist.length; i++) {
                    asmtretries.push(asmtsretrylist[i].navitemSequence + ":");
                    asmtretries.push(asmtsretrylist[i].score);
                    asmtretries.push(",");
                    asmtretries.push(asmtsretrylist[i].retrycount);
                    asmtretries.push(";");
                }
                asmtretries.push("[/R]");

                for (var k = 0; k < pageData.length; k++) {
                    suspendData += pageData[k];
                }

                for (k = 0; k < assesmentResults.length; k++) {
                    suspendData += assesmentResults[k];
                }

                for (k = 0; k < asmtretries.length; k++) {
                    suspendData += asmtretries[k];
                }

                //set suspend data
                this.SetValue("cmi.suspend_data", suspendData);
            }
        },


        two: function(x) { return ((x > 9) ? "" : "0") + x },

        TimeElapsedHHMMSSMS: function(ms) {

            var sec = Math.floor(ms / 1000);
            ms = ms % 100;
            t = this.two(ms);

            var min = Math.floor(sec / 60);
            sec = sec % 60;
            t = this.two(sec) + "." + t;

            var hr = Math.floor(min / 60);
            min = min % 60;
            t = this.two(min) + ":" + t;

            var day = Math.floor(hr / 60);
            hr = hr % 60;
            t = this.two(hr) + ":" + t;

            return t;
        }

    }

    /* Anonymous method which returns the singleton instance */
    return new function() {
        this.getInstance = function() {
            if (scormManagerInstance == null) {
                scormManagerInstance = new ScormHandler();
                scormManagerInstance.constructor = null;
            }
            return scormManagerInstance;
        }
    }

})();