/*
      ____
 __ _/  _/___ _ _ __  ___ _ _ ___
/ _` / (_/ _ \ '_|  \/ -_) '_(_-<
\__,_\___\___/_| |_|_\___|_| /__/
 alphaCorners v1.6, by L. Sorber

 _______
/Licence\

alphaCorners is free software, you may redistribute it and/or modify it under the terms of the GNU General Public License.
See the GNU General Public License, as published by the Free Software Foundation, for more details (http://www.gnu.org/).
 __________
/Quickstart\

// Include the alphaCorners javascript:
<script type="text/javascript" src="alphacorners.js"></script>

// Round elements like so:
<script type="text/javascript">
	// alphaCorners.antialias = false; // Turn antialiasing off (much faster but not as smooth).
	alphaCorners.round('div.classname a', 20); // Rounds all anchors that are childs of divs of the class 'classname' with 20px radius corners.
	alphaCorners.round('#idname p a',  20, [1,1,1,0], 'hoverClass'); // The southwest corner isn't rounded, mouseover adds 'hoverClass' to objects.
</script>
 _________
/Thanks to\

* A. Fulciniti for the idea.
* J. Zwysen for his row/column optimization idea.
* P.P. Koch for his eternal JS research (quirksmode.org).
 ____
/v1.6\

* Classbased hovering approach, which means half the amount of div's normally required.
* Rounded elements are now dynamically resizable in every browser!
* Fixed background-color not working for hoverstyles.
* Added a hasLayout hack for IE (using zoom), which should fix a lot of rendering bugs.
* Added a cursor:pointer; fix for hoverstyles in IE (when display:block; was set).
* Reincluded the Holly hack for IE (fixes background not displaying in IE6 behind elements with height:auto;).
* Reduced necessary amount of layers for the non-rounded part of the background, which should slightly increase speed.
 ____
/v1.5\

* Further research into onDOMReady led me to believe Opera's support for DOMContentLoaded is not satisfactory.
  As a result, I've had to adjust the current 'solution' most js devs are still using.
* Safari not loading bug fixed.
* Reduced compressed size back closer to 4kB.
* Quite a few minor stability improvements.
 ____
/v1.4\

* Fixed overwriting of hoverevents.
* Improved hovering responsiveness and reduced flicker substantially in IE.
* Improved onDOMReady stability and speed significantly (which is beneficial for loading alphaCorners).
* Rounded objects are now also dynamically resizable in IE, so long as they don't have rounded borders (I can not get around this bug unfortunately).
* Decreased chance of other possible inter-script conflicts by reducing the number of added props for rounded objects to one (namely object.alphaCorners).
* Fixed some other small bugs.
 ____
/v1.3\

* Improved CSS selector function. You can now specify a tagname along with the desired id or class (e.g. 'div.testclass' or 'span#testid').
* Added 'background-repeat' support. Unfortunately 'background-position' is very hard to implement, so it doesn't look like it will make a next update.
* Removed optimization described in the previous changelog, since the gain wasn't worth the extra code (actually, only IE rendered a bit faster).
* Commented some previously unexplained code for clarity.
 ____
/v1.2\

* Added all new and improved CSS-style object selection (e.g. '#testid p a', '#testid .testclass p', ...).
* A few small optimizations.
/ Wrote but did not include an optional optimalization in this release, which renders colors instead of opacity.
  It would only be turned on by default for Internet Explorer, which is the only browser that is more responsive with it turned on.
  You can manually turn off this optimalization with alphaCorners.optimize = false;
  I left it out because it might cause overlay problems, and it increases alphaCorners compressed size over 4kB.
 _______________________________
/          Performance          \
| in 20px radius corners/second |
|-------------------------------|
| antialias | FireFox 2.0 | IE7 |
|-------------------------------|
| true      | 250         |  85 |
| false     | 516         | 502 |
|(true+opt) |(129)        |(124)|
\_______________________________/
 ____
/v1.1\

* Refactored the OO design a bit. OOP in js is possible, but not always as evident.
* Rendering bugs fixed (parent triggering child hover, child disappearing behind parent, applying aC multiple times).
* Made rendering more resilient to flicker, transition from nonrounded to rounded is now as smooth as it can get.
* Improved hovering responsiveness.
* More speed optimizations and serious code cleanup.
* Compatible with Internet Explorer 6+, Firefox 1.5+, Opera 8.5+, Safari 1.2+, Mozilla 1.6+, Camino 1.0+, Netscape 6.2+.
  alphaCorners degrades gracefully on older browserversions, or in other words, does not alter layout in unsupporting browsers.
 _______________________________
/          Performance          \
| in 20px radius corners/second |
|-------------------------------|
| antialias | FireFox 2.0 | IE7 |
|-------------------------------|
| true      | 205         |  78 |
| false     | 516         | 502 |
\_______________________________/
 ____
/v1.0\

* Completly rewritten alphaCorners in an all new OO oriented design.
* Object- or classbased corner rendering, the alphaCorners.round() function can take both.
* Rewritten the opacity algorithm, which is no longer returns an approximation but the exact opacity for a given pixel and radius.
* Background is now based on z-index: -1, no more z-index issues!
* Hover event attachment is now faster and more reliable.
* Because of the OO design, alphaCorners is even easier to implement (see Quickstart).
* Several code optimizations increased performance notably (see table below).
 _______________________________
/          Performance          \
| in 20px radius corners/second |
|-------------------------------|
| antialias | FireFox 2.0 | IE7 |
|-------------------------------|
| true      | 163         |  81 |
| false     | 401         | 288 |
\_______________________________/
 ____
/v0.9\

* alphaCorners no longer waits for the full page to load, instead rendering begins as soon as the html objects are available.
  This does not improve rendering speed, but it certainly does seem to load faster.
 ____
/v0.8\

* Classbased corner rendering, no more rel="_radius_".
* Hovering is more responsive, doesn't require any manual coding anymore and has extended features (like background-images).
 ____
/v0.7\

* Fixed a flaw in an optimization which results less divs and a noticable performance increase.
* IE style fixes are no longer included by default, surprisingly this results in a performance increase.
* Acting on the style fix for IE, I reduced the number of remaining CSS tags substantially, increasing performance even more.
 _______________________________
/          Performance          \
| in 20px radius corners/second |
|-------------------------------|
| antialias | FireFox 2.0 | IE7 |
|-------------------------------|
| true      | 115         |  78 |
| false     | 238         | 261 |
\_______________________________/
 ____
/v0.6\

* Fixed a lot of IE rendering bugs (if not all). In total there are now 7 custom fixes for IE, and 0 for the remaining browsers.
 _____
/v0.5b\

* Optimized a lot of frequently executed code.
* Smarter table caching.
* Fixed a lot of small rendering bugs.
 ____
/v0.5\

* class="rounded" no longer necessary, only rel="_radius_".
* Added hover functions for mouseovers (see ac_hover), implementation is manual however.
* Rounded objects are now resizeable in realtime!
  However, no resize support for IE (see the '100%' bug). Even if IE would support h/w=100%, it still suffers from the '1px' bug.
 ____
/v0.4\

* Added border-drawing algorithm (autodetect) - doesn't support per-side border.
* Optimized algorithms further, which should to some extent counteract the performance loss caused by border-drawing.
* Cleaned up some more.
 _____
/v0.3b\

* IE6 and Opera now render correctly.
* Small optimizations.
* Cleaned up.
 ____
/v0.3\

* Better table caching.
* DOM object creation -> innerHTML replacement (quirksmode.org/dom/innerhtml.html) improves speed.
* Opaque row/column pixels are merged into one div.
 _______________________________
/          Performance          \
| in 20px radius corners/second |
|-------------------------------|
| antialias | FireFox 2.0 | IE7 |
|-------------------------------|
| true      |  49         |  22 |
| false     |  82         |  82 |
\_______________________________/
 ____
/v0.2\

* Optimized alphatable algorithm.
* Transparent pixels are no longer drawn.
* The largest opaque square is drawn as one div.
* Alphatable caching.
 _______________________________
/          Performance          \
| in 20px radius corners/second |
|-------------------------------|
| antialias | FireFox 2.0 | IE7 |
|-------------------------------|
| true      |  12         |  10 |
| false     |  14         |  17 |
\_______________________________/
 ____
/v0.1\

* Every corner pixel is represented by one div.
* Includes anti-aliasing.

*/

