var dBase = function() {
	if (arguments.length) {
		if (this == window) {
			dBase.prototype.extend.call(arguments[0], arguments.callee.prototype);
		} else {
			this.extend(arguments[0]);
		}
	}
};
dBase.version = "1.0.2";
dBase.prototype = {
	extend: function(source, value) {
		var extend = dBase.prototype.extend;
		if (arguments.length == 2) {
			var ancestor = this[source];
			if ((ancestor instanceof Function) && (value instanceof Function) &&
				ancestor.valueOf() != value.valueOf() && /\bdBase\b/.test(value)) {
				var method = value;
				value = function() {
					var previous = this.dBase;
					this.dBase = ancestor;
					var returnValue = method.apply(this, arguments);
					this.dBase = previous;
					return returnValue;
				};
				value.valueOf = function() {
					return method;
				};
				value.toString = function() {
					return String(method);
				};
			}
			this[source] = value;
			return value;
		} else if (source) {
			var _prototype = {toSource: null};
			var _protected = ["toString", "valueOf"];
			if (dBase._prototyping) _protected[2] = "constructor";
			for (var i = 0; (name = _protected[i]); i++) {
				if (source[name] != _prototype[name]) {
					extend.call(this, name, source[name]);
				}
			}
			for (var name in source) {
				if (!_prototype[name]) {
					extend.call(this, name, source[name]);
				}
			}
		}
		return this;
	},
	dBase: function() {
	}
};
dBase.extend = function(_instance, _static) {
	var extend = dBase.prototype.extend;
	if (!_instance) _instance = {};
	dBase._prototyping = true;
	var _prototype = new this;
	extend.call(_prototype, _instance);
	var constructor = _prototype.constructor;
	_prototype.constructor = this;
	delete dBase._prototyping;
	var klass = function() {
		if (!dBase._prototyping) constructor.apply(this, arguments);
		this.constructor = klass;
	};
	klass.prototype = _prototype;
	klass.extend = this.extend;
	klass.implement = this.implement;
	klass.toString = function() {
		return String(constructor);
	};
	extend.call(klass, _static);
	var object = constructor ? klass : _prototype;
	if (object.init instanceof Function) object.init();
	return object;
};
dBase.implement = function(_interface) {
	if (_interface instanceof Function) _interface = _interface.prototype;
	this.prototype.extend(_interface);
};
var aColor = dBase.extend({
	constructor : function (){ this.warn(); },
	toHex : function (){ this.warn(); },
	toRGB : function (){ this.warn(); },
	toXYZ : function (){ this.warn(); },
	toCIELab : function (){ this.warn(); },
	toCIELCh : function (){ this.warn(); },
	distance : function (destinationColor){
		var a = this.toCIELab();
		var b = destinationColor.toCIELab();
		return Math.sqrt(Math.pow((a.l - b.l),2)+Math.pow((a.a - b.a),2)+Math.pow((a.b-b.b),2));
	},
	equal : function(parts, include){
		if (include == null) include = false;
		if (parts < 2) parts = 2;
		var current = this.toCIELCh();
		var distance = 360/parts;
		var palette = [];
		if (include) palette.push(this);
		for (var i=1; i<parts; i++){
			palette.push(new CIELCh(current.l, current.c,current.h+(distance*i))[this.toSelf]());
		}

		return palette;
	},
	range : function (destinationColor, steps,include){
		if (include == null) include = false;
		var a = this.toRGB();
		var b = destinationColor.toRGB();
		var colors = [];
		steps--;
		for (n=1; n<steps;n++){
			var nr = Math.floor(a.r+(n*(b.r-a.r)/steps));
			var ng = Math.floor(a.g+(n*(b.g-a.g)/steps));
			var nb = Math.floor(a.b+(n*(b.b-a.b)/steps));
			colors.push(new RGB(nr,ng,nb)[this.toSelf]());
		}
		if (include) {
			colors.unshift(this);
			colors.push(destinationColor[this.toSelf]());
		}
		return colors;
	},
	warn : function (){
		alert('aColor should not be created directly. Treat this as an abstract class.');
	}
});

