// Main Collection JavaScript
// Copyright (c) 2006 Tom Dilatush
// All Rights Reserved

// generate html objects uniformly, esp. event handlers; fix their returns, etc.
// clean up all references to $( <global object>); make them global variables
// come up with pop-up handler of some kind

var mURLPath;
var mPaths;           // stack of visited page paths
var mInfoDB;          // information table categories, as an ordered array of category objects
var mInfoIndex;       // information table categories, indexed by tag
var mGlossary;				// glossary loaded from JSON
var mSO;							// ServerObjects instance
var myCollection;			// collection loaded from JSON
var mCH;							// ContextHandles instance
var mID;							// IDMaker instance
var mRandomInterval;  // the interval handle for the random photo switcher
var mRandomPhotos;		// the set of photos to pick a random one from
var meBusy;						// the busy managed element
var meHelp;						// the help managed element
var meCites;					// the citations managed element
var meGloss;					// the glossaries managed element
var meLabels;					// the info labels managed element
var myViewer;					// the ImageViewer instance
var myCookie;					// the "collection" cookie
var mMonths = { jan: 0, feb: 1, mar: 2, apr: 3, may: 4, jun: 5, jul: 6, aug: 7, sep: 8, oct: 9, nov: 10, dec: 11 };


function onBodyLoad() {
	
	// some setup...
	myCookie = new CollectionCookie();
	myCookie.store();
	meBusy = new ManagedElement( "busy", false );
	meCites = new ManagedElement( "cites", false, true, "" );
	meCites.setOnmouseoverMode( { handler: onCite } );
	meCites.setOnmouseoutMode( { visible: false } );
	meGloss = new ManagedElement( "gloss", false, true, "" );
	meGloss.setOnmouseoverMode( { handler: onGlossary } );
	meGloss.setOnmouseoutMode( { visible: false } );
	meLabels = new ManagedElement( "labels", false, true, "" );
	meLabels.setOnmouseoverMode( { handler: onLabel } );
	meLabels.setOnmouseoutMode( { visible: false } );
	meHelp = new ManagedElement( "help", false, false, "" );
	mSO = new ServerObjects( 50, meBusy, onSOError );
	mCH = new ContextHandles();
	mID = new IDMaker();
	mPaths = new Array();
	clearScreen();
	
	// get rid of our no-script message...
	Element.remove( "scriptless" );
	
	// get our URL, and any query attached to it...
	var vURL = document.URL.split( "?" );
	if( vURL.length < 2 ) vURL[1] = "Home";
	mURLPath = Path.fromFQPath( vURL[1] );
	
	// go get our stuff...
	mSO.getJSONObject( "collection.json", null, gotCollection );
	mSO.getJSONObject( "info.json", null, gotInfo );
	mSO.getJSONObject( "glossary.json", null, gotGlossary );
}

// default error handler for ServerObjects...
function onSOError( pStatus, pStatusText, pContext ) {
  var vDesc = $( "description" );
  vDesc.innerHTML = "Status: " + pStatus + "<br>" + pStatusText;	
}

function gotCollection( pObj ) {
  var vCollection = pObj;
  
  // munge the collection JSON to build our collection object...
  try {
	  var vRoot = new MyItem( vCollection );
	  myCollection = new MyCollection( vRoot );
	  var vTypes = vCollection.items;
	  for( var vI = 0; vI < vTypes.length; vI++ ) {
	  	var vType = new MyItem( vTypes[ vI ] );
	  	var x = 5/0;
	  	vRoot.addChild( vType );
	  	var vMakers = vTypes[ vI ].items;
	  	for( var vJ = 0; vJ < vMakers.length; vJ++ ) {
		  	var vMaker = new MyItem( vMakers[ vJ ] );
		  	vType.addChild( vMaker );
		  	var vModels = vMakers[ vJ ].items;
		  	if( vModels != undefined ) {
			  	for( var vK = 0; vK < vModels.length; vK++ ) {
				  	var vModel = new MyItem( vModels[ vK ] );
				  	vMaker.addChild( vModel );
					
						// now get references to any pictures in this item...
						if( vModel.getPhotos() != undefined ) {
							var vPhotos = vModel.getPhotos();
							for( var vL = 0; vL < vPhotos.length; vL++ ) {
								var vPhoto = vPhotos[ vL ];
								vPhoto[ "cParent" ] = vModel;
			  	
						  	// add some information to the photos...
						  	vPhoto[ "thumb" ] = vModel.getPath().getDocLocation( vPhoto.file ).replace( /\.(?=[a-zA-Z]+$)/, "-t\." );
						  	vPhoto[ "full" ] = vModel.getPath().getDocLocation( vPhoto.file );
							}
						}
			  	}
			  }
	  	}
	  }
	}
	catch( err ) {
		var vFakeItem = {
			path: "Error in collection JSON: " + err.name + ": " + err.message,
			dir: "Home",
			items: []
		}
		myCollection = new MyCollection( new MyItem( vFakeItem ) );
	}
  
  display( mURLPath );
}

function gotInfo( pObj ) {
  mInfoDB = pObj;
  mInfoIndex = new Object();
  for( var vI = 0; vI < mInfoDB.length; vI++ ) {
  	mInfoIndex[ mInfoDB[ vI ].tag ] = mInfoDB[ vI ];
  }
}

function gotGlossary( pObj ) {
  mGlossary = pObj;
}

function display( pPath ) {
	if( (mPaths.length == 0) || (! Path.same( pPath, mPaths.last() ) ) ) {
		mPaths.push( pPath );
	}
	displayNew( pPath );
}

function displayBack() {
	if( mPaths.length > 1 ) {
		mPaths.pop();
		displayTop();
	}
	return false;
}

function displayTop() {
	displayNew( mPaths.last() );
}

