var rgbEventDef = {
	R:1,
	G:2,
	B:4,
	H:8,
	S:16,
	V:32
}

function rgbEvent(properties, rgb, hsv, hex, colorHelper){
	this.properties = properties;
	this.hex = hex;
	this.colorHelper = colorHelper;
	this.rgb = this.copy(rgb);
	this.hsv = this.copy(hsv);
}
rgbEvent.prototype = {
	copy: function(src){
		var result = [], idx = src.length-1;
		while (idx >= 0){
			result[idx] = src[idx];
			idx--;
		}
		return result;
	}
}

function rgbColorModel(colorHlpr){
	this.rgb = [];
	this.hsv = [];
	this.listeners = [];
	this.colorHelper = colorHlpr ? colorHlpr : new colorHelper();
}
rgbColorModel.prototype = {
	setRGBHex: function(rgbhex){
		var colorHelper = this.colorHelper, rgb=[];
		colorHelper.hex2rgb(rgbhex, rgb);
		this.setRGB(rgb[0],rgb[1],rgb[2]);
	},
	setRGB: function(r,g,b){
		var undef, event=0, rgb=this.rgb, hsv=this.hsv,
			colorHelper = this.colorHelper, hex,
			newhsv = [];
		if ((r !== undef) && (r != null) && (r != rgb[0])){
			if ((r >= 0) && (r <= 255)){
				rgb[0] = r;
				event |= rgbEventDef.R;
			}
		}
		if ((g !== undef) && (g != null) && (g != rgb[1])){
			if ((g >= 0) && (g <= 255)){
				rgb[1] = g;
				event |= rgbEventDef.G;
			}
		}
		if ((b !== undef) && (b != null) && (b != rgb[2])){
			if ((b >= 0) && (b <= 255)){
				rgb[2] = b;
				event |= rgbEventDef.B;
			}
		}
		if (event > 0){
			colorHelper.rgb2hsv(rgb, newhsv);
			hex = colorHelper.rgb2hex(rgb);
			//if (window.console) console.log('rgb', rgb, 'event', event, 'hex', hex);
			if (newhsv[0]!=hsv[0]){
				event |= rgbEventDef.H;
			}
			if (newhsv[1]!=hsv[1]){
				event |= rgbEventDef.S;
			}
			if (newhsv[2]!=hsv[2]){
				event |= rgbEventDef.V;
			}
			this.hsv = newhsv;
			this.triggerEvent(event, rgb, this.hsv, hex, colorHelper);
		}
	},
	setHSV: function(h,s,v){
		var undef, event=0, rgb=this.rgb, hsv=this.hsv,
			colorHelper = this.colorHelper, hex,
			newrgb = [];
		if ((h !== undef) && (h != null) && (h != hsv[0])){
			if ((h >= 0) && (h <= 360)){
				hsv[0] = h;
				event |= rgbEventDef.H;
			}
		}
		if ((s !== undef) && (s != null) && (s != hsv[1])){
			if ((s >= 0) && (s <= 100)){
				hsv[1] = s;
				event |= rgbEventDef.S;
			}
		}
		if ((v !== undef) && (v != null) && (v != hsv[2])){
			if ((v >= 0) && (v <= 100)){
				hsv[2] = v;
				event |= rgbEventDef.V;
			}
		}
		if (event > 0){
			//if (window.console) console.log('rgb', rgb, 'event', event, 'hex', hex);
			colorHelper.hsv2rgb(hsv, newrgb);
			hex = colorHelper.rgb2hex(newrgb);
			if (newrgb[0]!=rgb[0]){
				event |= rgbEventDef.R;
			}
			if (newrgb[1]!=rgb[1]){
				event |= rgbEventDef.G;
			}
			if (newrgb[2]!=rgb[2]){
				event |= rgbEventDef.B;
			}
			this.rgb = newrgb;
			this.triggerEvent(event,this.rgb, hsv, hex, colorHelper);
		}
	},
	triggerEvent: function(eventProps, rgb, hsv, hex, colorHelper){
		var evt = new rgbEvent(eventProps, rgb, hsv, hex, colorHelper),
			listeners = this.listeners,
			listenerCount = listeners.length;
		for (var listenerIdx = 0; listenerIdx < listenerCount; listenerIdx++){
			var listener = listeners[listenerIdx];
			if (listener){
				listener(evt);
			}
		}
	},
	addEventListener: function(listener){
		this.listeners.push(listener);
	}
}

