// ==UserScript==
// @name          Geocaching Thumbnails
// @namespace     http://benchmarks.org.uk/geothumbs/
// @version       3.3
// @description   (v3.3) Changes the default picture icons on cache pages to image thumbnails.  New version for geocaching.com January 2010 update.
// @include       http://www.geocaching.com/seek/cache*
// ==/UserScript==

// Copyright (C) 2010 Gary Player <dev@benchmarks.org.uk>
// Released under the GPL http://www.gnu.org/copyleft/gpl.html
// This is a Greasemonkey user script, see http://greasemonkey.mozdev.org/.

// Number of images per row - firefox can change this via a by menu option
var imagesPerRow = 2;

// Image sizes - firefox can change these via a by menu option
var imageSize = "thumb";
var ownerImageSize = 100;

// Show spoilers (true to always show) - firefox can change this via a by menu option
var showSpoilers = false;

// regexp to use to check for spoilers
var spoilerPattern = /(spoil|spiol|spoli|It'?s here)/i;

// Show owner images (true to show) - firefox can change this via a by menu option
var showOwnerImages = true;

// Show descriptions with thumbnails (true to show) - firefox can change this via a by menu option
var showDescriptions = true;

// Change to true to cause owner image table to span the full page width
var ownerImagesSpanPage = false;

// rel id to use for all anchors to images
var lightboxRelId = "lightbox[geothumbs]";

// change string to set image box background colour e.g. #fcfcfc
var imageBoxColour = "";

// /////////////////////// functions //////////////////////////////

// gets all elements of the specified tag with the specified regexp class name
function getElementByTagAndClass(tag, classNamePattern)
{
	var collection = new Array();
	var found = 0;
	var tags = document.getElementsByTagName(tag);

	for (i = 0; i < tags.length; i++)
	{
		if (tags[i].className.match(classNamePattern))
		{
			collection[found++] = tags[i];
		}
	}
	return collection;
}

// inserts newNode after referenceNode
function insertAfter(referenceNode, newNode)
{
	referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
}

// climbs to tagname from specified element
function climbTo(element, tagname)
{
	var parentElement = null;
	var element;

	tagname = tagname.toUpperCase();
	while ((element = element.parentNode) && !parentElement)
	{
		if (element.tagName == tagname)
		{
			parentElement = element;
		}
	}
	return parentElement;
}

// returns the required node name sibling of the startNode
function getSibling(startNode, findNodeName)
{
	var foundNode = null;
	var node = startNode;

	findNodeName = findNodeName.toUpperCase();
	while ((node = node.nextSibling) && !foundNode)
	{
		if (node.nodeName == findNodeName)
		{
			foundNode = node;
		}
	}
	return foundNode;
}

// removes leading and trailing spaces, quotes and jpg extensions from received title
function tidyTitle(title)
{
	return title.replace(/^[\s\"']*/, "").replace(/[\s\"']*$/, "").replace(/\.jpg$/i, "");
}

// returns true if we want to see spoilers or the title indicates the image isn't a spoiler
// as determined by the spoiler regexp
function showImage(title)
{
	return (showSpoilers || !title.match(spoilerPattern));
}

// toggles whether spoiler images are shown or not
function showSpoilersMenuAction()
{
	showSpoilers == true ? showSpoilers = false : showSpoilers = true;
	GM_setValue("showSpoilers", showSpoilers);
	document.location.reload();
}

// toggles whether owner images are shown or not
function showOwnerImagesMenuAction()
{
	showOwnerImages == true ? showOwnerImages = false : showOwnerImages = true;
	GM_setValue("showOwnerImages", showOwnerImages);
	document.location.reload();
}

// toggles whether owner images are shown or not
function showDescriptionsMenuAction()
{
	showDescriptions == true ? showDescriptions = false : showDescriptions = true;
	GM_setValue("showDescriptions", showDescriptions);
	document.location.reload();
}

// cycles number of images per row between 2 and 4
function imagesPerRowMenuAction()
{
	imagesPerRow++;
	if (imagesPerRow > 4)
		imagesPerRow = 2;
	GM_setValue("imagesPerRow", imagesPerRow);
	document.location.reload();
}

// toggles image size between thumbnail (100px wide) and larger (300px wide) images
function imageSizeMenuAction()
{
	imageSize == "thumb" ? imageSize = "display" : imageSize = "thumb";
	GM_setValue("imageSize", imageSize);
	document.location.reload();
}

// sets the standard format of each image cell
function formatImageCell(imageCell)
{
	imageCell.align = "center";
	imageCell.width = "100px";
	imageCell.height = "81px";
	imageCell.style.border = "0px";
	imageCell.style.padding = "8px";
	imageCell.style.lineHeight = "0pt"; // remove space under image
	if (imageBoxColour)
	{
		imageCell.style.backgroundColor = imageBoxColour;
	}
}

// sets the standard format of each text cell
function formatTextCell(textCell)
{
	textCell.width = "1200px";
	textCell.style.border = "0px";
	if (imageBoxColour)
	{
		textCell.style.backgroundColor = imageBoxColour;
	}
}

// /////////////////////////// main /////////////////////////////////

// only enable menu options for firefox - opera and chrome can't handle them
if (window.navigator.userAgent.match("Firefox"))
{
	// set up user defined settings
	imagesPerRow = GM_getValue("imagesPerRow", 2);
	showOwnerImages == true ? current = "Yes" : current = "No";
	GM_registerMenuCommand("Geothumbs - Images per row : " + imagesPerRow, imagesPerRowMenuAction);

	imageSize = GM_getValue("imageSize", "thumb");
	if (imageSize == "thumb")
	{
		current = "Thumbnail";
		ownerImageSize = 100;
	}
	else
	{
		current = "Large";
		ownerImageSize = 300;
	}
	GM_registerMenuCommand("Geothumbs - Image size : " + current, imageSizeMenuAction);

	showSpoilers = GM_getValue("showSpoilers", false);
	showSpoilers == true ? current = "Yes" : current = "No";
	GM_registerMenuCommand("Geothumbs - Show spoilers : " + current, showSpoilersMenuAction);

	showDescriptions = GM_getValue("showDescriptions", true);
	showDescriptions == true ? current = "Yes" : current = "No";
	GM_registerMenuCommand("Geothumbs - Show image descriptions : " + current, showDescriptionsMenuAction);

	showOwnerImages = GM_getValue("showOwnerImages", true);
	showOwnerImages == true ? current = "Yes" : current = "No";
	GM_registerMenuCommand("Geothumbs - Show cache owner images : " + current, showOwnerImagesMenuAction);
}

///////////////////////////// process cache owner images //////////////////////////////

if (showOwnerImages)
{
	// get owner name
	var ownerName = document.getElementById("ctl00_ContentBody_CacheOwner").lastChild.text;

	var ownerImages = null;
	var imagesElement = document.getElementById("ctl00_ContentBody_Images");
	if (imagesElement)
	{
		// get all the cache owner images
		ownerImages = imagesElement.getElementsByTagName("a");
	}

	// only process if we have some owner images
	if (ownerImages.length != 0)
	{
		// create a table for our title row and image rows
		t = document.createElement('table');
		t.cellSpacing = 0;
		t.style.cssText = "border:1px solid #D7D7D7;";
		t.width = "100%";

		// force new row
		var col = imagesPerRow;

		for ( var i = 0; i < ownerImages.length; i++)
		{
			// process the non-edit image anchors
			if (ownerImages[i].text != "Edit")
			{
				// create a new row if required
				var imageRow;
				if (col == imagesPerRow)
				{
					// create an image row
					imageRow = t.insertRow(-1);

					col = 0;
				}

				// create a copy of the original anchor to modify - the original
				// is going to be removed later when the original row is removed
				var imageAnchor = ownerImages[i].cloneNode(true);

				// get anchor details
				var pictureTitle = imageAnchor.text;
				var largePicture = imageAnchor.href;

				// clean up the picture title
				var pictureTitle = tidyTitle(pictureTitle);

				// use the same lightbox rel id for all images on the page
				imageAnchor.rel = lightboxRelId;

				// look for a description (in text node after second <br> after anchor)
				var description = "";
				var lightboxDescription = "";
				var node = getSibling(ownerImages[i], "br");
				if (node)
				{
					if (node = getSibling(node, "br"))
					{
						if ((node = node.nextSibling) && node.nodeName == "#text")
						{
							description = node.textContent;
						}
					}
				}

				// if description exists create image and lightbox formats
				var lightboxDescription = "";
				var imageTitleDescription = "";
				if (description != "")
				{
					lightboxDescription = "<span style=\"font-weight: normal\">" + description + "</span><br>";
					imageTitleDescription = " - " + description;
				}

				// create anchor title - this is used by lightbox
				var title = "\"" + pictureTitle + "\" by " + ownerName + "<br>" + lightboxDescription +
							"<a href=\"javascript:pp('" + largePicture + "')\;\">Print Picture</a>";
				imageAnchor.title = title;

				// clean up the tooltip
				var img = imageAnchor.firstChild;
				img.title = pictureTitle + imageTitleDescription;
				img.alt = pictureTitle;

				// change camera image to picture unless we want to hide spoilers
				if (showImage(pictureTitle))
				{
					img.src = largePicture;
					img.width = ownerImageSize; // shrink it
				}
				else
				{
					img.title = "** SPOILER ** " + pictureTitle; // do not show description
					img.alt = "** SPOILER ** " + pictureTitle;
				}

				// remove the non image anchor text
				imageAnchor.removeChild(imageAnchor.lastChild);

				// create a new cell in the row and add the anchor
				imageCell = imageRow.insertCell(-1);
				formatImageCell(imageCell);
				imageCell.appendChild(imageAnchor);

				// create new anchor for text link to image or edit image page
				var textAnchor;
				if ((ownerImages[i + 1]) && (ownerImages[i + 1].text == "Edit"))
				{
					// use existing edit image anchor and modify title
					textAnchor = ownerImages[i + 1].cloneNode(true);

					// fix bug in anchor link (remove erroneous '.')
					textAnchor.href = textAnchor.href.replace(/\.image/, "image");

					pictureTitle = pictureTitle + " [Edit]";
				}
				else
				{
					// new anchor for link to image
					textAnchor = document.createElement('a');
					textAnchor.href = largePicture;
				}
				pictureTitle = pictureTitle;
				textAnchor.textContent = pictureTitle;
				textAnchor.title = pictureTitle; // sets tooltip
				textAnchor.style.cssText = "text-decoration: none;"; // remove link underline

				// add a cell for the anchor and add the anchor
				textCell = imageRow.insertCell(-1);
				formatTextCell(textCell);
				textCell.appendChild(textAnchor);

				// add description if required
				if (showDescriptions)
				{
					textCell.appendChild(document.createElement('br'));
					span = document.createElement('span');
					span.style.cssText = "font-size: smaller";
					textCell.appendChild(span);

					descriptionNode = document.createTextNode(description);
					span.appendChild(descriptionNode);
				}

				// increment column count
				col++;
			}
		}

		// create replacement span with new image table
		span = document.createElement('span');
		span.id = "ct100_ContentBody_Images";

		// add heading and table to span
		br = document.createElement('br');
		span.appendChild(br);
		strong = document.createElement('strong');
		strong.textContent = "Owner Images";
		span.appendChild(strong);
		span.appendChild(t);

		// position images before map if map exists (user has logged in)
		var mapDiv = document.getElementById("ctl00_ContentBody_uxlrgMap");
		if (mapDiv)
		{
			mapDiv.parentNode.insertBefore(span, mapDiv);
		}
		else
		{
			// no map (user has not logged in) so position images before find section
			var findDiv = document.getElementById("ctl00_ContentBody_FindText");
			if (findDiv)
			{
				// add trailing br to put space between images and find section
				br = document.createElement('br');
				span.appendChild(br);
				findDiv.parentNode.insertBefore(span, findDiv);
			}
		}
		
		// remove original span - go to parent and remove it
		p = imagesElement.parentNode;
		p.removeChild(imagesElement);
	}
}

/////////////////////////// process cache log images //////////////////////////////

// get every table row containing a log
var pattern = /Nothing|AlternatingRow/;
var logRows = getElementByTagAndClass("td", pattern);

// for each log row
for ( var li = 0; li < logRows.length; li++)
{
	var logRow = logRows[li];
	var dateAndOwner = logRow.firstChild.textContent.replace(/^\s+/, "");
	var logDate = dateAndOwner.replace(/ by .*$/, "");
	var owner = dateAndOwner.replace(/^.*by /, "");

	// log images are held in a child table
	var imageTable = logRow.getElementsByTagName('table');

	// process the images for this log
	if (imageTable.length != 0)
	{
		// force new row
		var col = imagesPerRow;

		var it = imageTable[0];

		// remove coloured border and increase size
		it.style.padding = 0;
		it.width = "95%";

		// get all the image anchors
		var anchors = it.getElementsByTagName('a');

		// process each image anchor
		var noAnchors = anchors.length;
		for ( var j = 0; j < noAnchors; j++)
		{
			// create a new row if required
			var imageRow;
			if (col == imagesPerRow)
			{
				// add row
				imageRow = it.insertRow(-1);
				col = 0;
			}

			// create a copy of the original anchor to modify - the original
			// is going to be removed later when the original row is removed
			var imageAnchor = anchors[j].cloneNode(true);

			// get anchor details
			var pictureTitle = imageAnchor.text;
			var largePicture = imageAnchor.href;
			var anchorTitle = imageAnchor.title; // contains title for lightbox

			// clean up picture title
			var pictureTitle = tidyTitle(pictureTitle);

			// extract log and possible description from title
			var log = "";
			var description = "";
			var parts = anchorTitle.match(/(log.aspx.*?LID=\d+).*>\s*(.*)\s*$/);
			if (parts)
			{
				log = parts[1];
				description = parts[2];
			}

			// use the same lightbox rel id for all images on the page
			imageAnchor.rel = lightboxRelId;

			// if description exists create image and lightbox formats
			var lightboxDescription = "";
			var imageTitleDescription = "";
			if (description != "")
			{
				lightboxDescription = "<span style=\"font-weight: normal\">" + description + "</span><br>";
				imageTitleDescription = " - " + description;
			}

			// create anchor title - this is used by lightbox
			var title = "\"" + pictureTitle + "\"" +
						" by " + owner + " on " + logDate + "<br>" + lightboxDescription +
						"<a href=\"" + log + "\" target=\"_blank\">View Log</a>&nbsp;" +
						"<a href=\"javascript:pp(\'" + largePicture + "\');\">Print Picture</a>";
			imageAnchor.title = title;

			// clean up the image tooltip
			var img = imageAnchor.firstChild;
			img.title = pictureTitle + imageTitleDescription;
			img.alt = pictureTitle;

			// change default photo.png image to thumbnail unless we want to hide spoilers!
			if (showImage(pictureTitle))
			{
				// get href to log image and convert to thumbnail or display href
				var thumbPicture = largePicture.replace(/log/, "log\/" + imageSize);
				img.src = thumbPicture;
			}
			else
			{
				img.title = "** SPOILER ** " + pictureTitle;
				img.alt = "** SPOILER ** " + pictureTitle;
			}
			// remove the non image anchor text
			imageAnchor.removeChild(imageAnchor.lastChild);

			// create a new cell for the image in the row and add the anchor
			var imageCell = imageRow.insertCell(-1);
			formatImageCell(imageCell);
			imageCell.appendChild(imageAnchor);

			// create new anchor for text link to image
			var textAnchor = document.createElement('a');
			textAnchor.href = largePicture;
			textAnchor.title = pictureTitle; // sets tooltip
			textAnchor.textContent = pictureTitle;
			textAnchor.style.cssText = "text-decoration: none;";

			// create a new cell in the row and add the new anchor
			var textCell = imageRow.insertCell(-1);
			formatTextCell(textCell);
			textCell.appendChild(textAnchor);

			// add description if required
			if (showDescriptions)
			{
				textCell.appendChild(document.createElement('br'));
				span = document.createElement('span');
				span.style.cssText = "font-size: smaller";
				textCell.appendChild(span);

				descriptionNode = document.createTextNode(description);
				span.appendChild(descriptionNode);
			}

			// increment column count
			col++;
		}

		// span last cell if more than one row and row not fully populated
		// this ensures table background colour fills last row
		if ((noAnchors > imagesPerRow) && (col != imagesPerRow))
		{
			textCell.colSpan = (imagesPerRow - col) * 2 + 1;
		}

		// delete original row containing un-enhanced links
		it.deleteRow(0);
	}

}