function displayNew( pPath ) {
	
	// figure out what to do with the banner...
	var vBanner = $( "banner" );
	vBanner.style.visibility = "hidden";
	var vText = [];
	if( ! CollectionCookie.areCookiesEnabled() ) {
		vText.push( "<p>" );
		vText.push( "Cookies appear to be disabled.  This web site depends on cookies for some of its features, and it does nothing " );
		vText.push( "evil with them.  Please consider enabling cookies, at least for this web site." );
		vText.push( "</p>" );
		vBanner.innerHTML = vText.join( "" );
		vBanner.style.visibility = "visible";
	}
	else {
		if( myCookie.getNickName().length == 0 ) {
			vText.push( "<p>" );
			vText.push( "Hello, stranger!&nbsp;&nbsp;Please visit our " );
			vText.push( "<td:ai href='Home/Info/Prefs'>preferences page</td:ai>" );
			vText.push( " to let us know a little bit about you..." );
			vText.push( "</p>" );
			vBanner.innerHTML = handleEmbedded( vText.join( "" ) );
			vBanner.style.visibility = "visible";
		}
		else {
			vText.push( "<p>" );
			vText.push( "Welcome back, " );
			vText.push( myCookie.getNickName() );
			vText.push( "!&nbsp;&nbsp;This is your " );
			vText.push( ordinalify( myCookie.getCount()) );
			vText.push( " visit to the site.&nbsp;&nbsp;You can visit the " );
			vText.push( "<td:ai href='Home/Info/Prefs'>preferences page</td:ai>" );
			vText.push( " to update your settings anytime..." );
			vText.push( "</p>" );
			vBanner.innerHTML = handleEmbedded( vText.join( "" ) );
			vBanner.style.visibility = "visible";
		}
		
	}
	
	switch( pPath.size() ) {
		case 1:
      displayHome( pPath );
			break;
		case 2:
      displayType( pPath );
			break;
		case 3:
      displayMaker( pPath );
			break;
		case 4: 
      displayItem( pPath );
			break;
	}
}

function displayHome( pPath ) {
	clearScreen();
  displayPath( pPath );
  displayRandomPhoto( pPath );
  displayDescription( pPath );
  var vTypeItems = myCollection.getItem( pPath ).getChildren();
	var vTypePat = new PatternSub( "<p class='l1'>%1%</p>\n%2%" );
  for( var vType = 0; vType < vTypeItems.length; vType++ ) {
    var vTypeItem = vTypeItems[ vType ];
    if( vTypeItem.isCollectionType() ) {
		  var vMakerItems = vTypeItem.getChildren();
			var vMakerPat = new PatternSub( "<p class='l2'>%1%</p>\n%2%" );
			for( var vMaker = 0; vMaker < vMakerItems.length; vMaker++ ) {
			  var vMakerItem = vMakerItems[vMaker];
			  var vModelItems = vMakerItem.getChildren();
			  var vModelPat = "";
			  if( vModelItems != undefined ) {
			  	vModelPat = new PatternSub( "<p class='l3'>\n%1%</p>\n" );
			  	vModelItemPat = new PatternSub( "%1%%2%" );
				  for( var vModel = 0; vModel < vModelItems.length; vModel++ ) {
				    var vModelItem = vModelItems[vModel];
				    var vSuff = ((vModel == (vModelItems.length - 1)) ? "" : ", \n");
						vModelItemPat.execute( makeLink( vModelItem.getPath() ), vSuff );
				  }
				  vModelPat.execute( vModelItemPat );
				}
			  vMakerPat.execute( makeLink( vMakerItem.getPath() ), vModelPat );
			}
			vTypePat.execute( makeLink( vTypeItem.getPath() ), vMakerPat );
		}
  }
  var vColl = $( "collection" );
  vColl.innerHTML = vTypePat;
}

function onLink( pPath ) {
	display( Path.fromFQPath( pPath ) );
	return false;
}

function displayType( pPath ) {
	clearScreen();
  var vTypeItem = myCollection.getItem( pPath );
  displayPath( pPath );
  displaySubtitle( vTypeItem.getPathElementName() );
  if( vTypeItem.isCollectionType() ) displayRandomPhoto( pPath );
  displayDescription( pPath );
  var vMakerItems = vTypeItem.getChildren();
	var vMakerPat = new PatternSub( "<p class='l2'>%1%</p>\n%2%" );
	for( var vMaker = 0; vMaker < vMakerItems.length; vMaker++ ) {
	  var vMakerItem = vMakerItems[vMaker];
	  var vModelItems = vMakerItem.getChildren();
	  var vModelPat = "";
	  if( vModelItems != undefined ) {
	  	vModelPat = new PatternSub( "<p class='l3'>\n%1%</p>\n" );
	  	vModelItemPat = new PatternSub( "%1%%2%" );
		  for( var vModel = 0; vModel < vModelItems.length; vModel++ ) {
		    var vModelItem = vModelItems[vModel];
		    var vSuff = ((vModel == (vModelItems.length - 1)) ? "" : ", \n");
				vModelItemPat.execute( makeLink( vModelItem.getPath() ), vSuff );
		  }
		  vModelPat.execute( vModelItemPat );
		}
	  vMakerPat.execute( makeLink( vMakerItem.getPath() ), vModelPat );
	}
  var vColl = $( "collection" );
  vColl.innerHTML = vMakerPat;
}

function displayMaker( pPath ) {
	clearScreen();
  var vMakerItem = myCollection.getItem( pPath );
  displayPath( pPath );
  displaySubtitle( vMakerItem.getPathElementName() + " " + vMakerItem.getParent().getPathElementName() );
  if( vMakerItem.getParent().isCollectionType() ) displayRandomPhoto( pPath );
  displayDescription( pPath );
  var vModelItems = vMakerItem.getChildren();
  var vModelPat = "";
  if( vModelItems != undefined ) {
  	vModelPat = new PatternSub( "<p class='l2'>\n%1%</p>\n" );
	  for( var vModel = 0; vModel < vModelItems.length; vModel++ ) {
	    var vModelItem = vModelItems[vModel];
			vModelPat.execute( makeLink( vModelItem.getPath() ) );
	  }
	}
  var vColl = $( "collection" );
  vColl.innerHTML = vModelPat;
}

function displayItem( pPath ) {
  var vItem = myCollection.getItem( pPath );
	clearScreen();
	displayPath( pPath );
	displaySubtitle( vItem.getBrand() + " " + vItem.getPathElementName() );
  displayDescription( pPath );
  displayInfoTable( vItem );
  
  var vThumbs = $( "thumbs" );
  vThumbs.innerHTML = "<span id='thumbShower'></span>" +
  										"<div id='fullShow' class='fullShow' style='visibility:hidden;'></div>";
  
  var vPhotos = vItem.getPhotos();
  
  // if we have any photos...
  if( vPhotos && (vPhotos.length > 0) ) {
		
		// now load the thumbnails offscreen...
  	showThumbs( vItem );
  	for( var vI = 0; vI < vPhotos.length; vI++ ) {
			mSO.getImageObject( vPhotos[vI][ "thumb" ], vPhotos[vI], gotThumb );
  	}
  }
  
  // display a price button, if needed...
  if( (vItem.getParent().getQuery() ) && (vItem.getQuery()) ) {
  	var vPrice = $( "price" );
  	vPrice.innerHTML = generatePriceButton( pPath );
	}
}