/*function time(box) {
	if(typeof __startTime == 'undefined')
		__startTime = (new Date()).getTime();
	else {
		var diff = (new Date()).getTime()-__startTime;
		if(box)
			alert(diff+'ms');
		delete __startTime;
		return diff;
	}
}*/

alphaCorners = new function() { // alphaCorners is designed to be used as a singleton.
	/*
	 *  -------------
	 *  PUBLIC FIELDS
	 *  -------------
	 */
	this.antialias = true;

	/*
	 *  --------------
	 *  PRIVATE FIELDS
	 *  --------------
	 */
	var that     = this,
		isIE     = /MSIE/.test(navigator.userAgent),
		isIE6    = /MSIE 6/.test(navigator.userAgent),
		isGecko  = /Gecko\//.test(navigator.userAgent),
		tables   = [,,,]; // Cache for each cornertype and size.

	/*
	 *  --------------
	 *  PUBLIC METHODS
	 *  --------------
	 */
	this.round = function(selector, radius, corners, hoverClass) {
		new onDOMReady(function(){roundSelect(selector, radius, corners, hoverClass)});
	};

	/*
	 *  ---------------
	 *  PRIVATE OBJECTS
	 *  ---------------
	 */
	function CStyle(radius, corners) {
		this.radius  = radius;
		this.corners = [,,,];
		for(var i=3; i>-1; --i)
			this.corners[i] = !corners||!!corners[i];
	}
	function BStyle(object, className) {
		this.bg          = [,];
		this.borderWidth = 0;

		if(className) {
			// This hack seems only to work with anchors, it's not foolproof though, needs some work.
			(object = document.createElement('a')).className = className;
			document.body.appendChild(object);
		}
		var repeat   = ' '+getStyle(object, 'background-repeat'),
			bgcolor      = getStyle(object, 'background-color');
		this.bg[0]       = 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]       = getStyle(object, 'border-top-color');
		this.borderWidth = getStyle(object, 'border-top-width');
		this.borderWidth = this.borderWidth>-1?this.borderWidth:0; // IE 'bug': returns nonnumerical value if not explicitly set.
		if(className)
			document.body.removeChild(object);
	}

	/*
	 *  -----------------
	 *  UTILITY FUNCTIONS
	 *  -----------------
	 */
	function addEvent(object, eventName, fn) {
		object.addEventListener?object.addEventListener(eventName, fn, false):object.attachEvent('on'+eventName, fn);
	}
	function onDOMReady(fn) {
		if(isGecko)
			// Firefox (and Opera sort of) behave very nicely compared the ugly hack below.
			addEvent(document, "DOMContentLoaded", fn);
		else {
			// 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.
			var object = isIE?document.getElementById('_onDOMReady'):document;
			if(isIE&&!object)
				document.write('<script id="_onDOMReady" defer="true" src="//:"></script>');
			object&&(new RegExp('complete'+(!isIE?'|loaded':''))).test(object.readyState)?fn():setTimeout(function(){onDOMReady(fn);}, 1);
		}
	}
	function selectElements(selector) {
		selector = selector.split(/\s+/);
		var elements = [document.body];
		for(var i=0; i<selector.length; ++i) {
			var next = [], 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-1));
				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 = [];
		for(var i=all.length-1; i>-1; --i)
			if(all[i].className.indexOf(className)!=-1)
				elements.push(all[i]);
		return elements;
	}
	function setCSS(selector, property, value) {
		var ss = document.styleSheets, rules = isIE?'rules':'cssRules';
		for(var i=0; i<ss.length; ++i)
			for(var j=ss[i][rules].length-1; j>-1; --j)
				if(ss[i][rules][j].selectorText==selector)
					return ss[i][rules][j].style[property] = value;
		property += ':'+value;
		ss[0].insertRule?ss[0].insertRule(selector+' {'+property+';}', ss[0][rules].length):ss[0].addRule(selector, property);
	}
	// By P.P. Koch, modified by L. Sorber (quirksmode.org/dom/getstyles.html).
	function getStyle(object, property) {
		var value = document.defaultView?
			document.defaultView.getComputedStyle(object, '').getPropertyValue(property)
			:object.currentStyle.getAttribute(property.toUpperCase().replace(/-/g, ''));
		return /px/.test(value)?1*value.substr(0, value.indexOf('px')):value;
	}

	/*
	 *  -----------------
	 *  PRIVATE FUNCTIONS
	 *  -----------------
	 */
	function hover(ac, mouseover) {
        var re = new RegExp('\\b'+ac.hoverClass+'\\b');
        if(mouseover&&!re.test(ac.object.className))
            ac.object.className += ' '+ac.hoverClass;
        else if(!mouseover&&re.test(ac.object.className))
            ac.object.className = ac.object.className.replace(new RegExp('\\s+|\\b'+ac.hoverClass+'\\b'), '');
        for(var b=1; b>-1; --b)
            setCSS('.ac'+ac.key+(b==0?'':'b'),
                   'background',
                   ac.bStyle[mouseover?1:0].bg[b]);
	}
	function makeElement(object, hoverClass, cStyle) {
		var bStyle = [new BStyle(object), hoverClass?new BStyle(object, hoverClass):''], key = Math.floor(Math.random()*999999);
		for(var b=1; b>-1; --b)
			setCSS('.ac'+key+(b==0?'':'b'), 'background', bStyle[0].bg[b]);
		return (object.alphaCorners = {
					hover:		 false,
					hoverClass:	 hoverClass,
					key:		 key,
					object:		 object,
					height:		 object.offsetHeight,
					width:		 object.offsetWidth,
					bStyle:		 bStyle,
					cStyle:		 cStyle,
					borderWidth: bStyle[0].borderWidth
				});
	}
	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(makeElement(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 object     = element.object,
			style      = object.style,
			bStyle     = element.bStyle[0];
			innerHTML  = '<div class="alphaCorners" style="'
				+'position:absolute;'
				+'overflow:hidden;'
				+'top:0px;'
				+'left:0px;'
				+'height:100%;'
				+'width:100%;'
				+'z-index:-1;'
				+((/a/i).test(object.tagName)?'cursor:pointer;':''); // IE bug: anchors lose this property in some conditions.

		// IE bug: a special case where expressions are necessary to fill the entire parent area.
		if(isIE)
			for(var i=1; i>-1; --i)
				innerHTML += (i==0?'height':'width')+':expression((this.parentNode.offset'+(i==0?'Height':'Width')+')+\'px\');';

		innerHTML += '">';

		// Add layers that automatically adjust to the parent width and height minus a given fixed offset.
		for(var b=(bStyle.borderWidth>0?1:0); b>-1; --b)
			for(var j=1; j>-1; --j) {
				var offset = [,];
				offset[j]   = b?element.cStyle.radius:Math.max(element.cStyle.radius, element.borderWidth); // Small fix for elements of which the bordersize surpasses the radius.
				offset[1-j] = bStyle.borderWidth>0?(1-b)*element.borderWidth:0;

				innerHTML += '<div class="ac'+element.key+(b==0?'':'b')+'" style="'
						+'position:absolute;'
						+(/\./.test(bStyle.bg[b])?'background-position:'
							+(offset[1-j]-offset[0])+'px '
							+(offset[1-j]-offset[1])+'px;':'')
						+'top:'   +offset[1]+'px;'
						+'right:' +offset[0]+'px;'
						+'bottom:'+offset[1]+'px;'
						+'left:'  +offset[0]+'px;';

				// IE6 bug: conflicting absolute positions do not work in this browser without a little help.
				if(isIE6)
					for(var i=1; i>-1; --i)
						innerHTML += (i==0?'height':'width')+':expression((this.parentNode.offset'+(i==0?'Height-':'Width-')+(2*offset[1-i])+')+\'px\');';

				innerHTML += '"></div>';
			}

		// Add rounded corners and close the (hover)background container.
		innerHTML += genCorners(element)+'</div>';

		// Parent positiontype has to be at least relative, so that the absolutely positioned bg is rendered correctly.
        // Force hasLayout for parent container.
        if(isIE)
            style.zoom = '1';
		if(/static/.test(getStyle(object, 'position')))
			style.position = 'relative';
		// Fix disappearing background if enclosed by parent with bg.
		if(!isIE&&!(getStyle(object, 'z-index')>0))
			style.zIndex = 0;
		// Remove the old background & border and add padding to make up for the border.
		if(element.borderWidth>0) {
			var padding  = '', position = ['left', 'bottom', 'right', 'top'];
			for(var i=3; i>-1; --i)
				padding += (element.borderWidth+getStyle(object, 'padding-'+position[i]))+'px ';
			style.padding = padding;
			style.border = 'none';
		}
		// Holly hack.
		if(isIE6&&/auto/.test(getStyle(object, 'height')))
			style.height = '1%';
		style.background = 'none';

		// Update the object with the new content lastly (slow, but at least it's faster than DOM).
		object.innerHTML += innerHTML+'</div>';

		// Add hoverevents if necessary.
		if(element.bStyle[1])
			for(var i=0; i<2; i++)
				addEvent(object, 'mouse'+(i==0?'over':'out'),
					(i==0?function(){hover(element, true)}
						 :function(){hover(element, false)}));
	}
	function genCorners(element) {
		// Initialize vars.
		var corners     = '',
			bStyle		= element.bStyle[0],
			borderWidth = element.borderWidth,
			hasBorder   = borderWidth>0;

		for(var type=3; type>-1; type--) {
			for(var b=(hasBorder?1:0); b>-1; b--) {
				var crPos    = [,],
					bgPos    = /\./.test(bStyle.bg[b]),
					radius   = Math.max(0, element.cStyle.radius-(1-b)*borderWidth),
					round    = element.cStyle.corners[type]&&radius,
					offset	 = b==0&&hasBorder?borderWidth:0,
					corner   = '<div '+(!round?'class="ac'+element.key+(b==0?'':'b')+'" ':'')+'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 += (bgPos&&!round?'background-position:'+(-crPos[0])+'px '+(-crPos[1])+'px;':'')+'">';

				// Insert divs that make up the corner.
				if(round) {
					var table = getTable(radius, type),
						pxPos = [,];

					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 class="ac'+element.key+(b==0?'':'b')+'" 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;')
								+(bgPos?'background-position:'+(-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 = [],
			pxPos = [,],
			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;
	}
};
