/** public static class RoundedCornersUtil : Interface for applying rounded corners to UI objects.
  * Usage examples:
  * Apply rounded corners to a single DOM element. Corners are 10x10px square.
  * RoundedCornersUtil.applyToElement(document.getElementById("foo"), 10, 10);
  * 
  * Apply rounded corners to an array of DOM elements. Corners are 5px wide, 10px tall.
  * RoundedCornersUtil.applyToElements([document.getElementById("foo"), document.getElementById("bar"),
  * document.getElementById("baz")], 5, 10);
  * 
  * Apply rounded corners to all DOM elements with the specified CSS className. Corners are 10px wide, 5px tall.
  * RoundedCornersUtil.applyToElementswithClassName("someClass", 10, 5);
  * 
  * Apply rounded corners to a single DOM element. Top corners are 10x10, bottom corners are 20x20.
  * RoundedCornersUtil.applyToElement(document.getElementById("foo"), 10, 10, 20, 20);
  * 
  * Apply rounded corners to a single DOM element. Use "divWithBGColor"'s backgroundColor instead of "foo"'s parent node.
  * RoundedCornersUtil.applyToElement(document.getElementById("foo"), 10, 10, 10, 10,
  * document.getElementById("divWithBGColor"));
  */
RoundedCornersUtil = {
	/** private static String BOTTOM_ID : Used as both classname and id suffix for both bottom corners. */
	BOTTOM_ID : "roundedCornersBottom",
	/** private static Array SUPPORTED_TAGS : Used when searching for elements by className. */
	SUPPORTED_TAGS : ["DIV", "P", "OL", "UL"],
	/** private static String TOP_ID : Used as both classname and id suffix for both top corners. */
	TOP_ID : "roundedCornersTop",
	/** public void applyToElement : Apply rounded corners to a single DOM element.
	  * @param HTMLElement element Element to apply rounded corners to
	  * @param int topCornerWidth Width of the top corners
	  * @param int topCornerHeight Height of the top corners
	  * @param int bottomCornerWidth Width of the bottom corners (optional)
	  * @param int bottomCornerHeight Height of the bottom corners (optional)
	  * @param HTMLElement bgColorNode Element to derive bgColor from, if not the element's parent (optional)
	  */
	applyToElement : function(element, topCornerWidth, topCornerHeight, bottomCornerWidth, bottomCornerHeight, bgColorNode, elementWidth) {
		if (!RoundedCornersUtil.isClientCompliant()) return;
		if (typeof(bottomCornerWidth) == "undefined") var bottomCornerWidth = topCornerWidth;
		if (typeof(bottomCornerHeight) == "undefined") var bottomCornerHeight = topCornerHeight;
		if (!bgColorNode) var bgColorNode = element.parentNode;
		var roundedElement = new RoundedElement(element, topCornerWidth, topCornerHeight, bottomCornerWidth, bottomCornerHeight, bgColorNode, elementWidth);
		roundedElement.apply();
	},
	/** public void applyToElements : Apply rounded corners to an array of DOM elements.
	  * @param Array elements List of element to apply rounded corners to
	  * @param int topCornerWidth Width of the top corners
	  * @param int topCornerHeight Height of the top corners
	  * @param int bottomCornerWidth Width of the bottom corners (optional)
	  * @param int bottomCornerHeight Height of the bottom corners (optional)
	  * @param HTMLElement bgColorNode Element to derive bgColor from, if not the element's parent (optional)
	  */
	applyToElements : function(elements, topCornerWidth, topCornerHeight, bottomCornerWidth, bottomCornerHeight, bgColorNode) {
		if (!RoundedCornersUtil.isClientCompliant()) return;
		for (var i=0; i<elements.length; i++) {
			RoundedCornersUtil.applyToElement(elements[i], topCornerWidth, topCornerHeight, bottomCornerWidth, bottomCornerHeight, bgColorNode);
		}
	},
	/** public void applyToElementsWithClassName : Apply rounded corners to all DOM elements
	  * with the specified CSS className.
	  * @param String className Name of CSS class
	  * @param int topCornerWidth Width of the top corners
	  * @param int topCornerHeight Height of the top corners
	  * @param int bottomCornerWidth Width of the bottom corners (optional)
	  * @param int bottomCornerHeight Height of the bottom corners (optional)
	  * @param HTMLElement bgColorNode Element to derive bgColor from, if not the element's parent (optional)
	  */
	applyToElementsWithClassName : function(className, topCornerWidth, topCornerHeight, bottomCornerWidth, bottomCornerHeight, bgColorNode,elementWidth) {
		if (!RoundedCornersUtil.isClientCompliant()) return;
		var classNameRE = new RegExp("\\b" + className + "\\b");
		var elements = [];
		for (var i=0; i<RoundedCornersUtil.SUPPORTED_TAGS.length; i++) {
			var tags = document.getElementsByTagName(RoundedCornersUtil.SUPPORTED_TAGS[i]);
			for (var j=0; j<tags.length; j++) {
				if (classNameRE.test(tags[j].className)) elements.push(tags[j]);
			}
		}
		for (var k=0; k<elements.length; k++) {
			if (classNameRE.test(elements[k].className)) {
				RoundedCornersUtil.applyToElement(elements[k], topCornerWidth, topCornerHeight, bottomCornerWidth, bottomCornerHeight, bgColorNode,elementWidth);
			}
		}
	},
	/** private String getBackgroundColor : Get the bgColor for the specified element.
	  * @param HTMLElement element Element to get bgColor for
	  * @param String illegalColor Colors to ignore, e.g. "transparent"
	  * @return Hex bgColor value
	  */
	getBackgroundColor : function(element, illegalColor) {
		var value = null;
		if (element.currentStyle) {
			value = element.currentStyle.getAttribute("backgroundColor");
		} else if (document.defaultView && document.defaultView.getComputedStyle) {
			value = document.defaultView.getComputedStyle(element, "").getPropertyValue("background-color");
		}
		if (value == null) return value;
		if (((value.indexOf("rgba") > -1) || (value == illegalColor)) && element.parentNode) {
			if (element.parentNode != document) value = RoundedCornersUtil.getBackgroundColor(element.parentNode, illegalColor);
			else value = "#FFFFFF";
		}
		if ((value.indexOf("rgb") > -1) && (value.indexOf("rgba") == -1)) value = RoundedCornersUtil.rgbToHex(value);
		if (value.length == 4) value = "#" + value.substring(1, 1) + value.substring(1, 1) + value.substring(2, 1) + value.substring(2, 1) + value.substring(3, 1) + value.substring(3, 1);
		return value;
	},
	/** private boolean isClientCompliant : Check whether browser can display rounded corners or not.
	  * @return True if browser can display rounded corners
	  */
	isClientCompliant : function() {
		if (!document.getElementById || !document.createElement) return false;
		var b = navigator.userAgent.toLowerCase();
		if ((b.indexOf("msie 5") > 0) && (b.indexOf("opera") == -1)) return false;
		return true;
	},
	/** private String rgbToHex : Convert an RGB value to hex string.
	  * @param String rgbValue RGB value to convert
	  * @return Hex string
	  */
	rgbToHex : function(rgbValue) {
		var hexValue = "#";
		var rgbRE = /(\d+)[, ]+(\d+)[, ]+(\d+)/;
		var rgbDigits = rgbRE.exec(rgbValue);
		for (var i=1; i<rgbDigits.length; i++) hexValue += ("0" + parseInt(rgbDigits[i]).toString(16)).slice(-2);
		return hexValue;
	},
	/** private void scheduleForResize : Schedule an onload event to correctly size the specified element.
	  * To prevent a weird wrapping bug in IE, the width of both top/bottom elements must be set
	  * explicitly. This mechanism waits till the page is completely rendered and it can get the offsetWidth
	  * of both elements, then explicitly sets each element's width property to its rendered width.
	  * If this seems stupid you're right, but unfortunately it's necessary thanks to IE.
	  * @param String roundedElementId ID of top/bottom rounded corner element
	  */
	scheduleForResize : function(roundedElementId,elementWidth) {
		var topId = roundedElementId + "_" + RoundedCornersUtil.TOP_ID;
		var bottomId = roundedElementId + "_" + RoundedCornersUtil.BOTTOM_ID;
		//alert("in scheduleForResize elementWidth=" + elementWidth);
		//alert("in scheduleForResize offsetWidth=" + document.getElementById(topId).offsetWidth);
		if(elementWidth) {
			if (document.getElementById(topId)) {
				jQuery(document).ready(function(){ document.getElementById(topId).style.width = elementWidth + "px"; });
			}
			if (document.getElementById(bottomId)) {
				jQuery(document).ready(function(){ document.getElementById(bottomId).style.width = elementWidth + "px"; });
			}	
		}
			else {
				if (document.getElementById(topId)) {
					jQuery(document).ready(function(){ document.getElementById(topId).style.width = document.getElementById(topId).offsetWidth + "px"; });
				}
				if (document.getElementById(bottomId)) {
					jQuery(document).ready(function(){ document.getElementById(bottomId).style.width = document.getElementById(bottomId).offsetWidth + "px"; });
				}
			}
	}
}

