/*  jim-gallery.js -- Photo gallery support subroutines
    Copyright © 2002 by James F. Carter.  2002-08-24.
    Supposedly works with Javascript 1.0 (not very likely).
    Works perfectly with Opera 6.02 for Linux and Microsoft Internet 
    Explorer 6.0.  Adequate with Netscape 4.78 except auto-resizing doesn't
    happen (because onLoad script isn't called; Netscape is capable of calling
    it; I never found out why it didn't happen here.)  With care, the calling
    HTML file can also function with text-only browsers.

    Begin the document with these items:

<script type="text/javascript" language="JavaScript" src="jim-gallery.js">
</script>
</head><body onload="doneidx()">
<script type="text/javascript" language="JavaScript"><!--
    initize(4);			// arg is number of columns in index page.
// --></script>

Plan:
Table cell in index page calls loadtnl to load thumbnail at right size.
Click on the link, it executes loadimg.
Loadimg opens a new window (default size, = original) and if not seen 
    before, injects HTML that loads the object at full size.  If the
    aspect ratio is known already, the size is right the first time.
On load, image's aspect ratio is saved and the image is resized (MSIE)
    or reloaded (Opera).
When either window is resized, so is the image(s).
onResize works in MSIE 6.0 and Nutscape 4.78 (width changes only), but is 
ignored in Opera 6.02.

Both windows are decorated with property Mine, referring to an object with
these members (separate for each window):
    baseURL	URL up to the last component, including ending / or \ 
    size	Images should be scaled to this fraction of the window size
    imgwin	Reference to image window
    imgs	Array of image information objects, same subscripts as the
		corresponding window.document.images[j].  Both windows refer
		to the same array.
    lix		Subscript in imgs of the big image currently displayed
    img		Reference to imgs[lix]
    benttnl	Number of thumbnail images that are out of shape (main window
		only).  Determines whether it has to be reloaded.

The image information objects have these members:
    base	Basename of image file, e.g. "jaguar"
    imar	Aspect ratio (height/width) of big image
    tnar	Aspect ratio of thumbnail image
    title	Text for window title
    lix		Subscript in images array of this object

*/

// Returns just the value for the cookie attribute "key" on the main window.
// Include the = in the key.  Returns "" if not found.
function cookiev(key) {
    var ck = window.document.cookie;
    var j = ck.indexOf(key);
    if (j < 0) return "";
    var k = ck.indexOf(";", j);
    if (k < 0) k = ck.length;
    return ck.substring(j + key.length, k);
}

// Window method, returns width and height of the window as an array.
// This is window.innerWidth for Netscape and Opera; it is 
// window.document.body.clientWidth for MSIE (4.x and above).
// If neither of these work, it returns [640, 480].  For debug, the type of
// browser is also returned.  
function Jwsize() {
    var siz;
    var csize = this.Mine.wsize;		//Cached window size
    while (true) {
		// Nutscape or Opera.  In Opera, these dimensions are always
		// defined and appear to exclude potential scrollbars, i.e.
		// scrollbars (if present) will overlap the image.  So knock
		// off 20 px to miss the scrollbars.
	if (typeof(this.innerHeight) != "undefined") {
	    siz = new Array(this.innerWidth-20, this.innerHeight-20, "Nutscape");
	    if (siz[0] > 0 && siz[1] > 0) { break; }
	}
                // Microsoft Internet Explorer 4.0 or above.  These dimensions
		// exclude actual scrollbars, but in MSIE you can get potential
		// scrollbars becoming real, so knock off 20px to avoid the
		// scrollbars.  In Opera, these dimensions become undefined
		// when you open the document.  So prefer innerXXX with Opera.
		// In MSIE you also get obscure conditions when the window
		// size is undefined.
	if (typeof(this.document) == "object" &&
		typeof(this.document.body) == "object" &&
		typeof(this.document.body.clientHeight) != "undefined") {
	    siz = new Array(this.document.body.clientWidth-20, 
			this.document.body.clientHeight-20, "MSIE");
	    if (siz[0] > 0 && siz[1] > 0) { break; }
	}
		// Use cached window size if there is one (to work around the
		// case where all the window dimensions are undefined).
	if (typeof(csize) != "undefined") {
	    siz = csize;
	    break;
	}
		// Random other browser, return an arbitrary answer
	siz = new Array(640, 480, "Other Browser");
	break;
    }
    if (typeof(csize) == "undefined" || 
		csize[0] != siz[0] || csize[1] != siz[1]) {
	csize = new Array(siz[0], siz[1], siz[2]);
    }
    return siz;
}