function displayInfoTable( pItem ) {
	mSO.getJSONObject( pItem.pathObj.getDocLocation( "info.json" ), pItem, gotItemInfo );
}

function gotItemInfo( pObj, pContext ) {

	var vGotOne = false;
	var vResults = "";
	var vCount = 0;
	
	var vTable = new Tag( "table", "%1%" ).addAttribute( "class", "infoTable",  "cellspacing", 0, "cellpadding", 0, "width", "100%" );
	var vInfoPat = new PatternSub( vTable );
	
	var vSpan = "<span class='poppable' id='label%1%' %5% %6%>%2%</span>";
	var vTD1 = new Tag( "td" ).addContent( vSpan, ":" ).addAttribute( "width", "20%", "class", "infoTable infoTableLabel" );
	var vTD2 = new Tag( "td", "%3%" ).addAttribute( "width", "80%", "class", "infoTable infoTableDesc" );
	var vTR = new Tag( "tr" ).addContent( vTD1, vTD2 ).addAttribute( "class", "%4%" );
	var vInfoLinePat = new PatternSub( vTR );

	for( var vI = 0; vI < mInfoDB.length; vI++ ) {
		var vLabel = mInfoDB[ vI ].tag;
		if( pObj[ vLabel ] ) {
			vGotOne = true;
			var vRowClass = (vCount % 2 == 0) ? "infoTableEven" : "infoTableOdd";
			vCount++;
			var vInfoItem = pObj[ vLabel ];
			if( ! (vInfoItem instanceof Array) ) {
				vInfoItem = new Array();
				vInfoItem.push( pObj[ vLabel ] );
			}
			var vInfoCellPat = new PatternSub( "%1%%2%" );
			for( var vJ = 0; vJ < vInfoItem.length; vJ++ ) {
				var vBr = ((vJ == (vInfoItem.length - 1)) ? "" : "<br>" );
				vInfoCellPat.execute( vInfoItem[ vJ ], vBr );
			}
			var vMouseOver = meLabels.getOnMouseoverString( vI );
			var vMouseOut = meLabels.getOnMouseoutString( vI );
			vInfoLinePat.execute( vI, mInfoDB[ vI ].label, vInfoCellPat, vRowClass, vMouseOver, vMouseOut );
		}
	}
	
	if( vGotOne ) {
		vInfoPat.execute( vInfoLinePat );
		vResults = vInfoPat.toString();
	}
	var vInfo = $( "info" );
	vInfo.innerHTML = handleEmbedded( vResults );
}

function onLabel( pInstance, pIndex ) {
	var vItem = $( "label" + pIndex );
	pInstance.setOverlappingLocation( vItem, -20 );
	pInstance.show( mInfoDB[pIndex].desc );
}

// shows whatever thumbnails are already loaded...
function showThumbs( pItem ) {
	  	
  // set up the table that will hold the thumbnails...
  var vP = new Tag( "p", "Scans and Photos" ).addAttribute( "class", "thumbHeader" );
  var vTD = new Tag( "td", vP ).addAttribute( "class", "photos" );
  var vTR = new Tag( "tr", vTD );
  var vTable = new Tag( "table", vTR ).addAttribute( "class", "photos" ).addContent( "%1%" );
  var vPatTbl = new PatternSub( vTable );
  
  var vP1 = new Tag( "p", "%1%" ).addAttribute( "class", "thumbTitle" );
  var vImg = new Tag( "img" ).addAttribute( "src", "%2%", "alt", "%3%", "title", "%3%" );
  var vA1 = new Tag( "a", vImg ).addAttribute( "onclick", "fetchFull(%4%)", "id", "%6%" );
  var vP2 = new Tag( "p", vA1 ).addAttribute( "class", "thumbImg" );
  var vP3 = new Tag( "p", "%3%" ).addAttribute( "class", "thumbDesc" );
  var vButt = new Tag( "button", "Fetch Large Image" ).addAttribute( "onclick", "fetchFull(%4%)" );
  var vP4 = new Tag( "p", vButt ).addAttribute( "class", "thumbButton", "id", "%7%" );
  vTD = new Tag( "td" ).addAttribute( "class", "photos" ).addContent( vP1, vP2, vP3, vP4 );
  vTR = new Tag( "tr", vTD ).addAttribute( "style", "visibility:hidden;", "id", "%5%" );
	var vPatRows = new PatternSub( vTR );
	
  var vPhotos = pItem.getPhotos();
	for( var vI = 0; vI < vPhotos.length; vI++ ) {
		var vPhoto = vPhotos[vI];
		var vDesc = vPhoto.description;
		var vRes = vPhoto.res;
		if( vRes != undefined ) vDesc += " (" + vRes + ")";
		vPhoto[ "id" ] = mID.generate();
		vPhoto[ "id1" ] = mID.generate();
		vPhoto[ "id2" ] = mID.generate();
		vPhoto[ "handle" ] = mCH.store( { photo: vPhoto, id: vPhoto.id, id1: vPhoto.id1, id2: vPhoto.id2 } );
		vPatRows.execute( vPhoto.title, vPhoto.thumb, vDesc, vPhoto.handle, vPhoto.id, vPhoto.id2, vPhoto.id1 );
	}
  vPatTbl.execute( vPatRows );
  var vColl = $( "thumbShower" );
  vColl.innerHTML = vPatTbl;
}

// called when a thumbnail loads...
function gotThumb( pImage, pContext ) {
	var vThumbRow = $( pContext.id );
	vThumbRow.style.visibility = "visible";
}

// called when the fetch button is clicked...
function fetchFull( pHandle ) {
	
	// get our context...
	var vContext = mCH.retrieve( pHandle );
	
	// update the button to say "loading"...
	var vButt = $( vContext.id1 );
	vButt.innerHTML = "<i>Fetching...</i>";
	
	// load the large image off-screen...
	mSO.getImageObject( vContext.photo[ "full" ], vContext, gotLarge );
}

