/**
 * @class ep
 * 
 */

/*
 * @copyright		© Copyright 2006-2010, epages GmbH, All Rights Reserved.
 *
 * @module			ep.colorpicker
 *
 * @based			ColorJack (http://www.colorjack.com/software/dhtml+color+picker.html) license (http://creativecommons.org/licenses/by/3.0/)
 *
 * @revision		$Revision: 1.6 $
 */

define("ep/colorpicker", [
	"jquery",
	"ep",

	"ep/color"
], function ($, ep) {

	/**
	 * Create a canvas color picker.
	 * 
	 * Create a "canvas" color picker.
	 * 
	 * The `.elem` property of an instance is the main element of the color picker.
	 * Use `.elem` to append the color picker to any DOM element.
	 * 
	 * ### Examples
	 * Create a color picker and change body background on select color.
	 * 
	 * JavaScript:
	 * 
	 *     var picker = new ep.Colorpicker({
	 *             hex: '#330099',
	 *             callback: function( color ){
	 *                 $('body').css( 'background-color', '#'+color );
	 *             }
	 *         });
	 *     
	 *     $('body').append(picker.elem);
	 * 
	 * 
	 * ### Dependencies
	 * 
	 *  + `ep`
	 *  + `ep.color`
	 * 
	 * @param {Object} [options] A map of additional options pass to the method.
	 * @param {String} [options.hex] A hex formated startup color.
	 * @param {Integer} [options.size] Size of the main color select field.
	 * @param {Integer} [options.margin] Margin around the color select fields.
	 * @param {Function} [options.callback] A function to be called after each step of the color selection. Receives the selected color as argument: `callback( color )`.
	 * 
	 * @method Colorpicker
	 * @static 
	 * @member ep
	 * 
	 * @since 6.12.0
	 */
	ep.Colorpicker = function( options ){
		/// loading properties
		var self = this,
			o = self.options = $.extend({
				hex:	'#ffffff',	// default color
				size:	200,		// size of colorpicker
				margin:	10			// margin around colorpicker
			},options);

		// public
		self.setHex = function( color, redraw ){
			var hsb = $.extend( {h:0,s:0,b:100}, ep.color.hexToHsb(color) );
			if( hsb ){
				self.options.hex	= color;
				self.hue			= hsb.h;
				self.sat			= hsb.s;
				self.lum			= hsb.b;
				if( redraw ){
					self.drawSample( true );
				}
			}
		};

		self.setHex( self.options.hex );
		self.size		= o.size;
		self.margin		= o.margin;
		self.offset		= self.margin / 2;
		self.hueWidth	= 30;

		/// creating media-resources
		var arrows = document.createElement("canvas");
		arrows.width = 40;
		arrows.height = 5;
		arrows.distance = 6;
		(function () { // creating arrows
			var ctx = arrows.getContext("2d");
			var width = 3;
			var height = 5;
			var size = 9;
			var top = -size / 4;
			var left = 1;
			for (var n = 0; n < 20; n++) { // multiply anti-aliasing
				ctx.beginPath();
				ctx.fillStyle = "#000";
				ctx.moveTo(left + size / 4, size / 2 + top);
				ctx.lineTo(left, size / 4 + top);
				ctx.lineTo(left, size / 4 * 3 + top);
				ctx.fill();
			}
			ctx.translate(width, height);
			ctx.rotate(180 * Math.PI / 180); // rotate arrows
			ctx.drawImage(arrows, -29, 0);
			ctx.translate(-width, -height);
		})();

		var circle = document.createElement("canvas");
		circle.width = 10;
		circle.height = 10;
		(function () { // creating circle-selection
			var ctx = circle.getContext("2d");
			ctx.lineWidth = 1;
			ctx.beginPath();
			var x = circle.width / 2;
			var y = circle.width / 2;
			ctx.arc(x, y, 4.5, 0, Math.PI * 2, true);
			ctx.strokeStyle = '#000';
			ctx.stroke();
			ctx.beginPath();
			ctx.arc(x, y, 3.5, 0, Math.PI * 2, true);
			ctx.strokeStyle = '#FFF';
			ctx.stroke();
		})();

		/// creating colorpicker sliders
		var canvas = (self.elem = $('<canvas>'))[0];
		var ctx = canvas.getContext("2d");
		canvas.className = 'ep-colorpicker';
		canvas.width = this.size + this.hueWidth + this.margin;
		canvas.height = this.size + this.margin;

		canvas.onmousedown = function (e) {
			var down = (e.type == "mousedown");
			var offset = self.margin / 2;
			var abs = abPos(canvas);
			var x0 = (e.pageX - abs.x) - offset;
			var y0 = (e.pageY - abs.y) - offset;
			var x = clamp(x0, 0, canvas.width);
			var y = clamp(y0, 0, self.size);

			if (x <= self.size) { // saturation-value selection
				canvas.style.cursor = 'crosshair';
				if( down ){
					dragElement({
						type: "relative",
						event: e,
						element: canvas,
						callback: function (coords, state) {
							var x = clamp(coords.x - self.offset, 0, self.size);
							var y = clamp(coords.y - self.offset, 0, self.size);
							self.sat = x / self.size * 100; // scale saturation
							self.lum = 100 - (y / self.size * 100); // scale value
							self.drawSample();
						}
					});
				}
			} else if (x > self.size + self.margin - arrows.distance && x <= self.size + self.hueWidth + arrows.distance) { // hue + arrows selection
				canvas.style.cursor = 'crosshair';
				if( down ){
					dragElement({
						type: "relative",
						event: e,
						element: canvas,
						callback: function (coords, state) {
							var y = clamp(coords.y - self.offset, 0, self.size);
							self.hue = Math.min(1, y / self.size) * 360;
							self.drawSample();
						}
					});
				}
			} else { // margin between hue/saturation-value
				canvas.style.cursor = 'default';
			}
			return false; // prevent selection
		};

		/// helper functions
		this.drawSample = function( prevent ){
			// clearing canvas
			ctx.clearRect(0, 0, canvas.width, canvas.height);
			self.drawSquare();
			self.drawHue();
			// retrieving hex-code
			var hex = ep.color.hsbToHex({
				h: self.hue,
				s: self.sat,
				b: self.lum
			});
			// arrow-selection
			var y = (self.hue / 362) * self.size - 2;
			ctx.drawImage(arrows, self.size + self.offset + 4, Math.round(y) + self.offset);
			// circle-selection
			var x = self.sat / 100 * self.size;
			y = (1 - (self.lum / 100)) * self.size;
			x = x - circle.width / 2;
			y = y - circle.height / 2;
			ctx.drawImage(circle, Math.round(x) + self.offset, Math.round(y) + self.offset);
			// run custom code
			if( self.options.callback && !prevent ){
				self.options.callback(hex);
			}
		};

		this.drawSquare = function () {
			// retrieving hex-code
			var hex = ep.color.hsbToHex({
					h: self.hue,
					s: 100,
					b: 100
				});
			var offset = self.offset;
			var size = self.size;
			// drawing color
			ctx.fillStyle = "#" + hex;
			ctx.fillRect(offset, offset, size, size);
			// overlaying saturation
			var gradient = ctx.createLinearGradient(offset, offset, size + offset, 0);
			gradient.addColorStop(0, "rgba(255, 255, 255, 1)");
			gradient.addColorStop(1, "rgba(255, 255, 255, 0)");
			ctx.fillStyle = gradient;
			ctx.fillRect(offset, offset, size, size);
			// overlaying value
			gradient = ctx.createLinearGradient(offset, offset, 0, size + offset);
			gradient.addColorStop(0, "rgba(0, 0, 0, 0)");
			gradient.addColorStop(1, "rgba(0, 0, 0, 1)");
			ctx.fillStyle = gradient;
			ctx.fillRect(offset, offset, size, size);
			// drawing outer bounds
			ctx.strokeStyle = "rgba(255,255,255,0.15)";
			ctx.strokeRect(offset+0.5, offset+0.5, size-1, size-1);
		};

		this.drawHue = function () {
			// drawing hue selector
			var left = self.size + self.margin + self.offset;
			var gradient = ctx.createLinearGradient(0, 0, 0, self.size);
			gradient.addColorStop(0, "rgba(255, 0, 0, 1)");
			gradient.addColorStop(0.15, "rgba(255, 255, 0, 1)");
			gradient.addColorStop(0.3, "rgba(0, 255, 0, 1)");
			gradient.addColorStop(0.5, "rgba(0, 255, 255, 1)");
			gradient.addColorStop(0.65, "rgba(0, 0, 255, 1)");
			gradient.addColorStop(0.8, "rgba(255, 0, 255, 1)");
			gradient.addColorStop(1, "rgba(255, 0, 0, 1)");
			ctx.fillStyle = gradient;
			ctx.fillRect(left, self.offset, 20, self.size);
			// drawing outer bounds
			ctx.strokeStyle = "rgba(255,255,255,0.2)";
			ctx.strokeRect(left + 0.5, self.offset + 0.5, 19, self.size-1);
		};

		this.destory = function (){
			for( var key in self ){
				delete self[key];
			}
		};

		// drawing color selection
		this.drawSample(true);

		return this;
	};

	/* GLOBALS LIBRARY */

	var dragElement = function(props) {
		function mouseMove(e, state) {
			if( state === undefined ){
				state = "move";
			}
			var coord = XY(e);
			switch (props.type) {
// execute test only for "relative" because the other types aren't used in this context
//#JSCOVERAGE_IF false
				case "difference":
					props.callback({
						x: coord.x + oX - eX,
						y: coord.y + oY - eY
					}, state);
					break;
//#JSCOVERAGE_ENDIF
				case "relative":
					props.callback({
						x: coord.x - oX,
						y: coord.y - oY
					}, state);
					break;
//#JSCOVERAGE_IF false
				default: // "absolute"
					props.callback({
						x: coord.x,
						y: coord.y
					}, state);
					break;
//#JSCOVERAGE_ENDIF
			}
		}
		function mouseUp(e) {
			window.removeEventListener("mousemove", mouseMove, false);
			window.removeEventListener("mouseup", mouseUp, false);
			mouseMove(e, "up");
		}
		// current element position
		var el = props.element;
		var origin = abPos(el);
		var oX = origin.x;
		var oY = origin.y;
		// current mouse position
		var e = props.event;
		var coord = XY(e);
		var eX = coord.x;
		var eY = coord.y;
		// events
		window.addEventListener("mousemove", mouseMove, false);
		window.addEventListener("mouseup", mouseUp, false);
		mouseMove(e, "down"); // run mouse-down
	};

	var clamp = function(n, min, max) {
		return (n < min) ? min : ((n > max) ? max : n);
	};

	var XY = window.ActiveXObject ? // fix XY to work in various browsers
		function(event) {
			return {
				x: event.clientX + document.documentElement.scrollLeft,
				y: event.clientY + document.documentElement.scrollTop
			};
		} : function(event) {
			return {
				x: event.pageX,
				y: event.pageY
			};
		};

	var abPos = function(o) {
		o = typeof(o) == 'object' ? o : $(o);
		var offset = { x: 0, y: 0 };
		while(o != null) {
			offset.x += o.offsetLeft;
			offset.y += o.offsetTop;
			o = o.offsetParent;
		}
		return offset;
	};

	return ep;

});