//BV._canvasCounter = 0;
BV.createCanvas = function() {
	//BV._canvasCounter++;
	//console.log(BV._canvasCounter);
	return document.createElement("canvas");
};

BV.out = function(msg)
{
	var timeout, timeout2;
	var console = document.getElementById("BVCONSOLE");
	if(!console)
	{
		console = document.createElement("div");
		console.id = "BVCONSOLE";
		BV.css(console, {
			display: "none"
		});
		document.body.appendChild(console);
		console.onmousedown = function()
		{
			document.body.removeChild(console);
		};
		console.style.display = "none";
	}
	
	var outarr = console.innerHTML.split("\n");
	console.innerHTML = msg+"<br/>";
	/*for(var i = 0; i < 10; i++)
	{
		if(outarr[i])
			console.innerHTML += "\n"+outarr[i];
	}*/
	if(console.timeout)
		clearTimeout(console.timeout);
	if(console.timeout2)
		clearTimeout(console.timeout2);
	console.style.webkitAnimationName = "consoleIn";
	console.style.MozAnimationName = "consoleIn";
	console.style.opacity = "1";
	console.timeout = setTimeout(function()
	{
		console.style.opacity = "0";
		console.style.webkitAnimationName = "consoleOut";
		console.style.MozAnimationName = "consoleOut";
		console.timeout2 = setTimeout(function() {
			console.style.display = "none";
			console.timeout2 = 0;
			}, 350);
		console.timeout = 0;
	}, 500);
	console.style.display = "block";
};

BV.loadImage = function(im, callback) {
	var counter = 0;
	function check() {
		if (counter == 1000) {
			BV.out("couldn't load");
			return;
		}
		if(im.complete) {
			counter++;
			callback();
		}
		else
			setTimeout(check, 1);
	};
	check();
};

BV.getGlobalOff = function(el) {
	var x = 0;
	var oElement = el;
	while( oElement != null ) {
		x += oElement.offsetLeft;
		oElement = oElement.offsetParent;
	}
	
	var y = 0;
	var oElement = el;
	while( oElement != null ) {
		y += oElement.offsetTop;
		oElement = oElement.offsetParent;
	}
	
	return {x: x, y: y};
}

BV.css = function(el, style) {
	for (var i in style) {
		if(style.hasOwnProperty(i))
		{
			if(i == "transform") {
				el.style.WebkitTransform = style[i];
				el.style.MozTransform = style[i];
				el.style.OTransform = style[i];
				el.style.msTransform = style[i];
			} else if(i == "transformOrigin") {
				el.style.WebkitTransformOrigin = style[i];
				el.style.MozTransformOrigin = style[i];
				el.style.OTransformOrigin = style[i];
				el.style.msTransformOrigin = style[i];
			} else if(i == "transition") {
				el.style.WebkitTransition = style[i];
				el.style.MozTransition = style[i];
				el.style.OTransition = style[i];
				el.style.msTransition = style[i];
			} else {
				el.style[i] = style[i];
			}
		}
	}
};

BV.createChecker = function(s) {
	s = parseInt(s);
	var canvas = BV.createCanvas();
	if(s < 1) {
		canvas.width = 1;
		canvas.height = 1;
		var ctxt = canvas.getContext("2d");
		ctxt.fillStyle = "rgb(128, 128, 128)";
		ctxt.fillRect(0, 0, 1, 1);
	} else if(s > 200){
		canvas.width = 401;
		canvas.height = 401;
	}	else {
		canvas.width = s*2;
		canvas.height = s*2;
		var ctxt = canvas.getContext("2d");
		ctxt.fillStyle = "rgb(255, 255, 255)";
		ctxt.fillRect(0, 0, s*2, s*2);
		ctxt.fillStyle = "rgb(200, 200, 200)";
		ctxt.fillRect(0, 0, s, s);
		ctxt.fillRect(s, s, s*2, s*2);
	}
	
	return canvas.toDataURL("image/png");
};

