(function($) {
	$.fn.jc = function(options) {
		var opts = $.extend({}, $.jcDefaults, options),
			obj = $(this),
			field = typeof opts.field == 'string'?$('<input type="hidden" name="' + opts.field + '" />').insertAfter(obj):opts.field,
			color = null,
			palette = null,
			pointer = null,
			pointer_cont = null,
			pointer_dx = 0,
			pointer_dy = 0,
			slider = null,
			slider_cont = null,
			slider_dy = 0,
			hue = 0,
			saturation = 0,
			brightness = 0,
			txt = null,
			init = function() {
				var bg = obj.css('backgroundColor'), hsb;
				color = field.val()?field.val():(bg?bg:opts.color);
				hsb = rgb2hsb(color);
				hue = hsb.h;
				saturation = hsb.s;
				brightness = hsb.b;
				reset_palette(color);
				obj.bind(opts.event, show_palette);
			},
			show_palette = function(e) {
				e.stopPropagation();
				if (palette && palette.is(':visible'))
			    	return reset_palette();
				$(document)
					.trigger(opts.triggerEvent)
					.bind(opts.triggerEvent, function(e) {
						reset_palette();
					});
					
				if (!palette) {
					palette = $('<div' + (opts['class']?' class="' + opts['class'] + '"':'') + ' style="position:absolute;z-index:100;width:' +  opts.width + 'px"><div class="tl"></div><div class="t"></div><div class="tr"></div><div class="r"></div><div class="bl"></div><div class="b"></div><div class="br"></div></div>')
						.click(function(e) {
							e.stopPropagation();
						})
						.appendTo('body').hide();
					var inner = $('<div' + (opts.innerClass?' class="' + opts.innerClass + '"':'') + '></div>').appendTo(palette);
					pointer_cont = $('<div' + (opts.sbClass?' class="' + opts.sbClass + '"':'') + ' style="float:left; background-color:' + hsb2rgb(hue, opts.maxS, opts.maxB) + '; cursor:pointer; position:relative; width:' + opts.maxS + 'px; height:' + opts.maxB + 'px; -moz-user-select:none; -khtml-user-select:none; user-select:none" unselectable="on"></div>')
						.select(function() {
							return false;
						})
						.mousedown(function(e) {
							$(document).bind(opts.trackEvent, track_pointer);
							$(document).bind(opts.releaseEvent, release);
						})
						.appendTo(inner),
					slider_cont = $('<div' + (opts.hClass?' class="' + opts.hClass + '"':'') + ' style="float:left; cursor:pointer; position:relative; height:' + opts.maxH + 'px; -moz-user-select:none;-khtml-user-select:none; user-select:none" unselectable="on"></div>')
						.select(function() {
							return false;
						})
						.mousedown(function(e) {
							$(document).bind(opts.trackEvent, {type:'slider'}, track_slider);
							$(document).bind(opts.releaseEvent, release);
						})
						.appendTo(inner);
					pointer = $('<div' + (opts.pointerClass?' class="' + opts.pointerClass + '"':'') + ' style="-moz-user-select:none;-khtml-user-select:none; user-select:none" unselectable="on"></div>')
						.select(function() {
							return false;
						})
						.appendTo(pointer_cont);
					pointer_dx = parseInt(pointer.css('width')) / 2;
					pointer_dy = parseInt(pointer.css('height')) / 2;
					pointer.css({
						'marginLeft': saturation - pointer_dx + 'px',
						'marginTop': opts.maxB - brightness - pointer_dy + 'px'
					});
					slider = $('<div' + (opts.sliderClass?' class="' + opts.sliderClass + '"':'') + ' style="-moz-user-select:none;-khtml-user-select:none; user-select:none" unselectable="on"></div>')
						.select(function() {
							return false;
						})
						.appendTo(slider_cont);
					slider_dy = parseInt(slider.height()) / 2;
					slider.css({'marginTop': hue - slider_dy + 'px'});
					$('<div style="clear:both"></div>').appendTo(inner);
					$('<input type="image" alt="Ok" src="' + opts.imgPath + 'palette_ok.gif" />')
						.click(function() {
							reset_palette();
						})
						.appendTo(palette);
            	}
			    var offset = obj.offset(),
			    	ow = obj.width(),
			    	oh = obj.height(),
			    	pw = palette.width(),
			    	ph = palette.height();
        		palette.css({
        			'left': (offset.left + pw > $(window).scrollLeft() + $(window).width()?offset.left + ow - pw:offset.left) + 'px',
        			'top': (offset.top + oh + ph > $(window).scrollTop() + $(window).height()?offset.top - ph - opts.offset:offset.top + oh + opts.offset) + 'px'
        		}).show();
        		if (opts.init)
					opts.init(color, obj);
    	        return false;
			},
			set_color = function(col) {
				color = col;
				field.val(color);
				if (opts.change) {
          			opts.change(color, obj);
          		}
			},
			set_hsb = function() {
				set_color(hsb2rgb(hue, saturation, brightness));
			},
			change_color = function(col) {
				field.value = col;
				//obj.css({backgroundColor: col});
				if (opts.change)
					opts.change(col, obj);
			},
			track_pointer = function(e) {
				e.stopPropagation();
				var offset = pointer_cont.offset();
				saturation = Math.round(e.pageX - offset.left),
				brightness = Math.round(e.pageY - offset.top);
				if (saturation < 0) saturation = 0;
				if (saturation > opts.maxS) saturation = opts.maxS;
				if (brightness < 0) brightness = 0;
				if (brightness > opts.maxB) brightness = opts.maxB;
				pointer.css({
					'marginLeft': saturation - pointer_dx + 'px',
					'marginTop': brightness - pointer_dy + 'px'
				});
				brightness = opts.maxS - brightness;
			},
			track_slider = function(e) {
			    e.stopPropagation();
			    var offset = slider_cont.offset();
				hue = Math.round(e.pageY - offset.top);
				if (hue < 0) hue = 0;
				if (hue > opts.maxH) hue = opts.maxH;
				slider.css({'marginTop': hue - slider_dy + 'px'});
			},
			release = function(e) {
				set_hsb();
				pointer_cont.css({'backgroundColor': hsb2rgb(hue, opts.maxS, opts.maxB)});
				$(document).unbind(opts.trackEvent, track_pointer);
				$(document).unbind(opts.trackEvent, track_slider);
				$(document).unbind(opts.releaseEvent, release);
			},
			reset_palette = function(col) {
				if (typeof col == 'string') {
					set_color(col);
					change_color(col);
				}
				if (opts.reset)
					opts.reset(color, obj);
				if (palette) palette.hide();
				$(document).unbind(opts.triggerEvent, reset_palette);
			},
			rgb2hsb = function(rgb) {
				var h = 0, s = 0, b = 0,
					rd = 0, gr = 0, bl = 0;
				if (rgb[0] == '#')
					rgb = rgb.substr(1);
				if (rgb.length == 3)
					rgb = rgb[0] + rgb[0] + rgb[1] + rgb[1] + rgb[2] + rgb[2];
				if (rgb.length == 6) {
					rd = dec(rgb[0] + rgb[1]) / 255;
					gr = dec(rgb[2] + rgb[3]) / 255;
					bl = dec(rgb[3] + rgb[4]) / 255;	
				}
				else if (rgb = /^rgb\((\d+).(\d+).(\d+)\)$/ig.exec(rgb.replace(/\s+/g, ''))) {
					rd = rgb[1] / 255;
					gr = rgb[2] / 255;
					bl = rgb[3] / 255;
				}
				min = Math.min(Math.min(rd, gr), bl),
				max = Math.max(Math.max(rd, gr), bl);
				if (min == max) {
				    h = s = 0;
				    b = max * opts.maxB;
				}
				else {
				    var f = (rd == min) ? gr - bl : ((gr == min) ? bl - rd : rd - gr);
				    	i = (rd == min) ? 3 : ((gr == min) ? 5 : 1);
					h = Math.floor((i - f / (max - min)) * 60 / 360 * opts.maxH);
					s = Math.floor((max - min) / max * opts.maxS);
					b = Math.floor(max * opts.maxB);
				}
				return {h:h, s:s, b:b};
			},
			hsb2rgb = function(h, s, b) {
				var rd, gr, bl;
				h /=  opts.maxH;
				s /=  opts.maxS;
				b /=  opts.maxB;
				if (!s) {
					rd = gr = bl = Math.floor(b * 255 + 0.5);
				}
				else {
					var _h = (h - Math.floor(h)) * 6.0,
						_f = _h - Math.floor(_h),
						_p = b * (1.0 - s),
						_q = b * (1.0 - s * _f),
						_t = b * (1.0 - (s * (1.0 - _f)));
                    switch (Math.floor(_h)) {
                    	case 0:
                    		rd = Math.floor(b * 255.0 + 0.5);
                    		gr = Math.floor(_t * 255.0 + 0.5);
                    		bl = Math.floor(_p * 255.0 + 0.5);
                    		break;
                    	case 1:
                    		rd = Math.floor(_q * 255.0 + 0.5);
                    		gr = Math.floor(b * 255.0 + 0.5);
                    		bl = Math.floor(_p * 255.0 + 0.5);
                    		break;
                    	case 2:
                    		rd = Math.floor(_p * 255.0 + 0.5);
                    		gr = Math.floor(b * 255.0 + 0.5);
                    		bl = Math.floor(_t * 255.0 + 0.5);
                    		break;
                    	case 3:
                    		rd = Math.floor(_p * 255.0 + 0.5);
                    		gr = Math.floor(_q * 255.0 + 0.5);
                    		bl = Math.floor(b * 255.0 + 0.5);
                    		break;
                    	case 4:
                    		rd = Math.floor(_t * 255.0 + 0.5);
                    		gr = Math.floor(_p * 255.0 + 0.5);
                    		bl = Math.floor(b * 255.0 + 0.5);
                    		break;
                    	case 5:
                    		rd = Math.floor(b * 255.0 + 0.5);
                    		gr = Math.floor(_p * 255.0 + 0.5);
                    		bl = Math.floor(_q * 255.0 + 0.5);
                    		break;
                    }
                }
				return '#' + hex(rd, 2) + hex(gr, 2) + hex(bl, 2);
			},
			dec = function(str) {
				var num = 0, i;
				str = str.toUpperCase();
				while (str) {
					i = str.charCodeAt(0);
					i -= i < 65?48:55;
					num = (num << 4) + i;
					str = str.substr(1);
				}
				return num;
			},
			hex = function(num, chars) {
				var str = '', i;
				while (num) {
					i = num % 16;
					str = (i < 10?i:String.fromCharCode(i+55)) + str;	
					num = Math.floor(num / 16);
				}
				i = (chars || 0) - str.length;
				while (i-- > 0)
					str = '0' + str;
				return str;
			};
		init();
		return obj;
	}
})(jQuery);

$.jcDefaults = {
	'class':		'jc',
	innerClass:		'inner',
	sbClass:		'inner-sb',
	hClass:			'inner-h',
	pointerClass:	'inner-pointer',
	sliderClass:	'inner-slider',
	color:			'#ffffff',
	imgPath:		'',
	
	maxH:			100,
	maxS:			100,
	maxB:			100,
	
	offset:			3,
	field:			'color',
	'event':		'click',
	triggerEvent:	'click',
	trackEvent:		'mousemove mouseup',
	releaseEvent:	'mouseup'
};
