/**
 * Nifty Corners
 * Gets elements that has class="niftyCorners" and build round corners for that element.
 * These corners based on elements border-color, border-width, padding and
 * backgroundColor styles. The padding of the container is converted to the corners radius.
 * Note: the values of border-color, border-width and padding are taken from their "top"
 * style attributes, i.e: border-top-width, border-top-height and padding-top.
 * Example:
 * &lt;div style="border:1px solid #DBE3E; backgroundColor:#FFFFF; padding:15px;"
 * class="niftyCorners"&gt;Hello World!!!&lt;/div&gt;
 * @author Simon Hanukaev
 * @version 0.4
 * Revision history:
 * 	0.4 - added class="clearfix" to the content container.
 * 		  Note: You need to define the clearfix class in your css files!
 * 		  Changed way the content elements were copied. From innerHTML method to
 * 		  copyChildNodes, as it preserves object references.
 * 	0.3 - added extraction of borderWidth, borderColor, backGroundColor and padding from
 * 		  elements computedStyle. The padding of the container is converted to the 
 * 		  radius of corners.
 * 	0.2 - appended div elements changed to span with display block.
 * 		  fixed bug that appeared when border width was larger than 1px.
 * 	0.1 - first release.
 * TODO Ideas for further development:
 * 	- Include backgorund pictures.
 * 	- extract borderWidth, borderColor, backGroundColor, backGroundImage from element style.
 * 	- extract width and padding from element style and attach it to the inner div.
 * 	  While padding will be extra padding in addition to the padding deriving from the radius.
 */
 
var doc = document;

window.onload = niftyCorners;
/*
$(document).ready(function(){
	niftyCorners();
});
*/

function niftyCorners() {
	var elements = getElementsByClassName("niftyCorners");
	for (var i=0; i < elements.length; i++) {
		var className = elements[i].className;
		
		var width = elements[i].offsetWidth;
		elements[i].style.width = width + "px";
		
		// wrong value in Opera
		//var origHeight = getStyle(elements[i],"height");

		// Because elements border and padding are removed
		// set its height to its current offsetHeight, so no matter what its
		// offsetHeight remains exactly the same.
		var offsetHeight = elements[i].offsetHeight;
		elements[i].style.height = offsetHeight + "px";

		var borderColor = getStyle(elements[i],"borderTopColor");
		if (!borderColor) borderColor = "#000000";

		var origBorderWidth = getStyle(elements[i],"borderTopWidth").match(/(\d+)px/);
		if (origBorderWidth)
			origBorderWidth = origBorderWidth[1];
		else
			origBorderWidth = 0;
	
		elements[i].style.border = "none";
		
		var radius = getStyle(elements[i],"paddingTop").match(/(\d+)px/);
		if (radius)
			radius = parseInt(radius[1]) + parseInt(origBorderWidth);
		else
			radius = 15;
		elements[i].style.padding = "0px";
		
		var backgroundColor = getStyle(elements[i],"backgroundColor");
		elements[i].style.backgroundColor = "transparent";
		
		var upperCorners = doc.createElement("div");
		var bottomCorners = doc.createElement("div");

//		var clearFix = doc.createElement("br");
//		clearFix.style.clear = "both";
		
		var div = doc.createElement("div");
		addClassName(div, "clearfix");
//		div.style.position = "relative";
//		div.style.height = origHeight;
		div.style.borderRight = origBorderWidth + "px solid " + borderColor;
		div.style.borderLeft = origBorderWidth + "px solid " + borderColor;
		div.style.backgroundColor = backgroundColor;
		div.style.padding = "0 " + (radius - origBorderWidth) + "px";

//		upperCorners.style.width = width + "px";
//		div.style.width = width + "px";
//		bottomCorners.style.width = width + "px";
		copyChildNodes(elements[i], div);

		elements[i].appendChild(upperCorners);
		elements[i].appendChild(div);
//		elements[i].appendChild(clearFix);
		elements[i].appendChild(bottomCorners);
		
		var perimeter = quantizer(radius);
		var height = 0;
		var margin = 0;
		var borderWidth = origBorderWidth;
		var newBorderWidth = borderWidth;
		var innerRadius = radius - origBorderWidth;
		var prevX = radius;
		var prevY = 0;
		var span = null;
		var innerX = null;
		var isOuter = false;
		var wasOuter = false;
		for (var j = perimeter.length-1; j >= 0; j--) {
			if (origBorderWidth > 1 && !isOuter) {
				if ( perimeter[j].y > innerRadius ) {
					isOuter = true;
				}
				else if ( perimeter[j].y != prevY ) {
					innerX = Math.round( getCathetus(innerRadius, perimeter[j].y - 0.5) );
					newBorderWidth = perimeter[j].x - innerX;
				}
			}
			if ( ( perimeter[j].x == prevX || perimeter[j].y == prevY ) && (newBorderWidth == borderWidth) && (isOuter == wasOuter) ) {
				if ( perimeter[j].x == prevX )
					height++;
				if ( origBorderWidth == 1 && perimeter[j].y == prevY ) {
					borderWidth++;
					newBorderWidth = borderWidth;
				}
				prevX = perimeter[j].x;
				prevY = perimeter[j].y;
			}
			else {
				// append the elements for styling round corners.
				var span = createSpan(margin, height, borderColor, backgroundColor, borderWidth, wasOuter);
				upperCorners.insertBefore(span ,upperCorners.firstChild); 
				span = span.cloneNode(true);
				bottomCorners.appendChild(span);
				
				// re initialize variables for next coordinate.
				wasOuter = isOuter;
				height = 1;
				if ( origBorderWidth == 1 ) {
					borderWidth = origBorderWidth;
					newBorderWidth = borderWidth;
				}
				if ( origBorderWidth > 1 )
					borderWidth = newBorderWidth;
				prevX =	perimeter[j].x;
				prevY = perimeter[j].y;
				margin = radius - prevX;	
			}
		}
		// append the elements for styling round corners for the last time.
		// these alements are the most top one and the most bottom one.
		span = createSpan(margin, height, borderColor, backgroundColor, origBorderWidth, true);
		upperCorners.insertBefore(span ,upperCorners.firstChild); 
		span = span.cloneNode(true);
		bottomCorners.appendChild(span);
	}
}

