/* Copyright (c) 2005-2006 Michael Alyn Miller. All rights reserved.
 *
 * The contents of this file and the algorithms contained herein may
 * only be used on sites owned and operated by Michael Alyn Miller.  Any
 * other use is expressly forbidden.
 *
 * The adler32() function is the exception to the above paragraph and is
 * made available under the 3-clause BSD license.
 */


/* -------------------------------------
 * Initialization functions.
 */

/** Stores the element ID of the <img> tag that we are replacing. */
var gLogoId = null;

/** Stores the Image and loaded state of the test image that we use to
 * determine if the browser supports data: URL based images. */
var gTestImage = null;
var gTestImageLoaded = false;

/** The function that is called to update the image.  When the script is
 * first run, this is the detection function.  Once we have determined
 * which update method to use, this variable is assigned the function
 * that will perform the periodic updates. */
var gLogoUpdateFunc = null;


/**
 * Initializes the logo generation system.  The primary purpose of this
 * function is to determine, based on the capabilities of the web
 * browser, which logo update method to use.
 *
 * @param logoId: The element ID of the <img> tag that is being
 *  replaced.
 */
function initLogo(logoId) {
	/* Store the logo id. */
	gLogoId = logoId;

	/* Find out if the browser supports data:image/png;base64," URLs.
	 * We could use a giant `if' statement here that knows about the
	 * various browsers and versions that support the data: URL, but
	 * instead we let the browser tell us what it supports.  We give the
	 * browser a small, 27x1 image and see if it gets the width right
	 * and/or loads the image.  Certain browsers (Safari, for example)
	 * do not synchronously load data: URL images, so we have to wait
	 * for onload.  Other browsers (Opera) will set the width, but never
	 * call onload.  And some browsers (Firefox) will do both. */
	gTestImg = new Image();
	gTestImg.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABsAAAABAQMAAAAVV0frAAAABlBMVEX///8AAABVwtN+AAAAE0lEQVR4nGJgYGDgBgAAAP//AwAAEAAMUV9+agAAAABJRU5ErkJggg==";
	gTestImg.onload = testImageLoaded;

	/* Start the logo timer.  In five seconds, we will see if the image
	 * has been loaded.  If it has, then we stick with the data:
	 * URL-based logo, otherwise we switch to the table-based method. */
	gLogoUpdateFunc = determineUpdateMethod;
	setInterval("gLogoUpdateFunc()", 5000);
}

/**
 * Called by the browser if our data: URL-based test image is loaded.
 */
function testImageLoaded() {
	/* The test image was loaded; set the flag so that the first pass through
	 * updateLogo will know what to do. */
	gTestImageLoaded = true;
}

/**
 * Determines which update method to use based on whether or not our
 * test image was correctly loaded.  Called by the interval timer.
 */
function determineUpdateMethod() {
	/* If the test image has the right width and/or was loaded, then we
	 * use our image-based logo.  OmniWeb supports data: URLs, but it
	 * has some problems with them.  First, it appears to leak the new
	 * images, or rather, it keeps a copy in memory of all of the old
	 * images.  Second, it flashes the page (similar to an HTTP-level
	 * refresh) every time the logo is updated.  Both of these are
	 * annoying, so we automatically use the table-based logo with
	 * OmniWeb. */
	if (	((gTestImg.width == 27) || gTestImageLoaded)
		 && (navigator.userAgent.indexOf("OmniWeb") == -1))
	{
		/* Initialize the image-based logo. */
		initLogoImage();

		/* Future timers should call the updateLogoImage function
		 * directly. */
		gLogoUpdateFunc = updateLogoImage;

	/* Use the table-based update method. */
	} else {
		/* Initialize the table-based logo. */
		initLogoTable();

		/* Future timers should call the updateLogoTable function
		 * directly. */
		gLogoUpdateFunc = updateLogoTable;
	}

	/* Perform an initial update of the logo (otherwise it would take
	 * two interval periods (10s) to get the first update. */
	gLogoUpdateFunc();
}



/* -------------------------------------
 * Image-based logo functions.
 */

/** The <img> that we are replacing. */
var gLogoImg = null;


/**
 * Initializes the image-based logo update process.
 */