BV.resizeCanvas = function(canvas, w, h) {
	if(!w || !h || (w === canvas.width && h === canvas.height)) {
		return;
	}
	w = Math.max(w, 1);
	h = Math.max(h, 1);
	if(w <= canvas.width && h <= canvas.height) {
		var base2 = {expw: 1, exph: 1, minw: 1, minh: 1, w:0, h:0};
		while(Math.pow(2,base2.expw) < canvas.width) {
			base2.expw++;
		}
		while(Math.pow(2,base2.exph) < canvas.height) {
			base2.exph++;
		}
		var scalew = w/canvas.width;
		var scaleh = h/canvas.height;
		var minw = scalew * Math.pow(2,base2.expw);
		var minh = scaleh * Math.pow(2,base2.exph);
		while(Math.pow(2,base2.minw) < minw) {
			base2.minw++;
		}
		while(Math.pow(2,base2.minh) < minh) {
			base2.minh++;
		}
		
		var tmp1 = BV.createCanvas();
		var tmp2 = BV.createCanvas();
		base2.w = Math.pow(2,base2.expw);
		base2.h = Math.pow(2,base2.exph);
		tmp1.width = base2.w;
		tmp1.height = base2.h;
		tmp2.width = base2.w;
		tmp2.height = base2.h;
		
		var ew, eh;
		var temp; //for switching
		var buffer1 = tmp1, buffer2 = tmp2, state = false;

		ew = base2.expw-1;
		eh = base2.exph-1;
		buffer2 = canvas;
		var counter = 0;
		for(; ew >= base2.minw || eh >= base2.minh; ew--, eh--) {
			counter++;
			buffer1.getContext("2d").clearRect(0,0,base2.w,base2.h);
			buffer1.getContext("2d").drawImage(buffer2, 0, 0, (ew >= base2.minw) ? buffer2.width/2 : buffer2.width, (eh >= base2.minh) ? buffer2.height/2 : buffer2.height);
			
			if(!state) {
				buffer1 = tmp2;
				buffer2 = tmp1;
			} else {
				buffer1 = tmp1;
				buffer2 = tmp2;
			}
			state = !state;
		}
		if(counter === 0) {
			buffer1.getContext("2d").drawImage(canvas, 0, 0);
			buffer2 = buffer1;
		}
		var ratiow = canvas.width/base2.w;
		var ratioh = canvas.height/base2.h;
		var finalscaleW = minw/Math.pow(2,base2.minw), finalscaleH = minh/Math.pow(2,base2.minh);
		canvas.width = w;
		canvas.height = h;
		canvas.getContext("2d").drawImage(buffer2, 0, 0, buffer2.width * finalscaleW, buffer2.height * finalscaleH);
		buffer1 = undefined;
		buffer2 = undefined;
		delete tmp1;
		delete tmp2;
		
	} else if(w >= canvas.width && h >= canvas.height) {
		var tmp1 = BV.createCanvas();
		var tmp2 = BV.createCanvas();
		tmp1.width = w;
		tmp1.height = h;
		tmp2.width = w;
		tmp2.height = h;
		var scaleX = 1, endScaleX = w/canvas.width;
		var scaleY = 1, endScaleY = h/canvas.height;
		var maxX = scaleX, maxY = scaleY;
		while(maxX <= endScaleX) {
			maxX *= 2;
		}
		while(maxY <= endScaleY) {
			maxY *= 2;
		}
		maxX /= 2;
		maxY /= 2;
		var buffer1 = tmp1, buffer2 = tmp2, state = false;
		function switchBuffers() {
			if(!state) {
				buffer1 = tmp2;
				buffer2 = tmp1;
			} else {
				buffer1 = tmp1;
				buffer2 = tmp2;
			}
			state = !state;
		}
		scaleX = 2;
		scaleY = 2;
		buffer2 = canvas;
		var counter = 0;
		while(scaleX <= maxX || scaleY <= maxY) {
			buffer1.getContext("2d").clearRect(0, 0, buffer1.width, buffer1.height);
			buffer1.getContext("2d").drawImage(buffer2, 0, 0, buffer2.width * ((scaleX <= maxX) ? 2 : 1), buffer2.height * ((scaleY <= maxY) ? 2 : 1) );
			
			scaleX *= 2;
			scaleY *= 2;
			counter++;
			switchBuffers();
		}
		if(counter == 0) {
			buffer1.getContext("2d").drawImage(canvas, 0, 0);
			buffer2 = buffer1;
		}
		var finalScaleX = endScaleX / maxX;
		var finalScaleY = endScaleY / maxY;
		canvas.width = w;
		canvas.height = h;
		canvas.getContext("2d").drawImage(buffer2, 0, 0, buffer2.width * finalScaleX, buffer2.height*finalScaleY);
	} else {
		BV.resizeCanvas(canvas, w, canvas.height);
		BV.resizeCanvas(canvas, w, h);
	}
};

//b needs to fit a
BV.fitInto = function(aw, ah, bw, bh) {
	var w = bw*aw, h = bh*aw;
	if(w > aw) {
		h = aw/w*h;
		w = aw;
	}
	if(h > ah) {
		w = ah/h*w;
		h = ah;
	}	
	return {width: w, height: h};
}

//center b in a
BV.centerWithin = function(aw, ah, bw, bh) {
	return {
		x: aw/2 - bw/2,
		y: ah/2 - bh/2
	};
}

BV.rotate = function(x, y, deg) {
	theta = deg * (Math.PI/180);

	cs = Math.cos(theta);
	sn = Math.sin(theta);

	px = x * cs - y * sn; 
	py = x * sn + y * cs;
	
	return {
		x: px,
		y: py
	};
}

