///////////////////////////////////////
// 角丸を書いてくれるモジュール
// Outer : 円の外側を描画することで角丸を表現（中が抜かれる:transparent）
// Inner : 円の内側を描画することで角丸を表現（外が抜かれる:transparent）
//
// [使い方]
// ex) Round.Outer.set(element, param);
//   element : String or DOM - 角丸にする要素のIDかDOM
//   param : Object - {r: 半径, bg: 描画部分のCSS-background, padding: 内部にとるpadding値}

// TODO : 試しに prototype.js からの独立 ($:6, each:3, compact:4, extend:2(clone?), Insertion:12)

var Round = {

	default_param : {r: 5, bg: "transparent", padding: 5, h: 20},

	/**********************************************************************************************
	[private] _set : 要素に角丸を設定する（Inner, Outer 各インタフェースはこの関数のエイリアス）
	**********************************************************************************************/
	_set : function(element, param, on_left_top, on_right_top, on_left_bottom, on_right_bottom, is_inner, is_vertical)
	{
		// ID or DOM
		if( typeof($(element)) == "object" )
		{
			param = (!!param) ? Object.extend(Round.clone(Round.default_param), param) : Round.default_param;
			
			// Inner
			var inner_html = this.Html.getInner(element, param, is_inner, is_vertical);
	
			// Top
			var top_html = "";
			if (on_left_top && on_right_top) top_html = this.Html.getTop(param, is_inner, is_vertical);
			else if(on_left_top)             top_html = this.Html.getLeftTop(param, is_inner, is_vertical);
			else if(on_right_top)            top_html = this.Html.getRightTop(param, is_inner, is_vertical);
	
			// Bottom
			var bottom_html = "";
			if (on_left_bottom && on_right_bottom) bottom_html = this.Html.getBottom(param, is_inner, is_vertical);
			else if(on_left_bottom)                bottom_html = this.Html.getLeftBottom(param, is_inner, is_vertical);
			else if(on_right_bottom)               bottom_html = this.Html.getRightBottom(param, is_inner, is_vertical);

			$(element).innerHTML = top_html + inner_html + bottom_html;
//			alert($(element).innerHTML);
//			if(is_vertical) $(element).style.cssFloat = "left";
			
//			$(element).innerHTML = '<div style="display:inline;"><div style="width:' + ($(element).offsetWidth + param.r * 2 + param.padding * 2) + 'px; line-height:100%; height:'+($(element).offsetHeight + param.r * 4 + 10)+'">' + top_html + inner_html + bottom_html + '</div></div>';
		}
		// className
		else if (typeof(element) == "string")
		{
			var elements = document.getElementsByClassName(element);
			elements.each((function(elm){this._set(elm, param, on_left_top, on_right_top, on_left_bottom, on_right_bottom, is_inner, is_vertical);}).bind(this));
		}
	},

	/**********************************************************************************************
	[private] Html : HTML片を返す
	**********************************************************************************************/
	Html : {
		getTop :            function(param, is_inner, is_vertical) { return this.getHorizontalEdge(param, true,  true,  true,  is_inner, is_vertical); },
		getBottom :         function(param, is_inner, is_vertical) { return this.getHorizontalEdge(param, false, true,  true,  is_inner, is_vertical); },
		getLeftTop :        function(param, is_inner, is_vertical) { return this.getHorizontalEdge(param, true,  true,  false, is_inner, is_vertical); },
		getRightTop :       function(param, is_inner, is_vertical) { return this.getHorizontalEdge(param, true,  false, true,  is_inner, is_vertical); },
		getLeftBottom :     function(param, is_inner, is_vertical) { return this.getHorizontalEdge(param, false, true,  false, is_inner, is_vertical); },
		getRightBottom :    function(param, is_inner, is_vertical) { return this.getHorizontalEdge(param, false, false, true,  is_inner, is_vertical); },
		getHorizontalEdge : function(param, is_top, on_left, on_right, is_inner, is_vertical) {
			if(is_vertical) return Round.getHTML(Round.Vertical.Div.getHorizontalEdge(param, is_top, on_left, on_right));
			else if(is_inner) return Round.getHTML(Round.Inner.Div.getHorizontalEdge(param, is_top, on_left, on_right)); // only inner
			else return Round.getHTML(Round.Outer.Div.getHorizontalEdge(param, is_top, on_left, on_right));
		},

		getInner: function(element, param, is_inner, is_vertical)
		{
			var p = (!!param && !!param.padding) ? param.padding : this.default_param.padding;
			var r = (!!param && !!param.r) ? param.r : this.default_param.r;
			var bg = (!!param && !!param.bg) ? param.bg : this.default_param.bg;
			$(element).style.padding = "0"; // remove padding set
	
			var div_inner = document.createElement("div");
			if(is_vertical)
			{
				var div_inner_element = document.createElement("div");
//				div_inner_element.style.fontSize = div_inner.style.fontSize;
				div_inner_element.style.fontSize = "12px";
				div_inner_element.style.marginTop = "5px";
//				div_inner_element.style.paddingTop = "0";
//				div_inner_element.style.width = param.w - param.r * 2;
				div_inner_element.style.border = "0px solid #f00";
				div_inner_element.style.lineHeight = "0";
				div_inner_element.innerHTML = $(element).innerHTML; 
//				div_inner_element.style.display = "inline"; // for IE on runtime

				div_inner.style.width = "30px";
				div_inner.style.cssFloat = "left";
				div_inner.style.display = "inline"; // for IE on runtime
				div_inner.style.fontSize = "0";
				div_inner.style.lineHeight = "0";
				div_inner.style.textAlign = "center"; // adhoc constant
				div_inner.style.marginTop = "0";
				div_inner.style.paddingTop = "0";
				div_inner.style.background = bg; // only inner
				div_inner.style.border = "0px solid #00f";
				if(!!param.h) div_inner.style.height = param.h;

				div_inner.appendChild(div_inner_element);
			}
			else
			{
				div_inner.style.padding = p + 'px ' + (p + r) + 'px';
				if(is_inner) div_inner.style.background = bg; // only inner
				if(!!$(element).id) div_inner.id = "inner_" + $(element).id; // accessibilize by id
				div_inner.innerHTML = $(element).innerHTML;
			}
			return Round.getHTML(div_inner);
		}
	},

	/**********************************************************************************************
	[public] Inner : 内部を塗りつぶして角丸を表現するインタフェース
	**********************************************************************************************/
	Inner : {

		set :                  function(element, param) { this.setAll(element, param); },

		//// 0 corner
		setNone :              function(element, param) { Round._set(element, param, false, false, false, false, true); },
		//// 1 corner
		setLeftTop :           function(element, param) { Round._set(element, param, true,  false, false, false, true); },
		setRightTop :          function(element, param) { Round._set(element, param, false, true,  false, false, true); },
		setLeftBottom :        function(element, param) { Round._set(element, param, false, false, true,  false, true); },
		setRightBottom :       function(element, param) { Round._set(element, param, false, false, false, true,  true); },
		//// 2 corner
		// edge
		setTop :               function(element, param) { Round._set(element, param, true,  true,  false, false, true); },
		setBottom :            function(element, param) { Round._set(element, param, false, false, true,  true,  true); },
		setLeft :              function(element, param) { Round._set(element, param, true,  false, true,  false, true); },
		setRight :             function(element, param) { Round._set(element, param, false, true,  false, true,  true); },
		// diagonal
		setDiagonalLeft :      function(element, param) { Round._set(element, param, true,  false, false, true,  true); },
		setDiagonalRight :     function(element, param) { Round._set(element, param, false, true,  true,  false, true); },
		//// 3 corner
		setExceptLeftTop :     function(element, param) { Round._set(element, param, false, true,  true,  true,  true); },
		setExceptRightTop :    function(element, param) { Round._set(element, param, true,  false, true,  true,  true); },
		setExceptLeftBottom :  function(element, param) { Round._set(element, param, true,  true,  false, true,  true); },
		setExceptRightBottom : function(element, param) { Round._set(element, param, true,  true,  true,  false, true); },
		//// 4 corner
		setAll :               function(element, param) { Round._set(element, param, true,  true,  true,  true,  true); },

		/* ------------------------------------------------------------------------- */
		Div : {

			getHorizontalEdge : function(param, is_top, on_left, on_right)
			{
				var div_edge = document.createElement("div");
				div_edge.appendChild(this.getCorner(is_top, param, on_left, on_right));

				return div_edge;
			},

			getCorner : function (is_top, param, on_left, on_right)
			{
				var bg = (!!param && !!param.bg) ? param.bg : Round.default_param.bg;
				var r = (!!param && !!param.r) ? param.r : Round.default_param.r;

				var width_array = (is_top) ? Round.getWidthArray(r) : Round.getWidthArray(r).reverse(); // 円を描く部分の座標配列を取得

				var div_corner = document.createElement("div");
	
	//			width_array.each((function(w){div_left.appendChild(this._getLeftDiv({w:w, h:h, bg:bg}));}).bind(this));
				// 同じwidthではheightを伸ばして使う
				{
					var old_w = "";
					var h = 1;
					width_array.each((function(w, i){
						if(old_w != "")	{ if (old_w != w) {div_corner.appendChild(this.getLine({w:old_w, h:h, bg:bg}, on_left, on_right)); h = 1;} else h++; }
						old_w = w;
					}).bind(this));
					div_corner.appendChild(this.getLine({w:old_w, h:h, bg:bg}, on_left, on_right));
				}			
	
				return div_corner;
			},
	
			getLine : function (param, on_left, on_right)
			{
				var div = document.createElement("div");
				div.style.marginTop = "0px";
				div.style.marginBottom = "0px";
				if(on_left) div.style.marginLeft = param.w + "px";
				if(on_right) div.style.marginRight = param.w + "px";
				div.style.height = param.h + "px";
				div.style.background = param.bg;
				div.style.overflow = "hidden";
				return div;
			}

		}

	},

	/**********************************************************************************************
	[public] Outer : 外部を塗りつぶして角丸を表現するインタフェース
	**********************************************************************************************/
	Outer : {

		set :                  function(element, param) { this.setAll(element, param); },

		//// 0 corner
		setNone :              function(element, param) { Round._set(element, param, false, false, false, false, false); },
		//// 1 corner
		setLeftTop :           function(element, param) { Round._set(element, param, true,  false, false, false, false); },
		setRightTop :          function(element, param) { Round._set(element, param, false, true,  false, false, false); },
		setLeftBottom :        function(element, param) { Round._set(element, param, false, false, true,  false, false); },
		setRightBottom :       function(element, param) { Round._set(element, param, false, false, false, true,  false); },
		//// 2 corner
		// edge
		setTop :               function(element, param) { Round._set(element, param, true,  true,  false, false, false); },
		setBottom :            function(element, param) { Round._set(element, param, false, false, true,  true,  false); },
		setLeft :              function(element, param) { Round._set(element, param, true,  false, true,  false, false); },
		setRight :             function(element, param) { Round._set(element, param, false, true,  false, true,  false); },
		// diagonal
		setDiagonalLeft :      function(element, param) { Round._set(element, param, true,  false, false, true,  false); },
		setDiagonalRight :     function(element, param) { Round._set(element, param, false, true,  true,  false, false); },
		//// 3 corner
		setExceptLeftTop :     function(element, param) { Round._set(element, param, false, true,  true,  true,  false); },
		setExceptRightTop :    function(element, param) { Round._set(element, param, true,  false, true,  true,  false); },
		setExceptLeftBottom :  function(element, param) { Round._set(element, param, true,  true,  false, true,  false); },
		setExceptRightBottom : function(element, param) { Round._set(element, param, true,  true,  true,  false, false); },
		//// 4 corner
		setAll :               function(element, param) { Round._set(element, param, true,  true,  true,  true,  false); },

		/* ------------------------------------------------------------------------- */
		Div : {

			getHorizontalEdge : function(param, is_top, on_left, on_right)
			{
				var div_edge = document.createElement("div");
				if(on_left) div_edge.appendChild(this.getCorner(is_top, param, true));
				if(on_right) div_edge.appendChild(this.getCorner(is_top, param, false));
				if(document.all) div_edge.style.height = "0"; // for IE
				div_edge.appendChild(this.getClear());
				return div_edge;
			},

			getClear : function ()
			{
				var div_clear = document.createElement("div");
				div_clear.style.clear = "both";
				return div_clear;
			},
			
			getCorner : function (is_top, param, is_left)
			{
				var bg = (!!param && !!param.bg) ? param.bg : Round.default_param.bg;
				var r = (!!param && !!param.r) ? param.r : Round.default_param.r;
				
				var width_array = (is_top) ? Round.getWidthArray(r) : Round.getWidthArray(r).reverse(); // 円を描く部分の座標配列を取得

				var div_corner = document.createElement("div");
				div_corner.style.cssFloat = (is_left) ? "left" : "right";
				// for IE
				if(document.all) eval('div_corner.style.float = (is_left) ? "left" : "right"');
				div_corner.style.textAlign = (is_left) ? "left" : "right";
	
	//			width_array.each((function(w){div_left.appendChild(this._getLeftDiv({w:w, h:h, bg:bg}));}).bind(this));
				// 同じwidthではheightを伸ばして使う
				{
					var old_w = "";
					var h = 1;
					width_array.each((function(w, i){
						if(old_w != "")	{ if (old_w != w) {div_corner.appendChild(this.getLine({w:old_w, h:h, bg:bg}, is_left)); h = 1;} else h++; }
						old_w = w;
					}).bind(this));
					div_corner.appendChild(this.getLine({w:old_w, h:h, bg:bg}, is_left));
				}			
	
				return div_corner;
			},
	
			getLine : function (param, is_left)
			{
				var div = document.createElement("div");
				div.style.width = param.w + "px";
				div.style.height = param.h + "px";
				div.style.background = param.bg;
				if (is_left === false) div.style.margin = "0 0 0 auto";
				else div.style.margin = "0";
				div.style.overflow = "hidden";
				return div;
			}

		}

	},
	
	/* ------------------------------------------------------------------------- */
	// Common
	/* ------------------------------------------------------------------------- */

	getWidthArray : function(r)
	{
		if(!!this.WidthArrayStock[r]) return this.WidthArrayStock[r].compact();
		
		// 半径より 1/8 円の座標配列を取得
		var p_octant = Round.michener(r-1);

		// 元の1/8円から 45度鏡対称の1/8円を作成
		var _p_octant = {};
		_p_octant.x = p_octant.x.compact(); // compactを配列のコピーに利用
		_p_octant.y = p_octant.y.compact(); // compactを配列のコピーに利用
		if (_p_octant.x.last() == _p_octant.y.last()) { _p_octant.x.pop(); _p_octant.y.pop(); }

		// 1/4 円の座標配列を作成
		var p_quater = {x:[],y:[]};
		p_quater.x = p_quater.x.concat(p_octant.x, _p_octant.y.reverse());
		p_quater.y = p_quater.y.concat(p_octant.y, _p_octant.x.reverse());

		// 1/4 円をたどり、yの値の変化点でのx座標を半径から引いた部分(outer)の配列を作成
		// ex) r = 5 ( -1 = 4 )
		//   x : [0, 1, 2, 3, 3, 4, 4]
		//   y : [4, 4, 3, 3, 2, 1, 0]
		//   outer : [5 -0, 5 -2, 5 -3, 5 -4, 5 -4] = [5, 3, 2, 1, 1]
		var outer = [];
		var old_y;
		p_quater.y.each(function(y, i){	if(old_y != y) { outer.push(r - p_quater.x[i]); old_y = y; } });

		this.WidthArrayStock[r] = outer;
		
		return outer.compact();
	},
	
	WidthArrayStock : {},
	
	// ミッチェナーの円の高速描画法のアルゴリズム
	michener : function(r)
	{
		var x = 0;
		var y = r;
		var px = [];
		var py = [];

		var d = 3 - 2 * r;

		for(var i=1; x<y; i++)
		{
	    	px[i - 1] = x;
	    	py[i - 1] = y;

	    	if(d < 0)
	    	{
				d = d + 4 * x + 6;
				y = y;
	    	}
	    	else
	    	{
				d = d + 4 * (x - y) + 10;
				y = y - 1;
	    	}
	    	x++;
		}

		if(x == y)
		{
			px[i - 1] = x;
			py[i - 1] = y;
		}

		return {x : px, y : py};
	},

	/* ------------------------------------------------------------------------- */
	// Utility
	/* ------------------------------------------------------------------------- */

	getHTML : function(dom)
	{
		var div = document.createElement('div');
		div.appendChild(dom);
		return div.innerHTML;
	},

	clone : function(p) {
	    p = (function(){return this}).apply(p); // objectify if atomic
	    switch (typeof p) {
	    case 'function':  return function() { return p.apply(this, arguments) };
	    case 'undefined': return p;
	    case 'object':
	        if (p == null) return p;
	        else {
	            var f = function() {};
	            f.prototype = p;
	            var o = new f;
	            switch (p.constructor) {
	            case String: case Number: case Boolean:
	                if (o.toSource) o.toSource = function() { return "clone(" + p.toSource.apply(p, arguments) + ")/*require clone function*/" };
	                o.toString = function() { return p.toString.apply(p, arguments) };
	                o.valueOf = function() { return p.valueOf.apply(p, arguments) };
	            }
	            return o;
	        }
	    }
	}

};