// Window method: Given an aspect ratio, returns the dimensions of an image
// (in pixels) as an array ([0] = width, [1] = height). OK to call with ar==0.
// Window properties used: size = image scale factor.
function Jsize(ar) {
    var siz = this.Jwsize();
    if (siz[0]*ar > siz[1]) {
	siz[0] = siz[1]/ar;
    } else {
	siz[1] = siz[0]*ar;
    }
    var scf = this.Mine.size;
    return new Array(Math.round(scf * siz[0]), Math.round(scf * siz[1]));
}

// Window method: converts the array returned by Jsize to HTML properties
// width= height=.  If aspect ratio was 0 it uses the "portrait" argument
// to guess a size.
// In Nutscape 4.78, <img width=100> gets you height=100 by default, not
// according to the image's aspect ratio, so you need both.  Hiss, boo!
function size2prop(siz, portrait) {
    var res = "";
    if (siz[1] > 0) {
	res = ' width=' + siz[0] + ' height=' + siz[1];
    } else {
	var M = this.Mine;
	var wsiz = (this.Jwsize())[portrait ? 1 : 0];
	res = (wsiz <= 0 || navigator.appName == "Netscape") ? "" : 
	    ((portrait ? "height=" : "width=") + Math.round(M.size * wsiz));
    }
    return res;
}

// Window method.  Expands or shrinks the big image (the only one in the
// document) to fit in the window.  May be called for onLoad or onResize.
function doneimg() {
    var M = this.Mine;
    var Mi = M.img;
    var siz = this.Jwsize();
    var ima = this.document.images[0];
		// Record the aspect ratio of the natively loaded image
    var iold = new Array(ima.width, ima.height);	//Size before resizing
    if (Mi.imar == 0 && iold[0] > 0 && iold[1] > 0) {
	Mi.imar = iold[1] / iold[0];
    }
		// Resize the image to fit the window (maybe)
    var outcome = "doneimg " + M.img.base + " win " + siz + " ar " + Mi.imar + " old " + iold; //DEBUG window size info
    while (true) {
	if (Mi.tries-- <= 0)	{ outcome += " too many tries";		break; }
	if (siz[1] <= 0)	{ outcome += " window asp rat == 0";	break; }
	if (Mi.imar <= 0)	{ outcome += " image asp rat == 0";	break; }
	var inew = this.Jsize(Mi.imar);  //Decide what size we want it to be
	outcome += " new " + inew;
		// If the image is the right size already, bail out.  Otherwise
		// attempt to change it.
	if (inew[0] == iold[0] && inew[1] == iold[1]) { 
	    outcome += " already OK"; break; 
	}
	ima.width = inew[0];
	ima.height = inew[1]
		// If the image size changed, bail out.  But some browsers
		// (specifically Nutscape 4.x and Opera 6.02) refuse to let
		// objects change size dynamically.  So actually reload the 
		// image.
	if (ima.width != iold[0] || ima.height != iold[1]) { 
	    outcome += " dynamic resize"; break; 
	}
	loadimg(this);
	outcome += " resize by reloading"; break;
    } 
    return true;
}

// Method of "this" window.  It initializes "win" window.
function loadimginit(win, M) {
    initwin(win, M);
    win.onload = this.doneimg;
//  win.onresize = this.doneimg;
    if (win.document.domain != M.domain) win.document.domain = M.domain;
}

// Method of "this" window.  Writes dynamic HTML into "win" window to 
// load the image.
function loadimg(win) {
    var M = win.Mine;
    var Mi = M.img;
    var absurl = M.baseURL + Mi.base;
    var widhgt = win.size2prop(win.Jsize(Mi.imar), Mi.portrait);
		// Changing onload="doneimg()" to "defaultStatus=\'loadimg\'"
		//	did not prevent the infinite loop, including not setting
		//	onload in loadimginit().
		// Omitting <script> avoided the loop.
//	    ('<script type="text/javascript" language="JavaScript"' +
//	    ' src="jscript.js"></script>' +
		// Omitting <script> but with onload (both places): no loop,
		//	no err msgs.  onload: MSIE no, Opera yes, Netscape yes
		// Omitting loadimginint after open: MSIE still loops.
//	    '</head><body onload="defaultStatus=\'loadimg\'">')) +
    var doc = '<html><head><title>' + Mi.title + '</title>' +
	((Mi.imar > 0) ? '</head><body>' :
	    '</head><body onload="doneimg()">') +
	'<img src="' + absurl + '.jpeg"' + widhgt + '></body></html>';
    loadimginit(win, M);
    win.document.open("text/html", "replace");
    win.document.writeln(doc);
    loadimginit(win, M);
    win.document.close();
                // MSIE 6.0 erases all user-defined members of the window,
		// when the document is opened.  Nutscape erases them when the
		// document is closed.  Better initialize the window *twice*.
    loadimginit(win, M);
    win.focus();
}