BV.rotateAround = function(center, point, deg) {
	var rot = BV.rotate(point.x-center.x, point.y-center.y, deg);
	rot.x += center.x;
	rot.y += center.y;
	return rot;
}

BV.angle = function(center, p1) {
	var p0 = {x: center.x, y: center.y - Math.sqrt(Math.abs(p1.x - center.x) * Math.abs(p1.x - center.x) + Math.abs(p1.y - center.y) * Math.abs(p1.y - center.y))};
	return (2 * Math.atan2(p1.y - p0.y, p1.x - p0.x)) * 180 / Math.PI;
}

BV.projectPointOnLine = function(line1, line2, toProject) {
	if(line1.x === line2.x) {
		var x = line1.x;
		var y = toProject.y;
		
		return {
			x: x,
			y: y
		};
	}
    var m = (line2.y - line1.y) / (line2.x - line1.x);
    var b = line1.y - (m * line1.x);

    var x = (m * toProject.y + toProject.x - m * b) / (m * m + 1);
    var y = (m * m * toProject.y + m * toProject.x + b) / (m * m + 1);

    return {
		x: x,
		y: y
	};
}

BV.dist = function(ax, ay , bx, by) {
	return Math.sqrt(Math.pow(ax-bx,2)+Math.pow(ay-by,2));
}

/**
*
*  Javascript color conversion
*  http://www.webtoolkit.info/
*
*  revised by bv-design.com 2010
*
**/
 
function HSV(h, s, v) {
	this.h = Math.max(0, Math.min(360, h));
	this.s = Math.max(0.001, Math.min(100, s)); //bug when 0
	this.v = Math.max(0, Math.min(100, v));
}
 
function RGB(r, g, b) {
	this.r = Math.max(0, Math.min(255, r));
	this.g = Math.max(0, Math.min(255, g));
	this.b = Math.max(0, Math.min(255, b));
}
 
function CMYK(c, m, y, k) {
	this.c = Math.max(0, Math.min(100, c));
	this.m = Math.max(0, Math.min(100, m));
	this.y = Math.max(0, Math.min(100, y));
	this.k = Math.max(0, Math.min(100, k));
}
 
BV.ColorConverter = {
 
	_RGBtoHSV : function  (RGB) {
		var result = new HSV(0, 0, 0);
 
		r = RGB.r / 255;
		g = RGB.g / 255;
		b = RGB.b / 255;
 
		var minVal = Math.min(r, g, b);
		var maxVal = Math.max(r, g, b);
		var delta = maxVal - minVal;
 
		result.v = maxVal;
 
		if (delta == 0) {
			result.h = 0;
			result.s = 0;
		} else {
			result.s = delta / maxVal;
			var del_R = (((maxVal - r) / 6) + (delta / 2)) / delta;
			var del_G = (((maxVal - g) / 6) + (delta / 2)) / delta;
			var del_B = (((maxVal - b) / 6) + (delta / 2)) / delta;
 
			if (r == maxVal) { result.h = del_B - del_G; }
			else if (g == maxVal) { result.h = (1 / 3) + del_R - del_B; }
			else if (b == maxVal) { result.h = (2 / 3) + del_G - del_R; }
 
			if (result.h < 0) { result.h += 1; }
			if (result.h > 1) { result.h -= 1; }
		}
 
		result.h = Math.round(result.h * 360);
		result.s = Math.round(result.s * 100);
		result.v = Math.round(result.v * 100);
 
		return result;
	},
 
	_HSVtoRGB : function  (HSV) {
		var result = new RGB(0, 0, 0);
 
		var h = HSV.h / 360;
		var s = HSV.s / 100;
		var v = HSV.v / 100;
 
		if (s == 0) {
			result.r = v * 255;
			result.g = v * 255;
			result.b = v * 255;
		} else {
			var_h = h * 6;
			var_i = Math.floor(var_h);
			var_1 = v * (1 - s);
			var_2 = v * (1 - s * (var_h - var_i));
			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};
 
			result.r = var_r * 255;
			result.g = var_g * 255;
			result.b = var_b * 255;
 
			result.r = Math.round(result.r);
			result.g = Math.round(result.g);
			result.b = Math.round(result.b);
		}
 
		return result;
	},
 
	_CMYKtoRGB : function (CMYK){
		var result = new RGB(0, 0, 0);
 
		c = CMYK.c / 100;
		m = CMYK.m / 100;
		y = CMYK.y / 100;
		k = CMYK.k / 100;
 
		result.r = 1 - Math.min( 1, c * ( 1 - k ) + k );
		result.g = 1 - Math.min( 1, m * ( 1 - k ) + k );
		result.b = 1 - Math.min( 1, y * ( 1 - k ) + k );
 
		result.r = Math.round( result.r * 255 );
		result.g = Math.round( result.g * 255 );
		result.b = Math.round( result.b * 255 );
 
		return result;
	},
 
	_RGBtoCMYK : function (RGB){
		var result = new CMYK(0, 0, 0, 0);
 
		r = RGB.r / 255;
		g = RGB.g / 255;
		b = RGB.b / 255;
 
		result.k = Math.min( 1 - r, 1 - g, 1 - b );
		result.c = ( 1 - r - result.k ) / ( 1 - result.k );
		result.m = ( 1 - g - result.k ) / ( 1 - result.k );
		result.y = ( 1 - b - result.k ) / ( 1 - result.k );
 
		result.c = Math.round( result.c * 100 );
		result.m = Math.round( result.m * 100 );
		result.y = Math.round( result.y * 100 );
		result.k = Math.round( result.k * 100 );
 
		return result;
	},
 
	toRGB : function (o) {
		if (o instanceof RGB) { return o; }
		if (o instanceof HSV) {	return this._HSVtoRGB(o); }
		if (o instanceof CMYK) { return this._CMYKtoRGB(o); }
	},
 
	toHSV : function (o) {
		if (o instanceof HSV) { return o; }
		if (o instanceof RGB) { return this._RGBtoHSV(o); }
		if (o instanceof CMYK) { return this._RGBtoHSV(this._CMYKtoRGB(o)); }
	},
 
	toCMYK : function (o) {
		if (o instanceof CMYK) { return o; }
		if (o instanceof RGB) { return this._RGBtoCMYK(o); }
		if (o instanceof HSV) { return this._RGBtoCMYK(this._HSVtoRGB(o)); }
	},
	toHexString : function(o) {
		if (o instanceof RGB) {
			var ha = (parseInt(o.r)).toString(16);
			var hb = (parseInt(o.g)).toString(16);
			var hc = (parseInt(o.b)).toString(16);
			if(ha.length == 1)
				ha = "0" + ha;
			if(hb.length == 1)
				hb = "0" + hb;
			if(hc.length == 1)
				hc = "0" + hc;
			return ha+hb+hc;
		}
	}
 
}