function initLogoImage() {
	/* Store a reference to the <img> tag. */
	gLogoImg = document.getElementById(gLogoId);
}

/**
 * Updates the image-based logo by rebuilding the PNG data contained in
 * the <img> tag.  Called by the interval timer.
 */
function updateLogoImage() {
	/* Build the new logo. */
	var imageData = "";
	for (var y = 0; y < 8; y++) {
		/* Generate this row of dots. */
		var row = "\x00";
		for (var x = 0; x < 8; x++) {
			if (Math.random() < 0.5) {
				row += "\x00";
			} else {
				row += "\xf8";
			}
		}

		/* Add the row to the buffer.  We add the row five times, since
		 * our dots are 5x5 in size. */
		imageData += row;
		imageData += row;
		imageData += row;
		imageData += row;
		imageData += row;

		/* If this is not the last row, then add our three empty
		 * scanlines. */
		if (y != 7) {
			imageData += "\x00\x00\x00\x00\x00\x00\x00\x00\x00";
			imageData += "\x00\x00\x00\x00\x00\x00\x00\x00\x00";
			imageData += "\x00\x00\x00\x00\x00\x00\x00\x00\x00";
		}
	}


	/* Compute the ADLER32 CRC for the image data. */
	var imageAdlerCrc = adler32(imageData, 0, imageData.length);

	/* Append the ADLER32 CRC to the image data. */
	imageData
		+= String.fromCharCode(
			(imageAdlerCrc >> 24) & 0xff,
			(imageAdlerCrc >> 16) & 0xff,
			(imageAdlerCrc >>  8) & 0xff,
			(imageAdlerCrc      ) & 0xff);


	/* Put the logo into an IDAT block. */
	var idatData = "IDAT\x78\x01\x01\x25\x02\xda\xfd" + imageData;
	var idatCrc = CRC32.getCRC(idatData, 0, idatData.length);
	var idatBlock = "\x00\x00\x02\x30"
		+ idatData
		+ String.fromCharCode(
			(idatCrc >> 24) & 0xff,
			(idatCrc >> 16) & 0xff,
			(idatCrc >>  8) & 0xff,
			(idatCrc      ) & 0xff);

	/* Build the PNG file. */
	var pngFile
		= "\x89PNG\x0d\x0a\x1a\x0a"
		+ "\x00\x00\x00\x0dIHDR\x00\x00\x00\x3d\x00\x00\x00\x3d\x01\x03\x00\x00\x00\x24\xac\xf3\xaa"
		+ "\x00\x00\x00\x06PLTE\xff\xff\xff\x00\xff\x00\xc6\xe6\x2e\x0c"
		+ "\x00\x00\x00\x01tRNS\x00\x40\xe6\xd8\x66"
		+ idatBlock
		+ "\x00\x00\x00\x00IEND\xae\x42\x60\x82";

	/* Base64-encode the PNG file.. */
	var encPngFile = Base64.encode(pngFile);


	/* Update the logo. */
	gLogoImg.src = "data:image/png;base64," + encPngFile;
}

/**
 * Returns the ADLER32 CRC for the given block of data.
 *
 * @param data The string of bytes for which to compute the ADLER32 CRC.
 * @param offset The offset from the beginning of 'data' from which to
 *  compute the CRC.
 * @param count The number of bytes from 'offset' to include in the CRC.
 * @return The ADLER32 CRC as an integer.
 */
function adler32(data, offset, count) {
	/* Initialize the CRC values. */
	var s1 = 1;
	var s2 = 0;

	/* Update the CRC values for each of the bytes in the given range. */
	for (var i = 0; i < count; i++) {
		var c = data.charCodeAt(offset+i) & 0xFF;
		s1 = (s1 + c) % 65521;
		s2 = (s2 + s1) % 65521;
	}

	/* Compute and return the CRC. */
	return s2*65536 + s1;
}



/* -------------------------------------
 * Table-based logo functions.
 */

/** The 8x8 array of <td> elements. */
var gLogoTableCells = null;


/**
 * Initializes the table-based logo update process.  This function
 * creates the table element and all of its children, then replaces the
 * <img> element with the new table element.
 */