// Window method.  Starts the process of loading the big image, by calling
// loadimg.
function clickimg (j) {
    var M = this.Mine;
    var imgwin = initimg(window);
    var Mj = imgwin.Mine;
    Mj.lix = j;
    Mj.tries = 3;		//Max nbr of tries to get the size right
    Mj.img = M.imgs[j];
    imgwin.focus();
    this.loadimg(imgwin);
    return false;
}

// Static method.  Adjusts the size of a thumbnail (in "window"); j = its index
// in images array.  But it cannot reload the image, for browsers which have
// fixed-size images.
function donetnl(j) {
    var M = this.Mine;
    var Mi = M.imgs[j];
    var img = window.document.images[j];
		// Compute the thumbnail image's aspect ratio.
		// Some browsers (specifically Opera 6.02) do not set the
		// image size until after the whole page is loaded.
    var iold = new Array(img.width, img.height);	//Size before resizing
    var arold = Mi.tnar;
    if (Mi.tnar <= 0 && iold[0] > 0 && iold[1] > 0) {
	Mi.tnar = iold[1]/iold[0];
    }
    var outcome = "donetnl(" + j + ") ar " + arold + " -> " + Mi.tnar + " old " + iold; //DEBUG window size info
    while (true) {
	if (Mi.tries-- <= 0) { outcome += " too many tries"; break; }
	if (iold[0] == 0 || iold[1] == 0) {
	    outcome += " zero image size";
	    break;
	}
	var inew = this.Jsize(Mi.tnar); //Decide what size we want it to be
		// If the image is the right size, bail out.  Otherwise
		// attempt to change it.
	outcome += " new " + inew;
	if (inew[0] == iold[0] && inew[1] == iold[1]) { 
	    outcome += " already OK"; 
	    break; 
	}
	img.width = inew[0];
	img.height = inew[1]
		// If that worked, get out.
	if (img.width != iold[0] || img.height != iold[1]) { 
	    outcome += " dynamic resize";
	    break; 
	}
		// But some browsers (Nutscape 4.x and Opera 6.02) refuse to
		// change already laid out images.  Remember that resizing
		// is needed.
	M.benttnl++;
	outcome += " img " + img.width + "," + img.height + " need reload";
	break;
    }
}

// Static method called from the actual page.  Stores a bunch of stuff in the
// current link and then emits an anchor including an img tag to load the
// thumbnail image.  Arguments:
//	(window) Implicitly puts data in "window".
//	base	Basename of image; "_t.jpeg" will be appended.
//	portrait True if image is likely to need its height shrunk (vs width).
//	title	Initial portion of caption that should go in big window title,
//		and below the thumbnail image.
//	caption	Rest of HTML after the title, for use below the thumbnail
//		image.  May contain HTML tags.  Start with colon for the effect
//		of "Title: Caption".
function loadtnl(base, portrait, title, caption) {
    var M = window.Mine;
    var j = window.document.images.length;	//Subscript of tnl in arrays
		// Extract the local data for this image.  It may be preloaded
		// from the cookie.
    var Mi = M.imgs[j];
    if (! Mi) {
	Mi = M.imgs[j] = new Object;
    }
    Mi.base = base;
    Mi.title = title;
    Mi.portrait = portrait;
    if (!Mi.imar) Mi.imar = 0;
    if (!Mi.tnar) Mi.tnar = 0;
    Mi.lix = j;
    Mi.tries = 3;
    var widhgt = window.size2prop(window.Jsize(Mi.tnar), portrait);
		// Note: don't use the onClick method, use href=javascript
		// instead, because if the link is followed by keyboard 
		// traversal, it doesn't count as a click, at least in MSIE
		// 6.0.  However, in Opera at least, the window has been
		// cleared of user-defined functions before the href is
		// executed, so it doesn't work.
		// In Opera, if you have both href and onclick, href has
		// priority.
	//What should the href be?  
	//	'<a href="javascript:void(\'' + base + '\')"' +
	// Kills MSIE, OK for Opera, Netscape loads pic at 100x100px.

	//	'<a href="' + base + '.jpeg" target=imgwin' +
        // MSIE can shrink but not expand (probably at native size), no
	// doneimg.  Netscape loads at native size with no doneimg.  Same for
	// Opera.  clickimg returning true or false is the same.

	//	'<a href="#" target=imgwin' +
	// MSIE page is blank.  Nutscape and Opera: loads index page.

	//	'<a '  (with onclick but no href)
	// Opera: loads native size with doneimg.  Nutscape: click ignored.
	// MSIE: onclick was executed but content not loaded.

	//	' onload="donetnl(' + j + ')">' +
	// Calling donetnl on the image makes thread problems for Nutscape.
    var doc = 
	'<a href="' + base + '.jpeg" target=imgwin' +
	' onclick="return clickimg(' + j + ')">' +
	'<img src="' + base + '_t.jpeg" ' + widhgt + '>' +
	'<br>' + title + caption + '</a>'
    document.writeln(doc);
}

