/*
    This file is part of LibSCORM 2004.

    LibSCORM 2004 is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    LibSCORM 2004 is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with LibSCORM 2004; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

*/

// Nav can function with or without an LMS. Without an LMS (lms==null in constructor)
// it simply displays the content and the UI.

function Nav(lms, interScoSeq, navDiv, contentDiv, pageUrls, asyncErrorHandler,
             prevEnabledImg, prevDisabledImg, nextEnabledImg, nextDisabledImg,
             pastTickImg,    presentTickImg,  futureTickImg) {
	this._lms        = lms;
	this._iss        = interScoSeq;
	this._navDiv     = navDiv;
	this._contentDiv = contentDiv;
	this._atPage     = -1;
	this._pages      = pageUrls;
	this._asyncErrorHandler = asyncErrorHandler;
	this._prevEnabledImg    = prevEnabledImg;
	this._prevDisabledImg   = prevDisabledImg;
	this._nextEnabledImg    = nextEnabledImg;
	this._nextDisabledImg   = nextDisabledImg;
	this._pastTickImg       = pastTickImg;
	this._presentTickImg    = presentTickImg;
	this._futureTickImg     = futureTickImg;

	this._queuedUnloadScript = "";

	if(this._navDiv && this.NumPages() > 1) {
		var x       = new Image();
		x.id        = "prev";
		x.src       = this._prevDisabledImg;
		x.className = "prevDisabled";
		this._navDiv.appendChild(x);

		for(var i = 0; i < this.NumPages(); i++) {
			x           = new Image();
			x.id        = "p"+i;
			x.src       = this._futureTickImg;
			x.onclick   = createEventHandler(this, "GotoPage", i);
			x.className = "tick";
			this._navDiv.appendChild(x);		
		}

		x           = new Image();
		x.id        = "next";
		x.src       = this._nextDisabledImg;
		x.className = "nextDisabled";
		this._navDiv.appendChild(x);
	}

	// If already been in the SCO, then go back to page where user left off
	this.GotoPage(this.LoadLocation());
}

Nav.prototype.GotoPage = function(number) {
	if(number < 0 || number >= this.NumPages()) {
		return false;
	}
	
	this.UnloadContent();
	if(this._navDiv && this.NumPages() > 1) {
		// Change the flashy lights and doodads on the nav bar
		if(this._atPage != -1) {
			document.getElementById("p"+this._atPage).src = this._pastTickImg;
		}
		document.getElementById("p"+number).src       = this._presentTickImg;	
	}
	
	this._atPage = number;

	// Start the asynchronous load of the document (content) div
	this._contentDiv.innerHTML = 'Loading...';
	if(this._pages[this._atPage].match(/\.swf/i)) {
		this._loadSWF(this._pages[this._atPage]);
	} else {
		this._ajaxRequest(this._pages[this._atPage], createEventHandler(this, "_fillContentDiv"), true);
	}

	if(this._navDiv && this.NumPages() > 1) {
		// Set the back/forward arrows' responsiveness
		var p = document.getElementById("prev");
		var n = document.getElementById("next");
		if(this._atPage > 0 || this._iss.CanExitBackward()) {
			p.onclick   = createEventHandler(this, "PrevPage");
			p.src       = this._prevEnabledImg;
			p.className = "prevEnabled";
		} else {
			p.onclick   = null;
			p.src       = this._prevDisabledImg;
			p.className = "prevDisabled";
		}

		if(this._atPage < this.NumPages() - 1 || this._iss.CanExitForward()) {
			n.onclick   = createEventHandler(this, "NextPage");
			n.src       = this._nextEnabledImg;
			n.className = "nextEnabled";
		} else {
			n.onclick   = null;
			n.src       = this._nextDisabledImg;
			n.className = "nextDisabled";
		}
	}
	return true;
}

Nav.prototype.NextPage = function() {
	return (this.CurrentPageNum() == this.NumPages() - 1)
		? this._iss.ExitForward()
		: this.GotoPage(this._atPage + 1);
}

Nav.prototype.PrevPage = function() {
	return (this.CurrentPageNum() == 0)
		? this._iss.ExitBackward()
		: this.GotoPage(this._atPage - 1);
}

Nav.prototype.CurrentPageNum = function() {
	return this._atPage;
}

Nav.prototype.NumPages = function() {
	return this._pages.length;
}

Nav.prototype.SaveLocation = function() {
	if(this._lms) {
		this._lms.SetValue("cmi.location", this.CurrentPageNum());
	}
}