// called when a large image has loaded offscreen...
function gotLarge( pImage, pContext ) {
	
	// update the button to say "view large"...
	var vButt = $( pContext.id1 );
	var vImg = $( pContext.id2 );
	pContext.photo[ "fullImage" ] = pImage;
	var vHandle = mCH.store( pContext.photo  );
	var vPat = new PatternSub( "<button onclick='showPic(%1%)'>View Large Image</button>" );
	vPat.execute( vHandle );
	vButt.innerHTML = vPat;
	var vFunk;
	eval( "vFunk = (function(){showPic(" + vHandle + ");})" );
	vImg.onclick = vFunk;
	showViewerHelp();
}

function showViewerHelp() {
	if( ! myCookie.isFetchMsgSuppressed() ) {
		var vHelp = [];
		vHelp.push( "You've now fetched a large image from the server.  To view it, you need to click on the \"View Large Image\" button." );
		vHelp.push( "You may have to scroll down to see this button.  If you're interested in viewing several large images, you can start " );
		vHelp.push( "them all fetching, and then view them as they finish loading.  You may also switch back-and-forth amongst them.<br><br>" );
		vHelp.push( "<button onclick='return onViewerHelpDone(true);'>Don't Show This Again</button>" );
		vHelp.push( "<button onclick='return onViewerHelpDone(false);'>Ok</button>" );
		meHelp.show( vHelp.join( "" ) );
	}
}

function onViewerHelpDone( pDone ) {
	myCookie.setFetchMsgSuppressed( pDone );
	myCookie.store();
	meHelp.hide();
}

function showPic( pHandle ) {
	var vPhoto = mCH.retrieve( pHandle );
	var vRes;
	if( vPhoto.res ) {
		var vResExt = vPhoto.res.match( /([0-9]+) *dpi/ );
		if( vResExt ) {
			vRes = 0 + vResExt[1];
		}
	}
	var vTitle = vPhoto.cParent.getBrand() + " " + 
					     vPhoto.cParent.getPathElementName() + " " +
					     vPhoto.title;
	myViewer = new ImageViewer( vPhoto.fullImage, vTitle, 20, vRes, myCookie.getDefaultClick(), myCookie.getDefaultDrag() );
}

function killPic() {
	var vShow = $( "fullShow" );
	vShow.style.visibility = "hidden";
}

// makes an HTML text link with onclick handlers for the given path, using the given text.  If text is not supplied, item's "path" value is used...
function makeLink( pPath, pText ) {

	var vItem = myCollection.getItem( pPath );
	var vItemPathName = vItem.getPathElementName();
	var vMod = "";
	if( pText != undefined ) vItemPathName = pText;
	var vAdded = vItem.getDateAdded();
	if( vAdded != undefined ) {
		var vNow = new Date();
		var vDiff = vNow.valueOf() - vAdded.valueOf();
		var vDiff = vDiff / (1000 * 60 * 60 * 24);
		if( vDiff <= 30 ) {
			vMod = "style=\"color: red;\" ";
		}
	}
	var vPat = new PatternSub(
		"<a %1%href=\"?%2%\" onclick=\"return onLink('%2%');\">%3%</a>"
	);
	vPat.execute( vMod, pPath.getFQPath(), vItemPathName );
	return vPat;	
}

function elemsToPath( pElems ) {
	var vPath = "";
	for( var vI = 0; vI < pElems.length; vI++ ) {
		if( vI > 0 ) vPath += "/";
		vPath += pElems[vI];
	}
	return vPath;
}

function normalPath( pUnknown ) {
	var vResult = pUnknown;
	if( pUnknown instanceof Array ) {
		vResult = elemsToPath( pUnknown );
	}
	return vResult;
}

function displayPath( pPath ) {
	var vPatOut = new PatternSub( "Path: %1%" );
	var vPatIn = new PatternSub( "%1%%2%" );
	for( var vI = 0; vI < pPath.size(); vI++ ) {
		vPatIn.execute( makeLink( pPath.getParent( vI + 1) ),
									  (vI < (pPath.size() - 1)) ? " &middot; " : "" );
	}
	vPatOut.execute( vPatIn );
	var vPath = $( "path" );
	vPath.innerHTML = vPatOut;
	
	// if we have some history, display a back link...
  var vPath = $( "back" );
  vPath.innerHTML = (mPaths.length > 1) ? "<a href=\"\" onclick=\"return displayBack();\">back</a>" : "";
}

// displays the HTML file with the given name in the description section...
function displayFile( pFilePath, pConcatText ) {
	mSO.getTextObject( pFilePath, pConcatText, gotDescription );
}

function displayDescription( pPath, pConcatText ) {
	displayFile( pPath.getDocLocation( "description.html"), pConcatText );
}

// get here when the description text loads...
function gotDescription( pText, pContext ) {
  var vHTML = pText;
  
  // if there's any script embedded, extract it and save it...
  var vScript = vHTML.match( /<script>[\s\S]*?<\/script>/igm );
  if( vScript != null ) {
  	var vBuff = [];
  	for( var vI = 0; vI < vScript.length; vI++ ) {
  		vBuff.push( vScript[vI].match( /<script>([\s\S]*?)<\/script>/im )[1] );
  	}
  	vScript = vBuff.join( "" );
  	vHTML = vHTML.replace( /<script>[\s\S]*?<\/script>/igm, "" );
	}  
  
  // make the HTML look good, and insert it...
  if( pContext )vHTML += pContext;
  vHTML = handleEmbedded( vHTML );
  vHTML = makePretty( vHTML );
  var vDesc = $( "description" );
  vDesc.innerHTML = vHTML;
  
  // now if we have any script, execute it...
  if( vScript != null ) {
  	eval( vScript );
  }
}

