scormManager = {
		
	//////////////////////////////////////////////
	//////// CONFIGURATION VALUES ////////////////
	//////////////////////////////////////////////
	debugMode: false
	,sendPercents: false
	//////////////////////////////////////////////
	//////// LMS     VALUES //////////////////////
	//////////////////////////////////////////////
	,userName: 			"user"
	,userId: 			"userId"
	,masteryScore: 		"75"		// Must be string; supposed to come from LMS as a string
	,maxPoints: 		0			// Must be number
	,minPoints: 		0			// Must be number
	,userPoints: 		"0"			// Must be string; supposed to come from LMS as a string
	,startSession: 		null
	,timeLimit: 		"900" 		// Format string "01:30:00" // Player should get seconds
	,lastLocation: 		"1"			// Must be string; supposed to come from LMS as a string
	,lastAudioPref: 	"ON"
	,suspendData:		""
	,lessonStatus: 		"incomplete"
	
	,API: {
		handle: null
		,isFound: false
		,isConnected: false
		,version: ""
	}
	
	,lastError: {
		errorCode: 0
		,errorDesc: ""
	}
	
	,parameters: {
		1.2: {
			status: 		"cmi.core.lesson_status"
			,location: 		"cmi.core.lesson_location"
			,duration: 		"cmi.core.session_time"
			,maxPoints: 	"cmi.core.score.max"
			,minPoints: 	"cmi.core.score.min"
			,userPoints: 	"cmi.core.score.raw"
			,masteryScore: 	"cmi.student_data.mastery_score"
			,userAudioPref: "cmi.student_preference.audio"
			,userName:		"cmi.core.student_name"
			,userId: 		"cmi.core.student_id"
			,timeLimit: 	"cmi.student_data.max_time_allowed"
			,suspendData: 	"cmi.suspend_data"
			,sitosVersion:	"sitos._version"
		}		

		,2004: {
			status: 		"cmi.completion_status"
			,location: 		"cmi.location"
			,duration: 		"cmi.session_time"
			,maxPoints: 	"cmi.score.max"
			,minPoints: 	"cmi.score.min"
			,userPoints: 	"cmi.score.raw"
			,masteryScore: 	"cmi.scaled_passing_score"
			,userAudioPref: "cmi.learner_preference.audio_level"
			,userName:		"cmi.learner_name"
			,userId: 		"cmi.learner_id"
			,timeLimit: 	"cmi.max_time_allowed"
			,suspendData: 	"cmi.suspend_data"
			,sitosVersion: 	"cmi._version"
		} 
	}
	////////////////////////////////////////////////////////////////////
	////////////// Public methods //////////////////////////////////////
	////////////////////////////////////////////////////////////////////
	,initialize: function(){
		//this._findAPI();
		this._getAPI();
		this._connect();
		//if(typeof scormManager.detectEnvironment != "undefined"){scormManager.detectEnvironment()}
	}
	
	,getDefaultValue: function(paramAlias){
		var defaultValues = {
			"status": 			this.lessonStatus	
			,"location": 		this.lastLocation
			,"duration": 		this._getSessionDurationString()
			,"maxPoints": 		this.maxPoints
			,"minPoints": 		this.minPoints
			,"userPoints": 		this.userPoints
			,"masteryScore": 	this.masteryScore
			,"userAudioPref": 	this.lastAudioPref
			,"userName":		this.userName
			,"userId": 			this.userId
			,"timeLimit": 		this.timeLimit
			,"suspendData": 	this.suspendData
		}
		
		if(typeof defaultValues[paramAlias] !== "undefined"){
			return defaultValues[paramAlias];
		}
	}
	
	,getParam: function(paramAlias){
		if(typeof paramAlias !== "string"){return}
		if(!this.API.isConnected){return this.getDefaultValue(paramAlias)}
		var traceMsgPrefix = "GetParam(" +paramAlias +") (alias)";
	    var value = "";
		var success = false;
		// Find the right param given an alias
		var parameter = this.parameters[this.API.version][paramAlias]
	    if(typeof parameter === "string" && parameter !== ""){  
	        var API = this._getHandle();
			if(API){
				switch(this.API.version){
					case "1.2" : value = API.LMSGetValue(parameter); break;
					case "2004": value = API.GetValue(parameter); break;
				}
	        }else {
	            this.trace(traceMsgPrefix +"failed: API is null.");
	        }
			
	    } else {
			this.trace(traceMsgPrefix +"failed: API connection is inactive or parameter is invalid");
	    }
		// Special params
		if(paramAlias == "userAudioPref"){
			switch (this.API.version){
				case "1.2":
					value = (value != "-1") ? "ON" : "OFF";
				break;
				
				case "2004":
					value = (value != "0") ? "ON" : "OFF";
				break;
				
				default:
					value = "ON";
				break;
			}
			this.lastAudioPref = value;
		}
		
		if(paramAlias == "masteryScore"){
			if(value == "" || typeof value === "undefined"){
				value = this.masteryScore;
			}
			this.masteryScore = Number(value);
		}
		
		if(paramAlias == "timeLimit"){
			if(value == "" || typeof value === "undefined"){
				value = this.timeLimit;
			}
			
			var aTime = value.split(":");
			var hSconds = Number(aTime[0])*3600;
			var mSconds = Number(aTime[1])*60;
			var sSconds = Number(aTime[2]);
			value = this.timeLimit = hSconds+mSconds+sSconds;
		}
		
		if(paramAlias == "location"){
			if(value == "" || typeof value === "undefined"){
				value = this.lastLocation;
			}
		}

		if(paramAlias == "userPoints"){
			if(value == "" || typeof value === "undefined"){
				tmpValue = "0";
			}else{
				var tmpValue;
				if(scormManager.sendPercents){
					var percent = Number(value);
					var maxPoints = Number(this.getParam("maxPoints"));
					var rawPoints = Math.round(percent*maxPoints/100);
					tmpValue = String(rawPoints);
				}else{
					tmpValue = Number(value);
				}
			}
			this.userPoints = value = String(tmpValue);
		}

		// /Special params
		this.trace(traceMsgPrefix + " "+ String(value));
		return String(value);
	}
	
	,setParam: function(paramAlias, value){
		
		var traceMsgPrefix = "SetParam";
		if(!this.API.isConnected){return false;}
		if(typeof paramAlias !== "string" || paramAlias === ""){return false}
		var parameter = this.parameters[this.API.version][paramAlias];
		if(typeof parameter !== "string"){return false}
		
		//this.trace(traceMsgPrefix+ " " +parameter +" with value: "+value);
		switch(paramAlias){
			case "duration":
				value = this._getSessionDurationString();
			break;
			
			case "maxPoints":
				this.maxPoints = Number(value); 
			break;
			
			case "minPoints":
				this.minPoints = Number(value);
			break;
			
			case "userAudioPref":
				value = this._getUserAudioPrefString(value);
			break;
			
			case "userPoints":
				// Check here lesson status. When "completed", make nothing
				//if(this.getParam("status") === "completed"){return false}
				this.userPoints = Number(value);
				
				// :UGLY: But the only way that some LMS seem to handle fine the points,
				// is sending the percent directly.
				if(this.sendPercents){
					var masteryPercent = this.masteryScore;
					 switch(this.API.version){
					 	case "1.2":
							percent = Math.round(Number(masteryPercent));
						break;
						
						case "2004":
							percent = Math.round(Number(masteryPercent*100));
						break;	
					 }
					 
					 if(typeof this.userPoints !== "number" 
						|| typeof this.maxPoints !== "number" 
						|| typeof percent !== "number"){return false}
						
					// Forced passed lesson
					if(this.maxPoints == 0 || this.userPoints == 1){
						value = "100";
					}else{
						value = String(Math.round((this.userPoints/this.maxPoints)*100));						
					}	
				}
			break;
			
			case "status":
			// Check here the lesson status. Only allow set when lesson status != "completed"
				if(this.getParam("status") === "completed"){
					return false;
				}else{
					
					var lessonStatus = "incomplete";
					var percent = this.masteryScore;
					if(this.API.version == "1.2"){
						if(this.getParam("masteryScore") !== ""){
							percent = Math.round(Number(this.getParam("masteryScore")));
						}
					}else if(this.API.version == "2004"){
						if(this.getParam("masteryScore") !== ""){
							percent = Math.round(Number(this.getParam("masteryScore")*100));
						}
					}
					// When lesson is not completed, instead of "failed" we set it to incomplete to manage the lesson status in some LMS showing the lesson status
					// As requested norbert and users with low self-confidence.
					this.trace("userpoints:"+this.userPoints);
					this.trace("maxpoints:"+this.maxPoints);
					this.trace("percent:"+percent);
					var userPercent = Math.round((this.userPoints/this.maxPoints)*100);
					var lessonStatus = (userPercent>=percent) ? "completed" : "incomplete";
					
					// Exception: when calling forcePassedLesson, we have to put the lesson status
					if(typeof value === "string" && value == "completed"){
						lessonStatus = "completed";
					}
				}
				value = this.lessonStatus = lessonStatus;				
			break;
			
			case "location":
				// this.lastLocation = Number(value);
				this.lastLocation = String(value); // 
			break;
			
			case "suspendData":
				value = String(value);
			break;

			case "duration":
				value = this._getSessionDurationString();
			break; 
			
			default:
				value = "";
			break;
		}
		
		if(this.API.isConnected){  
        	var API = this._getHandle();
			if(API){
				// Setting always strings
				if (typeof value !== "string") {
					value = String(value);
				}
				this.trace(parameter+" with value "+value);
				switch(this.API.version){
					case "1.2" : success = API.LMSSetValue(parameter, value); break;
					case "2004": success = API.SetValue(parameter, value); break;
				}
        	}else {
            	trace(traceMsgPrefix +"failed: API is null.");
        	}
    	} else {
        	trace(traceMsgPrefix +"failed: API connection is inactive");
    	}
		this.trace(traceMsgPrefix + "value: "+value+" success:"+ String(success));
		return success;
	}
	
	,saveData: function(){
		var	traceMsgPrefix = "scormManager.saveData: ";
		var success = false;
	    if(this.API.isConnected){
	        var API = this._getHandle();
	        if(API){
				switch(this.API.version){
					case "1.2" : success = API.LMSCommit(""); break;
					case "2004": success = API.Commit(""); break;
				}
	
	        } else {
				this.trace(traceMsgPrefix +": API not found.");
	        }
	
	    } else {
	        this.trace(traceMsgPrefix +": API connection is inactive.");
	    }
		this.trace(traceMsgPrefix + " "+ success);
	    return success;
	}
	
	,closeContent: function(){
		// Saving data and trying a custom close
		var saveSuccess = this.saveData();
		this.API.handle.LMSFinish();
		if(typeof this.customClose === "function"){
			this.customClose();
		}else{
			window.close();
		}
	}
	
	////////////////////////////////////////////////////////////////////
	////////////// Private methods //////////////////////////////////////
	////////////////////////////////////////////////////////////////////
	,_connect: function(){
		
		if(this.startSession === null){
			this.startSession = new Date();			
		}

		var	traceMsgPrefix = "SCORM.connection.initialize ";
		var completionStatus = "";
		var success = false;
		
	    if(!this.API.isConnected){
	        var API = this._getHandle();
	        
	        if(API){
				switch(this.API.version){
					case "1.2" : success = API.LMSInitialize(""); break;
					case "2004": success = API.Initialize(""); break;
				}
	
	            if(success){
					
	                this.API.isConnected = true;
					
					// Currently deactivated
					// TODO: Test
					if(this.autoHandleCompletionStatus){				
						//Automatically set new launches to incomplete 
						//completionStatus = this.handleCompletionStatus("get");
						if(completionStatus){
							switch(completionStatus){
								
								//Both SCORM 1.2 and 2004
								case "not attempted": 
									this.handleCompletionStatus("set", "incomplete"); 
									break;
								
								//SCORM 2004 only
								case "failed":
									this.handleCompletionStatus("set", "incomplete"); 
									break;
								
								//Additional options, presented here in case you'd like to use them
								//case "completed"  : break;
								//case "incomplete" : break;
								//case "passed"     : break;	//SCORM 1.2 only
								//case "failed"     : break;	//SCORM 1.2 only
								//case "browsed"    : break;	//SCORM 1.2 only	
								}	
							}	
						}
	            } else {
					this.trace(traceMsgPrefix +"failed: No response from server.");	
	            }
	        } else {
	            this.trace(traceMsgPrefix +"failed: API is null.");
	        }
	    } else {
	          this.trace(traceMsgPrefix +"aborted: Connection already active.");    
			var API = this._getHandle();
	        if(API){
				switch(this.API.version){
					case "1.2" : success = API.LMSFinish(""); break;
					case "2004": success = API.Terminate(""); break;
				}
			}
			this._connect();
	     }
		 
	     return success;
	}
	
	// Will always look for the API as an object of window.
	,_findAPI: function(win){
		
		if(typeof(win) == "undefined"){
			win = window;
		}
		//var win = window;
		var API = null;
		var findAttempts = 0;
	    var findAttemptLimit = 500;
		var traceMsgPrefix = "Scorm Find api: ";
	
	    while ((!win.API && !win.API_1484_11) &&
	           (win.parent) &&
	           (win.parent != win) &&
	           (findAttempts <= findAttemptLimit)){
	                findAttempts++; 
	                win = win.parent;
	
	    }
	
		if(this.API.version !== ""){	//If SCORM version is specified by user, look for specific API
			
			switch(this.API.version){
				case "2004" : 
					if(win.API_1484_11){
						API = win.API_1484_11;
					} else {
						this.trace(traceMsgPrefix +": SCORM version 2004 was specified by user, but API_1484_11 cannot be found.");	
					}
				break;
					
				case "1.2" : 
					if(win.API){
						API = win.API;
					} else {
						this.trace(traceMsgPrefix +": SCORM version 1.2 was specified by user, but API cannot be found.");
					}
				break;	
			}
			
		}else{														//If SCORM version not specified by user, look for APIs
			if(win.API_1484_11) {									//SCORM 2004-specific API.
				this.API.version = "2004"
				API = win.API_1484_11;
			} else if(win.API){										//SCORM 1.2-specific API
				this.API.version = "1.2";							
				API = win.API;
			}
		}
		
		
		if(API){
			this.API.isFound = true;
			this.API.handle = API;
		}else{
			// No api found...procedure?
		}
		return this.API.handle;
	}
	
	
	,_getAPI: function(){
		
		var theAPI = this._findAPI();
		
		if( (theAPI == null) && (window.opener != null) && (typeof(window.opener) != "undefined") ){
			theAPI = this._findAPI(window.opener);
		}
		
		if(theAPI == null){
			this.trace("Sorry, but i am unable to find an API adapter.");	
		}
	}
	
	
	
	,_getHandle: function(){
		
		if(this.API.isFound && this.API.handle !== null){
			return this.API.handle;
		}else{
			return this._getAPI();
		}
	}

	
	,_millisecondsToPrettyDate: function(elapsedMS){
		var milliseconds = Math.round(elapsedMS%1000);
		var seconds = Math.round((elapsedMS/1000) % 60);
		var minutes = Math.round((elapsedMS/60000) % 60);
		var hours = Math.round((elapsedMS/3600000) % 24);
		var millisecondsStr =  (milliseconds<10 ? "00" : (milliseconds<100 ? "0" : ""))+milliseconds;
    	var secondsStr = (seconds<10 ? "0" : "")+seconds; 
    	var minutesStr = (minutes<10 ? "0" : "")+minutes;
    	var hoursStr = (hours<10 ? "0" : "")+hours; 
    	
		return {hours: hoursStr, minutes:minutesStr, seconds: secondsStr}
	}
	
	
	,_getSessionDurationString: function(){
		
		var sessionDuration = "";
		var startMs = Date.parse(this.startSession);
		var exitDate = new Date(); // NOW
		var exitMs = Date.parse(exitDate);
	
		var oPrettyDate = this._millisecondsToPrettyDate(exitMs-startMs);

		switch(this.API.version){
			case "1.2":
				sessionDuration = oPrettyDate.hours+":"+oPrettyDate.minutes+":"+oPrettyDate.seconds;
			break;
			
			// ISO format for SCORM 2004
			case "2004":
				sessionDuration ="PT"+oPrettyDate.hours+"H"+oPrettyDate.minutes+"M"+oPrettyDate.seconds+"S";
			break;
		}
		return sessionDuration;
	}
	
	// Unused so far
	,_handleCompletionStatus: function(){}	
	
	,_getUserAudioPrefString: function(audioStatus){
		
		var audioValue = "";
		switch(this.API.version){
			case "1.2":
				// 0: no change
				// 1-100: volume level
				// -1: off 
				audioValue = (audioStatus === "ON") ? "100" : "-1"; 
			break;
			case "2004":
				// 0: Infinite attenuation a.k.a. off
				// 1: No changes in attenuation or amplification
				audioValue = (audioStatus === "ON") ? "1" : "0";
			break;
		}
		return audioValue;
	}
}
