
//instantiate the beatbox object with paramters
new BeatBox(
		'0.6',		//max image size (as proportion of available space)
		'.thumb'	//thumbnail extension value
		);

//object constructor
function BeatBox(maxsize, thumbex)
{
	//weed out unsupported browsers
	if(typeof document.getElementById == 'undefined' || typeof document.createElement == 'undefined') { return; }

	//parse and save the max image width if it's defined
	//otherwise use a default value 0.5
	this.maxsize = parseFloat(maxsize);

	//save the thumbnail extension if it's defined
	//otherwise use an empty string
	this.thumbex = typeof thumbex != 'undefined' ? thumbex : '';

	//specify groups that need behavioral tweaks
	this.isie = typeof document.uniqueID != 'undefined';
	this.quirksmode = (this.isie || typeof window.opera != 'undefined') && (typeof document.compatMode == 'undefined' || document.compatMode != 'CSS1Compat');
	this.isgecko = navigator.product == 'Gecko' && navigator.vendor != 'Apple Computer, Inc.' && navigator.vendor != 'KDE';
	//iframe shim is for firefox (so it covers flash) and IE6 (to cover select elements and flash)
	this.needsiframeshim = this.isgecko || (typeof document.uniqueID != 'undefined' && typeof window.XMLHttpRequest == 'undefined');

	//create some objects we'll need
	this.body = document.getElementsByTagName('body')[0];
	this.screen = null;
	this.window = null;
	this.iframe = null;

	//array of beatbox links
	this.links = [];

	//iterate through all links, and store all those
	//which have the "beatbox" class name
	//and which point to an image file
	var links = document.getElementsByTagName('a');
	for(var i=0; i<links.length; i++)
	{
		if((links[i].className && links[i].className.indexOf('beatbox') != -1)
			&& (links[i].href && /\.(jpg|jpeg|gif|png|mng|apng|bmp|tif|tiff)$/i.test(links[i].href)))
		{
			this.links[this.links.length] = links[i];
		}
	}

	//create a reference to this for inner functions
	var self = this;

	//then for each stored link
	for(i=0; i<this.links.length; i++)
	{
		//bind an onclick handler, which has to be a handler not a listener
		//so that we can cancel the default action
		//(for a listener that wouldn't work in safari)
		this.links[i].onclick = function()
		{
			//if we already have the loading screen, remove it
			//this will only be relevant if you get tired of waiting for an image to load
			//or if something else goes wrong somewhere
			if(self.screen)
			{
				self.removeLoadingScreen();
			}

			//otherwise we're good
			else
			{
				//put up the loading screen
				self.showLoadingScreen();

				//create a new image object
				var img = new Image;

				//save a reference to this link
				//to pass into the show image window function
				var link = this;

				//when the image has preloaded
				img.onload = function()
				{
					//show the image window
					//passing the image object and original link reference
					//we have to use "img" instead of "this" to make it work in Safari
					self.showImageWindow(img, link);
				};

				//if the image fails to load, just remove the loading screen
				img.onerror = function()
				{
					self.removeLoadingScreen();
				};

				//preload the image, removing the thumbnail extension if it's there
				img.src = this.href.replace(self.thumbex, '');
			}

			//cancel the default action of the link
			return false;
		};
	}
}

//show the loading screen
BeatBox.prototype.showLoadingScreen = function()
{
	//ignore this if the loading screen is present already
	if(this.screen) { return; }

	//create the loading screen element
	this.screen = document.createElement('div');
	this.screen.id = 'beatbox-loadingscreen';
	this.screen.appendChild(document.createTextNode('Loading image ...'));

	//append it to the page
	this.body.appendChild(this.screen);

	//get the canvas size and scrolling offset
	var canvas = this.getCanvasSize();
	var scrolling = this.getScrollingOffset();

	//size the loading screen to fit the canvas, and apply padding to position the text
	this.screen.style.width = canvas.width + 'px';
	this.screen.style.paddingTop = (canvas.height / 2) + 'px';
	this.screen.style.height = (canvas.height / (this.quirksmode && this.isie ? 1 : 2)) + 'px';

	//position the loading screen, accounting for scrolling offset
	this.screen.style.left = scrolling.left + 'px';
	this.screen.style.top = scrolling.top + 'px';

	//bind a click handler to the loading screen
	//that removes it, and the image window if present
	var self = this;
	this.screen.onclick = function()
	{
		self.removeImageWindow();
		self.removeLoadingScreen();
	};

	//make it visible
	this.screen.style.visibility = 'visible';
};

//remove the loading screen
BeatBox.prototype.removeLoadingScreen = function()
{
	//ignore this if the loading screen is not present
	if(!this.screen) { return; }

	//remove the screen and nullify the reference
	this.screen.parentNode.removeChild(this.screen);
	this.screen = null;
};