var Hex = aColor.extend({
	constructor : function (hex){
		this.hex = hex;
		if (this.hex.substring(0,1) == '#') this.hex = this.hex.substring(1,7);
	},
	toHex : function (){ return this; },
	toRGB : function (){
		var r = parseInt(this.hex.substring(0,2), 16);
		var g = parseInt(this.hex.substring(2,4), 16);
		var b = parseInt(this.hex.substring(4,6), 16);
    		return new RGB(r,g,b);
	},
	toXYZ : function (){
		return this.toRGB().toXYZ();
	},
	toCIELab : function (){
		return this.toXYZ().toCIELab();
	},
	toString : function (){
		return this.hex.toUpperCase();
	},
	toCIELCh : function (){
		return this.toCIELab().toCIELCh();
	},
	toSelf : "toHex"
});
var RGB = aColor.extend({
	constructor : function (r,g,b){
		this.r = Math.min(255,Math.max(r,0));
		this.g = Math.min(255,Math.max(g,0));
		this.b = Math.min(255,Math.max(b,0));
	},
	toHex : function (){
		var rgbcols = [this.r,this.g,this.b];
		var hex = '';
		for (i=0;i<rgbcols.length;i++){
		  var chara = "0123456789ABCDEF";
		  hex += chara.charAt(Math.floor(rgbcols[i] / 16)) + chara.charAt(rgbcols[i] - (Math.floor(rgbcols[i] / 16) * 16));
		}

		return new Hex(hex);
	},
	toRGB : function (){ return this; },
	toXYZ : function (){
		var tmp_r = this.r/255;
		var tmp_g = this.g/255;
		var tmp_b = this.b/255;
		if(tmp_r > 0.04045) {
		  tmp_r = Math.pow(((tmp_r + 0.055) / 1.055),2.4)
		}else {
		  tmp_r = tmp_r / 12.92;
		}
		if(tmp_g > 0.04045) {
		  tmp_g = Math.pow(((tmp_g + 0.055) / 1.055), 2.4)
		}else {
		  tmp_g = tmp_g / 12.92
		}
		if(tmp_b > 0.04045) {
		  tmp_b = Math.pow(((tmp_b + 0.055) / 1.055), 2.4);
		}else {
		  tmp_b = tmp_b / 12.92
		}
		tmp_r = tmp_r * 100
		tmp_g = tmp_g * 100
		tmp_b = tmp_b * 100
		var x = tmp_r * 0.4124 + tmp_g * 0.3576 + tmp_b * 0.1805;
		var y = tmp_r * 0.2126 + tmp_g * 0.7152 + tmp_b * 0.0722;
		var z = tmp_r * 0.0193 + tmp_g * 0.1192 + tmp_b * 0.9505;
		return new XYZ(x,y,z);
	},
	toCIELab : function (){
		return this.toXYZ().toCIELab();
	},
	toString : function (){
		return this.r+','+this.g+','+this.b;
	},
	toCIELCh : function (){
		return this.toCIELab().toCIELCh();
	},
	toSelf : "toRGB"
});

var XYZ = aColor.extend({
	constructor : function (x,y,z){
		this.x = x;
		this.y = y;
		this.z = z;
	},
	toHex : function (){
		return this.toRGB().toHex();
	},
	toRGB : function (){
		var var_X = this.x / 100;
		var var_Y = this.y / 100;
		var var_Z = this.z / 100;

		var var_R = var_X *  3.2406 + var_Y * -1.5372 + var_Z * -0.4986;
		var var_G = var_X * -0.9689 + var_Y *  1.8758 + var_Z *  0.0415;
		var var_B = var_X *  0.0557 + var_Y * -0.2040 + var_Z *  1.0570;

		if (var_R > 0.0031308) {
			var_R = 1.055 * Math.pow(var_R,(1/2.4)) - 0.055;
		}else {
			var_R = 12.92 * var_R;
		}
		if (var_G > 0.0031308){
			var_G = 1.055 * Math.pow(var_G,(1/2.4)) - 0.055;
		}else {
			var_G = 12.92 * var_G;
		}
		if (var_B > 0.0031308){
			var_B = 1.055 * Math.pow(var_B,(1/2.4)) - 0.055;
		}else {
			var_B = 12.92 * var_B;
		}
		var r = Math.round(var_R * 255);
		var g = Math.round(var_G * 255);
		var b = Math.round(var_B * 255);

		return new RGB(r,g,b);
	},
	toXYZ : function (){ return this; },
	toCIELab : function (){

		var Xn =  95.047;
		var Yn = 100.000;
		var Zn = 108.883;

		var x = this.x / Xn;
		var y = this.y / Yn;
		var z = this.z / Zn;

		if (x > 0.008856){
			x = Math.pow(x, 1/3);
		}else {
			x = (7.787 * x) + (16 / 116);
		}
		if (y > 0.008856){
			y = Math.pow(y, 1 / 3);
		}else {
			y = (7.787 * y) + (16 / 116);
		}
		if (z > 0.008856){
			z = Math.pow(z, 1 / 3);
		}else {
			z = (7.787 * z) + (16 / 116);
		}

		if (y>0.008856){
			var l = (116 * y) - 16;
		}else {
			var l=903.3*y;
		}
		var a = 500 * (x - y);
		var b = 200 * (y - z);

		return new CIELab(l, a, b);
	},
	toCIELCh : function (){
		return this.toCIELab().toCIELCh();
	},
	toString : function (){
		return this.x+','+this.y+','+this.z;
	},
	toSelf : "toXYZ"
});

