

function d2h(d) {
	var h = "0123456789ABCDEF".substr(d&15,1);
	while( d > 15) {
		d >>= 4;
		h = "0123456789ABCDEF".substr(d&15,1) + h;
	}
	return h;
}

function h2d(h) {
	return parseInt(h,16);
}



function Color( name)
{
	// HSL color (0 - 1)
	this.hue        = undefined;
	this.saturation = 1.0;
	this.luminance  = 0.5;
	
	this.SCALE_HUE        = 360;
	this.SCALE_SATURATION = 100;
	this.SCALE_LUMINANCE  = 100;
	this.SCALE_RGB        = 255;
	

	this.variation = function( h, s, l, real)
	{
		var newColor = clone( this);
		newColor.variateHue(        h, real);
		newColor.variateSaturation( s, real);
		newColor.variateLuminance(  l, real);
		return newColor;
	}

	this.variationRelative = function( h, s, l)
	{
		var newColor = clone( this);
		newColor.variateHue(                h, true);
		newColor.variateSaturationRelative( s);
		newColor.variateLuminanceRelative(  l);
		return newColor;
	}

	this.setName = function( name)
	{
		var RGB
		if( RGB = Color.namedColors[name.toLowerCase()]) {
			this.setRGB( RGB[0], RGB[1], RGB[2]);
		} else throw new Error( 'Color name does not exist (' + name + ')');
	}
	
	
	

	// HUE . color by angle

	this.getHue = function( real)
	{
		if( real || this.hue === undefined) return             this.hue;
		else                                return Math.round( this.hue * this.SCALE_HUE);
	}

	this.setHue = function( hue, real)
	{
		if( hue === undefined || isNaN( hue)) {
			//this.setSaturation( 0, false);
			this.hue = undefined;
		} else {
			if( ! real) hue = hue / this.SCALE_HUE;

			if(      hue < 0) hue += 1;
			else if( hue > 1) hue -= 1;
		
			this.hue = hue;
		}
	}

	this.variateHue = function( hue, real) // 0.0 -- 1
	{
		this.setHue( this.getHue( real) + hue, real);
	}


		

	

	// SATURATION . color vs. gray
	
	this.getSaturation = function( real) // 0.0 -- 1
	{
		if( real) return              this.saturation;
		else       return Math.round( this.saturation * this.SCALE_SATURATION);
	}
	
	this.setSaturation = function( s, real) // 0.0 -- 1
	{
		if( ! real) s = s / this.SCALE_SATURATION;

		if( s > 1) s = 1;
		if( s < 0) s = 0;
		//Bug::dump( this.saturation, s);
		this.saturation = s;
	}
	
	this.variateSaturation = function( s, real) // 0.0 -- 1
	{
		this.setSaturation( this.getSaturation( real) + s, real);
	}

	this.variateSaturationRound = function( s)
	{
		s = this.saturation + s;
		if     ( s < 0)  s = 0 + s;
		else if( s > 1)  s = 2 - s;
		this.setSaturation( s, true);
	}

	this.variateSaturationRelative = function( s)
	{
		var vari = (s >= 0 ? 1 - this.saturation : this.saturation) * s;
		this.variateSaturation( vari, true);
	}


	
	// LUMINANCE . black vs. white
	
	this.getLuminance = function( real) // 0.0 -- 1
	{
		if( real) return             this.luminance;
		else      return Math.round( this.luminance * this.SCALE_LUMINANCE);
	}
	
	this.setLuminance = function( l, real) // 0.0 -- 1
	{
		if( ! real) l = l / this.SCALE_LUMINANCE;
		
		if( l > 1) l = 1;
		if( l < 0) l = 0;
		
		this.luminance = l;
	}
	
	this.variateLuminance = function( l, real) // 0.0 -- 1
	{
		this.setLuminance( this.getLuminance( real) + l, real);
	}
	
	this.variateLuminanceRound = function( l)
	{
		l = this.luminance + l;
		if     ( l < 0) l = 0 + l;
		else if( l > 1) l = 2 - l;
		this.setLuminance( l, true);
		//Bug.name( 'l', this.getLuminance());
	}

	this.variateLuminanceRelative = function( l)
	{
		var vari = (l >= 0 ? 1 - this.luminance : this.luminance) * l;
		this.variateLuminance( vari, true);
	}




   this.hueToRGB = function(v1,v2,vh)
   {
		if(      vh < 0) vh += 1;
		else if( vh > 1) vh -= 1;

		if(      (6 * vh) < 1) return v1 + (v2 - v1) *           vh  * 6;
		else if( (2 * vh) < 1) return v2;
		else if( (3 * vh) < 2) return v1 + (v2 - v1) * ((2 / 3 - vh) * 6);
		else                   return v1;
   }



	this.getRGB = function( real)
	{
		var RGB = new Array();
		if( this.saturation == 0 || this.hue === undefined) {
			RGB['R'] = this.luminance;
			RGB['G'] = this.luminance;
			RGB['B'] = this.luminance;
		} else {
			if( this.luminance < 0.5 ) var var2 =  this.luminance * (1 + this.saturation);
			else                       var var2 = (this.luminance + this.saturation) - (this.saturation * this.luminance);
			
			var var1 = 2 * this.luminance - var2;

			RGB['R'] = this.hueToRGB( var1, var2, this.hue + (1 / 3));
			RGB['G'] = this.hueToRGB( var1, var2, this.hue);
			RGB['B'] = this.hueToRGB( var1, var2, this.hue - (1 / 3));
		}
		
		if( ! real) {
			for( var c in RGB) {
				RGB[c] = Math.round( RGB[c] * this.SCALE_RGB);
			}
		}

		return RGB;
	}


	this.setRGB = function( R, G, B, real)
	{
		if( ! real) {
			R = R / this.SCALE_RGB;
			G = G / this.SCALE_RGB;
			B = B / this.SCALE_RGB;
		}

		
		var min      = Math.min( R, G, B);
		var max      = Math.max( R, G, B);
		var deltaMax = max - min;            //Delta RGB value

		//Bug.dump( deltaMax)

		this.setLuminance( (max + min) / 2, true);

		if( deltaMax == 0) { //This is a gray, no chroma...
			this.setHue( undefined, true);
			this.setSaturation( 0, true);
		} else { //Chromatic data...
			if( this.luminance < 0.5) s = deltaMax /     (max + min);
			else                      s = deltaMax / (2 - max - min);
			
			this.setSaturation( s, true);

			// hue
			var deltaR = (((max - R) / 6) + (deltaMax / 2)) / deltaMax;
			var deltaG = (((max - G) / 6) + (deltaMax / 2)) / deltaMax;
			var deltaB = (((max - B) / 6) + (deltaMax / 2)) / deltaMax;
			
			//Bug::dump( deltaR, deltaG, deltaB, max);
			//Bug::dump( R, G, B, max, max == B, (2 / 3) + deltaG - deltaR);

			if      (R == max) this.setHue(           deltaB - deltaG, true);
			else if (G == max) this.setHue( (1 / 3) + deltaR - deltaB, true);
			else if (B == max) this.setHue( (2 / 3) + deltaG - deltaR, true);
		}

		//Bug::name( 'set', R, G, B, real, array( 'h' => this.getHue(), 's' => this.getSaturation(), 'ss' => this.saturation, 'l' => this.getLuminance()), this.getRGB( true));
	}


	this.setHex = function( hex) // with #!!!
	{
		return this.setRGB( 
			h2d( hex.substr( 1, 2)), 
			h2d( hex.substr( 3, 2)), 
			h2d( hex.substr( 5, 2))
			);
	}
	
	this.toHex = function()
	{
		var RGB = this.getRGB();
		
		RGB['R'] = d2h( RGB['R']);
		RGB['G'] = d2h( RGB['G']);
      RGB['B'] = d2h( RGB['B']);

		if( RGB['R'].length == 1) RGB['R'] = '0' + RGB['R'].toString()
		if( RGB['G'].length == 1) RGB['G'] = '0' + RGB['G'].toString()
		if( RGB['B'].length == 1) RGB['B'] = '0' + RGB['B'].toString()

		var s = RGB['R'] + RGB['G'] + RGB['B']

		//Bug.dump( RGB['R'], RGB['G'], RGB['B'], s)
		//Bug.dump( s)
		
		return s
	}
	
	this.toHtml = function()
	{
		return '#' + this.toHex();
	}

	this.toString = function()
	{
		return this.toHtml();
	}

	this.isWarm = function()
	{
		return this.getHue( true) <= 0.25 || this.getHue( true) >= 0.75;
	}

	this.isDark = function()
	{
		//return (this.getLuminance( true) + ( this.isWarm() ? 0 : 0.125)) <= 0.5
		return this.getLuminance( true) < 0.5
	}


	// initialisation
	if( typeof( name) == 'string') {
		this.setName( name);
	}
}