/** public class RoundedElement : Encapsulates one DOM element with rounded corners.
  * @param HTMLElement element Element to apply rounded corners to
  * @param int topCornerWidth Width of the top corners
  * @param int topCornerHeight Height of the top corners
  * @param int bottomCornerWidth Width of the bottom corners
  * @param int bottomCornerHeight Height of the bottom corners
  * @param HTMLElement bgColorNode Element to derive bgColor from
  */
function RoundedElement(element, topCornerWidth, topCornerHeight, bottomCornerWidth, bottomCornerHeight, bgColorNode, elementWidth) {
	this.element = element;
	this.fgColor = RoundedCornersUtil.getBackgroundColor(this.element, "transparent");
	this.bgColor = RoundedCornersUtil.getBackgroundColor(bgColorNode, "transparent");
	this.topCornerWidth = topCornerWidth;
	this.topCornerHeight = topCornerHeight;
	this.bottomCornerWidth = bottomCornerWidth;
	this.bottomCornerHeight = bottomCornerHeight;
	this.elementWidth = elementWidth;
	//alert("setting RoundedElement=" + this.elementWidth);
	return this;
}
/** public void apply : Apply rounded corners to this instance's DOM element. */
RoundedElement.prototype.apply = function() {
	this.round(true);
	this.round(false);
	RoundedCornersUtil.scheduleForResize(this.element.id, this.elementWidth);
}
/** private String getBlendedHexValue : Get the hex value of an anti-aliased pixel.
  * @param float alpha Differential between foreground and background colors
  * @return Blended hex value
  */
