var Global = this;
function d(args) {
	var out = [];
	for (var i = 0; i < arguments.length; i++) out.push(arguments[i]);
	if (navigator.userAgent.match(/Firefox/)) {
		dump(out.join(" ") + "\n");
	} else {
		// alert(out.join(" "));
	}
}

Object.prototype.p = function () {
	var t = Object.inspect(this);
	if (t == "[object Object]")
		t = this.inspect ? this.inspect() : $H(this).inspect().replace(/^#<Hash/, "#<Object");
	if (navigator.userAgent.match(/Firefox/)) {
		window.dump(t + "\n");
	} else {
		// alert(out.join(" "));
		window.status = t;
	}
	return this;
};

Color = Class.create(); {
	// Class method
	Object.extend(Color, {
		hsv2rgb : function (h, s, v) {
			h = h * 180 / Math.PI;
			var r = 0, g = 0, b = 0;
			if (s < 0) s = 0; else if (s > 1) s = 1;
			if (v < 0) v = 0; else if (v > 1) v = 1;
			h %= 360;
			if (h < 0) h += 360;
			h /= 60;
			var i = Math.floor(h);
			var f = h - i;
			var p1 = v * (1 - s);
			var p2 = v * (1 - s * f);
			var p3 = v * (1 - s * (1 - f));
			switch (i) {
				case 0: r = v;  g = p3; b = p1; break;
				case 1: r = p2; g = v;  b = p1; break;
				case 2: r = p1; g = v;  b = p3; break;
				case 3: r = p1; g = p2; b = v;  break;
				case 4: r = p3; g = p1; b = v;  break;
				case 5: r = v;  g = p1; b = p2; break;
			}
			return {r : r * 255, g : g * 255, b : b * 255};
		},

		rgb2hsv : function (r, g, b) {
			r /= 0xff; g /= 0xff; v /= 0xff;
			var h = 0, s = 0, v = 0, c = 0;
			var cmax, cmin;
			if ( r >= g)   cmax = r; else cmax = g;
			if ( b > cmax) cmax = b;
			if ( r <= g)   cmin = r; else cmin = g;
			if ( b < cmin) cmin = b;
			v = cmax;
			c = cmax - cmin;
			if (cmax == 0) s = 0; else s = c / cmax;
			if (s != 0) {
				if (r == cmax) {
					h = (g - b) / c;
				} else {
					if (g == cmax) {
						h = 2 + (b - r) / c;
					} else {
						if (b == cmax) h = 4 + ( r - g) / c;
					}
				}
				h = h * 60;
				if (h < 0) h += 360;
			}
			h = h * Math.PI / 180;
			return {h : h, s : s, v : v};
		},

		rgb2hex : function (r, g, b) {
			var rgb = b;
			rgb += g << 8;
			rgb += r << 16;
			rgb = Math.floor(rgb);
			var ret = rgb.toString(16);
			while (ret.length < 6) {
				ret = "0" + ret;
			}
			return "#" + ret;
		},

		hsv2hex : function (h, s, v) {
			var rgb = this.hsv2rgb(h, s, v);
			return this.rgb2hex(rgb.r, rgb.g, rgb.b);
		},

		hex2rgb : function (hex) {
			var m = hex.match(/#?([0-9a-f]{6}|[0-9a-f]{3})/i);
			if (m[1].length == 3) {
				m = m[1].match(/./g).collect(function (i) {
					return i + i;
				});
			} else {
				m = m[1].match(/../g);
			}
			return {
				r : Number("0x" + m[0]),
				g : Number("0x" + m[1]),
				b : Number("0x" + m[2])
				};
		},

		hex2hsv : function (hex) {
			var rgb = this.hex2rgb(hex);
			return this.rgb2hsv(rgb.r, rgb.g, rgb.b);
		}
	});

	// Instance method
	Color.prototype = {
		initialize : function (obj) {
			switch (true) {
				case (obj.hasOwnProperty("r") &&
					  obj.hasOwnProperty("g") &&
					  obj.hasOwnProperty("b")) : {

					this.setRGB(obj);
					break;
				}
				case (obj.hasOwnProperty("h") &&
					  obj.hasOwnProperty("s") &&
					  obj.hasOwnProperty("v")) : {

					this.setHSV(obj);
					break;
				}
				case (typeof obj == "string") : {
					this.setHex(obj);
					break;
				}
				default : {
					throw TypeError("Invalid Arguments");
				}
			}
		},

		setRGB : function (obj) {
			this.r = obj.r;
			this.g = obj.g;
			this.b = obj.b;

			var hsv = Color.rgb2hsv(this.r, this.g, this.b);
			for (i in hsv) this[i] = hsv[i];
			this.hex = Color.rgb2hex(this.r, this.g, this.b);
		},

		setHSV : function (obj) {
			this.h = obj.h;
			this.s = obj.s;
			this.v = obj.v;

			var rgb = Color.hsv2rgb(this.h, this.s, this.v);
			for (i in rgb) this[i] = rgb[i];
			this.hex = Color.rgb2hex(this.r, this.g, this.b);
		},

		setHex : function (str) {
			var rgb = Color.hex2rgb(obj);
			for (i in rgb) this[i] = rgb[i];

			var hsv = Color.rgb2hsv(this.r, this.g, this.b);
			for (i in hsv) this[i] = hsv[i];

			this.hex = Color.rgb2hex(this.r, this.g, this.b);
		}
	}
}



TriangleColorSelectorC = Class.create(); {
	TriangleColorSelectorC.prototype = {

		initialize : function (parent, size, bg) {
			this.container = document.createElement("div");
			this.container.className = "TriangleColorSelectorCanvas";
			this.canvas = document.createElement("canvas");
			this.canvas.setAttribute("width", size);
			this.canvas.setAttribute("height", size);
			this.h = this.s = this.v = 0;
			this.eventListener = {"change" : []};

			var pointerSize = size / 11;
			var pointerR    = pointerSize / 2;

			this.pointer = document.createElement("canvas");
			with (this.pointer) {
				setAttribute("width", pointerSize);
				setAttribute("height", pointerSize);
				style.position = "absolute";
				with (getContext('2d')) {
					fillStyle = "#000";
					beginPath();
					arc(pointerR, pointerR, pointerR - 2, 0, Math.PI * 2, true);
					fill();

					globalCompositeOperation = "destination-out";

					beginPath();
					arc(pointerR, pointerR, pointerR / 3, 0, Math.PI * 2, true);
					fill();
				}
			}

			this.tpointer = document.createElement("canvas");
			with (this.tpointer) {
				setAttribute("width", pointerSize);
				setAttribute("height", pointerSize);
				style.position = "absolute";
				with (getContext('2d')) {
					fillStyle = "#000";
					beginPath();
					arc(pointerR, pointerR, pointerR - 2, 0, Math.PI * 2, true);
					fill();

					fillStyle = "#fff";
					beginPath();
					arc(pointerR, pointerR, pointerR - 3, 0, Math.PI * 2, true);
					fill();

					fillStyle = "#000";
					beginPath();
					arc(pointerR, pointerR, pointerR / 4 + 1, 0, Math.PI * 2, true);
					fill();

					globalCompositeOperation = "destination-out";

					beginPath();
					arc(pointerR, pointerR, pointerR / 4, 0, Math.PI * 2, true);
					fill();
				}
			}

			with (this.container) {
				appendChild(this.canvas);
				appendChild(this.pointer);
				appendChild(this.tpointer);
			}
			parent.appendChild(this.container);

			this.width = size;
			this.height = size;
			this.bg = bg;

			this.ctx = this.canvas.getContext('2d');
			this.wheelradius = Math.min(this.width - 1, this.height - 1) / 2;
			this.triangleradius = 0.8 * this.wheelradius;
			with (this.ctx) {
				fillStyle = this.bg;
				fillRect(0, 0, size, size);

				// circle
				save(); {
					translate(this.wheelradius, this.wheelradius);

					var s = this.wheelradius - this.triangleradius + 2;
					rotate(90 * Math.PI / 180);
					for (var i = 0; i < 360; i++) {
						var rad = i * Math.PI / 180;
						rotate(Math.PI / 180);
						beginPath();
						fillStyle = Color.hsv2hex(rad, 1, 1);
						arc(this.wheelradius - (s / 2), 0, (s / 2) - 3, 0, Math.PI * 2, true);
						fill();
					}
				} restore();
			}

			var _this = this;
			var mousedown = function (e) {
				_this.map(e.pageX, e.pageY);
				e.stopPropagation();
				e.preventDefault();
			}
			var mousemove = function (e) {
				_this.move(e.pageX, e.pageY);
				e.stopPropagation();
				e.preventDefault();
			};
			var mouseup = function (e) {
				_this.down = null;
				e.stopPropagation();
				e.preventDefault();
			}

			this.canvas.addEventListener("mousedown", mousedown, true);
			this.canvas.addEventListener("mousemove", mousemove , true);
			this.canvas.addEventListener("mouseup", mouseup, true);

			this.pointer.addEventListener("mousedown", mousedown, true);
			this.pointer.addEventListener("mousemove", mousemove , true);
			this.pointer.addEventListener("mouseup", mouseup, true);

			this.tpointer.addEventListener("mousedown", mousedown, true);
			this.tpointer.addEventListener("mousemove", mousemove , true);
			this.tpointer.addEventListener("mouseup", mouseup, true);

			this.setColor({h:0, s:0, v:1});
		},

		setColor : function (hsv) {
			var hue, sat, val;
			var hx, hy, sx, sy, vx, vy, x0, y0;

			hue = -hsv.h;
			sat = hsv.s;
			val = hsv.v;

			hx = Math.sin(hue) * this.triangleradius;
			hy = Math.cos(hue) * this.triangleradius;
			sx = Math.sin(hue - 2 * Math.PI / 3) * this.triangleradius;
			sy = Math.cos(hue - 2 * Math.PI / 3) * this.triangleradius;
			vx = Math.sin(hue + 2 * Math.PI / 3) * this.triangleradius;
			vy = Math.cos(hue + 2 * Math.PI / 3) * this.triangleradius;
			x0 = (sx + (vx - sx) * val + (hx - vx) * sat * val);
			y0 = (sy + (vy - sy) * val + (hy - vy) * sat * val);


			var pointerR = (this.pointer.height / 2);

			with (this.tpointer.style) {
				top  = this.canvas.offsetTop  + y0 + this.wheelradius - pointerR + "px";
				left = this.canvas.offsetLeft + x0 + this.wheelradius - pointerR + "px";
			}
			this.update(hue);

			this.h = hsv.h;
			this.s = hsv.s;
			this.v = hsv.v;
			this.oncolorchange();
		},

		addEventListener : function (situation, func) {
			if (this.eventListener[situation]) {
				this.eventListener[situation].push(func);
			}
		},

		oncolorchange : function () {
			var listener = this.eventListener["change"];
			var rgb = Color.hsv2rgb(this.h, this.s, this.v);

			var deg = Math.floor(this.h * 180 / Math.PI);
			if (deg < 0) deg += 360;


			var e  = new Object;
			e.rgb = rgb;
			e.hsv = {h : this.h, s : this.s, v : this.v, h_deg : deg};
			if (e.hsv.h < 0) e.hsv.h += Math.PI * 2;
			e.hex = Color.rgb2hex(rgb.r, rgb.g, rgb.b);
			for (var i = 0, len = listener.length; i < len; i++) {
				listener[i](e);
			}
		},

		update : function (hue) {
			var hx, hy, sx, sy, vx, vy;


			/* Colored point (value = 1, saturation = 1) */
			hx = Math.sin(hue) * this.triangleradius + this.wheelradius;
			hy = Math.cos(hue) * this.triangleradius + this.wheelradius;

			/* Black point (value = 0, saturation not important) */
			sx = Math.sin(hue - (2 * Math.PI / 3)) * this.triangleradius + this.wheelradius;
			sy = Math.cos(hue - (2 * Math.PI / 3)) * this.triangleradius + this.wheelradius;

			/* White point (value = 1, saturation = 0) */
			vx = Math.sin(hue + (2 * Math.PI / 3)) * this.triangleradius + this.wheelradius;
			vy = Math.cos(hue + (2 * Math.PI / 3)) * this.triangleradius + this.wheelradius;

			hue = -hue;


			// triangle
			with (this.ctx) {
				fillStyle = this.bg;

				// 円内を初期化
				beginPath(); {
					arc(this.wheelradius,
						this.wheelradius,
						this.triangleradius, 0, Math.PI*2, true);
				} fill();

				// 各頂点と対辺の中点グラデーション
				var grad0 = createLinearGradient(hx, hy, (sx + vx) / 2, (sy + vy) / 2);
				var rgb = Color.hsv2rgb(hue, 1, 1);
				rgb = "rgba(" + [Math.floor(rgb.r), Math.floor(rgb.g), Math.floor(rgb.b)].join(", ");
				grad0.addColorStop(0, rgb + ", 1)");
				grad0.addColorStop(1, rgb + ", 0)");

				var grad1 = createLinearGradient(vx, vy, (hx + sx) / 2, (hy + sy) / 2);
				grad1.addColorStop(0, "rgba(255, 255, 255, 1)");
				grad1.addColorStop(1, "rgba(255, 255, 255, 0)");


				// lighter
				fillStyle = "#000";
				beginPath(); {
					moveTo(hx, hy);
					lineTo(sx, sy);
					lineTo(vx, vy);
					closePath();
				} fill();

				fillStyle = grad0;
				beginPath(); {
					moveTo(hx, hy);
					lineTo(sx, sy);
					lineTo(vx, vy);
					closePath();
				} fill();

				save(); {
					globalCompositeOperation = "lighter";

					fillStyle = grad1;
					beginPath(); {
						moveTo(hx, hy);
						lineTo(sx, sy);
						lineTo(vx, vy);
						closePath();
					} fill();


				} restore();
			}

			with (this.pointer.style) {
				var pointerR = (this.pointer.height / 2);
				top  = this.canvas.offsetTop  + (Math.cos(-hue) * (this.triangleradius + pointerR)) + this.wheelradius - pointerR + "px";
				left = this.canvas.offsetLeft + (Math.sin(-hue) * (this.triangleradius + pointerR))  + this.wheelradius - pointerR + "px";
			}
		},

		map : function (x, y) {
			var ox = x;
			var oy = y;
			x =   x - this.canvas.offsetLeft - this.wheelradius;
			y = -(y - this.canvas.offsetTop  - this.wheelradius);
			var rad = Math.atan2(y, x);
			var radius = Math.sqrt(x * x + y * y);
			if (radius > this.triangleradius && radius < this.wheelradius) {
				// in circle
				this.down = this.pointer;
				this.move(ox, oy);
			} else if (radius < this.triangleradius) {
				// inner circle
				this.down = this.tpointer;
				this.move(ox, oy);
			}
		},


		move : function (x, y) {
			x = x - this.canvas.offsetLeft - this.wheelradius;
			y = y - this.canvas.offsetTop  - this.wheelradius;
			switch (this.down) {
				case this.pointer: {
					this.h = (Math.atan2(-y, x) + Math.PI / 2);
					this.setColor({h:-this.h, s:this.s, v:this.v});

					break;
				}
				case this.tpointer: {
					var pointerR = (this.tpointer.height / 2);

					var hx, hy, sx, sy, vx, vy, x0, y0;
					var hue = -this.h, sat = 0, val = 0;


					hx = Math.sin(hue) * this.triangleradius;
					hy = Math.cos(hue) * this.triangleradius;
					sx = Math.sin(hue - 2 * Math.PI / 3) * this.triangleradius;
					sy = Math.cos(hue - 2 * Math.PI / 3) * this.triangleradius;
					vx = Math.sin(hue + 2 * Math.PI / 3) * this.triangleradius;
					vy = Math.cos(hue + 2 * Math.PI / 3) * this.triangleradius;

					hue = -hue;

					if ((x - sx) * vx + (y - sy) * vy < 0) {
						sat = 1;
						val = ( (x - sx) * (hx - sx) +  (y - sy) * (hy - sy))
							/ ((hx - sx) * (hx - sx) + (hy - sy) * (hy - sy));
						if (val < 0)
							val = 0;
						else if (val > 1)
							val = 1;
					} else if ((x - sx) * hx + (y - sy) * hy < 0) {
						sat = 0;
						val = ( (x - sx) * (vx - sx) +  (y - sy) * (vy - sy))
							/ ((vx - sx) * (vx - sx) + (vy - sy) * (vy - sy));
						if (val < 0)
							val = 0;
						else if (val > 1)
							val = 1;
					} else if ((x - hx) * sx + (y - hy) * sy < 0) {
						val = 1;
						sat = ( (x - vx) * (hx - vx) +  (y - vy) * (hy - vy))
							/ ((hx - vx) * (hx - vx) + (hy - vy) * (hy - vy));
						if (sat < 0)
							sat = 0;
						else if (sat > 1)
							sat = 1;
					} else {
						val = ( (x - sx) * (hy - vy) -  (y - sy) * (hx - vx))
							/ ((vx - sx) * (hy - vy) - (vy - sy) * (hx - vx));
						if (val <= 0) {
							val = 0;
							sat = 0;
						} else {
							if (val > 1)
								val = 1;
							if (hy == vy)
								sat = (x - sx - val * (vx - sx)) / (val * (hx - vx));
							else
								sat = (y - sy - val * (vy - sy)) / (val * (hy - vy));

							if (sat < 0)
								sat = 0;
							else if (sat > 1)
								sat = 1;
						}
					}

					this.s = sat;
					this.v = val;

					x0 = (sx + (vx - sx) * val + (hx - vx) * sat * val);
					y0 = (sy + (vy - sy) * val + (hy - vy) * sat * val);

					with (this.tpointer.style) {
						top  = this.canvas.offsetTop  + y0 + this.wheelradius - pointerR + "px";
						left = this.canvas.offsetLeft + x0 + this.wheelradius - pointerR + "px";
					}
					this.oncolorchange();
					break;
				}
			}

		}
	}

}

Demo = {
	timer : null,
	tcsc : null,
	on : false,

	start : function () {
		var _this = this;
		this.timer = setInterval(function () {
			var hue = _this.tcsc.h + Math.PI / 180;
			if (hue >= Math.PI * 2) hue = 0;
			_this.tcsc.setColor({h:hue, s:_this.tcsc.s, v:_this.tcsc.v});
		}, 50);
	},

	stop : function () {
		clearInterval(this.timer);
	},

	toggle : function () {
		if (this.on) {
			this.stop();
			this.on = false;
		} else {
			this.start();
			this.on = true;
		}
	}
}

function initialize() {
	var tcsc0 = new TriangleColorSelectorC($("content"), 150, "#666");
	var tcsc1 = new TriangleColorSelectorC($("content"), 200, "#fff");
	var tcsc2 = new TriangleColorSelectorC($("content"), 180, "#000");
	var l = [tcsc0, tcsc1, tcsc2];
	
	var change = function (e) {
		$("content").style.background = e.hex;
		var info = $("info");
		var dd = info.getElementsByTagName("dd");

		dd[0].replaceChild(document.createTextNode(
			"rgb(" +
			[e.rgb.r, e.rgb.g, e.rgb.b].collect(function (i) {
				return Math.floor(i);
			}).join(", ") +
			")"), dd[0].firstChild);
		
		dd[1].replaceChild(document.createTextNode(
			"hsv(" +
			[e.hsv.h_deg,
			 Math.floor(e.hsv.s * 100) + "%",
			 Math.floor(e.hsv.v * 100) + "%"].join(", ") +
			")"), dd[1].firstChild);
		dd[2].replaceChild(document.createTextNode(e.hex), dd[2].firstChild);

		var b = Math.floor(((e.rgb.r * 299) + (e.rgb.g * 587) + (e.rgb.b * 114)) / 1000)
		$("temp").style.color = (b < 127) ? "#fff" : "#000";
	}

	var h = 90 * Math.PI / 180;
	l.each(function (i) {
		i.addEventListener("change", change);
		i.setColor({h:h,s:0,v:1});
	});

	Demo.tcsc = tcsc1;

	/*
	var i = 0;
	var from = Color.hex2rgb("#ff0000");
	var to =  Color.hex2rgb("#660fff");
	[$H(from).inspect(), $H(to).inspect()].p();
	var diff = {};
	["r", "g", "b"].each(function (i) {
		diff[i] = to[i] - from[i];
	});
	diff.p();
	var t = setInterval(function () {
		for (c in diff) from[c] += Math.floor(diff[c] / 20);
		d($H(from).inspect());
		$("content").style.background = Color.rgb2hex(from.r, from.g, from.b);
		if (i >= 20) clearInterval(t);
		i++;
	}, 10);
	 */
}

Event.observe(window, 'load', initialize, false);