Nav.prototype.LoadLocation = function() {
	var loc;
	try {
		loc = (this._lms) ? Number(this._lms.GetValue("cmi.location")) : 0;
	} catch(e) {
		if(e.code == 403) { // cmi.location uninitialized: first time in the SCO
			loc = 0;
		} else { // a real problem
			throw e;
		}		
	}
	return loc;
}

Nav.prototype.UnloadContent = function() {
	eval(this._queuedUnloadScript);
}

//////////////////////////////////////////////////////////////////////////////
//////////////////////////////// PRIVATE STUFF ///////////////////////////////
//////////////////////////////////////////////////////////////////////////////

// This is a callback that gets triggered by GotoPage
Nav.prototype._fillContentDiv = function(htmlDoc) {
	// htmlDoc.responseXML is likely going to be empty since even XHTML
	// pages are sent by the server with mimetype text/html. So we must
	// resort to this regular expression junk.
	var body = htmlDoc.responseText.match(/<body([^>]*)>([\s\S]+?)<\/body>/im);
	// note the [\s\S] character class is a JavaScript workaround for the fact
	// that '.' does not match newlines, even with a multiline modifier.
	
	document.title = "Page " + (this.CurrentPageNum()+1) + " of " + this.NumPages();
	this._contentDiv.innerHTML   = htmlDoc.responseText;
	if(body) { // if we are loading HTML, then make sure to do body (un)load
		var loadattr             = body[1].match(/onload\s*=\s*["'](\S+)["']/i);
		var unloadattr           = body[1].match(/onunload\s*=\s*["'](\S+)["']/i);
		this._queuedUnloadScript = (unloadattr) ? unloadattr[1] : "";
		if(loadattr) {
			eval(loadattr[1]);
		}
		var title = htmlDoc.responseText.match(/<title>(.*?)<\/title>/i);
		if(title) { document.title = title[1]; }
	}
}

Nav.prototype._loadSWF = function(file) {
	var fo = new deconcept.SWFObject(file, "SWF_content", "100%", "100%", "8");
	fo.addParam("wmode", "transparent");  // Makes flash respect css z-index.
	// Some older browsers don't support wmode, for details see
	// http://www.adobe.com/cfusion/knowledgebase/index.cfm?id=tn_14201
	fo.addParam("allowScriptAccess", "always");
	fo.addParam("swLiveConnect", "true");
	fo.addParam("quality", "high");
	fo.addParam("scale", "scale");
	fo.addParam("loop", "false");
	fo.addParam("salign", "lt");
	fo.addVariable("lcId", "SWF_content");
	fo.write(this._contentDiv);
	document.title = "Page " + (this.CurrentPageNum()+1) + " of " + this.NumPages();
}

Nav.prototype._ajaxRequest = function(url, func, allowCache) {
	// relevant HTTP codes
	var HTTP_LOADING_COMPLETE = 4;
	var HTTP_STATUS_OK        = 200;

	if(!allowCache) {
		url += (url.indexOf('?') + 1)
		       ? '&'
		       : '?';
		url += (new Date()).getTime();
	}
	
	var req = null;
	if(window.XMLHttpRequest) { // the modern (mozilla etc) way
		req = new XMLHttpRequest();
	} else if(window.ActiveXObject && !navigator.__ice_version) { // the Microsoft way
		req = new ActiveXObject("Microsoft.XMLHTTP");
	} else {
		throw new LibScormException(-1, "Nav::_ajaxRequest", "Can't create XMLHttpRequest",
		                            "Are you using an exotic browser?");
	}
	
	var handler = this._asyncErrorHandler;
	req.onreadystatechange = function () {
		if(req.readyState == HTTP_LOADING_COMPLETE) {
			if(req.status == HTTP_STATUS_OK || req.status == 0) {
				func(req);
			} else {	
				handler(new LibScormException(req.status,
					"Nav::_ajaxRequest callback",
					"HTTP request for " + url + " failed",
					"See HTTP spec to interpret error code"));
			}
		}	
	};
	req.open("GET", url, true);
	req.send(null);
}

Nav.prototype._lms;
Nav.prototype._iss;
Nav.prototype._navDiv;
Nav.prototype._contentDiv;
Nav.prototype._atPage;
Nav.prototype._pages;
Nav.prototype._asyncErrorHandler;
Nav.prototype._queuedUnloadScript;

Nav.prototype._prevEnabledImg;
Nav.prototype._prevDisabledImg;
Nav.prototype._nextEnabledImg;
Nav.prototype._nextDisabledImg;
Nav.prototype._pastTickImg;
Nav.prototype._presentTickImg;
Nav.prototype._futureTickImg;