// Window method for index (main) window.  Re-adjusts sizes of thumbnails.
// Call it as the onLoad and onResize method.
function doneidx () {
    var M = this.Mine;
    var siz = this.Jwsize();
    var bent = M.benttnl;		//Nonzero if this was tried already
    M.benttnl = 0;			//Count thumbnails with wrong size
    var j;
    var Mi;
    for (j in M.imgs) {
	Mi = M.imgs[j];
	donetnl(j);
    }
		// Save the image data as a cookie.  Format: 
		// moddate=msec;imagesize=j,tn,im... where j = subscript, tn =
		// thumbnail aspect ratio, im = image aspect ratio (0 if 
		// unknown).  Some browsers (e.g. Opera 6.02) do not block
		// on cookie setting, so only check for the moddate cookie.
    var ck = "";
    for (j in M.imgs) {
	Mi = M.imgs[j];
	if (typeof(Mi) != "object") continue;
	ck += "," + j + "," + Mi.tnar + "," + Mi.imar;
    }
    this.document.cookie = "imagesize=" + ck.substring(1);
    ck = this.document.cookie;		//Did cookie get set OK?
    var cookieok = ck.indexOf("moddate=") >= 0;
    var reload = M.benttnl > 0 && bent <= 0;
    if (reload && !cookieok) {
	reload = false;
	alert("If a cookie could be set on this page, the images could be resized to fit more neatly.");
    }
		// If the thumbnails are misshapen, reload the document,
		// knowing their aspect ratio in advance.
    if (reload) {
	this.setTimeout("window.location.reload()", 100);
    }
}

// Static method.  Sets up local methods of a window.  Pass in a premade object
// or "undefined", for "mine".
function initwin(win, mine) {
    win.Jwsize = Jwsize;
    win.Jsize = Jsize;
    win.size2prop = size2prop;
    win.clickimg = clickimg;
    win.loadimg = loadimg;
    win.doneimg = doneimg;
    win.doneidx = doneidx;
    if (mine == undefined) {
	mine = new Object();
    }
    win.Mine = mine;
}

// Static method.  Opens and initializes the image window.  Parent
// window should be passed as an argument.  Saves in a window property,
// and returns the reference to the image window.
function initimg (win) {
    var M = win.Mine;
    var img = M.imgwin;
    var Mi;			//undefined, later it will hold img.Mine
    if (typeof(img) == "undefined" || img.closed) {
	M.imgwin = img = undefined;
	M.imgwin = img = win.open('', 'imgwin',		// dummy.html
	    "directories=no,dependent=yes,location=yes,menubar=no,resizable=yes,scrollbars=yes,status=yes,toolbar=no", true);
                // On img.blur(), MSIE and Nutscape send it to the back of the
		// stack. But not Opera 6.02.  Explicitly raise the main
		// window.
	win.blur();
	img.focus();
    } else {
	Mi = img.Mine;
    }
    initwin(img, Mi);
    Mi = img.Mine;			//Could have been created in initwin
    Mi.baseURL = M.baseURL;
    Mi.size = 0.99;
    Mi.imgs = M.imgs;
    Mi.imgwin = img;
    Mi.domain = win.document.domain;
    return img;
}

// Initializes the main (index) window.  Arg is number of columns of thumbnail
// images on this page.
function initize (ncol) {
    initwin(window, undefined);		//Init main window with new object
		// If the image window is pre-existing, retrieve the saved
		// Mine object, throw out new object.  If new image window,
		// it will be set up with the new object so this is a no-op.
    var M = window.Mine;
		// Compute base URL.
		//On MSIE, might have to deal with "file://D:\dir\file.htm"
    var url = window.document.URL;
    var j = Math.max(url.lastIndexOf('/'), url.lastIndexOf('\\'));
    M.baseURL = url.slice(0,j+1);
    M.size = 0.9/ncol;			// Scale factor for thumbnails
    M.benttnl = 0;			// Number of out-of-shape thumbnails
    M.imgs = new Array();		// Per-image local data
		// Extract the cookie.  Format: moddate=msec;imagesize=j,tn,im..
                // where j = subscript, tn = thumbnail aspect ratio, im =
		// image aspect ratio.
    var moddate = cookiev("moddate=");	// Mod date according to cookie
    var actmod = escape(window.document.lastModified);	// Actual mod date
    var ck = cookiev("imagesize=");	// Aspect ratios of images
    if (moddate != actmod) {
	window.document.cookie = "moddate=" + actmod;
    } else if (ck != "") {		// Might have 1 cookie without the other
	var cka = ck.split(',');
	for (j = 0; j < cka.length; j += 3) {
	    var Mi = M.imgs[cka[j]] = new Object();
	    Mi.tnar = cka[j+1];
	    Mi.imar = cka[j+2];
	}
    }
}