function getStyle(element,style) {
	var computedStyle = "";
	if (window.getComputedStyle)	
		computedStyle = window.getComputedStyle(element,"")[style];
	else
		computedStyle = element.currentStyle[style];
	return computedStyle;
}

/**
 * Creates wrapping element for rounded box that makes the rounded corners effect.
 * @param (number} margin Left and right margin in pixels of the returning element relating
 * the main box.
 * @param {number} hegiht Height of the element to append.
 * @param {HEX color} borderColor Border color in HEX format
 * @param {HEX color} backgroundColor Background color of the box in HEX format
 * @param (number} borderWidth Width of the border.
 * @param {boolean} isOuter Flag that defines if retuned element should be the most outer
 * element that makes the upper and bottom borders.
 * @return {Element Node} Element to append to the round corners box at the top and bottom. 
 */
function createSpan(margin, height, borderColor, backgroundColor, borderWidth, isOuter) {
	var span = doc.createElement("span");
	span.style.margin = "0 " + margin + "px";
	span.style.height = height + "px";
	span.style.display = "block";
	span.style.overflow = "hidden";
	if (!isOuter) {
		span.style.borderLeft = borderWidth + "px solid " + borderColor;
		span.style.borderRight = borderWidth + "px solid " + borderColor;
		span.style.backgroundColor = backgroundColor;
	}
	else {
		if (borderWidth != 0)
			span.style.backgroundColor = borderColor;
		else
			span.style.backgroundColor = backgroundColor;
	}
	return span;
}

/**
 * @constructor Builds new coordinate from x and y.
 * @param {number} x x coordinate
 * @param {number} y y coordinate 
 */
function Coordinate(x,y) {
	this.x = x;
	this.y = y;
}

/**
 * Gets cathethus of right triangle.
 * @param {float} hypotenuse Hypotenuse of the right triangle.
 * @param {float} cathetus Known cathetus of the right triangle.
 * @return {float} cathetus The unknown cathetus of the right triangle. 
 */
function getCathetus(hypotenuse, cathetus) {
	return Math.sqrt(Math.pow(hypotenuse,2) - Math.pow(cathetus,2));
}

/**
 * Quntizes quarter circle of given radius to a first pane of cartesian coordiantes.
 * @param {integer} radius Radius of the cicrle in pixels.
 * @return {Array} perimeter Array of {@link Coordinate} objects that defining circle perimeter
 * in the first pane of cartesian coordinates. 
 */
function quantizer(radius) {
	var perimeter = new Array();
	var x = 0;
	var y = radius;
	var xNew = null
	var yNew = null;
	while (y > 0) {
		xNew = Math.round( getCathetus(radius, y - 0.5) );
		while (xNew >= x + 1)
			perimeter.push(new Coordinate(++x,y));
		if (x != radius)
			yNew = Math.round( getCathetus(radius, x + 0.5) );
		else
			yNew = 0;
		while (yNew < --y)
			perimeter.push(new Coordinate(x, y));
	}
	return perimeter;
}

/**
 * Gets elements by their class names
 * @param {String} className The class name to search. Can be complex like "title bold", then it will
 * find all elements that have "title" and "bold" classes. Can also be a regular expression.
 * @param {String} parentElement The ancestor element in which to search for elements.
 * @return {Array} elements Array of element nodes that matched the class name.
 */
function getElementsByClassName(className, parentElement) {
	className = className.replace(/^\s+/, "").replace(/\s+$/, "");
	if (className == "") return null;
	var classNamesArray = className.split(/\s+/);
	elements = new Array();
	if (!parentElement)
		parentElement = document;
	try {
		elements = traverseSiblings(parentElement);
	} catch(e) {
		myAlert("Bubble script error in traverseSiblings(): " + e.name + " - " + e.message);
	} finally {
		return elements;
	}
	
	/**
	 * Private recursy function for traversing node tree.
	 */
	function traverseSiblings(parentElement) {
		var elements = new Array();
		var child = null;
		var hasClass = null;
		var regexp = null;
		for (var i = 0; i < parentElement.childNodes.length; i++) {
			child = parentElement.childNodes[i];
			if ( child.nodeType == 1 ) {
				hasClass = true;
				for (var j = 0; j < classNamesArray.length && hasClass; j++) {
					regexp = new RegExp("\\b"+classNamesArray[j]+"\\b");
					if (!regexp.test(child.className)) {
						hasClass = false;
					}
				}
				if (hasClass) {
					elements.push(child);
				}
				elements = elements.concat( traverseSiblings(child) );
			}
		}
		return elements;
	}
}

/*
 * Adds class name to an element node.
 * Parameters:
 * 1. element - element to append the class name
 * 2. className - class name to append, duuuh!
 */
function addClassName(element, className) {
	element.className += (element.className ? ' ' : '') + className;
}

/**
 * Copies child nodes from one element to another.
 * This function need for times when you can't just copy innerHTML because it is not preserves
 * the references to the objects.
 * @param {element node} elementFrom Source element
 * @param {element node} elementTo Target element
 */
function copyChildNodes(elementFrom, elementTo) {
	while (elementFrom.childNodes.length > 0) {
		elementTo.appendChild(elementFrom.childNodes[0]);
	}
}