

//--- BEGIN OBJECTS ---//
alphaCorners = new function() { // alphaCorners is designed to be used as a singleton.
	//--- begin constructor ---//
	var that     = this,
		isIE     = /MSIE/.test(navigator.userAgent),
		isIE6    = /MSIE 6/.test(navigator.userAgent),
		isGecko  = /Gecko\//.test(navigator.userAgent),
		tables   = new Array(4); // Cache for each cornertype and size.
	this.antialias = true;
	//--- end constructor ---//

	//--- begin methods ---//
	this.round = function(selector, radius, corners, hoverClass) {
		new onDOMReady(function(){roundSelect(selector, radius, corners, hoverClass)});
	};
	//--- end methods ---//

	//--- begin objects ---//
	function Element(object, hoverClass, cStyle) {
		//--- begin constructor ---//
		this.object         = object;
		this.height         = object.offsetHeight; // IE bug: requires proper DOCTYPE, I think.
		this.width          = object.offsetWidth;
		this.bStyle         = [new BStyle(object), hoverClass?new BStyle(object, hoverClass):''];
		this.cStyle			= cStyle;
		this.borderWidth    = this.bStyle[0].borderWidth;
		object.alphaCorners = ' '+hoverClass; // Needed for hovering, and also so we don't reapply corners to this object.
		//--- end constructor ---//
	}
	function CStyle(radius, corners) {
		//--- begin constructor ---//
		this.radius  = radius;
		this.corners = new Array(4);
		for(var i=3; i>-1; --i)
			this.corners[i] = !corners||!!corners[i];
		//--- end constructor ---//
	}
	function BStyle(object, className) {
		//--- begin constructor ---//
		this.bg          = new Array(2);
		this.borderWidth = 0;
		//--- end constructor ---//

		//--- begin methods ---//
		this.getStyle = function(object, property) {
			// By P.P. Koch, modified by L. Sorber (quirksmode.org/dom/getstyles.html).
			var value = document.defaultView?
				document.defaultView.getComputedStyle(object, '').getPropertyValue(property)
				:object.currentStyle.getAttribute(property.toUpperCase().replace(/-/g, ''));

			// Format output for ease of use.
			if(/px/.test(value))
				value = 1*value.substr(0, value.indexOf('px'));
			else if(/rgb/.test(value)) {
				var rgb = (new RegExp('([0-9]+)[, ]+([0-9]+)[, ]+([0-9]+)')).exec(value);
				value = '#';
				for(var i=1; i<4; ++i)
					value += ('0'+(1*rgb[i]).toString(16)).slice(-2);
			}
			return value;
		};
		this.setStyle = function(object, className) {
			if(className) {
				var parent = object.parentNode;
				object = object.cloneNode(false);
				object.className += ' '+className;
				object.style.visibility = 'hidden'; // Safari bug: display-none elements have no properties.
				object.style.position = 'absolute';
				parent.appendChild(object);
			}
			var repeat   = ' '+this.getStyle(object, 'background-repeat'),
				bgcolor      = this.getStyle(object, 'background-color');
			this.bg[0]       = this.getStyle(object, 'background-image');
			this.bg[0]       =  (/-/.test(repeat)?bgcolor+' ':'')
			                   +(/\./.test(this.bg[0])?this.bg[0].replace(/['"]/g, ''):bgcolor) // IE bug: no ['"] allowed in urls.
						       +(/-/.test(repeat)?repeat:'');
			this.bg[1]       = this.getStyle(object, 'border-top-color');
			this.borderWidth = this.getStyle(object, 'border-top-width');
			this.borderWidth = this.borderWidth>-1?this.borderWidth:0; // IE 'bug': returns nonnumerical value if not explicitly set.
			if(className)
				parent.removeChild(object);
		};
		this.setStyle(object, className);
		//--- end methods ---//
	}
	//--- end objects ---//

	//--- begin utility functions ---//
	function addEvent(object, eventName, fn) {
		if(object.addEventListener)
			object.addEventListener(eventName, fn, false);
		else if(object.attachEvent)
			object.attachEvent('on'+eventName, fn);
	}
	function onDOMReady(fn) {
		// Firefox (and Opera sort of) behave very nicely compared the ugly hack below.
		if(isGecko)
			addEvent(document, "DOMContentLoaded", fn);
		// IE and WebKit based browsers onDOMReady is based on a pretty ugly hack via readyState.
		// It gets even worse for IE, because the dummy script object for the readyState prop to work.
		// Opera does support DOMContentLoaded, but fires it before external css has been applied.
		// The only solution is to use window.onload for Opera, but using the WebKit method for Opera has the
		// same result as window.onload, only it requires less code.
		// Unfortunately this means Opera is the only browser that doesn't really have an onDOMReady event.
		else {
			var object = isIE?document.getElementById('_onDOMReady'):document;
			if(isIE&&!object)
				document.write('<script id="_onDOMReady" defer="true" src="//:"></script>');
			if(object&&(new RegExp('complete'+(!isIE?'|loaded':''))).test(object.readyState))
				fn();
			else
				setTimeout(function(){onDOMReady(fn);}, 1);
		}
	}
	function selectElements(selector) {
		// First trim and split the CSS selector.
		selector = selector.replace(/^\s+|\s+$/g,'').replace(/\s+/g,' ').split(' ');

		var elements = [document.body];
		for(var i=0; i<selector.length; ++i) {
			// Now get the next level of elements defined by the selector,
			// starting where we left off.
			var next = new Array(), index, add;
			for(var j=elements.length-1; j>-1; --j) {
				if((index=selector[i].indexOf('#')+1)!=0)
					add = [document.getElementById(selector[i].substr(index))];
				else if((index=selector[i].indexOf('.')+1)!=0)
					add = getElementsByClassName(elements[j], selector[i].substr(index), selector[i].substring(0,--index));
				else
					add = elements[j].getElementsByTagName(selector[i]);
				for(var k=add.length-1; k>-1; --k)
					next.push(add[k]);
			}
			elements = next;
		}
		return elements;
	}
	function getElementsByClassName(parent, className, tagName) {
		var all = parent.getElementsByTagName(tagName&&tagName.length>0?tagName:'*'),
			elements = new Array();
		for(var i=all.length-1; i>-1; --i)
			if(all[i].className.indexOf(className)!=-1)
				elements.push(all[i]);
		return elements;
	}
	//--- end utility functions ---//

	//--- begin functions ---//
	function hover(parent, mouseover) {
		// Add the requested hoverClass if hovering over object.
		parent.className = mouseover?parent.className+parent.alphaCorners:parent.className.replace(parent.alphaCorners, '');

		// Show hoverbackground if necessary, otherwise hide with an overflow technique (so that IE does not uncache backgroundimages).
		var bg = getElementsByClassName(parent, 'alphaCorners').pop().childNodes;
		for(var i=1; i>-1; --i)
			bg[i].style.left = /hover/.test(bg[i].className)?
				(mouseover?'0px':'200%')
				:(mouseover?'200%':'0px'); // Shame js doesn't have a xor operator.
	}
	function roundSelect(selector, radius, corners, hoverClass) {
		/*// Auto performance test
		// note: results vary a lot depending on the usage of borders, background-images, off-screen drawing, corner radius, ...
		time();*/

		// Get selected objects, and define a cStyle.
		var elements = selectElements(selector),
			cStyle = new CStyle(radius, corners);
		for(var i=0; i<elements.length; i++)
			if(!elements[i].alphaCorners)
				roundElement(new Element(elements[i], hoverClass, cStyle));

		/*var cpo = 0;
		for(var i=0; i<4; i++) cpo += (cStyle.corners[i]?1:0);
		alert(Math.round(cpo*elements.length/(time()/1000))+' corners/sec ('+cStyle.radius+'px*'+cStyle.radius+'px)');*/
	}
	function roundElement(element) {
		// Save some often used styleprops and triggers.
		var style      = element.object.style,
			properties = 'position:absolute;'
				+'overflow:hidden;'
				+'top:0px;'
				+'left:0px;'
				+'height:'+(isIE&&element.borderWidth>0?element.height+'px;':'100%;') // IE bug: (very strange) 100% does not work on rel/abs elements with a border.
				+'width:' +(isIE&&element.borderWidth>0?element.width +'px;':'100%;'),
			innerHTML  = '<div class="alphaCorners" style="'
				+properties
				+'z-index:-1;'
				+'">'; // Create the layer that will contain all generated content.

		// Create the entire new background (background, corners and hover versions).
		for(var h=(element.bStyle[1]?1:0); h>-1; h--) {
			// Add a layer for switching backgrounds on mouseover if necessary.
			innerHTML += '<div class="'+(h==0?'base':'hover')+'" style="'
				+properties
				+(h==1?'left:200%;':'') // Move out of sight (parent has overlow: hidden;).
				+'">';

			// Add layers that automatically adjust to the parent width and height minus a given fixed offset.
			// Makes use of a nifty little overflow trick, see if you can figure it out.
			var bStyle = element.bStyle[h];
			for(var b=(element.bStyle[h].borderWidth>0?1:0); b>-1; b--) {
				// This neat little trick requires two layers, one has overflow: hidden and the other is nested inside that layer.
				for(var r=1; r>-1; r--) {
					var offset  = new Array(2);
					offset[r]   = b?element.cStyle.radius:Math.max(element.cStyle.radius, element.borderWidth); // Small fix for elements of which the bordersize surpasses the radius.
					offset[1-r] = (bStyle.borderWidth>0?(1-b)*element.borderWidth:0);
					innerHTML += '<div style="'
						+properties
						+'top:' +(-offset[1])+'px;'
						+'left:'+(-offset[0])+'px;'
						+'">'
							+'<div style="'
							+properties
							+'top:' +(2*offset[1])+'px;'
							+'left:'+(2*offset[0])+'px;'
							+'background:'
								+ bStyle.bg[b]
								+(bStyle.bg[b].indexOf('.')!=-1?' '
									+(offset[1-r]-offset[0])+'px '
									+(offset[1-r]-offset[1])+'px;':';')
							+'"></div>'
						+'</div>';
				}
			}

			// Add rounded corners and close the (hover)background container.
			innerHTML += genCorners(element, bStyle)+'</div>';
		}

		// Parent positiontype has to be at least relative, so that the absolutely positioned bg is rendered correctly.
		if(!/absolute/.test(element.bStyle[0].getStyle(element.object, 'position')))
			style.position = 'relative';
		// Fix disappearing background if enclosed by parent with bg.
		if(!isIE&&!(element.bStyle[0].getStyle(element.object, 'z-index')>0))
			style.zIndex = 0;
		// Remove the old background & border and add padding to make up for the border.
		if(element.bStyle[0].borderWidth>0) {
			var padding  = '', position = ['left', 'bottom', 'right', 'top'];
			for(var i=3; i>-1; --i)
				padding += (element.borderWidth+element.bStyle[0].getStyle(element.object, 'padding-'+position[i]))+'px ';
			style.padding = padding;
			style.border = 'none';
		}
		style.background = 'none';

		// Update the object with the new content lastly (slow, but at least it's faster than DOM).
		element.object.innerHTML += innerHTML+'</div>';

		// Add hoverevents if necessary.
		if(element.bStyle[1])
			for(var i=0; i<2; i++)
				addEvent(element.object, 'mouse'+(i==0?'over':'out'),
					(i==0?function(){hover(element.object, true)}
						 :function(){hover(element.object, false)}));
	}
	function genCorners(element, bStyle) {
		// Initialize vars.
		var corners     = '',
			borderWidth = element.borderWidth,
			hasBorder   = bStyle.borderWidth>0;

		for(var type=3; type>-1; type--) {
			for(var b=(hasBorder?1:0); b>-1; b--) {
				var bg       = bStyle.bg[b],
					bgPos    = bg.indexOf('.')!=-1,
					crPos    = new Array(2),
					radius   = Math.max(0, element.cStyle.radius-(hasBorder?(1-b)*borderWidth:0)),
					offset	 = b==0&&hasBorder?borderWidth:0,
					corner   = '<div style="'
						+'position:absolute;'
						+'overflow:hidden;'
						+'height:'+radius+'px;'
						+'width:' +radius+'px;'; // Create corner parent.

				// Position corner.
				// IE6 bug: 1px deviation for odd height/width values in combination with abs pos, fixed using expressions.
				for(var i=1; i>-1; --i) {
					crPos[i] = (type==0||type==(3-2*i)?0:(i==0?element.width:element.height)-(hasBorder?2*borderWidth:0)-radius+b*2*borderWidth);
					corner  += (type==0||type==(1+2*i)?(i==0?'top:':'left:'):(i==0?'bottom:':'right:'))
								+(isIE6&&(type==(2-i)||type==(3-i))?'expression('+offset+'-(this.parentNode.offset'+(i==0?'Height':'Width')+'%2)+\'px\');':offset+'px;');
				}

				// Set the background for the corner.
				corner += (!element.cStyle.corners[type]||(radius==0)?'background:'+bg+(bgPos?' '+(-crPos[0])+'px '+(-crPos[1])+'px;':';'):'')+'">';

				// Insert divs that make up the corner.
				if(element.cStyle.corners[type]&&radius>0) {
					var table = getTable(radius, type),
						pxPos = new Array(2);

					for(var y=radius-1; y>-1; --y) {
						for(var x=radius-1; x>-1; --x) {
							// Go to next x if opacity is 0.
							if(!table[x][y])
								continue;

							// Extract height and width, then calculate the position for the <div>.
							for(var i=1; i>-1; --i)
								pxPos[i] = (type==(3-2*i)||type==0||table[x][y]!=1?radius-1:0)-(i==0?x:y);

							// Add a pixel row/column.
							corner += '<div style="'
								+'position:absolute;'
								+'top:' +pxPos[1]+'px;'
								+'left:'+pxPos[0]+'px;'
								+(isIE6?'overflow:hidden;':'') // IE6 bug: minimal div size.
								+(table[x][y]==1?'height:100%;width:100%;':'height:1px;width:1px;')
								+'background:'+bg+(bgPos?' '+(-crPos[0]-pxPos[0])+'px '+(-crPos[1]-pxPos[1])+'px;':';')
								+(isIE?'filter:alpha(opacity='+(100*table[x][y])+');':'opacity:'+table[x][y]+';')
								+'"></div>';
						}
					}
				}
				corners += corner+'</div>';
			}
		}
		return corners;
	}
	function getTable(radius, type) {
		var table = tables[radius+''+type];
		if(!table) {
			var flip = !tables[radius+'0']?genTable(radius):tables[radius+'0'];
			table = new Array(radius);
			for(var x=radius-1; x>-1; --x) {
				table[x] = new Array(radius);
				for(var y=radius-1; y>-1; --y)
					table[x][y] = flip[type==2||type==1?radius-1-x:x][type==2||type==3?radius-1-y:y];
			}
		}
		return table;
	}
	function genTable(radius) {
		// Calculates the area under the top half of a circle (0,0,radius) for [0..x].
		function integral(radius, x) {
			var ratio = x/radius;
			return 0.5*radius*(x*Math.sqrt(1-Math.pow(ratio, 2))+radius*Math.asin(ratio));
		}

		// Initialize vars.
		var bulk  = Math.round(radius/Math.sqrt(2))-1, // The amount of whole squares that fit into the radius.
			table = new Array(),
			pxPos = new Array(2),
			opacity;

		// Fill in the table.
		for(var x=radius-1; x>-1; --x)
			table.push(new Array(radius));
		for(var y=radius-1; y>=bulk; --y) {
			for(var x=bulk; x>-1; --x) {
				// Calculate the opacity for the current pixel w/o approximations.
				if(that.antialias) {
					// Calculates the positions where the circle intersects with the pixel boundaries.
					for(var i=0; i<2; i++)
						pxPos[i] = Math.max(x, Math.min(x+1, radius*Math.sqrt(1-Math.pow((y+1-i)/radius, 2))));
					opacity = Math.round(100*((pxPos[0]>x)*(1-(pxPos[1]-pxPos[0]))+(pxPos[0]!=pxPos[1]?integral(radius, pxPos[1])-integral(radius, pxPos[0])-y*(pxPos[1]-pxPos[0]):0)))/100;
				}
				else
					opacity = (x+0.5)*Math.sqrt(1+Math.pow((y+0.5)/(x+0.5), 2))<=radius?1:0;

				// Skip fully transparent pixels.
				if(opacity==0)
					continue;

				// Update the table based on the pixel coordinates and opacity.
				table[x][y] = opacity;
				table[y][x] = opacity;

				// All other pixels in this row||column will also be opaque.
				if(opacity==1)
					break;
			}
		}
		return table;
	}
	//--- end functions ---//
};
//--- END OBJECTS ---//