function colorHelper(){
}
colorHelper.prototype ={
	hexChars: '0123456789ABCDEF',
	hue2rgb:function(p, q, t){
		if(t < 0) t += 1;
		if(t > 1) t -= 1;
		if(t < 1/6) return p + (q - p) * 6 * t;
		if(t < 1/2) return q;
		if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
		return p;
	},
	rgb2hsl: function(rgb, hsl) {
		var r = rgb[0]/255, g = rgb[1]/255, b = rgb[2]/255;
		var max = Math.max(r, g, b), min = Math.min(r, g, b);
		var h, s, l = (max + min) / 2;

		if(max == min){
			h = s = 0; // achromatic
		}else{
			var d = max - min;
			s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
			switch(max){
				case r: h = (g - b) / d + (g < b ? 6 : 0); break;
				case g: h = (b - r) / d + 2; break;
				case b: h = (r - g) / d + 4; break;
			}
			h /= 6;
		}
		hsl[0]=h;
		hsl[1]=s;
		hsl[2]=l;
	},
	hsl2rgb: function(hsl,rgb) {
		var r, g, b, h=hsl[0], s=hsl[1], l=hsl[2];
		if(s == 0){
			r = g = b = l; // achromatic
		}else{
			var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
			var p = 2 * l - q;
			r = this.hue2rgb(p, q, h + 1/3);
			g = this.hue2rgb(p, q, h);
			b = this.hue2rgb(p, q, h - 1/3);
		}
		rgb[0]= Math.floor(r * 255);
		rgb[1]= Math.floor(g * 255);
		rgb[2]= Math.floor(b * 255);
	},
	hsv2rgb: function(hsv, rgb){
		var F, R, B, G, H=hsv[0]/360, S=hsv[1]/100, V=hsv[2]/100;
		if(S>0) {
			if(H>=1) H=0;
			H=6*H;
			F=H-Math.floor(H);
			A=Math.round(255*V*(1-S));
			B=Math.round(255*V*(1-(S*F)));
			C=Math.round(255*V*(1-(S*(1-F))));
			V=Math.round(255*V);
			switch(Math.floor(H)) {
				case 0: R=V; G=C; B=A; break;
				case 1: R=B; G=V; B=A; break;
				case 2: R=A; G=V; B=C; break;
				case 3: R=A; G=B; B=V; break;
				case 4: R=C; G=A; B=V; break;
				case 5: R=V; G=A; B=B; break;
			}
			rgb[0]=R?R:0;
			rgb[1]=G?G:0;
			rgb[2]=B?B:0;
			//return([R?R:0,G?G:0,B?B:0]);
		} else {
			//return([(V=Math.round(V*255)),V,V]);
			rgb[0]=(V=Math.round(V*255));
			rgb[1]=V;
			rgb[2]=V;
		}
	},
	rgb2hsv: function(rgb, hsv){
		var max=Math.max(rgb[0],rgb[1],rgb[2]), delta=max-Math.min(rgb[0],rgb[1],rgb[2]);
		if(max!=0) {
			hsv[1]=Math.round(delta/max*100);
			if(rgb[0]==max) hsv[0]=(rgb[1]-rgb[2])/delta;
			else if(rgb[1]==max) hsv[0]=2+(rgb[2]-rgb[0])/delta;
			else if(rgb[2]==max) hsv[0]=4+(rgb[0]-rgb[1])/delta;
			hsv[0]=Math.min(Math.round(hsv[0]*60),360);
			if(hsv[0]<0) hsv[0]+=360;
		}
		hsv[0] = hsv[0] ? hsv[0] : 0;
		hsv[1] = hsv[1] ? hsv[1] : 0;
		hsv[2] = Math.round((max/255)*100);
	},
	toHex: function(v){
		v=Math.round(Math.min(Math.max(0,v),255));
		return(this.hexChars.charAt((v-v%16)/16)+this.hexChars.charAt(v%16));
	},
	rgb2hex: function(rgb){
		return this.toHex(rgb[0]) + this.toHex(rgb[1]) + this.toHex(rgb[2]);
	},
	hex2rgb: function(hex,rgb){
		if (!hex){
			return null;
		}
		var len = hex.length;
		if (len == 7) {
			rgb[0] = hex.substr(1, 2);
			rgb[1] = hex.substr(3, 2);
			rgb[2] = hex.substr(5, 2);
		} else if (len == 6) {
			rgb[0] = hex.substr(0, 2);
			rgb[1] = hex.substr(2, 2);
			rgb[2] = hex.substr(4, 2);
		} else if (len == 4) {
			rgb[0] = hex.substr(1, 2);
			rgb[1] = hex.substr(3, 2);
			rgb[2] = hex.substr(5, 2);
		} else if (len == 3) {
			rgb[0] = hex.substr(0, 1);
			rgb[1] = hex.substr(1, 1);
			rgb[2] = hex.substr(2, 1);
		} else {
			rgb[0] = rgb[1] = rgb[2] = '255';
		}
		rgb[0] = parseInt(rgb[0],16);
		rgb[1] = parseInt(rgb[1],16);
		rgb[2] = parseInt(rgb[2],16);
	},
	hsv2hex: function(hsv){
		var hex=[];
		this.hsv2rgb(hsv, hex);
		return this.rgb2hex(hex);
	},
	hsv2purehsv: function(hsv, purehsv){
		purehsv[0]= hsv[0];
		purehsv[1]= 100;
		purehsv[2]= 100;
	}
}