Color.auto = function( str)
{
	var color = new Color();

	var RGBPattern = /^rgb.+?(\d+).+?(\d+).+?(\d+)/
	
	if( str instanceof Color) {
		color = str
	} else if( r = RGBPattern.exec( str)) {
		color.setRGB( r[1], r[2], r[3])
	} else if( typeof( str) == 'string' && str.charAt( 0) == '#') {
		color.setHex( str);
	} else if( typeof( str) == 'string' && Color.namedColors[str.toLowerCase()]) {
		color.setName( str)
	} else {
		return false;
	}
	
	return color
}

Color.hex = function( hex)
{
	var c = new Color();
	c.setHex( hex);
	return c;
}

Color.name = function( name)
{
	return new Color( name);
}

Color.RGB = function( R, G, B, real)
{
	var RGB = new Color();
	RGB.setRGB( R, G, B, real);
	return RGB;
}

Color.HSL = function( h, s, l, real)
{
	var HSL = new Color();
	HSL.setHue(        h, real);
	HSL.setSaturation( s, real);
	HSL.setLuminance(  l, real);
	return HSL;
}


Color.namedColors = {
	aliceblue       : new Array( 240, 248, 255),
	antiquewhite    : new Array( 250, 235, 215),
	aqua            : new Array(   0, 255, 255),
	aquamarine      : new Array( 127, 255, 212),
	azure           : new Array( 240, 255, 255),
	beige           : new Array( 245, 245, 220),
	bisque          : new Array( 255, 228, 196),
	black           : new Array(   0,   0,   0),
	blanchedalmond  : new Array( 255, 235, 205),
	blue            : new Array(   0,   0, 255),
	blueviolet      : new Array( 138,  43, 226),
	brown           : new Array( 165,  42,  42),
	burlywood       : new Array( 222, 184, 135),
	cadetblue       : new Array(  95, 158, 160),
	chartreuse      : new Array( 127, 255,   0),
	chocolate       : new Array( 210, 105,  30),
	coral           : new Array( 255, 127,  80),
	cornflowerblue  : new Array( 100, 149, 237),
	cornsilk        : new Array( 255, 248, 220),
	crimson         : new Array( 220,  20,  60),
	cyan            : new Array(   0, 255, 255),
	darkblue        : new Array(   0,   0, 139),
	darkcyan        : new Array(   0, 139, 139),
	darkgoldenrod   : new Array( 184, 134,  11),
	darkgray        : new Array( 169, 169, 169),
	darkgreen       : new Array(   0, 100,   0),
	darkgrey        : new Array( 169, 169, 169),
	darkkhaki       : new Array( 189, 183, 107),
	darkmagenta     : new Array( 139,   0, 139),
	darkolivegreen  : new Array(  85, 107,  47),
	darkorange      : new Array( 255, 140,   0),
	darkorchid      : new Array( 153,  50, 204),
	darkred         : new Array( 139,   0,   0),
	darksalmon      : new Array( 233, 150, 122),
	darkseagreen    : new Array( 143, 188, 143),
	darkslateblue   : new Array(  72,  61, 139),
	darkslategray   : new Array(  47,  79,  79),
	darkslategrey   : new Array(  47,  79,  79),
	darkturquoise   : new Array(   0, 206, 209),
	darkviolet      : new Array( 148,   0, 211),
	deeppink        : new Array( 255,  20, 147),
	deepskyblue     : new Array(   0, 191, 255),
	dimgray         : new Array( 105, 105, 105),
	dimgrey         : new Array( 105, 105, 105),
	dodgerblue      : new Array(  30, 144, 255),
	firebrick       : new Array( 178,  34,  34),
	floralwhite     : new Array( 255, 250, 240),
	forestgreen     : new Array(  34, 139,  34),
	fuchsia         : new Array( 255,   0, 255),
	gainsboro       : new Array( 220, 220, 220),
	ghostwhite      : new Array( 248, 248, 255),
	gold            : new Array( 255, 215,   0),
	goldenrod       : new Array( 218, 165,  32),
	gray            : new Array( 128, 128, 128),
	green           : new Array(   0, 128,   0),
	greenyellow     : new Array( 173, 255,  47),
	grey            : new Array( 128, 128, 128),
	honeydew        : new Array( 240, 255, 240),
	hotpink         : new Array( 255, 105, 180),
	indianred       : new Array( 205,  92,  92),
	indigo          : new Array(  75,   0, 130),
	ivory           : new Array( 255, 255, 240),
	khaki           : new Array( 240, 230, 140),
	lavender        : new Array( 230, 230, 250),
	lavenderblush   : new Array( 255, 240, 245),
	lawngreen       : new Array( 124, 252,   0),
	lemonchiffon    : new Array( 255, 250, 205),
	lightblue       : new Array( 173, 216, 230),
	lightcoral      : new Array( 240, 128, 128),
	lightcyan       : new Array( 224, 255, 255),
	lightgoldenrody : new Array( 250, 250, 210),
	lightgray       : new Array( 211, 211, 211),
	lightgreen      : new Array( 144, 238, 144),
	lightgrey       : new Array( 211, 211, 211),
	lightpink       : new Array( 255, 182, 193),
	lightsalmon     : new Array( 255, 160, 122),
	lightseagreen   : new Array(  32, 178, 170),
	lightskyblue    : new Array( 135, 206, 250),
	lightslategray  : new Array( 119, 136, 153),
	lightslategrey  : new Array( 119, 136, 153),
	lightsteelblue  : new Array( 176, 196, 222),
	lightyellow     : new Array( 255, 255, 224),
	lime            : new Array(   0, 255,   0),
	limegreen       : new Array(  50, 205,  50),
	linen           : new Array( 250, 240, 230),
	magenta         : new Array( 255,   0, 255),
	maroon          : new Array( 128,   0,   0),
	mediumaquamarin : new Array( 102, 205, 170),
	mediumblue      : new Array(   0,   0, 205),
	mediumorchid    : new Array( 186,  85, 211),
	mediumpurple    : new Array( 147, 112, 219),
	mediumseagreen  : new Array(  60, 179, 113),
	mediumslateblue : new Array( 123, 104, 238),
	mediumspringgre : new Array(   0, 250, 154),
	mediumturquoise : new Array(  72, 209, 204),
	mediumvioletred : new Array( 199,  21, 133),
	midnightblue    : new Array(  25,  25, 112),
	mintcream       : new Array( 245, 255, 250),
	mistyrose       : new Array( 255, 228, 225),
	moccasin        : new Array( 255, 228, 181),
	navajowhite     : new Array( 255, 222, 173),
	navy            : new Array(   0,   0, 128),
	oldlace         : new Array( 253, 245, 230),
	olive           : new Array( 128, 128,   0),
	olivedrab       : new Array( 107, 142,  35),
	orange          : new Array( 255, 165,   0),
	orangered       : new Array( 255,  69,   0),
	orchid          : new Array( 218, 112, 214),
	palegoldenrod   : new Array( 238, 232, 170),
	palegreen       : new Array( 152, 251, 152),
	paleturquoise   : new Array( 175, 238, 238),
	palevioletred   : new Array( 219, 112, 147),
	papayawhip      : new Array( 255, 239, 213),
	peachpuff       : new Array( 255, 218, 185),
	peru            : new Array( 205, 133,  63),
	pink            : new Array( 255, 192, 203),
	plum            : new Array( 221, 160, 221),
	powderblue      : new Array( 176, 224, 230),
	purple          : new Array( 128,   0, 128),
	red             : new Array( 255,   0,   0),
	rosybrown       : new Array( 188, 143, 143),
	royalblue       : new Array(  65, 105, 225),
	saddlebrown     : new Array( 139,  69,  19),
	salmon          : new Array( 250, 128, 114),
	sandybrown      : new Array( 244, 164,  96),
	seagreen        : new Array(  46, 139,  87),
	seashell        : new Array( 255, 245, 238),
	sienna          : new Array( 160,  82,  45),
	silver          : new Array( 192, 192, 192),
	skyblue         : new Array( 135, 206, 235),
	slateblue       : new Array( 106,  90, 205),
	slategray       : new Array( 112, 128, 144),
	slategrey       : new Array( 112, 128, 144),
	snow            : new Array( 255, 250, 250),
	springgreen     : new Array(   0, 255, 127),
	steelblue       : new Array(  70, 130, 180),
	tan             : new Array( 210, 180, 140),
	teal            : new Array(   0, 128, 128),
	thistle         : new Array( 216, 191, 216),
	tomato          : new Array( 255,  99,  71),
	turquoise       : new Array(  64, 224, 208),
	violet          : new Array( 238, 130, 238),
	wheat           : new Array( 245, 222, 179),
	white           : new Array( 255, 255, 255),
	whitesmoke      : new Array( 245, 245, 245),
	yellow          : new Array( 255, 255,   0),
	yellowgreen     : new Array( 154, 205,  50)
	}


