SL.color = {};

(function() {

	SL.color.Color = new function() {

		// Adapted from http://www.easyrgb.com/math.html
		// hsv values = 0 - 1
		// rgb values 0 - 255
		this.hsv2rgb = function(h, s, v) {
			var r, g, b;
			if (s == 0) {
				r = v * 255;
				g = v * 255;
				b = v * 255;
			} else {

				// h must be < 1
				var var_h = h * 6;
				if (var_h == 6) {
					var_h = 0;
				}

				// Or ... var_i = floor( var_h )
				var var_i = Math.floor(var_h);
				var var_1 = v * (1 - s);
				var var_2 = v * (1 - s * (var_h - var_i));
				var var_3 = v * (1 - s * (1 - (var_h - var_i)));

				if (var_i == 0) {
					var_r = v;
					var_g = var_3;
					var_b = var_1;
				} else if (var_i == 1) {
					var_r = var_2;
					var_g = v;
					var_b = var_1;
				} else if (var_i == 2) {
					var_r = var_1;
					var_g = v;
					var_b = var_3;
				} else if (var_i == 3) {
					var_r = var_1;
					var_g = var_2;
					var_b = v;
				} else if (var_i == 4) {
					var_r = var_3;
					var_g = var_1;
					var_b = v;
				} else {
					var_r = v;
					var_g = var_1;
					var_b = var_2;
				}

				r = var_r * 255; // rgb results = 0 ÷ 255
				g = var_g * 255;
				b = var_b * 255;

			}
			return [ Math.round(r), Math.round(g), Math.round(b) ];
		};

		// added by Matthias Platzer AT knallgrau.at
		this.rgb2hsv = function(r, g, b) {
			r = (r / 255); // RGB values = 0 ÷ 255
			g = (g / 255);
			b = (b / 255);

			var min = Math.min(r, g, b); // Min. value of RGB
			var max = Math.max(r, g, b); // Max. value of RGB
			deltaMax = max - min; // Delta RGB value

			var v = max;
			var s, h;
			var deltaRed, deltaGreen, deltaBlue;

			if (deltaMax == 0) // This is a gray, no chroma...
			{
				h = 0; // HSV results = 0 ÷ 1
				s = 0;
			} else // Chromatic data...
			{
				s = deltaMax / max;

				deltaRed = (((max - r) / 6) + (deltaMax / 2)) / deltaMax;
				deltaGreen = (((max - g) / 6) + (deltaMax / 2)) / deltaMax;
				deltaBlue = (((max - b) / 6) + (deltaMax / 2)) / deltaMax;

				if (r == max) {
					h = deltaBlue - deltaGreen;
				} else if (g == max) {
					h = (1 / 3) + deltaRed - deltaBlue;
				} else if (b == max) {
					h = (2 / 3) + deltaGreen - deltaRed;
				} else {
					h = 0;
				}

				if (h < 0)
					h += 1;
				if (h > 1)
					h -= 1;
			}

			return [ h, s, v ];
		};

		this.rgb2hex = function(r, g, b) {
			return this.toHex(r) + this.toHex(g) + this.toHex(b);
		};

		this.hexchars = '0123456789ABCDEF';

		this.toHex = function(n) {
			n = n || 0;
			n = parseInt(n, 10);
			if (isNaN(n))
				n = 0;
			n = Math.round(Math.min(Math.max(0, n), 255));

			return this.hexchars.charAt((n - n % 16) / 16) + this.hexchars.charAt(n % 16);
		};

		this.toDec = function(hexchar) {
			return this.hexchars.indexOf(hexchar.toUpperCase());
		};

		this.hex2rgb = function(str) {
			var rgb = [];
			rgb[0] = (this.toDec(str.substr(0, 1)) * 16) + this.toDec(str.substr(1, 1));
			rgb[1] = (this.toDec(str.substr(2, 1)) * 16) + this.toDec(str.substr(3, 1));
			rgb[2] = (this.toDec(str.substr(4, 1)) * 16) + this.toDec(str.substr(5, 1));
			return rgb;
		};

		this.isValidRGB = function(a) {
			if ((!a[0] && a[0] != 0) || isNaN(a[0]) || a[0] < 0 || a[0] > 255)
				return false;
			if ((!a[1] && a[1] != 0) || isNaN(a[1]) || a[1] < 0 || a[1] > 255)
				return false;
			if ((!a[2] && a[2] != 0) || isNaN(a[2]) || a[2] < 0 || a[2] > 255)
				return false;

			return true;
		};
	},

	SL.color.Picker = Class.create(SL.Component, {

		init : function() {

			this.field = $(this.config.get('input_id'));

			this.cb = this.config.ensureHash('cb');

			if (this.config.get('admin')) {
				this.e.setStyle({
					zIndex : (SL.admin._overlayIndex + 1) + ''
				});
			} else {
				this.e.setStyle({
					zIndex : SL.ui._getNewOverlayIndex() + ''
				});
			}

			this.connectDestroy(this.field);

			this.rgb = {};
			this.hsv = {};

			this.shown = false;

			SL.utils.reparentToBody(this.e);

			this.pickerArea = this.e.down('.gui_color_picker_view');
			this.selector = this.e.down('.gui_color_picker_selector');

			this.picker = new SL.dnd.Draggable(this.selector, {
				orientation : 'both',
				maxY : -21,
				maxX : -21,
				cb : {
					moved : function(picker) {
						var pos = picker.getPos();
						this.update(pos.left, pos.top, true);
					}.bind(this)
				}
			});

			this.thumb = this.e.down('.gui_color_picker_hue_thumb');
			this.slider = this.e.down('.gui_color_picker_hue_slider');

			this.thumbDnD = new SL.dnd.Draggable(this.thumb, {
				orientation : 'vertical',
				minY : -10,
				maxY : -20,
				cb : {
					moved : this.updateHue.bind(this, true)
				}
			});

			this.field.observe("click", this.toggle.bind(this));
			new Form.Element.Observer(this.field, 0.2, this.updateFromFieldValue.bind(this));

			this.slider.observe('click', this.onSliderClick.bind(this));

			this.updateInputColor();
		},

		setCB : function(cb) {
			this.cb = cb;
		},

		toggle : function() {
			if (!this.shown) {
				this.show();
			}
		},

		show : function() {
			this.shown = true;

			this.e.show();

			this.updatePosition(true);

			this.updateFromFieldValue(null, true);

			this.pickerArea.observe('mousedown', this.updateSelector.bind(this));

			this.bodyCB = this.onBodyClicked.bind(this);
			(function() {
				$$('body')[0].observe('click', this.bodyCB);
			}).bind(this).defer();
		},

		updatePosition : function(first) {

			if (!this.shown) {
				if (this.updateCB) {
					clearTimeout(this.updateCB);
				}
				return;
			}

			if (first && this.updateCB) {
				clearTimeout(this.updateCB);
			}

			var offset = this.field.cumulativeOffset();
			offset.left += this.field.getWidth() + 5;
			var fixedElement = this.field.getFixedParent();

			if (fixedElement) {
				var fixedScrollOffset = this.field.cumulativeScrollOffsetFixed();
				offset.left -= fixedScrollOffset.left;
				offset.top -= fixedScrollOffset.top;
			}

			var parentScrollOffset = this.field.cumulativeScrollOffset();
			offset.left -= parentScrollOffset.left;
			offset.right -= parentScrollOffset.right;

			var scrollOffsets = document.viewport.getScrollOffsets();

			if (fixedElement) {
				offset.top += scrollOffsets.top;
			}

			this.e.setStyle({
				left : offset.left + 'px',
				top : offset.top + 'px'
			});

			this.updateCB = setTimeout(this.updatePosition.bind(this), 250);
		},

		hide : function() {
			this.shown = false;
			this.e.hide();

			if (this.bodyCB) {
				$$('body')[0].stopObserving('click', this.bodyCB);
				this.bodyCB = undefined;
			}
		},

		updateHue : function(updateInput) {

			var h = this.getHue();

			var rgb = SL.color.Color.hsv2rgb(h, 1, 1);

			if (!SL.color.Color.isValidRGB(rgb)) {
				return;
			}

			this.pickerArea.setStyle({
				backgroundColor : 'rgb(' + rgb[0] + ',' + rgb[1] + ',' + rgb[2] + ')'
			});

			this.update(undefined, undefined, updateInput);
		},

		updateFromFieldValue : function(event, keepInitial) {

			var value = this.field.value;

			if (this.lastValue && this.lastValue == value) {
				return;
			}

			if (keepInitial) {
				this.initialValue = value;
			}

			if (!value) {
				value = 'ffffff';
			} else if (value.startsWith('#')) {
				value = value.substring(1);
			}

			if (value.length != 3 && value.length != 6) {
				this.field.setStyle({
					backgroundColor : '#ffffff',
					color : '#000000'
				});
				return;
			}

			if (value.length == 3) {
				value = value.substring(0, 1) + value.substring(0, 1) + value.substring(1, 2) + value.substring(1, 2) + value.substring(2, 3) + value.substring(2, 3);
			}

			var rgb = SL.color.Color.hex2rgb(value);

			if (!SL.color.Color.isValidRGB(rgb)) {
				this.field.setStyle({
					backgroundColor : '#ffffff',
					color : '#000000'
				});
				return;
			}

			var hsv = SL.color.Color.rgb2hsv(rgb[0], rgb[1], rgb[2]);

			this.selector.setStyle({
				left : Math.round(hsv[1] * this.pickerArea.offsetWidth) + 'px',
				top : Math.round((1 - hsv[2]) * this.pickerArea.offsetWidth) + 'px'
			});

			this.thumb.setStyle({
				top : (((hsv[0] == 0 ? 0 : 1 - hsv[0]) * this.slider.getHeight()) - this.thumb.getHeight() / 2) + 'px'
			});

			this.field.setStyle({
				backgroundColor : 'rgb(' + rgb[0] + ', ' + rgb[1] + ', ' + rgb[2] + ')',
				color : (hsv[2] > 0.65) ? '#000000' : '#FFFFFF'
			});

			if (event) {
				this.sendAsyncUpdate();
			}

			this.updateHue(false);
		},

		updateSelector : function(event) {

			var xPos = event.pointerX();
			var yPos = event.pointerY();

			var pos = this.pickerArea.down().cumulativeOffset();

			this.selector.setStyle({
				left : (xPos - pos[0] - 2) + 'px',
				top : (yPos - pos[1] - 2) + 'px'
			});

			var x = (xPos - pos[0]);
			var y = (yPos - pos[1]);

			this.picker.onMouseDown(event);

			this.update(x, y, true);
		},

		updateInputColor : function() {

			var value = this.field.value;
			if (!value) {
				value = 'ffffff';
			} else if (value.startsWith('#')) {
				value = value.substring(1);
			}

			if (value.length != 3 && value.length != 6) {
				value = 'ffffff';
			}

			if (value.length == 3) {
				value = value.substring(0, 1) + value.substring(0, 1) + value.substring(1, 2) + value.substring(1, 2) + value.substring(2, 3) + value.substring(2, 3);
			}

			var rgb = SL.color.Color.hex2rgb(value);

			if (!SL.color.Color.isValidRGB(rgb)) {
				return;
			}

			var hsv = SL.color.Color.rgb2hsv(rgb[0], rgb[1], rgb[2]);

			this.field.setStyle({
				backgroundColor : 'rgb(' + rgb[0] + ', ' + rgb[1] + ', ' + rgb[2] + ')',
				color : (hsv[2] > 0.65) ? '#000000' : '#FFFFFF'
			});
		},

		getHue : function() {

			var h = 1.0 - (this.thumb.positionedOffset().top + this.thumb.getHeight() / 2) / this.slider.getHeight();

			if (h >= 1) {
				h = 0;
			} else if (h < 0) {
				h = 0;
			}
			return h;
		},

		update : function(x, y, updateInput) {

			if (!x) {
				x = this.picker.getPos().left;
			}

			if (!y) {
				y = this.picker.getPos().top;
			}

			var h = this.getHue();

			this.hsv = {
				hue : h,
				saturation : x / this.pickerArea.offsetWidth,
				brightness : (this.pickerArea.offsetHeight - y) / this.pickerArea.offsetHeight
			};

			var rgb = SL.color.Color.hsv2rgb(this.hsv.hue, this.hsv.saturation, this.hsv.brightness);

			this.rgb = {
				red : rgb[0],
				green : rgb[1],
				blue : rgb[2]
			};

			if (updateInput) {
				this.lastValue = (this.config.get('with_prefix') ? '#' : '') + SL.color.Color.rgb2hex(rgb[0], rgb[1], rgb[2]);
				this.field.value = this.lastValue;
				if (this.cb.changed) {
					this.cb.changed(this, this.field.value);
				}
				this.sendAsyncUpdate();
			}

			this.updateInputColor();
		},

		onBodyClicked : function(e) {
			if (this.shown) {
				var offset = this.e.cumulativeOffset();

				var x = e.pageX;
				var left = offset.left;
				var w = this.e.offsetWidth;
				var y = e.pageY;
				var top = offset.top;
				var h = this.e.offsetHeight;

				if (x > left && x < left + w && y > top && y < top + h) {
					return;
				}

				var offset = this.field.cumulativeOffset();

				var x = e.pageX;
				var left = offset.left;
				var w = this.field.offsetWidth;
				var y = e.pageY;
				var top = offset.top;
				var h = this.field.offsetHeight;

				if (x > left && x < left + w && y > top && y < top + h) {
					return;
				}

				this.hide();
			}
		},

		onSliderClick : function(event) {

			var pos = event.pointerY();
			pos = pos - this.slider.cumulativeOffset().top - 10;

			this.thumbDnD.updatePos(undefined, pos);

			event.stop();
		},

		sendAsyncUpdate : function() {

			if (!this.config.get('update_url')) {
				return;
			}

			if (this.asyncUpdate) {
				clearTimeout(this.asyncUpdate);
			}

			var url = this.config.get('update_url');

			v = this.field.value;
			if (v.startsWith('#')) {
				v = v.substring(1);
			}

			url = url.replace("@value@", v);

			this.asyncUpdate = setTimeout(function() {
				new Ajax.Request(url, {
					method : 'get',
					onSuccess : function(o) {
						eval(o.responseText);
						this.asyncUpdate = undefined;
					}.bind(this)
				});
			}, 250);
		}
	});

})();