// displays randomly-selected appropriate photos from the collection below the given path...
function displayRandomPhoto( pPath ) {
	
	// if we're supposed to be doing this...
	if( myCookie.isShowRandom() ) {
	
		// first we gather up all the relevant photos...
	  var vItem = myCollection.getItem( pPath );
	  mRandomPhotos = new Array();
	  gatherPhotos( vItem, mRandomPhotos );
	  
	  // set a timer to repeat this, if we need to...
	  if( mRandomInterval == null ) {
	  	mRandomInterval = setInterval( "switchPhoto();", 1000 * myCookie.getRandomInterval() );
	  }
	  
	  // figure out our title...
		var vTitle = "Random Item Photo";
		switch( pPath.size() ) {
			case 1:
				vTitle = "Random Item from Collection";
				break;
			case 2: 
			  vTitle = "Random Item from " + vItem.getPathElementName();
				break;
			case 3:
			  vTitle = "Random Item from " + vItem.getPathElementName() + " " + vItem.getParent().getPathElementName();
			  break;
		}
		
		// make the static part of the html...
		
		var vTable = new Tag( "table" );
		vTable.addAttribute( "cellspacing", "0", "cellpadding", "0", "border", "0", "class", "randomPhoto" );
		
		vRow = new Tag( "tr" );
		vTable.addContent( vRow );
		vCell = new Tag( "td" );
		vRow.addContent( vCell );
		vRow.addAttribute( "class", "randomPhotoTitle" );
		vCell.addAttribute( "class", "randomPhotoTitle" );
		vCell.addContent( vTitle );
		
		vRow = new Tag( "tr" );
		vTable.addContent( vRow );
		vCell = new Tag( "td", "Loading..." );
		vRow.addContent( vCell );
		vRow.addAttribute( "class", "randomPhotoCell" );
		vCell.addAttribute( "class", "randomPhotoCell", "id", "randomPhotoCell" );
		
		vRow = new Tag( "tr" );
		vTable.addContent( vRow );
		vCell = new Tag( "td" );
		vRow.addContent( vCell );
		vCell.addAttribute( "class", "randomPhotoLink", "id", "randomPhotoLink" );
		
		var vRP = $( "randomPhoto" );
		vRP.innerHTML = vTable;
		vRP.style.visibility = "visible";
		
		// now get a photo loading...
		switchPhoto();
	}
}

// called on intervals to switch random photo...
function switchPhoto() {
	var vPhoto = mRandomPhotos[ Math.round( Math.random() * mRandomPhotos.length ) % mRandomPhotos.length ];
			mSO.getImageObject( vPhoto[ "thumb" ], vPhoto, randomLoaded, undefined, true );
}

// called when a random thumbnail has loaded...
function randomLoaded( pImage, pContext ) {
	var vPhoto = pContext;
	var vImg = new Tag( "img" ).addAttribute( "src", vPhoto.thumb );
	var vBrand = vPhoto.cParent.getBrand();
	var vLink = makeLink( vPhoto.cParent.getPath(), vBrand + " " + vPhoto.cParent.getPathElementName() );
	var vLinkRef = $( "randomPhotoLink" );
	var vCellRef = $( "randomPhotoCell" );
	if( (vLinkRef) && (vCellRef) ) {
		vLinkRef.innerHTML = vLink;
		vCellRef.innerHTML = vImg;
	}
}

// recursive function to gather up photos eligible for random viewing under a given item...
function gatherPhotos( pItem, pResults ) {
	var vKids = pItem.getChildren();
	if( vKids ) {
		for( var vI = 0; vI < vKids.length; vI++ ) {
			gatherPhotos( vKids[ vI ], pResults );
		}
	}
	var vPhotos = pItem.getPhotos();
	if( vPhotos ) {
		for( var vI = 0; vI < vPhotos.length; vI++ ) {
			var vPhoto = vPhotos[ vI ];
			if( vPhoto.showRandom ) pResults.push( vPhoto );
		}
	}	
}

/*
 * This function processes the text in the description to look for special embedded tags and replace them with the correct HTML.  The tags
 * processed include:
 * <td:ax href="{external link}">{text}</td:ax>
 *   replaced by <a> tag with _blank target
 * <td:ai href="{Home/{internal path}}">{text}</td:ai>
 *   replaced by <a> tag with fake href and onclick processor
 * <td:cite>{citation text}</td:cite>
 *   replaced by a superscripted citation number, and citation text is put in a popup
 * <td:gl word="{word}">{text}<td:gl>
 *   replaced by an underlined {text}, popping up a reference to {word}
 * <td:gl>{word}<td:gl>
 *   exactly as above, except that {word} and {text} are the same
 */