var CIELab = aColor.extend ({
	constructor : function (l,a,b){
		this.l = l;
		this.a = a;
		this.b = b;
	},
	toHex : function (){
		return this.toRGB().toHex();
	},
	toRGB : function (){
		return this.toXYZ().toRGB();
	},
	toXYZ : function (){
		var ref_X =  95.047;
		var ref_Y = 100.000;
		var ref_Z = 108.883;

		var var_Y = (this.l + 16 ) / 116;
		var var_X = this.a / 500 + var_Y;
		var var_Z = var_Y - this.b / 200;

		if (Math.pow(var_Y,3) > 0.008856){
			var_Y = Math.pow(var_Y,3);
		}else {
			var_Y = (var_Y - 16 / 116) / 7.787;
		}
		if(Math.pow(var_X,3) > 0.008856){
			var_X = Math.pow(var_X,3);
		}else {
			var_X = (var_X - 16 / 116) / 7.787;
		}
		if (Math.pow(var_Z,3) > 0.008856){
			var_Z = Math.pow(var_Z,3);
		}else {
			var_Z = (var_Z - 16 / 116) / 7.787;
		}
		x = ref_X * var_X;
		y = ref_Y * var_Y;
		z = ref_Z * var_Z;
		return new XYZ(x,y,z);
	},
	toCIELab : function (){
		return this;
	},
	toCIELCh : function (){
		var var_H = Math.atan2( this.b, this.a );

		if ( var_H > 0 ) {
			var_H = ( var_H / Math.PI ) * 180;
		}else{
			var_H = 360 - ( Math.abs( var_H ) / Math.PI ) * 180
		}

		var l = this.l;
		var c = Math.sqrt(Math.pow(this.a,2) + Math.pow(this.b,2));
		var h = var_H;

		return new CIELCh(l,c,h);
	},
	toString : function (){
		return this.l+','+this.a+','+this.b;
	},
	toSelf : "toCIELab"
});
var CIELCh = aColor.extend({
	constructor : function (l,c,h){
		this.l = l;
		this.c = c;
		this.h = h<360?h:(h-360);
	},
	toHex : function (){
		return this.toCIELab().toHex();
	},
	toRGB : function (){
		return this.toCIELab().toRGB();
	},
	toXYZ : function (){
		return this.toCIELab().toXYZ();
	},
	toCIELab : function (){
		var l = this.l;
		var hradi = this.h * (Math.PI/180);
		var a = Math.cos(hradi) * this.c;
		var b = Math.sin(hradi) * this.c;
		return new CIELab(l,a,b);
	},
	toString : function (){
		return this.l + ','+this.c +','+this.h;
	},
	toCIELCh : function (){
		return this;
	},
	toSelf : "toHex"
});
var target;
var current;
var targetBg = null;
var range = null;
var inter = null;
var counter = null;
var steps = null;
var active = false;
function changeOpac(opacity, id) {
	var object = document.getElementById(id);

	object.style.opacity = (opacity / 100);
	object.style.MozOpacity = (opacity / 100);
	object.style.KhtmlOpacity = (opacity / 100);
	object.style.filter = "alpha(opacity=" + opacity + ")";
	
	object.thisOpacity = opacity;
};
function SwitchTo(toColor, toImg){
	if (targetBg!= null && targetBg == toImg) {
		return;
	}
	clearTimeout(inter);
	if (active){
		if (range[counter] != null){
			current = range[counter];
		}
	}else {
		changeOpac(100, 'bg');
	}
	active = true;

	target = new Hex(toColor);
	targetBg = toImg;

	counter = 0;
	distance = current.distance(target);
    	steps = Math.floor(distance/10);

	range = current.range(target, steps, true);

	inter = setInterval(doFadeOut, 50);
	doFadeOut();

}
function doFadeOut(){
	var opac = document.getElementById('bg').thisOpacity;
	if (opac > 0){
		changeOpac(Math.max(0, (opac-20)), 'bg');
	}else {
		document.getElementById('bg').style.backgroundImage = 'url('+targetBg+')';
		clearTimeout(inter);
		inter = setInterval(doFadeColor, 50);
		doFadeColor();
	}
}
function doFadeIn(){
	var opac = document.getElementById('bg').thisOpacity;
	if (opac < 100){
		changeOpac(Math.min(100, (opac+20)), 'bg');
	}else {
		active = false;
		current = target;
		clearTimeout(inter);
	}
}
function doFadeColor(){
	if (counter < range.length){
		document.body.style.backgroundColor = '#'+range[counter].toString();
		counter++;
	}else {
		clearTimeout(inter);
		inter = setInterval(doFadeIn, 50);
		doFadeIn();
	}
}