package {
    
    import Box2D.Collision.Shapes.b2BoxDef;
    import Box2D.Collision.Shapes.b2CircleShape;
    import Box2D.Collision.Shapes.b2PolyShape;
    import Box2D.Collision.Shapes.b2Shape;
    import Box2D.Collision.b2AABB;
    import Box2D.Common.Math.b2Math;
    import Box2D.Common.Math.b2Vec2;
    import Box2D.Dynamics.Joints.b2DistanceJointDef;
    import Box2D.Dynamics.Joints.b2Joint;
    import Box2D.Dynamics.Joints.b2PrismaticJointDef;
    import Box2D.Dynamics.Joints.b2PulleyJoint;
    import Box2D.Dynamics.b2Body;
    import Box2D.Dynamics.b2BodyDef;
    import Box2D.Dynamics.b2World;
    
    import flash.display.Sprite;
    import flash.display.StageQuality;
    import flash.display.StageScaleMode;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.utils.getTimer;

    [SWF(width=640,height=360,backgroundColor=0x404040,frameRate=120)]

    public class Box2dAttractTest extends Sprite {
        
        public static const m_iterations:int = 10;
        public static const m_timeStep:Number = 1 / 30;
        
        private var _canvas:Sprite;
        private var _world:b2World;
        private var _targets:Array;
        private var _mouseDown:Boolean;
        
        public function Box2dAttractTest() {
            this.stage.scaleMode = StageScaleMode.NO_SCALE;
            this.stage.quality = StageQuality.LOW;
            
            this._canvas = new Sprite();
            this.addChild(this._canvas);
            this.buildWorld();
            this.addEventListener(Event.ENTER_FRAME, update, false, 0, true);
            
            this.stage.addEventListener(MouseEvent.MOUSE_DOWN, function (e:MouseEvent):void {
                _mouseDown = true;
            });
            this.stage.addEventListener(MouseEvent.MOUSE_UP, function (e:MouseEvent):void {
                _mouseDown = false;
            });
        }
        
        public function buildWorld():void {
            var worldAABB:b2AABB = new b2AABB();
            worldAABB.minVertex.Set(-1000.0, -1000.0);
            worldAABB.maxVertex.Set(1000.0, 1000.0);
            
            // Define the gravity vector
            var gravity:b2Vec2 = new b2Vec2(0.0, 100.0);
            // Allow bodies to sleep
            var doSleep:Boolean = false;
            // Construct a world object
            this._world = new b2World(worldAABB, gravity, doSleep);
            
            // Create border of boxes
            var wallSd:b2BoxDef = new b2BoxDef();
            var wallBd:b2BodyDef = new b2BodyDef();
            wallBd.AddShape(wallSd);
            
            // Left
            wallSd.extents.Set(100, 400 / 2);
            wallBd.position.Set(-95, 360 / 2);
            this._world.CreateBody(wallBd);
            // Right
            wallBd.position.Set(640 + 95, 360 / 2);
            this._world.CreateBody(wallBd);
            // Top
            wallSd.extents.Set(680/2, 100);
            wallBd.position.Set(640/2, -95);
            this._world.CreateBody(wallBd);
            // Right
            wallBd.position.Set(640/2, (360+95));
            this._world.CreateBody(wallBd);
            
            this._targets = [];

            const n:int = 50;
            while (n--) {
                wallSd.extents.Set(Math.random() * 10 + 2, Math.random() * 30 + 5);
                wallSd.density = 1;
                wallSd.restitution = 0.3;
                wallBd.position.Set(320 + (Math.random() - 0.5) * 200, 180 + (Math.random() - 0.5) * 200);
                wallBd.rotation = Math.random() * Math.PI;
                this._targets.push(this._world.CreateBody(wallBd));
            }
        }
        
        public function update(e:Event):void {
            // clear for rendering
            this._canvas.graphics.clear()
            
            if (this._mouseDown) {
                var mp:b2Vec2;
                for each (var t:b2Body in this._targets) {
                    mp = new b2Vec2(this.mouseX, this.mouseY)
                    mp.Subtract(t.m_position);
                    var d:Number = mp.Length();
                    d = Math.max(20, Math.min(300, d));
                    var f:Number = -1 / (d * d) * (-d / 300 + 1) * 1e+9;
                    mp.Normalize();
                    mp.Multiply(f);
                    t.ApplyForce(mp, t.m_position);
                    t.WakeUp();
                }
            }
            
            // Update physics
            var physStart:uint = getTimer();
            this._world.Step(m_timeStep, m_iterations);
            
            // Render
            // joints
            for (var jj:b2Joint = this._world.m_jointList; jj; jj = jj.m_next){
                DrawJoint(jj);
            }
            // bodies
            for (var bb:b2Body = this._world.m_bodyList; bb; bb = bb.m_next){
                for (var s:b2Shape = bb.GetShapeList(); s != null; s = s.GetNext()){
                    DrawShape(s);
                }
            }
        }
        
        //======================
        // Draw Shape 
        //======================
        public function DrawShape(shape:b2Shape):void{
            switch (shape.m_type)
            {
            case b2Shape.e_circleShape:
                {
                    var circle:b2CircleShape = shape as b2CircleShape;
                    var pos:b2Vec2 = circle.m_position;
                    var r:Number = circle.m_radius;
                    var k_segments:Number = 16.0;
                    var k_increment:Number = 2.0 * Math.PI / k_segments;
                    this._canvas.graphics.lineStyle(1,0xffffff,1);
                    this._canvas.graphics.moveTo((pos.x + r), (pos.y));
                    var theta:Number = 0.0;
                    
                    for (var i:int = 0; i < k_segments; ++i)
                    {
                        var d:b2Vec2 = new b2Vec2(r * Math.cos(theta), r * Math.sin(theta));
                        var v:b2Vec2 = b2Math.AddVV(pos , d);
                        this._canvas.graphics.lineTo((v.x), (v.y));
                        theta += k_increment;
                    }
                    this._canvas.graphics.lineTo((pos.x + r), (pos.y));
                    
                    this._canvas.graphics.moveTo((pos.x), (pos.y));
                    var ax:b2Vec2 = circle.m_R.col1;
                    var pos2:b2Vec2 = new b2Vec2(pos.x + r * ax.x, pos.y + r * ax.y);
                    this._canvas.graphics.lineTo((pos2.x), (pos2.y));
                }
                break;
            case b2Shape.e_polyShape:
                {
                    var poly:b2PolyShape = shape as b2PolyShape;
                    var tV:b2Vec2 = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[i]));
                    this._canvas.graphics.lineStyle(1,0xffffff,1);
                    this._canvas.graphics.moveTo(tV.x, tV.y);
                    
                    for (i = 0; i < poly.m_vertexCount; ++i)
                    {
                        v = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[i]));
                        this._canvas.graphics.lineTo(v.x, v.y);
                    }
                    this._canvas.graphics.lineTo(tV.x, tV.y);
                }
                break;
            }
        }
        
        
        //======================
        // Draw Joint 
        //======================
        public function DrawJoint(joint:b2Joint):void
        {
            var b1:b2Body = joint.m_body1;
            var b2:b2Body = joint.m_body2;
            
            var x1:b2Vec2 = b1.m_position;
            var x2:b2Vec2 = b2.m_position;
            var p1:b2Vec2 = joint.GetAnchor1();
            var p2:b2Vec2 = joint.GetAnchor2();
            
            this._canvas.graphics.lineStyle(1,0x44aaff,1/1);
            
            switch (joint.m_type)
            {
            case b2Joint.e_distanceJoint:
            case b2Joint.e_mouseJoint:
                this._canvas.graphics.moveTo(p1.x, p1.y);
                this._canvas.graphics.lineTo(p2.x, p2.y);
                break;
                
            case b2Joint.e_pulleyJoint:
                var pulley:b2PulleyJoint = joint as b2PulleyJoint;
                var s1:b2Vec2 = pulley.GetGroundPoint1();
                var s2:b2Vec2 = pulley.GetGroundPoint2();
                this._canvas.graphics.moveTo(s1.x, s1.y);
                this._canvas.graphics.lineTo(p1.x, p1.y);
                this._canvas.graphics.moveTo(s2.x, s2.y);
                this._canvas.graphics.lineTo(p2.x, p2.y);
                break;
                
            default:
                if (b1 == this._world.m_groundBody){
                    this._canvas.graphics.moveTo(p1.x, p1.y);
                    this._canvas.graphics.lineTo(x2.x, x2.y);
                }
                else if (b2 == this._world.m_groundBody){
                    this._canvas.graphics.moveTo(p1.x, p1.y);
                    this._canvas.graphics.lineTo(x1.x, x1.y);
                }
                else{
                    this._canvas.graphics.moveTo(x1.x, x1.y);
                    this._canvas.graphics.lineTo(p1.x, p1.y);
                    this._canvas.graphics.lineTo(x2.x, x2.y);
                    this._canvas.graphics.lineTo(p2.x, p2.y);
                }
            }
        }        
        
    }
    
}