function handleEmbedded( pText ) {
	var vResult = pText.replace( /<td:ax href=["'](.*?)["']>([\s\S]*?)<\/td:ax>/gm, "<a href='$1' target='_blank'>$2</a>" );
	vResult = vResult.replace( /<td:ai href=["'](.*?)["']>([\s\S]*?)<\/td:ai>/gm, 
	                           "<a href=\"index.html?$1\" onclick=\"return onLink('$1');\">$2</a>" );
	vResult = vResult.replace( /<td:gl word=["'](.*?)["']>([\s\S]*?)<\/td:gl>/gm, makeGlossary );
	vResult = vResult.replace( /<td:gl()>([\s\S]*?)<\/td:gl>/gm, makeGlossary );
	vResult = vResult.replace( /<td:cite>([\s\S]*?)<\/td:cite>/gm, makeCite );
	return vResult;

	function makeGlossary( pStr, pWord, pText ) {
		var vWord = pWord;
		if( vWord == "" ) vWord = pText;
		var vID = mID.generate();
		var vHandle = mCH.store( { word: vWord, id: vID } );
		var vMouse = meGloss.getOnMouseOverOutString( vHandle );
		var vPat = new PatternSub( "<span class=\"poppable\" %1% id=\"%2%\">%3%</span>" );
		vPat.execute( vMouse, vID, pText );
		return vPat;
	}

	function makeCite( pStr, pPat ) {
		var vID = mID.generate();
		var vHandle = mCH.store( { text: pPat, id: vID } );
		var vMouse = meCites.getOnMouseOverOutString( vHandle );
		var vPat = new PatternSub( "<span class='cite poppable' %1% id='%2%'>cite</span>" );
		vPat.execute( vMouse, vID );
		return vPat;
	}
}

// prettifies the text by making curly quotes and en-dashes...
function makePretty( pText ) {
	var vResults = pText.replace( /([^a-zA-Z0-9])"(?=[a-zA-Z0-9][^>]*?(<|$))/gm, "$1&ldquo;" );
	var vResults = vResults.replace( /([a-zA-Z0-9])"(?=[^a-zA-Z0-9>][^>]*?(<|$))/gm, "$1&rdquo;" );
	var vResults = vResults.replace( /([a-zA-Z0-9])'(?=[^>]*?(<|$))/gm, "$1&rsquo;" );
	var vResults = vResults.replace( /--(?=[^>]*?(<|$))/gm, "&ndash;" );
	var vResults = vResults.replace( /\.\.\.(?=[^>]*?(<|$))/gm, "&hellip;" );
	return vResults;
}

function onGlossary( pInstance, pHandle ) {
	var vContext = mCH.retrieve( pHandle );
	var vItem = $( vContext.id );
	pInstance.setOverlappingLocation( vItem, -20 );
	pInstance.show( mGlossary[ vContext.word.toLowerCase() ] );
}

function onCite( pInstance, pHandle ) {
	var vContext = mCH.retrieve( pHandle );
	var vItem = $( vContext.id );
	pInstance.setOverlappingLocation( vItem, -20 );
	pInstance.show( vContext.text );
}

function displaySubtitle( pText ) {
	var vSubTitle = $( "subtitle" );
	vSubTitle.innerHTML = pText;
	vSubTitle.style.visibility = "visible";
}
	
function clearScreen() {
	if( mRandomInterval != null ) {
		clearInterval( mRandomInterval );
		mRandomInterval = null;
	}
	mCH.reset();
	mID.reset();
	mPopLevel = 0;
	mWorks = 0;
	var vBusy = $( "busy" );
	var vSubTitle = $( "subtitle" );
	var vRandomPhoto = $( "randomPhoto" );
	vSubTitle.innerHTML = "";
	vSubTitle.style.visibility = "hidden";
	vRandomPhoto.innerHTML = "";
	vRandomPhoto.style.visibility = "hidden";
	vBusy.style.visibility = "hidden";
	var vPath = $( "path" );
	var vDesc = $( "description" );
	var vColl = $( "collection" );
	var vInfo = $( "info" );
	var vThumbs = $( "thumbs" );
	var vPrice = $( "price" );
	vPath.innerHTML = "&nbsp;";
	vDesc.innerHTML = "<p><i>Loading information from web server &ndash; please wait...</i></p>";
	vColl.innerHTML = "";
	vInfo.innerHTML = "";
	vThumbs.innerHTML = "";
	vPrice.innerHTML = "";
	scrollTo( 0, 0);
}

/* stuff to handle getting prices from Ron Lovett's site */

// generates the HTML for a price button for the given path
function generatePriceButton( pPath ) {
	var vText = new Array();
	vText.push( "<p><button onclick=\"return fetchRod('" );
	vText.push( pPath.getFQPath() );
	vText.push( "');\">Fetch eBay Price Information</button></p>" );
	return vText.join( "" );
}

// invoked when the fetch ebay price info button is clicked...
function fetchRod( pPath ) {
	var vPriceDiv = $( "price" );
	vPriceDiv.innerHTML = "<p><i>Fetching eBay Price Information...</i></p>";
	queryRod( pPath );
	return false;
}

// submits a query to Rod Lovett's slide rule search for the given model and maker...
function queryRod( pItemPath ) {
	
	// some setup...
	var vItem = myCollection.getItem( Path.fromFQPath( pItemPath ) );
	var vRod = new Object();
	
	// get our search terms...
	var vMakerTerm = vItem.getBrandQuery();
	var vModelTerm = vItem.getQuery();
	vModelTerm = vModelTerm.replace( /\[([0-9])*\]/g, ".*?[^0-9]$1[^0-9]" );  // distinct number modification...
	
	// save some info about this query...
	var vMaker = vItem.getBrand();
	var vModel = vItem.getPathElementName();
	vRod[ "maker" ] = vMaker;
	vRod[ "model" ] = vModel;
	vRod[ "makerTerm" ] = vMakerTerm;
	vRod[ "modelTerm" ] = vModelTerm;
	
	// construct our URL...
	var vURL = new Array();
	vURL.push( "http://sliderules.lovett.com/srsearch.cgi?string1=" );
	vURL.push( encodeURIComponent( vMakerTerm ) );
	vURL.push( "&string2=" );
	vURL.push( encodeURIComponent( vModelTerm ) );
	vURL.push( "&string3=&string4=&string5=%240&month=10&year=1999&R=Text&British=on&German=on" );
	var vWholeURL = "RemoteQuery.jsp?url=" + encodeURIComponent( vURL.join("") );
	var vURL = [];
	vURL.push( "http://sliderules.lovett.com/srsearch.cgi?string1=" );
	vURL.push( encodeURIComponent( vMakerTerm ) );
	vURL.push( "&string2=" );
	vURL.push( encodeURIComponent( vModelTerm ) );
	vURL.push( "&string3=&string4=&string5=%240&month=10&year=1999&R=Text&British=on&German=on" );
	vRod[ "url" ] = vURL.join( "" );

	// make the request...	
	mSO.getTextObject( vWholeURL, vRod, gotRodAnswer );
}

// processes the response from Rod Lovett's site...
function gotRodAnswer( pAns, pContext ) {
	
	// some setup...
	var vAns = pAns;
	var vRod = pContext;
	var vBuys = new Array();
	
	// fish out the answers...
	var vPat = /<TR><TD>(.*?)<\/TD><TD>(.*?)<\/TD><TD>(.*?)<\/TD><TD>(.*?)<\/TD><\/TR>/gm;
	var vRow;
	while( (vRow = vPat.exec( vAns )) != null ) {
		var vDate = rodDate( vRow[1] );
		var vBids = Number( vRow[2] );
		var vPrice = Number( vRow[3] );
		if( vBids > 0 ) {
			var vBuy = new Object();
			vBuy[ "price" ] = vPrice;
			vBuy[ "date" ] = vDate;
			vBuys.push( vBuy );
		}
	}
	
	// now compute our stats...
	var vNumBuys = vBuys.length;
	var vLowestPrice = 0;
	var vHighestPrice = 0;
	var vAveragePrice = 0;
	var vMeanPrice = 0;
	var vTotalPrice = 0;
	var vMostRecentDate = new Date();
	var vMostRecentPrice = 0;
	if( vNumBuys > 0 ) {
		vBuys.sort( sortBuysByPrice );
		for( var vI = 0; vI < vNumBuys; vI++ ) {
			vTotalPrice += vBuys[vI].price;
		}
		vAveragePrice = vTotalPrice / vNumBuys;
		vLowestPrice = vBuys[0].price;
		vHighestPrice = vBuys[ vNumBuys - 1 ].price;
		vMeanPrice = vBuys[ Math.floor( vNumBuys / 2 ) ].price;
		vBuys.sort( sortBuysByDate );
		var vMostRecent = vBuys[ vNumBuys - 1 ];
		vMostRecentDate = vMostRecent.date;
		vMostRecentPrice = vMostRecent.price;
	}
	
	// now save the results...
	vRod[ "buys" ] = vNumBuys;
	vRod[ "lowPrice" ] = vLowestPrice;
	vRod[ "highPrice" ] = vHighestPrice;
	vRod[ "meanPrice" ] = vMeanPrice;
	vRod[ "averagePrice" ] = vAveragePrice;
	vRod[ "totalPrice" ] = vTotalPrice;
	vRod[ "mostRecentDate" ] = "" + (vMostRecentDate.getMonth() + 1) + "/" + vMostRecentDate.getDate() + "/" + vMostRecentDate.getFullYear();
	vRod[ "mostRecentPrice" ] = vMostRecentPrice;

	// display our data...
	var vHTML = new Array();
	vHTML.push( "<p><b>Pricing information for " );
	vHTML.push( vRod[ "maker" ] );
	vHTML.push( " model " );
	vHTML.push( vRod[ "model" ] );
	vHTML.push( " slide rules sold on eBay between October 1999 and now.</b><br><br>  This information is taken from Rod Lovett's very useful " );
	vHTML.push( "<td:ax href=\"http://sliderules.lovett.com/srsearch.html\">Slide Rule Search</td:ax> site, with his kind permission.<br><br>" );
	if( vRod[ "buys" ] > 1 ) {
		vHTML.push( "A total of " );
		vHTML.push( vRod[ "buys" ] );
		vHTML.push( " purchases of the " );
		vHTML.push( vRod[ "maker" ] );
		vHTML.push( " " );
		vHTML.push( vRod[ "model" ] );
		vHTML.push( " were found.  In those purchases, the price ranged from $" );
		vHTML.push( vRod[ "lowPrice" ].toFixed(2) );
		vHTML.push( " to $" );
		vHTML.push( vRod[ "highPrice" ].toFixed(2) );
		vHTML.push( ", with an average price of $" );
		vHTML.push( vRod[ "averagePrice" ].toFixed(2) );
		vHTML.push( " and a mean price of $" );
		vHTML.push( vRod[ "meanPrice" ].toFixed(2) );
		vHTML.push( ".  The total amount spent on purchases of this model was $" );
		vHTML.push( vRod[ "totalPrice" ].toFixed(2) );
		vHTML.push( ". The most recent purchase occured on " );
		vHTML.push( vRod[ "mostRecentDate" ] );
		vHTML.push( ", at a price of $" );
		vHTML.push( vRod[ "mostRecentPrice" ].toFixed(2) );
		vHTML.push( "." );
		vHTML.push( "<br><br>" );
	} else if( vRod[ "buys" ] == 1) {
		vHTML.push( "Only one purchase of an " );
		vHTML.push( vRod[ "maker" ] );
		vHTML.push( " " );
		vHTML.push( vRod[ "model" ] );
		vHTML.push( " was found, on " );
		vHTML.push( vRod[ "mostRecentDate" ] );
		vHTML.push( ", at a price of $" );
		vHTML.push( vRod[ "mostRecentPrice" ].toFixed(2) );
		vHTML.push( "." );
		vHTML.push( "<br><br>" );
	} else {
		vHTML.push( "There were no purchases of the " );
		vHTML.push( vRod[ "maker" ] );
		vHTML.push( " " );
		vHTML.push( vRod[ "model" ] );
		vHTML.push( " found.<br><br>" );
	}
	vHTML.push( "Notes:<br><br>" );
	vHTML.push( "The results of the eBay data search are not perfect, and sometimes can be quite misleading.  One problem is that sometimes " );
	vHTML.push( "an auction has multiple items for sale, and the price paid for <i>all</i> of them will be included in these data.  Another " );
	vHTML.push( "problem is that many eBay listings fail to include the maker or model (or both!), or they are misspelled, or they have some " );
	vHTML.push( "other characteristic that causes them to be excluded from these data.  Yet another problem arises when an eBay listing mentions " );
	vHTML.push( "a particular maker and model, but doesn't actually include it (as when a manual is being offered for sale).  All of these " );
	vHTML.push( "things (and I'm sure some others as well) contribute to errors in these results -- so please don't take these results as gospel.  " );
	vHTML.push( "Think of them as what they are: an <i>indication</i> of what prices people are paying for this make and model, and how " );
	vHTML.push( "frequently it shows up on the market." );
	vHTML.push( "<br><br>" );
	vHTML.push( "If you'd like to see the actual detailed search results from Rod Lovett's engine, " );
	vHTML.push( "<td:ax href='" );
	vHTML.push( vRod[ "url" ] );
	vHTML.push( "'>click here</td:ax>" );
	vHTML.push( "<br><br>" );
	vHTML.push( "The two search terms used were:<span class='term'> " );
	vHTML.push( entify( vRod.makerTerm ) );
	vHTML.push( " </span>and<span class='term'> " );
	vHTML.push( entify( vRod.modelTerm ) );
	vHTML.push( "&nbsp;</span>" );
	vHTML.push( "</p>" );
	var vPriceDiv = $( "price" );
	vPriceDiv.innerHTML = handleEmbedded( vHTML.join("") );
}

function sortBuysByPrice( a, b ) {
	return a.price - b.price;
}

function sortBuysByDate( a, b ) {
	return a.date.getMilliseconds() - b.date.getMilliseconds();
}

// converts a date in the form dd-MMM-yyyy (e.g., "21-Apr-2002" ) to a Date object...
function rodDate( pText ) {
	var vElems = pText.match( /([0-9]{2})-([a-zA-Z]{3})-([0-9]{4})/ );
	var vMonth = mMonths[ vElems[2].toLowerCase() ];
	if( vMonth == undefined ) vMonth = 0;
	return new Date( vElems[3], vMonth, vElems[1] );
}

// runs when preferences load...
function onPrefsLoad() {
	$( "prfEMerr" ).style.visibility = "hidden";
	$( "prfRIerr" ).style.visibility = "hidden";
	$( "prfNM" ).value = myCookie.getName();
	$( "prfNN" ).value = myCookie.getNickName();
	$( "prfEM" ).value = myCookie.getEmail();
	$( "prfSM" ).checked = myCookie.isFetchMsgSuppressed();
	$( "prfSR" ).checked = ! myCookie.isShowRandom();
	$( "prfRI" ).value = myCookie.getRandomInterval();
	selectOption( $( "prfDC" ), myCookie.getDefaultClick() );
	selectOption( $( "prfDD" ), myCookie.getDefaultDrag() );
}

// invoked when "Save" button on preferences screen is clicked.
function onPrefsSave() {
	
	// some setup...
	var vOK = true;
	var vEM = $( "prfEM" );
	var vRI = $( "prfRI" );
	var vDC = $( "prfDC" );
	var vDD = $( "prfDD" );
	var vNM = $( "prfNM" );
	var vNN = $( "prfNN" );
	var vSM = $( "prfSM" );
	var vSR = $( "prfSR" );
	var vEMerr = $( "prfEMerr" );
	var vRIerr = $( "prfRIerr" );
	vEMerr.style.visibility = "hidden";
	vRIerr.style.visibility = "hidden";
	
	// does email look ok?
	if( (vEM.value.length > 0) && (! vEM.value.match( /^[^@]*?@[^@]*$/ )) ) {
		vOK = false;
		vEMerr.style.visibility = "visible";
	}
	
	
	// is the random interval ok?
	if( (vRI.value.length > 0) && (isNaN( Number( vRI.value )) || (Number( vRI.value ) <= 0)) ) {
		vOK = false;
		vRIerr.style.visibility = "visible";
	}
	
	// if we're valid, submit and get out...
	if( vOK ) {
		myCookie.setName( vNM.value );
		myCookie.setNickName( vNN.value );
		myCookie.setEmail( vEM.value );
		myCookie.setFetchMsgSuppressed( vSM.checked );
		myCookie.setShowRandom( ! vSR.checked );
		myCookie.setRandomInterval( Number( vRI.value ) );
		myCookie.setDefaultClick( Number( vDC.options[ vDC.selectedIndex ].value ) );
		myCookie.setDefaultDrag( Number( vDD.options[ vDD.selectedIndex ].value ) );
		myCookie.store();
		displayBack();
	}

	return false;
}

// invoked when "Cancel" button on preferences screen is clicked.
function onPrefsCancel() {
	displayBack();
	return false;
}

/*
 * CollectionCookie class
 * Implements an object that maintains a cookie with attributes relevant to this web site.
 */
 
// creates a new instance of this class...
function CollectionCookie() {
	this.cookie = new Cookie( "collection" );
	this.setFetchMsgSuppressed( this.isFetchMsgSuppressed() );
	this.setName( this.getName() );
	this.setNickName( this.getNickName() );
	this.setEmail( this.getEmail() );
	this.setShowRandom( this.isShowRandom() );
	this.setRandomInterval( Number( this.getRandomInterval() ) );
	this.setDefaultClick( Number( this.getDefaultClick() ) );
	this.setDefaultDrag( Number( this.getDefaultDrag() ) );
	this.cookie.ct = Number( this.getCount() ) + 1;
	this.store();
}

// stores the cookie we have right now...
CollectionCookie.prototype.store = function() { this.cookie.store( 10000 ); }

// getters
CollectionCookie.prototype.isFetchMsgSuppressed = 
	function() { return (this.cookie.suppressFetched) ? (this.cookie.suppressFetched == "T") : false; }
CollectionCookie.prototype.getName = 
	function() { return (this.cookie.nm) ? this.cookie.nm : ""; }
CollectionCookie.prototype.getNickName = 
	function() { return (this.cookie.nn) ? this.cookie.nn : ""; }
CollectionCookie.prototype.getEmail = 
	function() { return (this.cookie.em) ? this.cookie.em  : ""; }
CollectionCookie.prototype.isShowRandom = 
	function() { return (this.cookie.sr) ? (this.cookie.sr == "T") : true; }
CollectionCookie.prototype.getRandomInterval = 
	function() { return (this.cookie.ri) ? this.cookie.ri : 60; }
CollectionCookie.prototype.getDefaultClick = 
	function() { return (this.cookie.dc) ? this.cookie.dc : 1; }
CollectionCookie.prototype.getDefaultDrag = 
	function() { return (this.cookie.dd) ? this.cookie.dd : 3; }
CollectionCookie.prototype.getCount = 
	function() { return (this.cookie.ct) ? this.cookie.ct : 0; }
	
// setters
CollectionCookie.prototype.setFetchMsgSuppressed =
	function( pSupp ) { this.cookie.suppressFetched = pSupp ? "T" : "F"; }
CollectionCookie.prototype.setName = 
	function( pName ) { this.cookie.nm = "" + pName; }
CollectionCookie.prototype.setNickName = 
	function( pName ) { this.cookie.nn = "" + pName; }
CollectionCookie.prototype.setEmail = 
	function( pMail ) { this.cookie.em = "" + pMail; }
CollectionCookie.prototype.setShowRandom = 
	function( pShow ) { this.cookie.sr = pShow ? "T" : "F"; }
CollectionCookie.prototype.setRandomInterval = 
	function( pIntr ) { this.cookie.ri = pIntr; }
CollectionCookie.prototype.setDefaultClick = 
	function( pClik ) { this.cookie.dc = pClik; }
CollectionCookie.prototype.setDefaultDrag = 
	function( pDrag ) { this.cookie.dd = pDrag; }

// returns true if cookies appear to be enabled.
CollectionCookie.areCookiesEnabled = function() { return Cookie.enabled(); }

// invoked when Mysteries screen is loaded...
function onMysteriesLoad() {
	mSO.getJSONObject( "mysteries.json", null, gotMysteries );
}

// invoked when mysteries JSON has been retrieved...
function gotMysteries( pMysteries ) {
	
	// set up the table that will hold our mysteries...
  var vTable = new Tag( "table" ).addAttribute( "class", "mysteries" );
  
  // iterate through all our mysteries...
  for( var vI = 0; vI < pMysteries.length; vI++ ) {
  	
  	// build our mysterious row...
	  var vTD1 = new Tag( "td" ).addAttribute( "class", "mysteries" ).addContent( pMysteries[vI].mystery );
	  var vTD2 = new Tag( "td" )
	  	.addAttribute( "class", "mysteries" )
	  	.addContent( makeLink( Path.fromFQPath( pMysteries[vI].item ) ) );
	  var vTR = new Tag( "tr" ).addContent( vTD1, vTD2 );
	  vTable.addContent( vTR );
	}
	
	// now display the results...
	var vMyst = $( "mysteries" );
	vMyst.innerHTML = vTable;
}
