/**
elsBookPlayerState is an object that represents the state of the book player
content area, along with methods for manipulating it.

Originally, elsBookPlayerState objects were kept in a history stack maintained
by Dojo's ajax history manager.  With the change to the Yahoo history manager,
this is no longer the case (the Yahoo history manager doesn't maintain entire
objects for state, only individual state *strings*).  We still do create multiple
instances of elsBookPlayerState - though that could be avoided now, we've
left things as they were.  The Yahoo history manager mentions that they may
add support for full blown javascript objects at a later date, so this may be
a reason to consider keeping things as they are. 

Note:  Currently "htmlWithOptionalMeta" is the actual xhtml for the book 
		chapter/section content.  It is normally not a fragment, but an actual
		page whose title will become the browser title and whose meta tags include
		an indication of the next/prev eids, breadcrumb eids, etc.  If a fragment 
		(instead of an actual page) is passed in, then the title and next/prev eids 
		will not change from what the current page has.  If undefined is passed in 
		for the html, then it assumes the html currently displayed should be used 
		(e.g. that this represents a jump within the current page).
*/
function elsBookPlayerState(requestedEid,
						dartId,
						htmlWithOptionalMeta, 
						layoutContainerId, 
						contentAreaDivId,
						prevAnchorId,						
						nextAnchorId,
						tocTree,
						scrollFullPageInBook,
						type,		
						updateContentTools,				
						onContentChange,						
						onUpdateComplete,
						trackWebAnalytics) {
						
	this.contentAreaDivId = contentAreaDivId;
	this.contentAreaElement = dojo.byId(this.contentAreaDivId);	
	this.contentHeadingElement = dojo.byId(this.contentAreaDivId + 'Heading');
	
	if(typeof(scrollFullPageInBook) != 'undefined' && scrollFullPageInBook) {
	    if(dojo.render.html.ie70 || dojo.render.html.safari) {
			this.scrollElement = document.documentElement;
		}
	   else {
		this.scrollElement = document.body;
		}
    } else {
		this.scrollElement = this.contentAreaElement;
	}
		
	this.prevAnchorId = prevAnchorId;	
	this.prevAnchorElement = dojo.byId(this.prevAnchorId);

	this.nextAnchorId = nextAnchorId;	
	this.nextAnchorElement = dojo.byId(this.nextAnchorId);	

	this.layoutContainerId = layoutContainerId;
	this.layoutContainerElement = dojo.widget.byId(this.layoutContainerId);						
	this.tocTree = tocTree;	
	this.contentAreaType = type;	
		
	// Store away their completion methods (if they gave any):
	this.onContentChange = onContentChange; 	
	this.onUpdateComplete = onUpdateComplete;
	this.updateContentTools = updateContentTools;
	this.trackWebAnalytics = trackWebAnalytics;	

	// If we get a dartId then scroll directly to that dart.
	this.dartId = dartId;

	// Get the body of the html to display
	if(!htmlWithOptionalMeta) {
		
		// When generating the page initially off the server, the server
		// embeds the equivalent of the Ajax reply right in the page:
		var embeddedSectionHtml = dojo.byId("sectionContentHtml");
		var embeddedSectionBody = dojo.byId("sectionContentBody");
		
		if(!embeddedSectionHtml && !embeddedSectionBody) {
			// Otherwise assume this is a within-page jump and just use the current page's html:
			this.contentAreaHtml = this.contentAreaElement.innerHTML; 	
			this.updateStyle = 'within';	
			
			// Where they want to jump to in the current page: 
			this.requestedEid = requestedEid;		
			
			// No body this was just a fragment of the body by itself (no body tag)		
			// Get the meta information describing the current content previously 
			// stored on the DOM itself:
			restoreMetaDataFromDOM(this);	
		}
		else if(typeof(isAppContentEmbedded) != 'undefined' && isAppContentEmbedded && embeddedSectionBody) {
			var embeddedSectionMetaBreadCrumbs = dojo.byId("sectionContentBreadCrumbsWrapper");
			
			this.updateStyle = 'embedded';	
			
			// The body is a div tag with a predefined id:
			this.contentAreaHtml = dojo.byId("sectionContentBody").innerHTML;
			
			if(embeddedSectionMetaBreadCrumbs) {
			
				// Parse the breadcrumb info from the meta tags:			
				this.metaBreadCrumbs = getEmbeddedMetaBreadCrumbs();

				// Get the bookTitle from the meta tag if we got it:								
				this.bookTitle = getEmbeddedMetaValue("sectionContentBookTitle");
				
				// Get the author from the meta tag if we got it:
				this.author = getEmbeddedMetaValue("sectionContentAuthor");
				
				// Get the heading from the meta tag if we got it:
				this.heading = getEmbeddedMetaValue("sectionContentHeading");
				
				// Decode the heading
				if(this.heading) {
					this.heading = URLDecode(this.heading);
				}	
				
				// And get the title from the actual xhtml page title tag:				
				this.sectionTitle = getBrowserTitle(this.bookTitle, this.author, this.heading, this.metaBreadCrumbs);					
				
				// And now use the bread crumb meta data to record what actual eid we're displaying:
				setEidsFromMetaData(this, requestedEid);	
			}
			
		}
		else {
			this.updateStyle = 'embedded';	
						
			// The body is a div tag with a predefined id:
			this.contentAreaHtml = dojo.byId("sectionContentBody").innerHTML;
			
			// Get the meta data out of the embedded section content html
			getEmbeddedMetaData(this);
						
			// And now use the bread crumb meta data to record what actual eid we're displaying:
			setEidsFromMetaData(this, requestedEid);				
		}
						
	}
	else {
		// When we get *new* content, we don't want to animate the scroll position:
		this.updateStyle = 'ajax';	
		
		// Try and get just the body from the xhtml passed in (it normally will, but it's optional)
		this.contentAreaHtml = parseTagValue(htmlWithOptionalMeta, "body");					
		
		// Parse the meta data out of it
		parseAjaxMetaData(this, htmlWithOptionalMeta);
			
		// And now use the bread crumb meta data to record what actual eid we're displaying:
		setEidsFromMetaData(this, requestedEid);			
		
	}
	
	// Get all meta data values from the "embedded" form of the section content						
	function getEmbeddedMetaData(bookPlayerState, htmlWithOptionalMeta) {
				
		// We can get a login page if their session has timed out in response
		bookPlayerState.pageType = getEmbeddedMetaValue("sectionContentElsPageType");
						
		// Get the core eid from the meta tag if we got it:	
		bookPlayerState.coreEid = getEmbeddedMetaValue("sectionContentCoreEid");		

		// Get the previous type from the meta tag if we got it:	
		bookPlayerState.prevType = getEmbeddedMetaValue("sectionContentPrevType");

		// Get the previous eid from the meta tag if we got it:	
		bookPlayerState.prevEid = getEmbeddedMetaValue("sectionContentPrevEid");		
		
		// Get the next type from the meta tag if we got it:	
		bookPlayerState.nextType = getEmbeddedMetaValue("sectionContentNextType");
				
		// Get the next eid from the meta tag if we got it:	
		bookPlayerState.nextEid = getEmbeddedMetaValue("sectionContentNextEid");	
		
		// Get the heading from the meta tag if we got it:
		bookPlayerState.heading = getEmbeddedMetaValue("sectionContentHeading");	
		
		if(bookPlayerState.heading) {
			bookPlayerState.heading = URLDecode(bookPlayerState.heading);
		}		
		
		// Get the heading from the meta tag if we got it:
		bookPlayerState.bookTitle = getEmbeddedMetaValue("sectionContentBookTitle");
		
		// Get the isbn from the meta tag if we got it:
		bookPlayerState.isbn = getEmbeddedMetaValue("sectionContentIsbn");
		
		// Get the author from the meta tag if we got it:
		bookPlayerState.author = getEmbeddedMetaValue("sectionContentAuthor");
		
		// Get the edition from the meta tag if we got it:
		bookPlayerState.edition = getEmbeddedMetaValue("sectionContentEdition");	
				
		// Parse the breadcrumb info from the meta tags:			
		bookPlayerState.metaBreadCrumbs = getEmbeddedMetaBreadCrumbs();

		// And get the title from the actual xhtml page title tag:			
		bookPlayerState.sectionTitle = getBrowserTitle(bookPlayerState.bookTitle, bookPlayerState.author, bookPlayerState.heading, bookPlayerState.metaBreadCrumbs);					
	};		
	
	// Get the value of one of the meta data values from the "embedded" form of the section content
	function getEmbeddedMetaValue(metaName) {
		var metaValue;
		
		// We can get a login page if their session has timed out in response
		var span = dojo.byId(metaName);
		if(span) {
			metaValue = span.innerHTML; 
		}	
		
		return metaValue;
	}
	
	// Parse the meta data values from the "ajax" form of the section content
	function parseAjaxMetaData(bookPlayerState, htmlWithOptionalMeta) {
				
		// We can get a login page if their session has timed out in response 
		bookPlayerState.pageType = parseAttributeValue(htmlWithOptionalMeta, "meta", "content", "name", "elsPageType"); 

		// Get the core eid from the meta tag if we got it:	
		bookPlayerState.coreEid = parseAttributeValue(htmlWithOptionalMeta, "meta", "content", "name", "coreEid");

		// Get the previous type from the meta tag if we got it:	
		bookPlayerState.prevType = parseAttributeValue(htmlWithOptionalMeta, "meta", "content", "name", "prevType");

		// Get the previous eid from the meta tag if we got it:	
		bookPlayerState.prevEid = parseAttributeValue(htmlWithOptionalMeta, "meta", "content", "name", "prevEid");
			
		// Get the next type from the meta tag if we got it:	
		bookPlayerState.nextType = parseAttributeValue(htmlWithOptionalMeta, "meta", "content", "name", "nextType");	
				
		// Get the next eid from the meta tag if we got it:	
		bookPlayerState.nextEid = parseAttributeValue(htmlWithOptionalMeta, "meta", "content", "name", "nextEid");	
		
		// Get the heading from the meta tag if we got it:
		bookPlayerState.heading = parseAttributeValue(htmlWithOptionalMeta, "meta", "content", "name", "heading");	
		
		if(bookPlayerState.heading) {
			bookPlayerState.heading = URLDecode(bookPlayerState.heading);
		}
		
		// Get the bookTitle from the meta tag if we got it:
		bookPlayerState.bookTitle = parseAttributeValue(htmlWithOptionalMeta, "meta", "content", "name", "bookTitle");	
		
		// Get the isbn from the meta tag if we got it:
		bookPlayerState.isbn = parseAttributeValue(htmlWithOptionalMeta, "meta", "content", "name", "isbn");	
		
		// Get the author from the meta tag if we got it:
		bookPlayerState.author = parseAttributeValue(htmlWithOptionalMeta, "meta", "content", "name", "author");	
		
		// Get the edition from the meta tag if we got it:
		bookPlayerState.edition = parseAttributeValue(htmlWithOptionalMeta, "meta", "content", "name", "edition");				
	
		// Parse the breadcrumb info from the meta tags:			
		bookPlayerState.metaBreadCrumbs = parseAjaxMetaBreadCrumbs(htmlWithOptionalMeta);

		// And get the title from the actual xhtml page title tag:			
		bookPlayerState.sectionTitle = getBrowserTitle(bookPlayerState.bookTitle, bookPlayerState.author, bookPlayerState.heading, bookPlayerState.metaBreadCrumbs);							
	};		
	
	
	function setEidsFromMetaData(bookPlayerState, requestedEid) {	
	
		// The bottom-most breadcrumb indicates the breadth of the section content
		// we're displaying (although there can be paragraphs that precede the first
		// named section).  i.e. This is the eid/div we want to highlight in the TOC.
		if(bookPlayerState.metaBreadCrumbs && bookPlayerState.metaBreadCrumbs.length > 0) {
			bookPlayerState.sectionEid = bookPlayerState.metaBreadCrumbs[bookPlayerState.metaBreadCrumbs.length -1].eid;
		}
		else
		{
			// We normally *always* get the bread crumbs - but just in case:
			bookPlayerState.sectionEid = requestedEid;
		}
		
		// The eid that we jump to within the displayed section is different: 
		bookPlayerState.requestedEid = requestedEid;	
	};	
			
	// Update the content area to show the html passed to our constructor.
	// It fades out the old content and fades in the new content.
	// As of when this method returns, the animation is running but not
	// necessarily complete.  You can pass an onContentChange method which
	// will be called as soon as the new html is displayed, and/or an
	// onUpdateComplete function which will called after the update is
	// fully complete (e.g. the animation to fade in the new content is 
	// complete, the new title is displayed in the non-scrolling area, etc). 
	// 						
	this.updateContentArea = function() {

		// Have we gotten redirected to the login in response to our ajax call?
		if(this.pageType == "login") {
			// We're not expecting this anymore since we've now installed our
			// own inactivity timer that will cause a re-login prior to the 
			// A&E session ever timing out - but JIC we'll leave this check:
			reloginUser();

			return;
		} 
		
		// Have we gotten redirected to the upsell in response to our Ajax call?
		if(this.pageType == "upsell") {
		
			// In the normal page flow, they should see the upsell upon entering
			// the book player, before they've ever gotten this far - just in case:
			alert("You're attempting to access premium content from a basic account.");

			return;
		} 		
				
		// Update the browser title if the section title has changed:
		if(this.contentAreaType && (this.contentAreaType == "aboutPage" || this.contentAreaType == "bookHome") && typeof(bookPageTitle) != 'undefined' && bookPageTitle) {		
			
			//bookPageTitle from request attribute sets in Filter and the variable is initialized in application's bookPlayer.vm only for "aboutPage"
			document.title = bookPageTitle + " on " +appName;			
			
		} else if(this.sectionTitle && document.title != this.sectionTitle) {
			var browserTitle = document.title;			
			if((this.contentAreaType == "figurePage" || this.contentAreaType == "figureFromContent") && !this.heading) {
				document.title = browserTitle;
			} else {
				document.title = this.sectionTitle;		
			}
		
			// Track this new (logical) "page view":
			if(this.trackWebAnalytics) {
			var a= getBookCrumbdata(this.heading, this.metaBreadCrumbs);
			this.trackWebAnalytics(this.ajaxURL, a.toString());	
							
			}				
		}	
		
		if(typeof(breadCrumbLinks) != 'undefined' && breadCrumbLinks){			
			var newCrumbsHtml = "You are here: &nbsp;<a target=\"_top\" href=\"";		 
			var crumbNodes = breadCrumbLinks.getElementsByTagName("A");		
			var lastChildFromParentLink = breadCrumbLinks.lastChild.nodeValue;  
			if(crumbNodes.length >1){
				var current1 = crumbNodes[0];
				newCrumbsHtml = newCrumbsHtml + current1 + "\">Home</a>&nbsp;&gt;&nbsp;"
				var current2 = crumbNodes[1];
				newCrumbsHtml = newCrumbsHtml + "<nobr><a target=\"_top\" href=\"" + current2 + "\">Books</a></nobr>";
						
				if (typeof(bookPageTitle) != 'undefined' && bookPageTitle)
				{
					var bcTitle = bookPageTitle;
					if(bcTitle.length > 35){
						bcTitle = bcTitle.substring(0, 35) + "...";		
					}			
					var aboutLinkUrl = "linkTo?type=bookHome&isbn="+isbn+"&eid="+topLevelEid;			
					newCrumbsHtml = newCrumbsHtml +"&nbsp;&gt;&nbsp;"+ "<nobr><a target=\"_top\" href=\""+ aboutLinkUrl + "\">" + bcTitle + "</a></nobr>";
				} else if(this.bookTitle){
					var bcTitle = this.bookTitle;
					if(bcTitle.length > 35){
						bcTitle = bcTitle.substring(0, 35) + "...";												
					}
					var aboutLinkUrl = "linkTo?type=bookHome&isbn="+isbn+"&eid="+topLevelEid;							
					newCrumbsHtml = newCrumbsHtml +"&nbsp;&gt;&nbsp;" + "<nobr><a target=\"_top\" href=\""+ aboutLinkUrl + "\">" + bcTitle + "</a></nobr>";		  
				} 

				if(this.contentAreaType && 
					(this.contentAreaType == "figurePage" || this.contentAreaType == "figureFromContent") 
				&& this.heading){
					var bcTitle = this.heading;
					if(bcTitle.length > 50){
						bcTitle = bcTitle.substring(0, 50) + "...";												
					}
					newCrumbsHtml = newCrumbsHtml +"&nbsp;&gt;&nbsp;"+ bcTitle;
				} else if(this.contentAreaType && this.contentAreaType != "aboutPage" && this.contentAreaType != "bookHome" && this.metaBreadCrumbs && this.metaBreadCrumbs != ""){
					var bc = this.metaBreadCrumbs[this.metaBreadCrumbs.length -1];	
					var bcTitle = bc.title;
					if(bcTitle.length > 50){
						bcTitle = bcTitle.substring(0, 50) + "...";												
					}
					newCrumbsHtml = newCrumbsHtml +"&nbsp;&gt;&nbsp;"+ bcTitle;
				}
			}
			if((this.contentAreaType == "figurePage" || this.contentAreaType == "figureFromContent") && !this.heading && lastChildFromParentLink) {
				breadCrumbLinks.innerHTML = newCrumbsHtml+lastChildFromParentLink;
			} else {
				breadCrumbLinks.innerHTML = newCrumbsHtml;			
			}		
			// Call their content change method if they gave one:
			if(this.onContentChange) {
				this.onContentChange(this);
			}
		 }
		
		
		// Store some of the meta information describing the currently
		// displayed section content onto the DOM itself, for use later
		// (i.e. from a different elsBookPlayerState object):
		this.storeMetaDataOnDOM();
		
		// If we've got new content to show - display the new html:
		if(this.updateStyle == 'ajax' && this.contentAreaHtml) {
			// Animate a fade out of the old content, and when the fade 
			// animation ends, display the new:
			dojo.lfx.html.fadeHide(this.contentAreaElement, 400, dojo.lfx.easeIn, 
				dojo.lang.hitch(this, "displayNewContent")).play();	
		}
		else {
			// Even if it's already in the page, we still have to scroll, etc.
			this.handleUpdate();
		}
	};	
	
	// display new content within the content area
	this.displayNewContent = function() {
		// Display the new html!
		if(this.contentAreaElement) {
		
			// Change the content			
			this.contentAreaElement.innerHTML = this.contentAreaHtml;
			
			// Call their content change method if they gave one:
			if(this.onContentChange) {
				this.onContentChange(this);
			}
													
			// And smooth fade it in:	
			dojo.lfx.html.fadeShow(this.contentAreaElement, 750, dojo.lfx.easeOut).play();					
		}
		
		this.handleUpdate();			
	};	
	
	// Perform processing needed after new content has been displayed/updated
	this.handleUpdate = function() {
		
		// And set the header title:
		if(this.contentHeadingElement) {
			// And get the title from the actual xhtml page title tag:			
			this.contentHeadingElement.innerHTML = getHeading(this.heading, this.metaBreadCrumbs);			
		}

		// Adjust the font size of the new content to the user's selected size
		// (this uses it's own cookie to know what the current size should be)
		dw_fontSizerDX.init();

		// Set the eids on the next/prev anchor tags, and show/hide them
		// as appropriate:
		this.adjustNextPrevPageButtons();		
							
		// This will cause the scroll bar to appear/disappear as needed based on the new content		
		if(this.layoutContainerElement) {
			this.layoutContainerElement.onResized();
		}
		
		// Was there a dartId?
		var namedAnchor;		
		if(this.dartId) {
			// Does this dart exist as a named anchor on the (now) updated page?
			namedAnchor = findNamedAnchor(this.dartId, this.contentAreaDivId);			
		}
		
		// Otherwise, does the requested eid exist as a named anchor on the page?
		if(!namedAnchor) {
			namedAnchor = findNamedAnchor(this.requestedEid, this.contentAreaDivId);
		}
		
		// Did we find it?
		if(!namedAnchor) {		
			// Nope - we should not be scrolled down at all:
			this.scrollElement.scrollTop = 0;						
		}
		else {			
			// Yup - scroll down to the appropriate position
			// Note:  Originally we used window.location.hash to jump directly to 
			//        the named anchor - BUT DON'T DO THIS NOW BECAUSE IT CONFLICTS
			//        WITH AJAX HISTORY MANAGEMENT'S USE OF THE HASH!!		
			if(this.updateStyle == 'embedded' || this.updateStyle == 'ajax') {			
				// New pages jump
				this.jumpToNamedAnchor(namedAnchor);					
			}
			else {
				// Within pages scroll:			
				this.scrollToNamedAnchor(namedAnchor);				
			}
		}		
								
		// Highlight the TOC to match the newly display content based on the eid				
		this.syncTocTree();	
		
		// Call their completion method if they gave one:
		if(this.onUpdateComplete) {
			this.onUpdateComplete(this);
		}					
	};		
	
	// Store some of the meta information describing the currently
	// displayed section content onto the DOM itself, for use later
	// (i.e. from a different elsBookPlayerState object):
	this.storeMetaDataOnDOM = function() {

		if(this.requestedEid) {
			this.contentAreaElement.requestedEid = this.requestedEid;
		}
		if(this.sectionEid) {
			this.contentAreaElement.sectionEid = this.sectionEid;
		}
		if(typeof(isbn) != 'undefined' && isbn){			
			this.contentAreaElement.isbn = isbn;
		}
		if(this.coreEid) {
			this.contentAreaElement.coreEid = this.coreEid;
		}					
		if(this.metaBreadCrumbs) {
			this.contentAreaElement.metaBreadCrumbs = this.metaBreadCrumbs;
		}
		if(this.pageType) { 
			this.contentAreaElement.pageType = this.pageType;
		}
		if(this.contentAreaType) {
			this.contentAreaElement.contentAreaType = this.contentAreaType;
		}
		if(this.prevType) {
			this.contentAreaElement.prevType = this.prevType;
		}	
		if(this.nextType) {
			this.contentAreaElement.nextType = this.nextType;
		}
		if(document.title) {
			this.contentAreaElement.sectionTitle = document.title;
		}
		if(this.updateContentTools) {
			this.updateContentTools(this.contentAreaElement);
		}
	};	
	
	// Get some of the meta information describing the currently
	// displayed section content from the DOM back into our javascript
	// object variables.
	function restoreMetaDataFromDOM(bookPlayerState) {
	
		if(!bookPlayerState.requestedEid) {
			bookPlayerState.requestedEid = bookPlayerState.contentAreaElement.requestedEid;
		}
		if(!bookPlayerState.sectionEid) {
			bookPlayerState.sectionEid = bookPlayerState.contentAreaElement.sectionEid;
			if(!bookPlayerState.sectionEid) {
				// Otherwise the one we requested with does work in many situations:		
				bookPlayerState.sectionEid = bookPlayerState.requestedEid;
			}				
		}
		if(!bookPlayerState.isbn && typeof(isbn) != 'undefined' && isbn) {
			bookPlayerState.isbn = isbn;
		}
		if(!bookPlayerState.coreEid) {
			bookPlayerState.coreEid = bookPlayerState.contentAreaElement.coreEid;
		}					
		if(!bookPlayerState.metaBreadCrumbs) {
			bookPlayerState.metaBreadCrumbs = bookPlayerState.contentAreaElement.metaBreadCrumbs;
		}	
		// Use the current next/prev eid			
		if(bookPlayerState.prevAnchorElement){
		bookPlayerState.prevEid = bookPlayerState.prevAnchorElement.eid;
		}	
		if(bookPlayerState.nextAnchorElement){	
		bookPlayerState.nextEid = bookPlayerState.nextAnchorElement.eid;
		}			
		// Use the current browser title:
		bookPlayerState.sectionTitle = document.title;	
			
		if(!bookPlayerState.prevType) {
			bookPlayerState.prevType = bookPlayerState.contentAreaElement.prevType;
		}
		if(!bookPlayerState.nextType) {
			bookPlayerState.nextType = bookPlayerState.contentAreaElement.nextType;
		}		
	};		
	
	// Set the eids on the next/prev anchor tags, and show/hide them
	// as appropriate:
	this.adjustNextPrevPageButtons = function() {
		if(this.prevAnchorElement) {
			if(this.prevType) {
				this.prevAnchorElement.type = this.prevType;
			} else {
				// If type is not given set it to bookPage. Otherwise the
				// previous setting would still be set causing an incorrect
				// type!!!!
				this.prevAnchorElement.type = "bookPage";
			}
			this.prevAnchorElement.eid = this.prevEid;
			this.prevAnchorElement.href = "#";
			
			// Hide it if there's no previous to go to:
			if(this.prevEid) {
				this.prevAnchorElement.style.visibility = "visible";		
			}
			else {
				this.prevAnchorElement.style.visibility = "hidden";		
			}	
		}
		
		if(this.nextAnchorElement) {	
			if(this.nextType) {
				this.nextAnchorElement.type = this.nextType;	
			} else {
				// If type is not given set it to bookPage. Otherwise the
				// previous setting would still be set causing an incorrect
				// type!!!!
				this.nextAnchorElement.type = "bookPage";
			}
			this.nextAnchorElement.eid = this.nextEid;
			this.nextAnchorElement.href = "#";
			
			// Hide it if there's no next to go to:
			if(this.nextEid) {
				this.nextAnchorElement.style.visibility = "visible";		
			}
			else {
				this.nextAnchorElement.style.visibility = "hidden";		
			}	
		}
	}	
	
	// Animate a smooth scroll to the specified named anchor within the book content		
	this.scrollToNamedAnchor = function(namedAnchor) {

		if(namedAnchor && this.scrollElement) {
		
			var targetTop = this.scrollTopAnchorPosition(namedAnchor);
						
			// Don't waste time/resources animating for no reason!
			if(targetTop == this.scrollElement.scrollTop) {
				return;
			}							
			
			var scrollAnim = new dojo.lfx.Animation({  
			             onAnimate: function(value){ 
			             	if(this.scrollElement) {
			                     this.scrollElement.scrollTop = value;
							}
			             } 
			     }, 
			     750,  
			     [this.scrollElement.scrollTop, targetTop],
			     dojo.lfx.easeInOut 
			);
			
	        if(scrollAnim) {
	        	// Make the contentArea available to the onAnimate handler:
	        	scrollAnim.scrollElement = this.scrollElement;
	        	scrollAnim.play();
	        }        
		}
	};

	// Jump to the specified named anchor within the current book content
	this.jumpToNamedAnchor = function(namedAnchor) {	
		var targetTop = this.scrollTopAnchorPosition(namedAnchor);
		if(targetTop && targetTop >= 0) {			
			this.scrollElement.scrollTop = targetTop; 			       
		}
		else
		{
			// It's unclear to me why - but often we come here
			// when we should be at the top of a new page which
			// happens to have an anchor at the top:
			this.scrollElement.scrollTop = 0; 		
		}
	};	

	this.getTopPosition = function(element) {
		
		var namedAnchorPos = dojo.html.getAbsolutePosition(element,true,dojo.html.boxSizing.BORDER_BOX);	
		
		// We are having a problem under IE where the position of the named anchor seems wrong!
		// (this was happening on the superscripted footnotes that jump to the end of a table)
		if((dojo.render.html.ie || dojo.render.html.safari) &&
			namedAnchorPos.top == -2 && 
			namedAnchorPos.left == -2 &&
			namedAnchorPos.x == -2 && 
			namedAnchorPos.y == -2) { 

			var prior = element.previousSibling;
			if(prior) {
				// Hopefully the previous sibling is close enough to
				// not seem terrible:
				return this.getTopPosition(parent);			
			}
			else {
				// Otherwise hopefully we're not too deeply nested!
				var parent = element.parentNode;
				if(parent) {		
					return this.getTopPosition(parent);
				}
			}
		}
		
		return namedAnchorPos;			
	}
	
	// Get the position of the specified anchor within the content area
	// (i.e. what would the div tag's scrollTop be so the anchor's at the top) 
	this.scrollTopAnchorPosition = function(namedAnchor) {

		var targetTop = 0;
		
		if(namedAnchor) {
			
			var contentAreaPos = this.getTopPosition(this.scrollElement);
			var namedAnchorPos = this.getTopPosition(namedAnchor);
			 							 
			// Need the relative offset between the two (these are absolute to the browser as a whole):
			var targetTop = namedAnchorPos.top - contentAreaPos.top;
			
			// For Mozilla there's some kind of fudge factor going on (not sure why): 	
			if (dojo.render.html.moz) {			
				targetTop -= 20;
			}
							
		}
		
		// In IE, what we've just computed is not the actual target, but the
		// difference between what the target currently is and what it *should* be:
		if (dojo.render.html.ie || dojo.render.html.safari) {
			targetTop = this.scrollElement.scrollTop + targetTop;				
		}		

		// There are anchors at the top of some of sections, but...
		if(targetTop < 20) {
			// It looks better to not scroll these at all:
			targetTop = 0;				
		}		
				
		return targetTop;
	}	
	
	// Expand the table of contents tree so that the node representing the 
	// displayed content is visible and highlighted.
	this.syncTocTree = function() {
	
		// For some unclear reason - under IE we sometimes don't have the toc tree
		if(!this.tocTree) {		
			// So reach out and get it:
			this.tocTree = getTocTree();
		}
	
		if(!this.tocTree || !this.sectionEid) {		
			return;
		}
	
		// Do we already have this node in our browser's DOM for the TOC?		
		var li = dojo.byId(this.tocTree.treeRootULId + "_" + this.sectionEid);
		if(li) {		
			// Yup - highlight it:	
			this.tocTree.highlight(li);
			
			// And we want whatever's selected to be open, so we can see
			// the children of the current node
			this.tocTree.openPathToLINode(li);				
		}
		else {			
			// Nope - find the deepest breadcrumb node that we do have:
			var closestLI;
			if(this.metaBreadCrumbs) {
				for (var crumbNum = this.metaBreadCrumbs.length-1; !closestLI && crumbNum >= 0; crumbNum--)
				{
					closestLI = dojo.byId(this.tocTree.treeRootULId + "_" + this.metaBreadCrumbs[crumbNum].eid);
				}
			}
			
			// Did we find at least one of the breadcrumb nodes in our TOC tree?
			if(closestLI) {			
				// Yup expand underneath it down to the new node:
				this.tocTree.unhighlightSelected();					
				this.tocTree.setLIToBeSelected(this.tocTree.treeRootULId + "_" + this.sectionEid);				
				this.tocTree.openLIWithCookie(closestLI, this.tocTree.treeRootULId + "_" + this.sectionEid);			
			}
			else {				
				if(this.contentAreaType && this.contentAreaType != "figurePage" && this.contentAreaType != "figureFromContent") {
					// Then we'll try and recover by resetting the full TOC from the top down:		
					var rootLI = dojo.byId(this.tocTree.treeRootULId + "LI");
					var rootContainerUL = dojo.byId(this.tocTree.treeRootULId + "ContainerUL");			
					if(rootLI && rootContainerUL) {
					
						// We prefix the tree ids with the tree name:
						var treeId = "tocTree_" + this.sectionEid;
					
						// Change the tree's root level eid to be loaded from the server:			
						rootLI.setAttribute("childId", treeId);								
		
						// And change the eid to be highlighted:		
						rootContainerUL.setAttribute("selected", treeId);
						
						// Then completely reload the tree from the server with the
						// path down to this eid open (and this eid highlighted):
						this.tocTree.tocReloaded = true;
						this.tocTree.reloadULTree(rootContainerUL);
					}	
				}
			}	
		}		
	};	
				
	// Get bread crumb information from the meta data included in the ajax section content
	function parseAjaxMetaBreadCrumbs(htmlWithMeta) {		
	
		var metaBreadCrumbs = new Array();
	
		var metaBreadCrumbCount = parseAttributeValue(htmlWithMeta, "meta", "content", "name", "breadCrumbCount");
		if(metaBreadCrumbCount && metaBreadCrumbCount > 0) {

			for (var crumbNum = 1; crumbNum <= metaBreadCrumbCount; crumbNum++)
			{
				var metaBreadCrumb = new Object();
				metaBreadCrumb.eid = parseAttributeValue(htmlWithMeta, "meta", "content", "name", "breadCrumb" + crumbNum + "Eid");
				metaBreadCrumb.label = parseAttributeValue(htmlWithMeta, "meta", "content", "name", "breadCrumb" + crumbNum + "Label");
				metaBreadCrumb.title = parseAttributeValue(htmlWithMeta, "meta", "content", "name", "breadCrumb" + crumbNum + "Title");				
				metaBreadCrumbs.push(metaBreadCrumb);								
			}
		}
			
		return metaBreadCrumbs;
	};	
	
	// Get bread crumb information from the meta data included in the embedded section content
	function getEmbeddedMetaBreadCrumbs() {		
	
		var metaBreadCrumbs = new Array();
	
		var metaBreadCrumbCount = getEmbeddedMetaValue("sectionContentBreadCrumbCount");
		if(metaBreadCrumbCount && metaBreadCrumbCount > 0) {

			for (var crumbNum = 1; crumbNum <= metaBreadCrumbCount; crumbNum++)
			{
				var metaBreadCrumb = new Object();
				metaBreadCrumb.eid = getEmbeddedMetaValue("sectionContentBreadCrumb" + crumbNum + "Eid");
				metaBreadCrumb.label = getEmbeddedMetaValue("sectionContentBreadCrumb" + crumbNum + "Label");
				metaBreadCrumb.title = getEmbeddedMetaValue("sectionContentBreadCrumb" + crumbNum + "Title");				
				metaBreadCrumbs.push(metaBreadCrumb);								
			}
		}
			
		return metaBreadCrumbs;
	};	

	// Get the label to use for the browser title
	// (people often don't notice it but it's the default used when they
	// create browser bookmarks).
	function getBrowserTitle(bookTitle, author, heading, metaBreadCrumbs) {		
	
		var browserTitle ="";		
		if(heading) {			
				browserTitle = heading.replace(":", "/");
		} else if(metaBreadCrumbs) {
			var lastWas = 'none';
			for (var crumbNum = 0; crumbNum < metaBreadCrumbs.length; crumbNum++) {											
				if(metaBreadCrumbs[crumbNum].label) {
					if(browserTitle != "") {
						browserTitle = browserTitle + '/' + metaBreadCrumbs[crumbNum].label;
					} else {
						browserTitle = metaBreadCrumbs[crumbNum].label;
					}										
					lastWas = 'label';
				}				
				if(metaBreadCrumbs[crumbNum].title) {					
					if(lastWas == 'title' || lastWas == 'none') {
						if(browserTitle != "") {
							browserTitle = browserTitle + '/' + metaBreadCrumbs[crumbNum].title;
						} else {
							browserTitle = metaBreadCrumbs[crumbNum].title;
						}
					}
					if(lastWas == 'label' && (crumbNum+1) == metaBreadCrumbs.length) {
						browserTitle = browserTitle + '/' + metaBreadCrumbs[crumbNum].title;
					}
					lastWas = 'title';
				}				
			}
		}
		var maximumHeaderChars = 60;
		if(browserTitle.length > maximumHeaderChars) {
			// Break on a word - find the first space before this length
			var lastSpace = browserTitle.lastIndexOf(" ", maximumHeaderChars-1);
			if(lastSpace == -1) {
				lastSpace = maximumHeaderChars-1;
			} 
			
			browserTitle = browserTitle.substring(0,lastSpace) + " ...";
		}	
		if(bookTitle) {
			browserTitle = browserTitle + " from " + bookTitle;
		}
		if(typeof(showAppNameInTitle) != 'undefined' && showAppNameInTitle) {
			browserTitle = browserTitle + " on " + appName;
		}	
		return browserTitle;
	};	

	// Get the label to place in the header above the scrolled content area:
	function getHeading(heading, metaBreadCrumbs) {		
	
		// TODO:  Make this length be computed based on the current width!
		var maximumHeaderChars = 60;  // We truncate titles longer than this	
	
		// We use the deepest breadcrumb which has a label (not just a title).
		// Otherwise if we use the deepest breadcrumb title, it's confusing, 
		// e.g. if they click Chapter 2 the heading was showing the title of the 
		// first section in chapter 2.  So the heading doesn't match the thing
		// they clicked on.  So it looks better to be a little broader than more
		// specific.  Also because it looks silly to simply repeat the heading
		// at the top of the section when the page displays - you literally
		// see the same title repeated twice until/unless they scroll down...

		var headerTitle = "";
		
		if(heading) { 
			headerTitle = heading;
		} else if(metaBreadCrumbs) {
			// Find the closest label towards the end:
			for (var labelNum = metaBreadCrumbs.length-1; !headerTitle && labelNum >= 0; labelNum--) {
				var crumbLabel = metaBreadCrumbs[labelNum].label;
				if(crumbLabel) {
					headerTitle = crumbLabel;
					// And now find the closest title after it:
					var crumbTitle;
					for (var titleNum = labelNum; !crumbTitle && titleNum < metaBreadCrumbs.length; titleNum++) {					
						crumbTitle = metaBreadCrumbs[titleNum].title;
					}
					if(crumbTitle) {
						headerTitle = headerTitle + ":  " + crumbTitle;
					}					 
				}
			}
			
			// But if we don't find any it's certainly better to show something!
			if(!headerTitle) {
				if(metaBreadCrumbs.length > 0) {
					headerTitle = getMetaBreadCrumbLabelTitle(metaBreadCrumbs[metaBreadCrumbs.length-1]);
				}
				else {
					headerTitle = "";
				}
			}
		}
		
		// We just don't have screen real estate enough to show some of these
		// super long titles:
		if(headerTitle.length > maximumHeaderChars) {
			// Break on a word - find the first space before this length
			var lastSpace = headerTitle.lastIndexOf(" ", maximumHeaderChars-1);
			if(lastSpace == -1) {
				lastSpace = maximumHeaderChars-1;
			} 
		
			headerTitle = headerTitle.substring(0,lastSpace) + " ...";
		}
		
		return headerTitle;
		
	};	
	function getBookCrumbdata(heading, metaBreadCrumbs) {		
	
		// TODO:  Make this length be computed based on the current width!
		
		// We use breadcrumbs to fetch all the meta data of the book.

		var headerTitle = "";
		var crumbTitle = "";
		if(heading) { 
			headerTitle = heading;
		} else if(metaBreadCrumbs) {
		
			for (var labelNum = 0; labelNum <= metaBreadCrumbs.length-1; labelNum++) 
			{
				var crumbLabel = metaBreadCrumbs[labelNum].label;
				if(crumbLabel && crumbLabel!= undefined)
				{
					
					if(crumbLabel.substring(0,6).toLowerCase()=="volume")
					{
					headerTitle = headerTitle+"volumefortag::"+crumbLabel;
					}					
					else if(crumbLabel.substring(0,7).toLowerCase()=="chapter")
					{
					headerTitle = headerTitle+"chapterfortag::" +crumbLabel;
					}
					else if(crumbLabel.substring(0,7).toLowerCase()=="section")
					{
					headerTitle = headerTitle+"sectionfortag::"+crumbLabel;
					}					
					else if(crumbLabel.substring(0,6).toLowerCase()=="figure")
					{
					headerTitle = headerTitle+"figurefortag::"+crumbLabel;
					}					
					else if(crumbLabel.substring(0,4).toLowerCase()=="part")
					{
					headerTitle = headerTitle+"partfortag::"+crumbLabel;
					}
					// And now find the closest title after it:
					for (var titleNum = labelNum; titleNum <= metaBreadCrumbs.length-1; titleNum++) 
					{
						crumbTitle = metaBreadCrumbs[titleNum].title;
						if(crumbTitle && crumbTitle!= undefined) 
						{
						labelNum=titleNum;
						headerTitle = headerTitle + "::" + crumbTitle + "//";
						break;
						}
					}
					
				}
				else if(crumbLabel== undefined)
				{	
					crumbTitle = metaBreadCrumbs[labelNum].title;
					if(crumbTitle && crumbTitle!= 'undefined') 
						{

						if(crumbTitle.substring(0,6).toLowerCase()=="volume")
						{
						headerTitle = headerTitle+"volumefortag::"+"::" + crumbTitle + "//";
						}					
						else if(crumbTitle.substring(0,7).toLowerCase()=="chapter")
						{
						headerTitle = headerTitle+"chapterfortag::"+"::" + crumbTitle + "//";
						}
						else if(crumbTitle.substring(0,7).toLowerCase()=="section")
						{
						headerTitle = headerTitle+"sectionfortag::"+ "::" + crumbTitle + "//";;
						}					
						else if(crumbTitle.substring(0,6).toLowerCase()=="figure")
						{
						headerTitle = headerTitle+"figurefortag::"+"::" + crumbTitle + "//";
						}

						}
				}
			}
			if(metaBreadCrumbs[metaBreadCrumbs.length-1] && metaBreadCrumbs[metaBreadCrumbs.length-1]!= 'undefined')
			{
			headerTitle= headerTitle+ metaBreadCrumbs[metaBreadCrumbs.length-1].title;
			}
		}
		return headerTitle;
	};	
	// Get the label and/or title from a bread crumb (it can have a label,
	// a title, or both - though currently through building blocks we get
	// just one or another separate label and title nodes with the same
	// eid might be consolidated sometime later).
	function getMetaBreadCrumbLabelTitle(metaBreadCrumb) {
		var labelTitle;
		
		if(metaBreadCrumb) {
			if(metaBreadCrumb.label && metaBreadCrumb.title) {
				labelTitle = metaBreadCrumb.label + '.  ' + metaBreadCrumb.title;
			} 
			else {
				if(metaBreadCrumb.label) {
					labelTitle = metaBreadCrumb.label; 
				}
				if(metaBreadCrumb.title) {
					labelTitle = metaBreadCrumb.title; 
				}			
			}
		}
		return labelTitle;
	}
}