function initLogoTable() {
	/* Create the logo table array. */
	gLogoTableCells = new Array(8);

	/* Create the logo table with all of its cells. */
	var logoTable = document.createElement("table");
	logoTable.bgColor = "#000000";
	logoTable.cellPadding = 0;
	logoTable.cellSpacing = 0;
	logoTable.style.padding = "0";
	logoTable.style.margin = "0";

	/* Create the tbody element to add all of our rows to; IE will not
	 * render any of the dynamic rows unless they are contained in a
	 * tbody. */
	var logoTbody = document.createElement("tbody");
	logoTable.appendChild(logoTbody);

	for (var r = 0; r < 8; r++) {
		/* Create the array for this row. */
		gLogoTableCells[r] = new Array(8);

		/* Create the table row. */
		var row = document.createElement("tr");
		logoTbody.appendChild(row);

		/* Create all of the table cells. */
		for (var c = 0; c < 8; c++) {
			/* Create the table cell. */
			var cell = document.createElement("td");
			row.appendChild(cell);

			/* Set the cell size and clear its background.  We have to
			 * set the width/height using both the table properties and
			 * the CSS properties, because some browsers (MacIE) ignore
			 * the cell values and only consider the style values. */
			cell.width = 5;
			cell.height = 5;
			cell.bgColor = "#000000";
			cell.style.width = "5px";
			cell.style.height = "5px";

			/* Add the cell to our array. */
			gLogoTableCells[r][c] = cell;

			/* Add a padding column if this is the first row, but not
			 * the last column. */
			if ((r == 0) && (c != 7)) {
				var padColumn = document.createElement("td");
				row.appendChild(padColumn);
				padColumn.width = 3;
				padColumn.rowSpan = 15;
				padColumn.style.width = "3px";
			}
		}

		/* Add a padding row if this is not the last row. */
		if (r != 7) {
			var padRow = document.createElement("tr");
			logoTbody.appendChild(padRow);

			var padColumn = document.createElement("td");
			padRow.appendChild(padColumn);
			padColumn.height = 3;
			padColumn.colSpan = 15;
			padColumn.style.height = "3px";
		}
	}

	/* The CSS renderer in MacIE is different enough that it needs
	 * special treatment.  On MacIE, we display the logoTable directly
	 * as an inline-block inside of our logoimgdiv.  OmniWeb also has
	 * the same problem, so we include it in this test as well.  On all
	 * other browsers, we wrap the logoTable in an inline div, mark the
	 * table itself as inline (which would make MacIE go berzerk) and
	 * display the wrapper div. */
	var replaceTag;
	if (	(navigator.userAgent.indexOf("Mac") != -1)
		 && (
				(navigator.userAgent.indexOf("MSIE") != -1)
			 || (navigator.userAgent.indexOf("OmniWeb") != -1)))
	{
		/* Get the div that surrounds the logo image. */
		var logoImgDiv = document.getElementById(gLogoId + "div");
		logoImgDiv.style.display = "inline-block";

		/* Swap the <img> out for the table. */
		replaceTag = logoTable;
	} else {
		/* Create the div that will wrap the table. */
		var logoDiv = document.createElement("div");
		logoDiv.style.background = "black";
		logoDiv.style.display = "inline";
		logoDiv.style.padding = "0";
		logoDiv.style.margin = "0";

		/* Display the table as an inline component and add it to the
		 * div. */
		logoTable.style.display = "inline";
		logoDiv.appendChild(logoTable);

		/* Swap the <img> out for the wrapper div. */
		replaceTag = logoDiv;
	}

	/* Remove the <img> tag and put the new element in its place. */
	var logoImg = document.getElementById(gLogoId);
	logoImg.parentNode.replaceChild(replaceTag, logoImg);
}

/**
 * Updates the table-based logo by randomly changing the background of
 * each of the 256 table cells.  Called by the interval timer.
 */
function updateLogoTable() {
	/* Go through each row and column, setting each cell's background
	 * color according to the output of our random number generator. */
	for (r = 0; r < 8; r++) {
		for (c = 0; c < 8; c++) {
			var cell = gLogoTableCells[r][c];
			if (Math.random() < 0.5) {
				cell.bgColor = "#000000";
			} else {
				cell.bgColor = "#00ff00";
			}
		}
	}
}

