define(function(){

	var Events = {
		RGB_UPDATED : 'RGBUpdated',
		HSL_UPDATED : 'HSLUpdated',
		HSV_UPDATED : 'HSVUpdated',
		HEX_UPDATED : 'HexUpdated',
		INT_UPDATED : 'IntUpdated',
		UPDATED : 'updated'
	};

	var namedColors = {
		'transparent':'rgba(0, 0, 0, 0)','aliceblue':'#F0F8FF','antiquewhite':'#FAEBD7','aqua':'#00FFFF','aquamarine':'#7FFFD4',
		'azure':'#F0FFFF','beige':'#F5F5DC','bisque':'#FFE4C4','black':'#000000','blanchedalmond':'#FFEBCD','blue':'#0000FF','blueviolet':'#8A2BE2',
		'brown':'#A52A2A','burlywood':'#DEB887','cadetblue':'#5F9EA0','chartreuse':'#7FFF00','chocolate':'#D2691E','coral':'#FF7F50',
		'cornflowerblue':'#6495ED','cornsilk':'#FFF8DC','crimson':'#DC143C','cyan':'#00FFFF','darkblue':'#00008B','darkcyan':'#008B8B','darkgoldenrod':'#B8860B',
		'darkgray':'#A9A9A9','darkgrey':'#A9A9A9','darkgreen':'#006400','darkkhaki':'#BDB76B','darkmagenta':'#8B008B','darkolivegreen':'#556B2F',
		'darkorange':'#FF8C00','darkorchid':'#9932CC','darkred':'#8B0000','darksalmon':'#E9967A','darkseagreen':'#8FBC8F','darkslateblue':'#483D8B',
		'darkslategray':'#2F4F4F','darkslategrey':'#2F4F4F','darkturquoise':'#00CED1','darkviolet':'#9400D3','deeppink':'#FF1493','deepskyblue':'#00BFFF',
		'dimgray':'#696969','dimgrey':'#696969','dodgerblue':'#1E90FF','firebrick':'#B22222','floralwhite':'#FFFAF0','forestgreen':'#228B22',
		'fuchsia':'#FF00FF','gainsboro':'#DCDCDC','ghostwhite':'#F8F8FF','gold':'#FFD700','goldenrod':'#DAA520','gray':'#808080','grey':'#808080',
		'green':'#008000','greenyellow':'#ADFF2F','honeydew':'#F0FFF0','hotpink':'#FF69B4','indianred':'#CD5C5C','indigo':'#4B0082','ivory':'#FFFFF0',
		'khaki':'#F0E68C','lavender':'#E6E6FA','lavenderblush':'#FFF0F5','lawngreen':'#7CFC00','lemonchiffon':'#FFFACD','lightblue':'#ADD8E6',
		'lightcoral':'#F08080','lightcyan':'#E0FFFF','lightgoldenrodyellow':'#FAFAD2','lightgray':'#D3D3D3','lightgrey':'#D3D3D3','lightgreen':'#90EE90',
		'lightpink':'#FFB6C1','lightsalmon':'#FFA07A','lightseagreen':'#20B2AA','lightskyblue':'#87CEFA','lightslategray':'#778899',
		'lightslategrey':'#778899','lightsteelblue':'#B0C4DE','lightyellow':'#FFFFE0','lime':'#00FF00','limegreen':'#32CD32','linen':'#FAF0E6',
		'magenta':'#FF00FF','maroon':'#800000','mediumaquamarine':'#66CDAA','mediumblue':'#0000CD','mediumorchid':'#BA55D3','mediumpurple':'#9370D8',
		'mediumseagreen':'#3CB371','mediumslateblue':'#7B68EE','mediumspringgreen':'#00FA9A','mediumturquoise':'#48D1CC','mediumvioletred':'#C71585',
		'midnightblue':'#191970','mintcream':'#F5FFFA','mistyrose':'#FFE4E1','moccasin':'#FFE4B5','navajowhite':'#FFDEAD','navy':'#000080','oldlace':'#FDF5E6',
		'olive':'#808000','olivedrab':'#6B8E23','orange':'#FFA500','orangered':'#FF4500','orchid':'#DA70D6','palegoldenrod':'#EEE8AA',
		'palegreen':'#98FB98','paleturquoise':'#AFEEEE','palevioletred':'#D87093','papayawhip':'#FFEFD5','peachpuff':'#FFDAB9','peru':'#CD853F',
		'pink':'#FFC0CB','plum':'#DDA0DD','powderblue':'#B0E0E6','purple':'#800080','red':'#FF0000','rosybrown':'#BC8F8F','royalblue':'#4169E1',
		'saddlebrown':'#8B4513','salmon':'#FA8072','sandybrown':'#F4A460','seagreen':'#2E8B57','seashell':'#FFF5EE','sienna':'#A0522D','silver':'#C0C0C0',
		'skyblue':'#87CEEB','slateblue':'#6A5ACD','slategray':'#708090','slategrey':'#708090','snow':'#FFFAFA','springgreen':'#00FF7F','yellow':'#FFFF00',
		'steelblue':'#4682B4','tan':'#D2B48C','teal':'#008080','thistle':'#D8BFD8','tomato':'#FF6347','turquoise':'#40E0D0','violet':'#EE82EE'
	};
	
	var absround = function(number){
		return (0.5 + number) << 0;
	};

	var hue2rgb = function(a, b, c) {  
		if(c < 0) c += 1;
		if(c > 1) c -= 1;
		if(c < 1/6) return a + (b - a) * 6 * c;
		if(c < 1/2) return b;
		if(c < 2/3) return a + (b - a) * (2/3 - c) * 6;
		return a;
	};

	var p2v = function( p ){
		if ( typeof p === 'string' || typeof p === 'String' ) { p = Number( p ) }
		return isPercent.test( p ) ? absround( parseInt( p ) * 2.55 ) : p;
	};
	
	var isHex = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i;
	var isHSL = /^hsla?\((\d{1,3}?),\s*(\d{1,3}%),\s*(\d{1,3}%)(,\s*[01]?\.?\d*)?\)$/;
	var isRGB = /^rgba?\((\d{1,3}%?),\s*(\d{1,3}%?),\s*(\d{1,3}%?)(,\s*[01]?\.?\d*)?\)$/;
	var isPercent = /^\d+(\.\d+)*%$/;
	var isString = function( s ) {
        return ( typeof( s ) === 'string' || s instanceof String );
    }

	var hexBit = /([0-9a-f])/gi;
	var leadHex = /^#/;

	var matchHSL = /^hsla?\((\d{1,3}),\s*(\d{1,3})%,\s*(\d{1,3})%(,\s*([01]?\.?\d*))?\)$/;
	var matchRGB = /^rgba?\((\d{1,3}%?),\s*(\d{1,3}%?),\s*(\d{1,3}%?)(,\s*([01]?\.?\d*))?\)$/;
	
	function Color(value){

		this._listeners = {};

		this.subscribe(Events.RGB_UPDATED, this._RGBUpdated);
		this.subscribe(Events.HEX_UPDATED, this._HEXUpdated);
		this.subscribe(Events.HSL_UPDATED, this._HSLUpdated);
		this.subscribe(Events.HSV_UPDATED, this._HSVUpdated);
		this.subscribe(Events.INT_UPDATED, this._INTUpdated);
		
		this.parse(value);

	};

	Color.prototype._decimal = 0;  
	Color.prototype._hex = '#000000';  
	Color.prototype._red = 0;  
	Color.prototype._green = 0;  
	Color.prototype._blue = 0;  
	Color.prototype._hue = 0;  
	Color.prototype._saturation = 0;  
	Color.prototype._lightness = 0;  
	Color.prototype._brightness = 0;  
	Color.prototype._alpha = 1;  	
	
	Color.prototype.parse = function(value){
		if(typeof value == 'undefined'){
			return this;
		};
		switch(true){
			case isFinite(value) :
				this.decimal(value);
				this.output = Color.INT;
				return this;
			case (value instanceof Color) :
				this.copy(value);
				return this;
			case ( isString( value ) ):
				value = value.replace( /\s/g, '' );
				switch(true){
					case (namedColors.hasOwnProperty(value)) :
						value = namedColors[value];
						var stripped = value.replace(leadHex, '');
						this.decimal(parseInt(stripped, 16));
						return this;
					case isHex.test(value) :
						var stripped = value.replace(leadHex, '');
						if(stripped.length == 3) {
							stripped = stripped.replace(hexBit, '$1$1');
						};
						this.decimal(parseInt(stripped, 16));
						return this;
					case isRGB.test(value) :
						var parts = value.match(matchRGB);
						var alphaIsValid = !( isNaN( parseFloat( parts[5] ) ) );
						this.red(p2v(parts[1]));
						this.green(p2v(parts[2]));
						this.blue(p2v(parts[3]));
						this.alpha( alphaIsValid ? parseFloat( parts[5] ) : 1 );
						this.output = alphaIsValid ? Color.RGBA : ( isPercent.test( parts[1] ) ? Color.PRGB : Color.RGB );
						return this;
					case isHSL.test(value) :  
						var parts = value.match(matchHSL);
						this.hue(parseInt(parts[1]));
						this.saturation(parseInt(parts[2]));
						this.lightness(parseInt(parts[3]));
						this.alpha( isNaN( parseFloat( parts[5] ) ) ? 1 : parseFloat( parts[5] ) );
						this.output = parts[5] ? 6: 5; 
						return this;
					default:
						console.info( "WARNING: color not parsed" );
						break;
				};				
				break;
			default : 
				switch(typeof value) {
					case 'object' :
						if ( value instanceof Array ) {
							if ( value.length == 3 ) {
								this.red( p2v( value[0] ) );
								this.green( p2v( value[1] ) );
								this.blue( p2v( value[2] ) );
								this.alpha( 1 );
								this.output = Color.RGB;
							} else if ( value.length == 4 ) {
								this.red( p2v( value[0] ) );
								this.green( p2v( value[1] ) );
								this.blue( p2v( value[2] ) );
								this.alpha( isNaN( parseFloat( value[3] ) ) ? 1 : parseFloat( value[3] ) );
								this.output = Color.RGBA;								
							}
						} else {
							this.set( value );
						}
						return this;
					case 'string' :
						value = value.replace( /\s/g, '' );
						switch(true){
							case (namedColors.hasOwnProperty(value)) :
								value = namedColors[value];
								var stripped = value.replace(leadHex, '');
								this.decimal(parseInt(stripped, 16));
								return this;
							case isHex.test(value) :
								var stripped = value.replace(leadHex, '');
								if(stripped.length == 3) {
									stripped = stripped.replace(hexBit, '$1$1');
								};
								this.decimal(parseInt(stripped, 16));
								return this;
							case isRGB.test(value) :
								var parts = value.match(matchRGB);
								var alphaIsValid = !( isNaN( parseFloat( parts[5] ) ) );
								this.red(p2v(parts[1]));
								this.green(p2v(parts[2]));
								this.blue(p2v(parts[3]));
								this.alpha( alphaIsValid ? parseFloat( parts[5] ) : 1 );
								this.output = alphaIsValid ? Color.RGBA : ( isPercent.test( parts[1] ) ? Color.PRGB : Color.RGB );
								return this;
							case isHSL.test(value) :  
								var parts = value.match(matchHSL);
								this.hue(parseInt(parts[1]));
								this.saturation(parseInt(parts[2]));
								this.lightness(parseInt(parts[3]));
								this.alpha( isNaN( parseFloat( parts[5] ) ) ? 1 : parseFloat( parts[5] ) );
								this.output = parts[5] ? 6: 5; 
								return this;
							default:
								console.info( "WARNING: color not parsed" );
								break;
						};
				};		
			
		};
		return this;
	};


	Color.prototype.clone = function(){
		return new Color(this.decimal());
	};
	
	Color.prototype.copy = function(color){
		this.set(color.decimal());
		return this;
	};
	
	Color.prototype.set = function(key, value){
		if(arguments.length == 1){
			if(typeof key == 'object'){
				for(var p in key){
					if(typeof this[p] == 'function'){
						this[p](key[p]);					
					};
				};
			} else if(isFinite(key)){
				this.decimal(key);
			}
		} else if(typeof this[key] == 'function'){
			this[key](value);
		};
		return this;
	};
	
	Color.prototype.interpolate = function(destination, factor){
		if(!(destination instanceof Color)){
			destination = new Color(destination);
		};
		this._red = absround( +(this._red) + (destination._red - this._red) * factor );
		this._green = absround( +(this._green) + (destination._green - this._green) * factor );
		this._blue = absround( +(this._blue) + (destination._blue - this._blue) * factor );
		this._alpha = absround( +(this._alpha) + (destination._alpha - this._alpha) * factor );
		this.broadcast(Events.RGB_UPDATED);
		this.broadcast(Events.UPDATED);
		return this;
	};

	Color.prototype._RGB2HSL = function(){

		var r = this._red / 255;
		var g = this._green / 255;
		var b = this._blue / 255;

		var max = Math.max(r, g, b);
		var min = Math.min(r, g, b);
		var l = (max + min) / 2;
		var v = max;

		if(max == min) {
			this._hue = 0;
			this._saturation = 0;
			this._lightness = absround(l * 100);
			this._brightness = absround(v * 100);
			return;
		};

		var d = max - min;
		var s = d / ( ( l <= 0.5) ? (max + min) : (2 - max - min) );
		var h = ((max == r)
			? (g - b) / d + (g < b ? 6 : 0)
			: (max == g)
			 ? ((b - r) / d + 2)
			 : ((r - g) / d + 4)) / 6;

		this._hue = absround(h * 360);
		this._saturation = absround(s * 100);
		this._lightness = absround(l * 100);
		this._brightness = absround(v * 100);
	};
	
	Color.prototype._HSL2RGB = function(){
		var h = this._hue / 360;
		var s = this._saturation / 100;
		var l = this._lightness / 100;
		var q = l < 0.5	? l * (1 + s) : (l + s - l * s);
		var p = 2 * l - q;
		this._red = absround(hue2rgb(p, q, h + 1/3) * 255);
		this._green = absround(hue2rgb(p, q, h) * 255);
		this._blue = absround(hue2rgb(p, q, h - 1/3) * 255);
	};
	
	Color.prototype._HSV2RGB = function(){  
		var h = this._hue / 360;
		var s = this._saturation / 100;
		var v = this._brightness / 100;
		var r = 0;
		var g = 0;
		var b = 0;
		var i = Math.floor(h * 6);
		var f = h * 6 - i;
		var p = v * (1 - s);
		var q = v * (1 - f * s);
		var t = v * (1 - (1 - f) * s);
		switch(i % 6){
			case 0 :
				r = v, g = t, b = p;
				break;
			case 1 :
				r = q, g = v, b = p;
				break;
			case 2 :
				r = p, g = v, b = t;
				break;
			case 3 :
				r = p, g = q, b = v;
				break;
			case 4 :
				r = t, g = p, b = v
				break;
			case 5 :
				r = v, g = p, b = q;
				break;
		}
		this._red = absround(r * 255);
		this._green = absround(g * 255);
		this._blue = absround(b * 255);
	};
	
	Color.prototype._INT2HEX = function(){
		var x = this._decimal.toString(16);
		x = '000000'.substr(0, 6 - x.length) + x;
		this._hex = '#' + x.toUpperCase();
	};
	
	Color.prototype._INT2RGB = function(){
		this._red = this._decimal >> 16;
		this._green = (this._decimal >> 8) & 0xFF;
		this._blue = this._decimal & 0xFF;
	};
	
	Color.prototype._HEX2INT = function(){
		this._decimal = parseInt(this._hex, 16);
	};
	
	Color.prototype._RGB2INT = function(){
		this._decimal = (this._red << 16 | (this._green << 8) & 0xffff | this._blue);
	};

	Color.prototype._RGBUpdated = function(){
		this._RGB2INT();  
		this._RGB2HSL();  
		this._INT2HEX();  
	};
	Color.prototype._HSLUpdated = function(){
		this._HSL2RGB();  
		this._RGB2INT();  
		this._INT2HEX();  
	};
	Color.prototype._HSVUpdated = function(){
		this._HSV2RGB();  
		this._RGB2INT();  
		this._INT2HEX();  
	};
	Color.prototype._HEXUpdated = function(){
		this._HEX2INT();  
		this._INT2RGB();  
		this._RGB2HSL();  
	};
	Color.prototype._INTUpdated = function(){
		this._INT2RGB();  
		this._RGB2HSL();  
		this._INT2HEX();  
	};
	
	Color.prototype._broadcastUpdate = function(){
		this.broadcast(Event.UPDATED);
	};
	
	Color.prototype.decimal = function(value){
		return this._handle('_decimal', value, Events.INT_UPDATED);
	};
	
	Color.prototype.hex = function(value){
		return this._handle('_hex', value, Events.HEX_UPDATED);
	};
	
	Color.prototype.red = function(value){
		return this._handle('_red', value, Events.RGB_UPDATED);
	};
	
	Color.prototype.green = function(value){
		return this._handle('_green', value, Events.RGB_UPDATED);
	};	
	
	Color.prototype.blue = function(value){
		return this._handle('_blue', value, Events.RGB_UPDATED);
	};
	
	Color.prototype.hue = function(value){
		return this._handle('_hue', value, Events.HSL_UPDATED);
	};	
	
	Color.prototype.saturation = function(value){
		return this._handle('_saturation', value, Events.HSL_UPDATED);
	};	
		
	Color.prototype.lightness = function(value){
		return this._handle('_lightness', value, Events.HSL_UPDATED);
	};		
	
	Color.prototype.brightness = function(value){
		return this._handle('_brightness', value, Events.HSV_UPDATED);
	};
	
	Color.prototype.alpha = function(value){
		return this._handle('_alpha', value);
	};
	
	Color.prototype._handle = function(prop, value, event){
		if(typeof this[prop] != 'undefined'){
			if(typeof value != 'undefined'){
				if(value != this[prop]){
					this[prop] = value;
					if(event){
						this.broadcast(event);
					};
				};
				this.broadcast(Event.UPDATED);
			};
		};
		return this[prop];
	};	
	
	Color.prototype.getHex = function(){
		return this._hex;
	};
	
	Color.prototype.getRGB = function(){
		var components = [absround(this._red), absround(this._green), absround(this._blue)];
		return 'rgb(' + components.join(', ') + ')';
	};
	
	Color.prototype.getPRGB = function(){
		var components = [absround(100 * this._red / 255) + '%', absround(100 * this._green / 255) + '%', absround(100 * this._blue / 255) + '%'];
		return 'rgb(' + components.join(', ') + ')';
	};
	
	Color.prototype.getRGBA = function(){
		var components = [absround(this._red), absround(this._green), absround(this._blue), this._alpha];
		return 'rgba(' + components.join(', ') + ')';
	};
	
	Color.prototype.getPRGBA = function(){
		var components = [absround(100 * this._red / 255) + '%', absround(100 * this._green / 255) + '%', absround(100 * this._blue / 255) + '%', this._alpha];
		return 'rgba(' + components.join(', ') + ')';
	};
	
	Color.prototype.getHSL = function(){
		var components = [absround(this._hue), absround(this._saturation) + '%', absround(this._lightness) + '%'];
		return 'hsl(' + components.join(', ') + ')';
	};
	
	Color.prototype.getHSLA = function(){
		var components = [absround(this._hue), absround(this._saturation) + '%', absround(this._lightness) + '%', this._alpha];
		return 'hsla(' + components.join(', ') + ')';
	};
	
	Color.prototype.format = function(string){
		var tokens = {
			r : this._red,
			g : this._green,
			b : this._blue,
			h : this._hue,
			s : this._saturation,
			l : this._lightness,
			v : this._brightness,
			a : this._alpha,
			x : this._hex,
			d : this._decimal
		};
		for(var token in tokens){
			string = string.split('%' + token + '%').join(tokens[token]);
		};
		return string;
	};
	
	Color.prototype.output = 0;

	Color.HEX = 0;  
	Color.RGB = 1;  
	Color.PRGB = 2;  
	Color.RGBA = 3;  
	Color.PRGBA = 4;  
	Color.HSL = 5;  
	Color.HSLA = 6;  
	Color.INT = 7;  

	Color.prototype.toString = function(){
		switch(this.output){
			case 0 :  
				return this.getHex();
			case 1 :  
				return this.getRGB();
			case 2 :  
				return this.getPRGB();
			case 3 :  
				return this.getRGBA();
			case 4 :  
				return this.getPRGBA();
			case 5 :  
				return this.getHSL();
			case 6 :  
				return this.getHSLA();
			case 7 :  
				return this._decimal;
		};
		return this.getHex();
	};

	Color.prototype.toArray = function(){
		if ( this._alpha != 1 ) {
			return [ this._red, this._green, this._blue, this._alpha ];
		} else {
			return [ this._red, this._green, this._blue ];
		}
	};	
	
	Color.prototype._listeners = null;
	Color.prototype._isSubscribed = function(type){
		return this._listeners[type] != null;
	};
	
	Color.prototype.subscribe = function(type, callback){
		if(!this._isSubscribed(type)) {
			this._listeners[type] = [];
		};
		this._listeners[type].push(callback);
	};	
	
	Color.prototype.unsubscribe = function(type, callback){
		if(!this._isSubscribed(type)) {
			return;
		};
		var stack = this._listeners[type];
		for(var i = 0, l = stack.length; i < l; i++){
			if(stack[i] === callback){
				stack.splice(i, 1);
				return this.unsubscribe(type, callback);
			};
		};
	};	
	
	Color.prototype.broadcast = function(type, params){
		if(!this._isSubscribed(type)) {
			return;
		}
		var stack = this._listeners[type];
		var l = stack.length;
		for(var i = 0; i < l; i++) {
			stack[i].apply(this, params);
		}
	};	
	
	Color.prototype.tween = function(duration, color){
		if(!(color instanceof Color)){
			color = new Color(color);
		};
		var start = +(new Date());
		var ref = this;
		this.broadcast('tweenStart');
		var interval = setInterval(function(){
			var ellapsed = +(new Date()) - start;
			var delta = Math.min(1, ellapsed / duration);
			ref.interpolate(color, delta);
			ref.broadcast('tweenProgress');
			if(delta == 1){
				clearInterval(interval);
				ref.broadcast('tweenComplete');
			};
		}, 20);
		return interval;  
	};
	
	Color.prototype.bind = function(object, property){
		var ref = this;
		this.subscribe('updated', function(){
			object[property] = ref.toString();
		});
	};	
	
	Color.random = function(){
		return new Color(absround(Math.random() * 16777215));
	};	
	
	Color.bind = function(object, property){
		var color = new Color(object[property]);
		color.bind(object, property);
		return color;
	};
		
	Color.Events = Events;
	return Color;
	
});