/*
Original code by MrDoob - http://mrdoob.com/91/Ball_Pool
Modified slightly by Dustin Haffner.

TODO:
- look into browser resize bug
*/
(function(ballpool, undefined) {
    var canvas;
    var delta = [0, 0];
    var stage = [window.screenX, window.screenY, window.innerWidth, window.innerHeight];
    getBrowserDimensions();


    var worldAABB, world, iterations = 1, timeStep = 1 / 20;
    var walls = [];
    var wall_thickness = 200;
    var wallsSetted = false;
    var bodies, elements, text;
    var dragging = false;
    var radii = {};
    
    var mouse = {};
    mouse.isMouseDown = false;
    mouse.mouseJoint = null;
    mouse.position = { x: 0, y: 0 };
    mouse.timeOfLastTouch = 0; // used in double click 'fake'
    
    var gravity = { x: 0, y: 1 };
    
    var PI2 = Math.PI * 2;
    
    ballpool.init = function(id) {
        canvas = document.getElementById(id);
        // add mouse listeners to canvas instead?
        document.onmousedown = mouse.onMouseDown;
        document.onmouseup = mouse.onMouseUp;
        document.onmousemove = mouse.onMouseMove;
        document.ondblclick = mouse.onDoubleClick;
        
        document.addEventListener('touchstart', mouse.onTouchStart, false);
        document.addEventListener('touchmove', mouse.onTouchMove, false);
        document.addEventListener('touchend', mouse.onTouchEnd, false);
        window.addEventListener('deviceorientation', mouse.onWindowDeviceOrientation, false);
        
        document.body.style.backgroundColor = '#FFF'; // hack
        
        // init box2d
        worldAABB = new b2AABB();
        worldAABB.minVertex.Set(-200, -200);
        worldAABB.maxVertex.Set(window.innerWidth + 200, window.innerHeight + 200);
        world = new b2World(worldAABB, new b2Vec2(0, 0), true);
        setWalls();
        ballpool.reset();   
    };
    
    ballpool.play = function() {
        setInterval(loop, 1000 / 40);
    };
    
    ballpool.pause = function() {
        // TODO
    };
    
    ballpool.reset = function() {
        var i;
        if (bodies) {
            for (i = 0; i < bodies.length; i++) {
                var body = bodies[i];
                canvas.removeChild(body.GetUserData().element);
                world.DestroyBody(body);
                body = null;
            }
        }
        bodies = [];
        elements = [];
    };
    
    // add size / color options
    ballpool.create = function(label, x, y) {
        var x = x || Math.random() * stage[2];
        var y = y || Math.random() * -200;
        var size = ballpool.size(label);
        var colors = ballpool.color(label);
        var circles = ballpool.circles(label);

        var element = document.createElement("canvas");
        element.width = size;
        element.height = size;
        element.style['position'] = 'absolute';
        element.style['left'] = -200 + 'px';
        element.style['top'] = -200 + 'px';

        var graphics = element.getContext("2d");

        for (var i = size, s = 0; i > 0; i -= (size / circles)) {
            graphics.fillStyle = colors[s];
            graphics.beginPath();
            graphics.arc(size * .5, size * .5, i * .5, 0, PI2, true);
            graphics.closePath();
            graphics.fill();
            s = ++s % colors.length;
        }

        canvas.appendChild(element);
        elements.push(element);

        var circle = new b2CircleDef();
        circle.radius = size >> 1;
        circle.density = 1;
        circle.friction = 0.3;
        circle.restitution = 0.3;
        radii[label] = circle.radius;

        var b2body = new b2BodyDef();
        b2body.AddShape(circle);
        b2body.userData = {
            element: element,
            label: label
        };
        b2body.position.Set(x, y);
        b2body.linearVelocity.Set(Math.random() * 400 - 200, Math.random() * 400 - 200);
        bodies.push(world.CreateBody(b2body));
    };
    
    // Called when a ball moves.
    ballpool.move = function(label, x, y) { };
    
    // Called when a click occurs. 'label' param may be null.
    ballpool.click = function(x, y, label) {
        return false;
    };
    
    // Called when the background is doubleclicked
    ballpool.doubleclick = function(x, y, label) { };
    
    // Called when a mouse drag begins
    ballpool.dragstart = function(x, y) { };

    // Called when a mouse drag ends
    ballpool.dragend = function(x, y) { };    

    // Called per-frame as the mouse is being dragged
    ballpool.drag = function(x, y) {
        return false;
    };
    
    // Called during a ball's creation.
    // Return the desired colors of a ball with the given label.
    ballpool.color = function(label) {
        return ['#000000', '#ffffff'];
    };
    
    // Called during a ball's creation.
    // Return the intended number of circles inide the ball.
    ballpool.circles = function(label) {
        return (Math.random() * 10 >> 0);
    };
    
    // Called during a ball's creation.
    // Return the size of the ball.
    ballpool.size = function(label) {
        return (Math.random() * 80 >> 0) + 40;
    };

    ballpool.width = function() {
        return stage[2];
    };
    
    ballpool.height = function() {
        return stage[3];
    };    

    ballpool.radius = function(label) {
        return radii[label];
    };

    //
    mouse.onMouseDown = function() {
        mouse.isMouseDown = true;
        var body = getBodyAtMouse();
        return ballpool.click(mouse.position.x, mouse.position.y,
                              (body? body.GetUserData().label : null));       
    };

    mouse.onMouseUp = function() {
        mouse.isMouseDown = false;
        return true;
    };

    mouse.onMouseMove = function(event) {
        mouse.position.x = event.clientX;
        mouse.position.y = event.clientY;
    };

    mouse.onDoubleClick = function() {
        var body = getBodyAtMouse(), label;
        if (body)
            label = body.GetUserData().label;
        ballpool.doubleclick(mouse.position.x, mouse.position.y, label);
    };

    mouse.onTouchStart = function(event) {
        if (event.touches.length == 1) {
            event.preventDefault();
            // Faking double click for touch devices
            var now = new Date().getTime();
            if (now - mouse.timeOfLastTouch < 250) {
                mouse.onDoubleClick();
                return;
            }
            
            mouse.timeOfLastTouch = now;
            mouse.position.x = event.touches[0].pageX;
            mouse.position.y = event.touches[0].pageY;
            mouse.onMouseDown();
        }
    };

    mouse.onTouchMove = function(event) {
        if (event.touches.length == 1) {
            event.preventDefault();
            mouse.position.x = event.touches[0].pageX;
            mouse.position.y = event.touches[0].pageY;
        }
    };

    mouse.onTouchEnd = function(event) {
        if (event.touches.length == 0) {
            event.preventDefault();
            mouse.isMouseDown = false;
        }
    };

    mouse.onWindowDeviceOrientation = function(event) {
        if (event.beta) {
            gravity.x = Math.sin(event.gamma * Math.PI / 180);
            gravity.y = Math.sin((Math.PI / 4) + event.beta * Math.PI / 180);
        }
    };
    
    function createBox(world, x, y, width, height, fixed) {
        if (typeof (fixed) == 'undefined') {
            fixed = true;
        }
        var boxSd = new b2BoxDef();
        if (!fixed) {
            boxSd.density = 1.0;
        }
        boxSd.extents.Set(width, height);
        var boxBd = new b2BodyDef();
        boxBd.AddShape(boxSd);
        boxBd.position.Set(x, y);
        return world.CreateBody(boxBd);
    }
    
    function mouseDrag() {
        // mouse press        
        if (dragging && ballpool.drag(mouse.position.x, mouse.position.y)) {    
        } else if (mouse.isMouseDown && !mouse.mouseJoint) {
            var body = getBodyAtMouse();
            if (body) {
                var md = new b2MouseJointDef();
                md.body1 = world.m_groundBody;
                md.body2 = body;
                md.target.Set(mouse.position.x, mouse.position.y);
                md.maxForce = 30000 * body.m_mass;
                md.timeStep = timeStep;
                mouse.mouseJoint = world.CreateJoint(md);
                body.WakeUp();
            } else {
                dragging = ballpool.dragstart(mouse.position.x, mouse.position.y);
            }
        }
        
        // mouse release
        if (!mouse.isMouseDown) {
            if (dragging) {
                dragging = false;
                ballpool.dragend(mouse.position.x, mouse.position.y);
            }
            if (mouse.mouseJoint) {
                world.DestroyJoint(mouse.mouseJoint);
                mouse.mouseJoint = null;
            }
        }
        
        // mouse move
        if (mouse.mouseJoint) {
            var p2 = new b2Vec2(mouse.position.x, mouse.position.y);
            mouse.mouseJoint.SetTarget(p2);
        }
    }

    function getBodyAtMouse() {
        // Make a small box.
        var mousePVec = new b2Vec2();
        mousePVec.Set(mouse.position.x, mouse.position.y);
        var aabb = new b2AABB();
        aabb.minVertex.Set(mouse.position.x - 1, mouse.position.y - 1);
        aabb.maxVertex.Set(mouse.position.x + 1, mouse.position.y + 1);
        // Query the world for overlapping shapes.
        var k_maxCount = 10;
        var shapes = new Array();
        var count = world.Query(aabb, shapes, k_maxCount);
        var body = null;
        for (var i = 0; i < count; ++i) {
            if (shapes[i].m_body.IsStatic() == false) {
                if (shapes[i].TestPoint(mousePVec)) {
                    body = shapes[i].m_body;
                    break;
                }
            }
        }
        return body;
    }
    
    function loop() {
        if (getBrowserDimensions()) {
            setWalls();
        }
        delta[0] += (0 - delta[0]) * .5;
        delta[1] += (0 - delta[1]) * .5;
        world.m_gravity.x = gravity.x * 350 + delta[0];
        world.m_gravity.y = gravity.y * 350 + delta[1];
        mouseDrag();
        world.Step(timeStep, iterations);
        for (i = 0; i < bodies.length; i++) {
            var body = bodies[i];
            var element = elements[i];
            element.style.left = (body.m_position0.x - (element.width >> 1)) + 'px';
            element.style.top = (body.m_position0.y - (element.height >> 1)) + 'px';
            
            if (ballpool.move)
                ballpool.move(body.GetUserData().label, body.m_position0.x, body.m_position0.y);
            if (element.tagName == 'DIV') {
                var rotationStyle = 'rotate(' + (body.m_rotation0 * 57.2957795) + 'deg)';
                text.style.WebkitTransform = rotationStyle;
                text.style.MozTransform = rotationStyle;
                text.style.OTransform = rotationStyle;
                text.style.msTransform = rotationStyle;
            }
        }        
    }
        
    function setWalls() {
        if (wallsSetted) {
            world.DestroyBody(walls[0]);
            world.DestroyBody(walls[1]);
            world.DestroyBody(walls[2]);
            world.DestroyBody(walls[3]);
            walls[0] = null;
            walls[1] = null;
            walls[2] = null;
            walls[3] = null;
        }
        walls[0] = createBox(world, stage[2] / 2, -wall_thickness, stage[2], wall_thickness);
        walls[1] = createBox(world, stage[2] / 2, stage[3] + wall_thickness, stage[2], wall_thickness);
        walls[2] = createBox(world, -wall_thickness, stage[3] / 2, wall_thickness, stage[3]);
        walls[3] = createBox(world, stage[2] + wall_thickness, stage[3] / 2, wall_thickness, stage[3]);
        wallsSetted = true;
    }
  
    function getBrowserDimensions() {
        var changed = false;
        if (stage[0] != window.screenX) {
            delta[0] = (window.screenX - stage[0]) * 50;
            stage[0] = window.screenX;
            changed = true;
        }
        if (stage[1] != window.screenY) {
            delta[1] = (window.screenY - stage[1]) * 50;
            stage[1] = window.screenY;
            changed = true;
        }
        if (stage[2] != window.innerWidth) {
            stage[2] = window.innerWidth;
            changed = true;
        }
        if (stage[3] != window.innerHeight) {
            stage[3] = window.innerHeight;
            changed = true;
        }
        return changed;
    }
})(window.ballpool = window.ballpool || {}, 'canvas');	