//show the image window
BeatBox.prototype.showImageWindow = function(img, link)
{
	//ignore this if the image window is present already
	if(this.window) { return; }

	//create the image window element
	this.window = document.createElement('div');
	this.window.id = 'beatbox-imagewindow';

	//get the canvas size and scrolling offset
	var canvas = this.getCanvasSize();
	var scrolling = this.getScrollingOffset();

	//save image dimensions
	//we have to save them rather than overwriting the original properties
	//because overwriting doesn't work in Safari
	var imgwidth = img.width;
	var imgheight = img.height;

	//if the image is wider than it is tall
	//constrain it to maxwidth based on canvas width
	//and proportionately change the image height to match
	if(imgwidth > imgheight)
	{
		if(imgwidth > (canvas.width * this.maxsize))
		{
			imgheight = img.height * ((canvas.width * this.maxsize) / imgwidth);
			imgwidth = (canvas.width * this.maxsize);
		}
	}

	//otherwise use canvas height to do this
	//this condition will also catch images that are square
	else
	{
		if(imgheight > (canvas.height * this.maxsize))
		{
			imgwidth = imgwidth * ((canvas.height * this.maxsize) / imgheight);
			imgheight = (canvas.height * this.maxsize);
		}
	}

	//create an actual image out of the image object
	//(linked to the original image) and set its size
	var newimg = document.createElement('img');
	newimg.setAttribute('src', img.src);
	newimg.setAttribute('width', imgwidth);
	newimg.setAttribute('height', imgheight);

	//append it to the image window
	this.window.appendChild(newimg);

	//create the controls
	var controls = document.createElement('ul');
	
	li = controls.appendChild(document.createElement('li'));
	li.className = 'close';
	var close = li.appendChild(document.createElement('a'));
	close.setAttribute('href', 'javascript:void("Close the image window")');
	close.setAttribute('title', 'Close the image window');
	close.appendChild(document.createTextNode('Close'));

	//append them to the image window
	this.window.appendChild(controls);

	//bind a handlers to the close link
	//to remove the loading screen and image window
	//and set focus back on the original link
	var self = this;
	close.onclick = function()
	{
		self.removeImageWindow();
		self.removeLoadingScreen();
		link.focus();
	};

	//append the image window to the page
	this.body.appendChild(this.window);

	//gecko and IE need some silly style hacks,
	//but they break other browsers
	if(this.isgecko || this.isie)
	{
		if(this.isgecko)
		{
			controls.setAttribute('style', 'width:' + imgwidth + 'px;float:left;');
		}
		else
		{
			controls.style.width = imgwidth + 'px';
		}
		if(controls.offsetWidth < 200)
		{
			controls.style.width = '200px';
		}
	}

	//now position it in the center using its rendered size to calculate
	//and accounting for scrolling offset
	this.window.style.left = (((canvas.width - this.window.offsetWidth) / 2) + scrolling.left) + 'px';
	this.window.style.top = (((canvas.height - this.window.offsetHeight) / 2) + scrolling.top) + 'px';

	//hide the loading animation in the loading screen and remove the text
	this.screen.style.backgroundImage = 'none';
	this.screen.removeChild(this.screen.firstChild);

	//create an iframe shim beneath the image window
	this.showIframeShim();

	//make the window visible
	this.window.style.visibility = 'visible';

	//set focus on the image link
	newimg.focus();
};

//remove the image window
BeatBox.prototype.removeImageWindow = function()
{
	//ignore this if the image window is not present
	if(!this.window) { return; }

	//remove the window and nullify the reference
	this.window.parentNode.removeChild(this.window);
	this.window = null;

	//remove the iframe shim
	this.removeIframeShim();
};


//create an iframe shim to cover windowed controls in IE5.5 and IE6
BeatBox.prototype.showIframeShim = function()
{
	//ignore this if we're not a browser that needs the iframe shim
	//or if we don't have an image window
	//or if we already have an iframe shim
	if(!this.needsiframeshim || !this.window || this.iframe) { return; }

	//create an iframe
	this.iframe = document.createElement('iframe');
	this.iframe.id = 'beatbox-iframeshim';

	//the javascript src prevents IE from trying to load a page
	//and hence avoids the "secure/insecure" dialogue issue on SSL pages
	this.iframe.src = 'javascript:false;';

	//the negative tabindex takes it out of the taborder
	this.iframe.tabIndex = '-1';

	//append the iframe to the page
	this.body.appendChild(this.iframe);

	//set its position and dimensions to match the loading screen
	this.iframe.style.left = this.screen.style.left;
	this.iframe.style.top = this.screen.style.top;
	this.iframe.style.width = this.screen.offsetWidth + 'px';
	this.iframe.style.height = this.screen.offsetHeight + 'px';

	//make the iframe visible
	this.iframe.style.visibility = 'visible';
};

//remove the iframe shim
BeatBox.prototype.removeIframeShim = function()
{
	//ignore this if the iframe shim is not present
	if(!this.iframe) { return; }

	//remove the iframe and nullify the reference
	this.iframe.parentNode.removeChild(this.iframe);
	this.iframe = null;
};


//get the canvas size
BeatBox.prototype.getCanvasSize = function()
{
	if(typeof window.innerWidth != 'undefined')
	{
		var canvas = {
			'width' : window.innerWidth,
			'height' : window.innerHeight
			};

		if(this.isgecko)
		{
			if(this.body.offsetHeight > canvas.height)
			{
				canvas.width -= 17;
			}
			else
			{
				canvas.width -= 1;
			}
			canvas.height -= 1;
		}
	}
	else if(this.quirksmode)
	{
		canvas = {
			'width' : document.body.clientWidth,
			'height' : document.body.clientHeight
			};
	}
	else
	{
		canvas = {
			'width' : document.documentElement.offsetWidth - (this.needsiframeshim ? 21 : 0),
			'height' : document.documentElement.offsetHeight
			};
	}

	return canvas;
};

//get the scrolling offset
BeatBox.prototype.getScrollingOffset = function()
{
	if(typeof window.pageXOffset != 'undefined')
	{
		var scrolling = {
			'left' : window.pageXOffset,
			'top' : window.pageYOffset
			};
	}
	else if(this.quirksmode)
	{
		scrolling = {
			'left' : document.body.scrollLeft,
			'top' : document.body.scrollTop
			};
	}
	else
	{
		scrolling = {
			'left' : document.documentElement.scrollLeft,
			'top' : document.documentElement.scrollTop
			};
	}

	return scrolling;
};