//from glfx.js by evanW:

// from SplineInterpolator.cs in the Paint.NET source code

function SplineInterpolator(points) {
    var n = points.length;
    this.xa = [];
    this.ya = [];
    this.u = [];
    this.y2 = [];

    points.sort(function(a, b) {
        return a[0] - b[0];
    });
    for (var i = 0; i < n; i++) {
        this.xa.push(points[i][0]);
        this.ya.push(points[i][1]);
    }

    this.u[0] = 0;
    this.y2[0] = 0;

    for (var i = 1; i < n - 1; ++i) {
        // This is the decomposition loop of the tridiagonal algorithm. 
        // y2 and u are used for temporary storage of the decomposed factors.
        var wx = this.xa[i + 1] - this.xa[i - 1];
        var sig = (this.xa[i] - this.xa[i - 1]) / wx;
        var p = sig * this.y2[i - 1] + 2.0;

        this.y2[i] = (sig - 1.0) / p;

        var ddydx = 
            (this.ya[i + 1] - this.ya[i]) / (this.xa[i + 1] - this.xa[i]) - 
            (this.ya[i] - this.ya[i - 1]) / (this.xa[i] - this.xa[i - 1]);

        this.u[i] = (6.0 * ddydx / wx - sig * this.u[i - 1]) / p;
    }

    this.y2[n - 1] = 0;

    // This is the backsubstitution loop of the tridiagonal algorithm
    for (var i = n - 2; i >= 0; --i) {
        this.y2[i] = this.y2[i] * this.y2[i + 1] + this.u[i];
    }
}

SplineInterpolator.prototype.interpolate = function(x) {
    var n = this.ya.length;
    var klo = 0;
    var khi = n - 1;

    // We will find the right place in the table by means of
    // bisection. This is optimal if sequential calls to this
    // routine are at random values of x. If sequential calls
    // are in order, and closely spaced, one would do better
    // to store previous values of klo and khi.
    while (khi - klo > 1) {
        var k = (khi + klo) >> 1;

        if (this.xa[k] > x) {
            khi = k; 
        } else {
            klo = k;
        }
    }

    var h = this.xa[khi] - this.xa[klo];
    var a = (this.xa[khi] - x) / h;
    var b = (x - this.xa[klo]) / h;

    // Cubic spline polynomial is now evaluated.
    return a * this.ya[klo] + b * this.ya[khi] + 
        ((a * a * a - a) * this.y2[klo] + (b * b * b - b) * this.y2[khi]) * (h * h) / 6.0;
};