function createColorPickerUI(model, parent){

	parent.append('<div class="colorpicker"><div class="sliders"><div class="hsvcontainer"><div class="hsvBg"></div><div class="hsvCursor"></div></div><div class="hueSlider"></div><div class="satSlider"></div><div class="valSlider"></div></div><div class="labels"><div class="HEX">HEX</div><div class="R">R</div><div class="G">G</div><div class="B">B</div><div class="H">H</div><div class="S">S</div><div class="V">V</div></div><div class="inputs"><input name="HEX" type="text" class="HEX ui-widget-content ui-corner-all" maxlength="6"><input name="R" type="text" class="R ui-widget-content ui-corner-all" maxlength="3"><input name="G" type="text" class="G ui-widget-content ui-corner-all" maxlength="3"><input name="B" type="text" class="B ui-widget-content ui-corner-all" maxlength="3"><input name="H" type="text" class="H ui-widget-content ui-corner-all" maxlength="3"><input name="S" type="text" class="S ui-widget-content ui-corner-all" maxlength="3"><input name="V" type="text" class="V ui-widget-content ui-corner-all" maxlength="3"></div></div>');

	var shared={},
		colorPicker = $('.colorpicker', parent),
		hsvBg = $('.hsvBg', colorPicker),
		hsvCursor = $('.hsvCursor', colorPicker),
		hexInput = $('.HEX', colorPicker),
		bgCntrl = $('.hsvBg, .satSlider, .valSlider', colorPicker),
		sliders = [$('.hueSlider', colorPicker), $('.satSlider', colorPicker), $('.valSlider', colorPicker)],
		hsvInputs = [$('input.H', colorPicker),$('input.S', colorPicker),$('input.V', colorPicker)],
		rgbInputs = [$('input.R', colorPicker),$('input.G', colorPicker),$('input.B', colorPicker)];

	function modelValueChanged(event){
		var prop = event.properties,
			rgb = event.rgb,
			hsv = event.hsv,
			hex = event.hex,
			value,
			updateR = (prop & rgbEventDef.R),
			updateG = (prop & rgbEventDef.G),
			updateB = (prop & rgbEventDef.B),
			updateH = (prop & rgbEventDef.H),
			updateS = (prop & rgbEventDef.S),
			updateV = (prop & rgbEventDef.V);
		//if (window.console) console.log(arguments.callee);
		if (updateR || updateG || updateB){
			hexInput.val(hex);
		}
		if (updateR){
			rgbInputs[0].val(rgb[0]);
		}
		if (updateG){
			rgbInputs[1].val(rgb[1]);
		}
		if (updateB){
			rgbInputs[2].val(rgb[2]);
		}
		if (updateH){
			hsvInputs[0].val(hsv[0]);
			if (!shared.fromSlide){
				sliders[0].slider('value', 360-hsv[0]);
			}
			var purehsv = [],colorHelper = event.colorHelper;
			colorHelper.hsv2purehsv(hsv, purehsv);
			bgCntrl.css('background-color', '#' + colorHelper.hsv2hex(purehsv));
		}
		if (updateS){
			hsvInputs[1].val(hsv[1]);
			if (!shared.fromSlide){
				sliders[1].slider('value', 100-hsv[1]);
			}
		}
		if (updateV){
			hsvInputs[2].val(hsv[2]);
			if (!shared.fromSlide){
				sliders[2].slider('value', hsv[2]);
			}
		}
		if ((updateS || updateV) && !shared.fromSVDrag){
			hsvCursor.css({	'left': Math.floor(hsv[1] * 1.49) + 'px',
							'top': Math.floor((100-hsv[2]) * 1.49) + 'px'});
		}
		if (false && window.console){
			var modified=[];
			if (updateR){
				modified.push('modelValueChanged R:'); 
				modified.push(rgb[0]);
			}
			if (updateG){
				modified.push('modelValueChanged G:');
				modified.push(rgb[1]);
			}
			if (updateB){
				modified.push('modelValueChanged B:');
				modified.push(rgb[2]);
			}
			if (updateH){
				modified.push('modelValueChanged H:');
				modified.push(hsv[0]);
			}
			if (updateS){
				modified.push('modelValueChanged S:');
				modified.push(hsv[1]);
			}
			if (updateV){
				modified.push('modelValueChanged V:');
				modified.push(hsv[2]);
			}
			if (modified.length > 0){
				console.log(modified);
			}
		}
	}

	model.addEventListener(modelValueChanged);

	function drag(left, top){
		//if (window.console) console.log('drag(left, top)');
		model.setHSV(null, Math.floor(left / 1.49), 100 - Math.floor(top / 1.49));
	}

	function svDrag(event, ui){
		//if (window.console) console.log('svDrag()');
		var position = ui.position;
		shared.fromSVDrag = true;
		drag(position.left, position.top);
		shared.fromSVDrag = false;
	}

	hsvCursor.draggable({ drag: svDrag, containment: $('.hsvcontainer',colorPicker), scroll: false });
	hsvBg.click(function(event){
		//if (window.console) console.log('hsvBg.click()');
		var originalEvent = event.originalEvent,
			left = originalEvent.layerX || originalEvent.offsetX,
			top = originalEvent.layerY || originalEvent.offsetY;
		if (left && top){
			drag(left, top);
		}
	});

	function hueSlide(event, ui){
		//if (window.console) console.log('hueSlide()');
		shared.fromSlide = true;
		model.setHSV(360-ui.value);
		shared.fromSlide = false;
	}
	function satSlide(event, ui){
		//if (window.console) console.log('satSlide()');
		shared.fromSlide = true;
		model.setHSV(null, 100-ui.value);
		shared.fromSlide = false;
	}
	function valSlide(event, ui){
		//if (window.console) console.log('valSlide()');
		shared.fromSlide = true;
		model.setHSV(null, null, ui.value);
		shared.fromSlide = false;
	}

	sliders[0].slider({orientation: 'vertical', slide: hueSlide, /*start: colorSlide, stop: colorSlide,*/ min:0, max:360});
	sliders[1].slider({orientation: 'vertical', slide: satSlide, /*start: colorSlide, stop: colorSlide,*/ min:0, max:100});
	sliders[2].slider({orientation: 'vertical', slide: valSlide, /*start: colorSlide, stop: colorSlide,*/ min:0, max:100});

	function handleInputChange(){
		if (!shared.inputchange){
			return;
		}
		//if (window.console) console.log('handleInputChange()');
		var inputData = shared.inputchange,
			ctrl = inputData.ctrl,
			name = inputData.name,
			value = ctrl.val();
		if (name == 'HEX'){
			if (value.length == 6){
				//if (window.console) console.log('keydown from HEX');
				model.setRGBHex(value);
			}
		} else if (name == 'R'){
			if (value.length > 0){
				model.setRGB(parseInt(value));
			}
		} else if (name == 'G'){
			if (value.length > 0){
				model.setRGB(null, parseInt(value));
			}
		} else if (name == 'B'){
			if (value.length > 0){
				model.setRGB(null, null, parseInt(value));
			}
		} else if (name == 'H'){
			if (value.length > 0){
				model.setHSV(parseInt(value));
			}
		} else if (name == 'S'){
			if (value.length > 0){
				model.setHSV(null, parseInt(value));
			}
		} else if (name == 'V'){
			if (value.length > 0){
				model.setHSV(null, null, parseInt(value));
			}
		}
		//shared.inputchange = null;
	}

	function keydown(event){
		var ctrl = $(event.target),
			name = ctrl.attr('name'),
			allowedChars = '0123456789',
			keyCode = event.keyCode,
			alphaNum='0123456789ABCDEF',
			specialChars={0:true,8:true,9:false,13:false,16:false,17:true,18:false,20:false,27:false,35:false,36:false,37:false,38:false,39:false,40:false,45:false,46:true,144:false},
			allowed=false,
			charFound=null,
			change = false,
			inputchange = shared.inputchange;

		if (name == 'HEX'){
			allowedChars = alphaNum;
		}
		if ((keyCode >= 48) && (keyCode <=57)){
			charFound = alphaNum.charAt(keyCode-48);
		} else if ((keyCode >= 96) && (keyCode <= 105)){
			charFound = alphaNum.charAt(keyCode-96);
		} else 	if ((keyCode >= 65) && (keyCode <= 70)){
			charFound = alphaNum.charAt(keyCode-55);
		}
		if (charFound){
			change = allowed = (allowedChars.indexOf(charFound) != -1);
		} else {
			//alt: 18, arrow keys: 37-40, backspace: 8, caps lock: 20, ctrl: 17, delete: 46, end: 35, enter: 13, escape: 27, home: 36, insert: 45, num lock: 144, shift: 16, tab: 9
			change = specialChars[keyCode];
			if (change != null){
				allowed = true;
			}
		}

		if (!allowed){
			if (inputchange && inputchange.name && (inputchange.name == name)){
				switch(inputchange.keyCode){
					case 9:
					case 16:
					case 17:
						allowed = true;
						change = true;
						break;
				}
			}
		}

		shared.inputchange = {ctrl:ctrl, name:name, keyCode:keyCode};
		if (allowed && change){
			setTimeout(handleInputChange, 50);
		}
		if (window.console) console.log('event.keyCode', event.keyCode);
		return allowed;
	}

	colorPicker.keydown(keydown);
}
