//basic and non gui functionality
var LIB = {};

requestAnimFrame = (function () {
        return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) {
            window.setTimeout(callback, 1000 / 60);
        };
    }());

(function() {
	"use strict";
	
	
	/*LIB.addCssRule(selector, declaration)
	Add css rule(declaration) for a class, id, etc.(selector)
	selector {
		declaration
	}
	*/
    LIB.addCssRule = (function () {
        var style_node = document.createElement("style");
        style_node.setAttribute("type", "text/css");
        style_node.setAttribute("media", "screen");
        document.getElementsByTagName("head")[0].appendChild(style_node);
        return function (selector, declaration) {
            var ua, isIE, last_style_node;
            ua = navigator.userAgent.toLowerCase();
            isIE = (navigator.appName === 'Microsoft Internet Explorer');
            if (!isIE) {
                style_node.appendChild(document.createTextNode(selector + " {" + declaration + "}"));
            }
            if (isIE && document.styleSheets && document.styleSheets.length > 0) {
                last_style_node = document.styleSheets[document.styleSheets.length - 1];
                last_style_node.addRule(selector, declaration);
            }
        };
    }());
	
	
	
	/*LIB.css(el, style)
	Assign multiple style rules to one element. Automatically adds prefix for "transform", "transformOrigin",
	"transition", and "boxSizing"
	example:
	LIB.css(someDiv, {
		border: "1px solid #00f",
		margin: "10px"
	});
	*/
    LIB.css = function (el, style) {
        var i;
        for (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 if (i === "boxSizing") {
                    el.style.WebkitBoxSizing = style[i];
                    el.style.MozBoxSizing = style[i];
                    el.style.boxSizing = style[i]; //ie, opera
                } else {
                    el.style[i] = style[i];
                }
            }
        }
    };
	
	/*LIB.color
	Basic color operations
	c is either {r: (0-255), g: (0-255), b: (0-255)} or {h: (0-360), s: (0-100), v: (0-100)}
	*/
    LIB.color = {
        rgbToHsv: function (c) {
            var result, r, g, b, minVal, maxVal, delta, del_R, del_G, del_B;
            result = {
                h: 0,
                s: 0,
                v: 0
            };
            r = Math.max(0, Math.min(255, c.r)) / 255;
            g = Math.max(0, Math.min(255, c.g)) / 255;
            b = Math.max(0, Math.min(255, c.b)) / 255;
            minVal = Math.min(r, g, b);
            maxVal = Math.max(r, g, b);
            delta = maxVal - minVal;
            result.v = maxVal;
            if (delta === 0) {
                result.h = 0;
                result.s = 0;
            } else {
                result.s = delta / maxVal;
                del_R = (((maxVal - r) / 6) + (delta / 2)) / delta;
                del_G = (((maxVal - g) / 6) + (delta / 2)) / delta;
                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 (c) {
            var result, h, s, v, var_h, var_i, var_1, var_2, var_3, var_r, var_g, var_b;
            result = {
                r: 0,
                g: 0,
                b: 0
            };
            h = Math.max(0, Math.min(360, c.h)) / 360;
            s = Math.max(0.001, Math.min(100, c.s)) / 100; //bug if 0
            v = Math.max(0, Math.min(100, c.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;
        },
        rgbToHex: function (c) {
            var ha, hb, hc;
            ha = (parseInt(c.r, 10)).toString(16);
            hb = (parseInt(c.g, 10)).toString(16);
            hc = (parseInt(c.b, 10)).toString(16);

            function fillUp(p) {
                if (p.length === 1) {
                    p = "0" + p;
                }
                return p;
            }
            return "#" + fillUp(ha) + fillUp(hb) + fillUp(hc);
        }
    };
	/*LIB.setAttributes(el, attributes)
	set multiple attributes(array) for an element via setAttribute
	*/
    LIB.setAttributes = function (el, attr) {
        var i = 0;
        for (i = 0; i < attr.length; i += 1) {
            el.setAttribute(attr[i][0], attr[i][1]);
        }
    };
    /*LIB.getGlobalOff(el)
	Returns offset({x: , y: }) of DOM element relative to the origin of the page
	*/
    LIB.getGlobalOff = function (el) {
        var result, oElement;
        result = {
            x: 0,
            y: 0
        };
        oElement = el;
        while (oElement !== null) {
            result.x += oElement.offsetLeft;
            result.y += oElement.offsetTop;
            oElement = oElement.offsetParent;
        }
        return result;
    };
    /*LIB.loadImageToDataURL(p)
	p = {src: <string>, callback: <function(strDataURL)>}
	Takes url of image and returns DataURL of that image via callback
	*/
    LIB.loadImageToDataURL = function (p) {
        var src, im;
        src = p.src;
        im = new Image();
        im.onload = function () {
            var canvas = document.createElement("canvas");
            canvas.width = im.width;
            canvas.height = im.height;
            canvas.getContext("2d").drawImage(im, 0, 0);
            p.callback(canvas.toDataURL("image/png"));
        };
        im.src = src;
    };
	/*LIB.setClickListener(p)
	Listens to mousedown and touchstart(quicker than onClick) and can spawn a glow effect for touch
	p = {
		el: <DOMElement>		Listening element
		glowPos: {x: , y: }		Coordinates relative to element origin where glow effect should spawn on touch (optional)
		callback: <function()>	Called on click. Return value(boolean) determines if glow will be visible
	}
	*/
    LIB.setClickListener = function (p) {
        p.el.onmousedown = function () {
            p.callback();
            return false;
        };
        p.el.ontouchstart = function (event) {
            var off;
            if (event.touches.length !== 1) {
                return false;
            }
            if (p.callback() !== false && p.glowPos) {
                off = LIB.getGlobalOff(p.el);
                UI.spawnGlow(off.x + p.glowPos.x, off.y + p.glowPos.y);
            }
            return false;
        };
    };
     /*LIB.attachMouseListener(element, callback)
	Sends all mouseevents(move, drag, down, release, touch, scrollwheel) back via callback on AnimationFrame.
	What you get in the callback:
	event = {
		event:			Original event
		x: (0.0-1.0)	Position inside element
		y: (0.0-1.0)	Position inside element
		absX:			Position in px, element is the origin
		absY:			Position in px, element is the origin
		over:			True on mouseover else undefined
		out:			True on mouseout else undefined
		out:			True on mousedown else undefined
		code:			Buttoncode
		mode:			True on mousemove else undefined
		dragdone:		True/false when dragging, else undefined
		dX:				Delta x since last event in px, ONLY when dragging
		dY:				Delta y since last event in px, ONLY when dragging
		delta:			+/-0 ScrollWheel
		pageX:			pageX from event only when dragging
		pageY:			pageY from event only when dragging
		touch:			True when using touch
		
		pinch: {		Touch input with 2 fingers -> pinch gesture
			down:		Started pinch gesture
			dX:			Delta x of center point between both fingers
			dY:			Delta y of center point between both fingers
			absX:		Position of center point in px, element is the origin
			absY:		Position of center point in px, element is the origin
			dZoom:		Change of the zoom factor since the pinch/zoom gesture started (initially 1)
		}
	}
	*/
    LIB.attachMouseListener = function (el, callback) {
        var requested, inputData, wheel;
        if (!el || !callback) {
            return false;
        }
        requested = false;
        inputData = [];

        function handleInput() {
            while (inputData.length > 0) {
                callback(inputData.shift());
            }
            requested = false;
        }

        function push(p) {
            inputData.push(p);
            if (!requested) {
                requestAnimFrame(handleInput);
                requested = true;
            }
        }

        function limit(val) {
            return Math.max(0, Math.min(1, val));
        }
        el.onmouseover = function (event) {
            var offset, x, y;
            if (document.onmousemove) {
                return false;
            }
            offset = LIB.getGlobalOff(el);
            x = event.pageX - offset.x;
            y = event.pageY - offset.y;
            push({
                event: event,
                x: limit(x / el.offsetWidth),
                y: limit(y / el.offsetHeight),
                over: true,
                absX: x,
                absY: y
            });
            return false;
        };
        el.onmouseout = function (event) {
            var offset, x, y;
            if (document.onmousemove) {
                return false;
            }
            offset = LIB.getGlobalOff(el);
            x = event.pageX - offset.x;
            y = event.pageY - offset.y;
            push({
                event: event,
                x: limit(x / el.offsetWidth),
                y: limit(y / el.offsetHeight),
                out: true,
                absX: x,
                absY: y
            });
            return false;
        };
        el.onmousemove = function (event) {
            var offset, x, y;
            if (document.onmousemove) {
                return false;
            }
            offset = LIB.getGlobalOff(el);
            x = event.pageX - offset.x;
            y = event.pageY - offset.y;
            push({
                event: event,
                x: limit(x / el.offsetWidth),
                y: limit(y / el.offsetHeight),
                move: true,
                absX: x,
                absY: y
            });
            return false;
        };
        el.onmousedown = function (event) {
            var offset, x, y, lastX, lastY, buttoncode;
            offset = LIB.getGlobalOff(el);
            x = event.pageX - offset.x;
            y = event.pageY - offset.y;
            lastX = x;
            lastY = y;
            buttoncode = event.button;
            push({
                event: event,
                x: limit(x / el.offsetWidth),
                y: limit(y / el.offsetHeight),
                dragdone: false,
                absX: x,
                absY: y,
                code: buttoncode,
                dX: 0,
                dY: 0,
                down: true
            });
            document.onmousemove = function (event) {
                x = event.pageX - offset.x;
                y = event.pageY - offset.y;
                push({
                    event: event,
                    x: limit(x / el.offsetWidth),
                    y: limit(y / el.offsetHeight),
                    dragdone: false,
                    absX: x,
                    absY: y,
                    code: buttoncode,
                    dX: x - lastX,
                    dY: y - lastY,
                    pageX: event.pageX,
                    pageY: event.pageY
                });
                lastX = x;
                lastY = y;
                return false;
            };
            document.onmouseup = function (event) {
                push({
                    event: event,
                    x: limit(x / el.offsetWidth),
                    y: limit(y / el.offsetHeight),
                    dragdone: true,
                    absX: x,
                    absY: y,
                    code: buttoncode
                });
                document.onmousemove = undefined;
                document.onmouseup = undefined;
                return false;
            };
            return false;
        };

        function handlePinch(event) {
            var offset, x, y, lastX, lastY, dist, initialDist;
            offset = LIB.getGlobalOff(el);
            x = 0.5 * (event.touches[0].pageX - offset.x + event.touches[1].pageX - offset.x);
            y = 0.5 * (event.touches[0].pageY - offset.y + event.touches[1].pageY - offset.y);
            lastX = x;
            lastY = y;
            dist = LIB.Vec2.dist({
                x: event.touches[0].pageX,
                y: event.touches[0].pageY
            }, {
                x: event.touches[1].pageX,
                y: event.touches[1].pageY
            });
            initialDist = dist;
            push({
                event: event,
                pinch: {
                    down: true,
                    absX: x,
                    absY: y
                },
                absX: event.touches[0].pageX - offset.x,
                absY: event.touches[0].pageY - offset.y,
                touch: true
            });
            document.ontouchmove = function (event) {
                if (event.touches.length !== 2) {
                    return false;
                }
                x = 0.5 * (event.touches[0].pageX - offset.x + event.touches[1].pageX - offset.x);
                y = 0.5 * (event.touches[0].pageY - offset.y + event.touches[1].pageY - offset.y);
                dist = LIB.Vec2.dist({
                    x: event.touches[0].pageX,
                    y: event.touches[0].pageY
                }, {
                    x: event.touches[1].pageX,
                    y: event.touches[1].pageY
                });
                push({
                    event: event,
                    pinch: {
                        dX: x - lastX,
                        dY: y - lastY,
                        absX: x,
                        absY: y,
                        dZoom: dist / initialDist
                    },
                    absX: event.touches[0].pageX - offset.x,
                    absY: event.touches[0].pageY - offset.y,
                    touch: true
                });
                lastX = x;
                lastY = y;
                return false;
            };
        }
        el.ontouchstart = function (event) {
            var offset, x, y, lastX, lastY;
            if (event.touches.length === 2) {
                handlePinch(event);
                return false;
            }
            if (event.touches.length > 2) {
                return false;
            }
            offset = LIB.getGlobalOff(el);
            x = event.touches[0].pageX - offset.x;
            y = event.touches[0].pageY - offset.y;
            lastX = x;
            lastY = y;
            push({
                event: event,
                x: limit(x / el.offsetWidth),
                y: limit(y / el.offsetHeight),
                dragdone: false,
                absX: x,
                absY: y,
                code: 0,
                down: true,
                dX: 0,
                dY: 0,
                touch: true
            });
            document.ontouchmove = function (event) {
                if (event.touches.length !== 1) {
                    return false;
                }
                x = event.touches[0].pageX - offset.x;
                y = event.touches[0].pageY - offset.y;
                push({
                    event: event,
                    x: limit(x / el.offsetWidth),
                    y: limit(y / el.offsetHeight),
                    dragdone: false,
                    absX: x,
                    absY: y,
                    code: 0,
                    dX: x - lastX,
                    dY: y - lastY,
                    touch: true
                });
                lastX = x;
                lastY = y;
                return false;
            };
            document.ontouchend = function (event) {
                push({
                    event: event,
                    x: limit(x / el.offsetWidth),
                    y: limit(y / el.offsetHeight),
                    dragdone: true,
                    absX: x,
                    absY: y,
                    code: 0,
                    dX: x - lastX,
                    dY: y - lastY,
                    touch: true
                });
                document.ontouchmove = undefined;
                document.ontouchend = undefined;
                return false;
            };

            return false;
        };
        wheel = function (event) {
            var delta, offset, x, y;
            delta = 0;
            if (!event) {
                event = window.event;
            }
            if (event.wheelDelta) {
                delta = event.wheelDelta / 120;
            } else if (event.detail) {
                delta = -event.detail / 3;
            }
            offset = LIB.getGlobalOff(el);
            x = event.pageX - offset.x;
            y = event.pageY - offset.y;
            push({
                event: event,
                x: limit(x / el.offsetWidth),
                y: limit(y / el.offsetHeight),
                delta: delta,
                absX: x,
                absY: y
            });
            return false;
        };
        el.onmousewheel = wheel;
        el.addEventListener('DOMMouseScroll', wheel, false);
        return true;
    };
    /*LIB.CatmullLine()
	Figures out additional control points that create a fairly smooth and natural line in real time(as you add points)
	add(x, y)		add a point
	getPoints()		returns you all points with additional control points between them like this:
	[{x:, y: , c1: {x: , y: }, c2: {x: , y: }, c3: next}, next, ...]
	*/
    LIB.CatmullLine = function () {
        var path, tension;
		path = [];
        tension = 1;
		var temporary;
		this.temp = function(x, y) {
			temporary = {
				x: x,
				y: y
			};
		};
        this.add = function (x, y) {
            if(path.length === 0) { //otherwise the line won't start at the beginning
				path.push({
					x: x,
					y: y
				});
				temporary = undefined;
			}
			
			path.push({
                x: x,
                y: y
            });			
        };
        this.getPoints = function () {
            var length, result, n, p1, p2, p3, p4;
			length = path.length - 3;
            if (length <= 0) {
                return [];
            }

			if(temporary) {
				path.push(temporary);
				length++;
			}
            result = [];
            for (n = 0; n < path.length; n += 1) {
                p1 = path[n];
                p2 = path[Math.min(path.length - 1, n + 1)];
                p3 = path[Math.min(path.length - 1, n + 2)];
                p4 = path[Math.min(path.length - 1, n + 3)];
                if (n === 0) {
                    result[n] = {
                        x: p2.x,
                        y: p2.y
                    };
                }
                result[n].c1 = {
                    x: p2.x + (tension * p3.x - tension * p1.x) / 6,
                    y: p2.y + (tension * p3.y - tension * p1.y) / 6
                };
                result[n].c2 = {
                    x: p3.x + (tension * p2.x - tension * p4.x) / 6,
                    y: p3.y + (tension * p2.y - tension * p4.y) / 6
                };
                result[n].c3 = {
                    x: p3.x,
                    y: p3.y
                };
                result[n + 1] = {
                    x: p3.x,
                    y: p3.y
                };
            }
			if(temporary) {
				path.pop();
			}
            return result;
        };
    };
	/*LIB.drawPathOnCanvas(pathObject, context)
	Draws a path string from svg onto a canvas via its context. Changes the
	pathObject to store parsed drawing instructions for quicker drawing.
	pathObject = {
		d: <string>			The path string from SVG
		parsed: <string>	Already parsed string(optional -> but will be written if not given)
	}
	Note: Some shapes cause hanging. Obviously the parsing isn't perfect.
	*/
    LIB.drawPathOnCanvas = function (pathObject, context) {
        var path, lastpos, i, p, p1, p2, mode;
		if (!pathObject.parsed) {
            pathObject.parsed = pathObject.d.split(" ");
        }
        path = pathObject.parsed;
        lastpos = [0, 0];
        i = 0;
        p = [0, 0]; //temp pos
        context.beginPath();
        while (i < path.length) {
            if (path[i] === "m") {
                p = path[i + 1].split(",");
                lastpos[0] += parseFloat(p[0]);
                lastpos[1] += parseFloat(p[1]);
                context.moveTo(lastpos[0], lastpos[1]);
                i += 2;
                mode = undefined;
            } else if (path[i] === "M") {
                p = path[i + 1].split(",");
                lastpos[0] = parseFloat(p[0]);
                lastpos[1] = parseFloat(p[1]);
                context.moveTo(lastpos[0], lastpos[1]);
                i += 2;
                mode = undefined;
            } else if (path[i] === "c") {
                mode = "curveRel";
                i += 1;
            } else if (path[i] === "l") {
                mode = "linearRel";
                i += 1;
            } else if (path[i] === "C") {
                mode = "curve";
                i += 1;
            } else if (path[i] === "L") {
                mode = "linear";
                i += 1;
            } else if (path[i] === "z" || path[i] === "Z") {
                context.closePath();
                i += 1;
                mode = undefined;
            }
            if (mode === "curveRel") {
                p = path[i].split(",");
                p1 = path[i + 1].split(",");
                p2 = path[i + 2].split(",");
                p[0] = lastpos[0] + parseFloat(p[0]);
                p[1] = lastpos[1] + parseFloat(p[1]);
                p1[0] = p[0] + parseFloat(p1[0]);
                p1[1] = p[1] + parseFloat(p1[1]);
                lastpos[0] = p1[0] + parseFloat(p2[0]);
                lastpos[1] = p1[1] + parseFloat(p2[1]);
                context.bezierCurveTo(p[0], p[1], p1[0], p1[1], lastpos[0], lastpos[1]);
                i += 3;
            } else if (mode === "linearRel") {
                p = path[i].split(",");
                lastpos[0] += parseFloat(p[0]);
                lastpos[1] += parseFloat(p[1]);
                context.lineTo(lastpos[0], lastpos[1]);
                i += 1;
            } else if (mode === "curve") {
                p = path[i].split(",");
                p1 = path[i + 1].split(",");
                p2 = path[i + 2].split(",");
                p[0] = parseFloat(p[0]);
                p[1] = parseFloat(p[1]);
                p1[0] = parseFloat(p1[0]);
                p1[1] = parseFloat(p1[1]);
                lastpos[0] = parseFloat(p2[0]);
                lastpos[1] = parseFloat(p2[1]);
                context.bezierCurveTo(p[0], p[1], p1[0], p1[1], lastpos[0], lastpos[1]);
                i += 3;
            } else if (mode === "linear") {
                p = path[i].split(",");
                lastpos[0] = parseFloat(p[0]);
                lastpos[1] = parseFloat(p[1]);
                context.lineTo(lastpos[0], lastpos[1]);
                i += 1;
            }
        }
    };
	
	
	
    /*LIB.drawSVGToContext(p)
	A non manual way of drawing SVG shapes onto a canvas. Should yield more accurate results since
	the browser knows best how to draw SVG shapes.
	Works with Chrome. Some issues with Firefox. Doesn't really work with other browsers yet.
	Not sure how much overhead it causes(Blob, ObjectURL, Image,...).
	I would prefer having this as part of the canvas API: context.drawSVG(svgString or svgElement, 0, 0);
	p = {
		width: <number>			Width of the SVG viewport
		height: <number>		Height of the SVG viewport
		defs: <string>			Everything inside <defs>...</defs>
		shapes: <string>		Everything that follows <defs>
		context:				Context of the canvas you wish to draw on
		callback: <function()>	Gets called when the SVG is ready to be painted onto the canvas
	}
	*/
    LIB.drawSVGToContext = function (p) {
        var width, height, shapes, defs, context, callback, svgStart, svgString, DOMURL, blob, url, img;
        width = p.width;
        height = p.height;
        shapes = p.shapes;
        defs = p.defs;
        context = p.context;
        callback = p.callback;
        svgStart = '<?xml version="1.0" encoding="UTF-8" standalone="no"?> \n' + '<svg \n' + 'xmlns:dc="http://purl.org/dc/elements/1.1/" \n' + 'xmlns:cc="http://creativecommons.org/ns#" \n' + 'xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" \n' + 'xmlns:svg="http://www.w3.org/2000/svg" \n' + 'xmlns="http://www.w3.org/2000/svg" \n' + 'xmlns:xlink="http://www.w3.org/1999/xlink" \n' + 'width="' + width + '" \n' + 'height="' + height + '" \n' + 'id="svg3112" \n' + 'version="1.1">';
        svgString = svgStart + "<defs>" + defs + "</defs>" + shapes + "</svg>";
        DOMURL = window.URL || window.webkitURL || window;
        blob = new window.Blob([svgString], {
            "type": "image/svg+xml;charset=utf-8"
        });
        url = DOMURL.createObjectURL(blob);
        img = new Image();
        img.onload = function () {
            callback();
            context.drawImage(img, 0, 0);
            DOMURL.revokeObjectURL(url);
        };
        img.src = url;
    };

    /*LIB.Vec2
	Basic vector operations. Parameters and results are usually {x: , y: }
	*/
    LIB.Vec2 = {
        add: function (p1, p2) {
            return {
                x: p1.x + p2.x,
                y: p1.y + p2.y
            };
        },
        sub: function (p1, p2) {
            return {
                x: p1.x - p2.x,
                y: p1.y - p2.y
            };
        },
        nor: function (p) {
            var len = Math.sqrt(Math.pow(p.x, 2) + Math.pow(p.y, 2));
            if (len === 0) {
                return {
                    x: 0,
                    y: 0
                };
            }
            return {
                x: p.x / len,
                y: p.y / len
            };
        },
        len: function (p) {
            return Math.sqrt(Math.pow(p.x, 2) + Math.pow(p.y, 2));
        },
        dist: function (p1, p2) {
            return LIB.Vec2.len(LIB.Vec2.sub(p1, p2));
        },
        mul: function (p, s) {
            return {
                x: p.x * s,
                y: p.y * s
            };
        }
    };
	
	//b needs to fit a
	LIB.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};
	};
	
	//higher quality image resizing
	//w, h - target size
	LIB.resizeCanvas = function(canvas, w, h) {
		var tmp1, tmp2;
		
		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++;
			}
			
			tmp1 = document.createElement("canvas");
			tmp2 = document.createElement("canvas");
			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;
			
		} else if(w >= canvas.width && h >= canvas.height) {
			tmp1 = document.createElement("canvas");
			tmp2 = document.createElement("canvas");
			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;
			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++;
				
				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 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 {
			LIB.resizeCanvas(canvas, w, canvas.height);
			LIB.resizeCanvas(canvas, w, h);
		}
	};

})();