RoundedElement.prototype.getBlendedHexValue = function(alpha) {
	var bgDigits = new Array(
		parseInt("0x" + this.bgColor.substring(1, 3)), 
		parseInt("0x" + this.bgColor.substring(3, 5)), 
		parseInt("0x" + this.bgColor.substring(5, 7))
	);
	var fgDigits = new Array(
		parseInt("0x" + this.fgColor.substring(1, 3)), 
		parseInt("0x" + this.fgColor.substring(3, 5)), 
		parseInt("0x" + this.fgColor.substring(5, 7))
	);
	return "#" 	+ ("0" + Math.round(bgDigits[0] + (fgDigits[0] - bgDigits[0]) * alpha).toString(16)).slice(-2).toString(16)
				+ ("0" + Math.round(bgDigits[1] + (fgDigits[1] - bgDigits[1]) * alpha).toString(16)).slice(-2).toString(16)
				+ ("0" + Math.round(bgDigits[2] + (fgDigits[2] - bgDigits[2]) * alpha).toString(16)).slice(-2).toString(16);
}
/** private void round : Create rounded corners for top/bottom of element.
  * @param boolean isTopCorners True if applying rounded corners to top of element
  */
RoundedElement.prototype.round = function(isTopCorners) {
	var width = (isTopCorners) ? this.topCornerWidth : this.bottomCornerWidth;
	var height = (isTopCorners) ? this.topCornerHeight : this.bottomCornerHeight;
	if (!width && !height) return;
	var d = document.createElement("div");
	d.style.backgroundColor = this.bgColor;
	d.style.height = height;
	d.style.overflow = "hidden";
	d.className = (isTopCorners) ? RoundedCornersUtil.TOP_ID : RoundedCornersUtil.BOTTOM_ID;
	d.id = this.element.id + "_" + ((isTopCorners) ? RoundedCornersUtil.TOP_ID : RoundedCornersUtil.BOTTOM_ID);
	var lastArc = 0;
	for (var i=1; i<=height; i++) {
		var coverage, arc2, arc3;
		// Find intersection of arc with bottom of pixel row
		var arc = Math.sqrt(1.0 - Math.pow(1.0 - i / height, 2)) * width;
		// Calculate how many pixels are bg, fg and blended.
		var n_bg = width - Math.ceil(arc);
		var n_fg = Math.floor(lastArc);
		var n_aa = width - n_bg - n_fg;
		// Create pixel row wrapper
		var x = document.createElement("div");
		var y = d;
		x.style.margin = "0px " + n_bg + "px";
		x.style.height = "1px";
		x.style.overflow = "hidden";
		// Make a wrapper per anti-aliased pixel (at least one)
		for (var j=1; j<=n_aa; j++) {
			// Calculate coverage per pixel
			// (approximates circle by a line within the pixel)
			if (j == 1) {
				if (j == n_aa) {
					// Single pixel
					coverage = ((arc + lastArc) * .5) - n_fg;
				} else {
					// First in a run
					arc2 = Math.sqrt(1.0 - Math.pow((width - n_bg - j + 1) / width, 2)) * height;
					coverage = (arc2 - (height - i)) * (arc - n_fg - n_aa + 1) * .5;
					// Coverage is incorrect. Why?
					coverage = 0;
				}
			} else if (j == n_aa) {
				// Last in a run
				arc2 = Math.sqrt(1.0 - Math.pow((width - n_bg - j + 1) / width, 2)) * height;
				coverage = 1.0 - (1.0 - (arc2 - (height - i))) * (1.0 - (lastArc - n_fg)) * .5;
			} else {
				// Middle of a run
				arc3 = Math.sqrt(1.0 - Math.pow((width - n_bg - j) / width, 2)) * height;
				arc2 = Math.sqrt(1.0 - Math.pow((width - n_bg - j + 1) / width, 2)) * height;
				coverage = ((arc2 + arc3) * .5) - (height - i);
			}
			x.style.backgroundColor = this.getBlendedHexValue(coverage);
			if (isTopCorners) y.appendChild(x);
			else y.insertBefore(x, y.firstChild);
			y = x;
			var x = document.createElement("div");
			x.style.height = "1px";
			x.style.overflow = "hidden";
			x.style.margin = "0px 1px";
		}
		x.style.backgroundColor = this.fgColor;
		if (isTopCorners) y.appendChild(x);
		else y.insertBefore(x, y.firstChild);
		lastArc = arc;
	}
	if (isTopCorners) this.element.insertBefore(d, this.element.firstChild);
	else this.element.appendChild(d);
}

