package {
    import Box2D.Collision.Shapes.b2CircleDef;
    import Box2D.Collision.Shapes.b2PolygonDef;
    import Box2D.Collision.Shapes.b2Shape;
    import Box2D.Collision.b2AABB;
    import Box2D.Common.Math.b2Vec2;
    import Box2D.Dynamics.Joints.b2MouseJoint;
    import Box2D.Dynamics.Joints.b2MouseJointDef;
    import Box2D.Dynamics.Joints.b2RevoluteJointDef;
    import Box2D.Dynamics.b2Body;
    import Box2D.Dynamics.b2BodyDef;
    import Box2D.Dynamics.b2World;

    import General.FpsCounter;
    import General.Input;

    import flash.display.Sprite;
    import flash.events.Event;
    import flash.text.TextField;
    import flash.text.TextFieldAutoSize;
    import flash.text.TextFormat;
    import flash.utils.getTimer;

    [SWF(width=640, height=480, backgroundColor=0x666666, frameRate=30)]
    public class FallBall extends Sprite
    {
        public static var SCREEN_C:uint = 0x666666,
                          SCREEN_W:int = 640,
                          SCREEN_H:int = 480,
                          NX_SCALE:Number = 1.0,
                          NX_ITER:Number = 10,
                          NX_STEP:Number = 1/30,
                          NX_BALL_NUM:int = 10,
                          NX_BALL_R:int = 10;

        private var base:Sprite;
        private var balls:Array;
        private var world:b2World;
        private var mousejoint:b2MouseJoint;
        private var mousepos:b2Vec2 = new b2Vec2;
        private var mousepos_scale:b2Vec2 = new b2Vec2;
        private var input:Input;
        static public var fpsCounter:FpsCounter = new FpsCounter();
        private var tf:TextField;
        private var is_start:Boolean = false;

        public function FallBall()
        {
            initFallBase();
            initFallWorld();
        }

        private function initFallBase():void
        {
            // ベース画面設定
            this.base = new Sprite();
            this.addChild( this.base );

            // 入力初期化
            this.input = new Input( this.base );

            // 文字
            var fmt:TextFormat = new TextFormat();
            fmt.size = 50;
            this.tf = new TextField();
            this.tf.text = "Click Start";
            this.tf.autoSize = TextFieldAutoSize.CENTER;
            this.tf.x = SCREEN_W / 2 - tf.width / 2;
            this.tf.y = SCREEN_H / 2 - tf.height / 2;
            this.tf.setTextFormat( fmt );
            this.base.addChild( this.tf );

            this.is_start = false;

            // fps
            fpsCounter.x = 15;
            fpsCounter.y = 30;
            this.addChildAt( fpsCounter, 0 );

            // イベント設定
            this.base.addEventListener( Event.ENTER_FRAME, procFallBall, false, 0, true );
        }

        /**
         * @brief   ワールド初期化
         */
        private function initFallWorld():void
        {
            // 衝突の世界を構築
            // 判定パラメータ設定
            var worldaabb:b2AABB = new b2AABB();
            worldaabb.lowerBound.Set( -1000.0, -1000.0 );
            worldaabb.upperBound.Set( 1000.0, 1000.0 );
            // 重力設定
            var gravity:b2Vec2 = new b2Vec2( 0.0, 300.0 );
            // 判定外のオブジェクトをスリープさせるか
            var doSleep:Boolean = true;
            this.world = new b2World( worldaabb, gravity, doSleep );

            /*
            // デバッグ表示
            var dbgDraw:b2DebugDraw = new b2DebugDraw();
            dbgDraw.m_sprite = this.base;
            dbgDraw.m_drawScale = NX_SCALE;
            dbgDraw.m_fillAlpha = 0.8;
            dbgDraw.m_lineThickness = 1.0;
            dbgDraw.m_drawFlags = b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit;
            this.world.SetDebugDraw(dbgDraw);
            */
        }

        /**
         * @brief   ボール初期化
         */
        private function initFallBall():void
        {
            // ボール格納先
            if( this.balls == null )
            {
                this.balls = new Array();
            }

            var color:Array = [ 0xf0f0f0, 0x232323 ];
            var wpos:int = SCREEN_W;
            var hpos:int = 50;
            var ballshape:b2CircleDef = new b2CircleDef();
            var ballbody:b2BodyDef = new b2BodyDef();

            for( var i:int = 0; i < NX_BALL_NUM; ++i )
            {
                // シェイプを作成
                ballshape.density = 2;
                ballshape.restitution = 0.5;
                ballshape.friction = 0.2;
                ballshape.radius = (Math.random() * NX_BALL_R + NX_BALL_R) / NX_SCALE;
                // ボディを作成
                ballbody.position.Set( (Math.random()*wpos-20 + 20) / NX_SCALE, (Math.random()*hpos+hpos) / NX_SCALE );
                // 表示部分を作成
                var ballview:Sprite = new Sprite();
                ballview.graphics.beginFill( color[Math.floor( Math.random() * color.length )] );
                ballview.graphics.drawCircle( 0, 0, ballshape.radius * NX_SCALE );
                ballview.graphics.endFill();
                ballview.x = ballbody.position.x * NX_SCALE;
                ballview.y = ballbody.position.y * NX_SCALE;
                this.base.addChild( ballview );
                // 実体を作成
                var ball:b2Body = this.world.CreateBody( ballbody );
                ball.SetUserData( ballview );
                ball.CreateShape( ballshape );
                ball.SetMassFromShapes();

                this.balls.push( ball );
            }

            // 100個までに制限
            while( this.balls.length > 100 )
            {
                var destroybody:b2Body = b2Body( this.balls.shift() );
                var destroyview:Sprite = Sprite( destroybody.GetUserData() );
                this.world.DestroyBody( destroybody );
                this.base.removeChild( destroyview );
            }
        }

        /**
         * @brief   床や障害物を初期化
         */
        private function initFallWall():void
        {
            var polyshape:b2PolygonDef = new b2PolygonDef();
            var polybody:b2BodyDef = new b2BodyDef();
            var polyview:Sprite;
            var poly:b2Body;
            var color:uint = 0xe0e0e0;
            var alpha:Number = 0.7;
            var i:int;

            // 床 (bottom)
            polyshape.SetAsBox( SCREEN_W/2/NX_SCALE , 10/2/NX_SCALE );
            polybody.position.Set( SCREEN_W/2/NX_SCALE, (SCREEN_H-5)/NX_SCALE );
            // 表示部分を作成
            polyview = new Sprite();
            polyview.graphics.beginFill( color, alpha );
            polyview.graphics.drawRect( -SCREEN_W/2, -5, SCREEN_W, 10 );
            polyview.graphics.endFill();
            polyview.x = polybody.position.x * NX_SCALE;
            polyview.y = polybody.position.y * NX_SCALE;
            this.base.addChild( polyview );
            // 実体を作成
            poly = this.world.CreateBody( polybody );
            poly.CreateShape( polyshape );
            poly.SetMassFromShapes();

            // 床 (top)
            polyshape.SetAsBox( SCREEN_W/2/NX_SCALE , 10/2/NX_SCALE );
            polybody.position.Set( SCREEN_W/2/NX_SCALE, 5/NX_SCALE );
            // 表示部分を作成
            polyview = new Sprite();
            polyview.graphics.beginFill( color, alpha );
            polyview.graphics.drawRect( -SCREEN_W/2, -5, SCREEN_W, 10 );
            polyview.graphics.endFill();
            polyview.x = polybody.position.x * NX_SCALE;
            polyview.y = polybody.position.y * NX_SCALE;
            this.base.addChild( polyview );
            // 実体を作成
            poly = this.world.CreateBody( polybody );
            poly.CreateShape( polyshape );
            poly.SetMassFromShapes();

            // 床 (left)
            polyshape.SetAsBox( 10/2/NX_SCALE , SCREEN_H/2/NX_SCALE );
            polybody.position.Set( 5/NX_SCALE, SCREEN_H/2/NX_SCALE );
            // 表示部分を作成
            polyview = new Sprite();
            polyview.graphics.beginFill( color, alpha );
            polyview.graphics.drawRect( -5, -SCREEN_H/2, 10, SCREEN_H );
            polyview.graphics.endFill();
            polyview.x = polybody.position.x * NX_SCALE;
            polyview.y = polybody.position.y * NX_SCALE;
            this.base.addChild( polyview );
            // 実体を作成
            poly = this.world.CreateBody( polybody );
            poly.CreateShape( polyshape );
            poly.SetMassFromShapes();

            // 床 (right)
            polyshape.SetAsBox( 10/2/NX_SCALE , SCREEN_H/2/NX_SCALE );
            polybody.position.Set( (SCREEN_W-5)/NX_SCALE, SCREEN_H/2/NX_SCALE );
            // 表示部分を作成
            polyview = new Sprite();
            polyview.graphics.beginFill( color, alpha );
            polyview.graphics.drawRect( -5, -SCREEN_H/2, 10, SCREEN_H );
            polyview.graphics.endFill();
            polyview.x = polybody.position.x * NX_SCALE;
            polyview.y = polybody.position.y * NX_SCALE;
            this.base.addChild( polyview );
            // 実体を作成
            poly = this.world.CreateBody( polybody );
            poly.CreateShape( polyshape );
            poly.SetMassFromShapes();

            // たれさがる物体
            // ジョイント設定
            var joint_num:int = 7;
            var leftsize_w:int = 48;
            var leftsize_h:int = 15;
            var initial_w:int = SCREEN_W - leftsize_w * joint_num;
            var initial_h:int = 300;
            var resize_val:Number = 0.9;
            var ground:b2Body = this.world.GetGroundBody();
            var prev:b2Body = ground;
            var anchor:b2Vec2 = new b2Vec2();

            // シェイプを作成
            polyshape.density = 5;
            polyshape.friction = Math.random();
            polyshape.SetAsBox( leftsize_w/2/NX_SCALE, leftsize_h/2/NX_SCALE );
            // ジョインを作成
            var joint:b2RevoluteJointDef = new b2RevoluteJointDef();
            joint.enableLimit = true;
            joint.lowerAngle = -15 * Math.PI/180.0;
            joint.upperAngle = 15 * Math.PI/180.0;
            for( i = 0; i < joint_num; ++i )
            {
                // ボディを作成
                polybody.position.Set( (initial_w + (i*leftsize_w*resize_val + leftsize_w/2))/NX_SCALE, (leftsize_h + initial_h)/NX_SCALE );
                // 表示部分を作成
                polyview = new Sprite();
                polyview.graphics.beginFill( color, alpha );
                polyview.graphics.drawRect( -leftsize_w/2, -leftsize_h/2, leftsize_w, leftsize_h );
                polyview.graphics.endFill();
                polyview.rotation = polybody.angle * 180 / Math.PI;
                polyview.x = polybody.position.x * NX_SCALE;
                polyview.y = polybody.position.y * NX_SCALE;
                this.base.addChild( polyview );
                // 実体を作成
                poly = this.world.CreateBody( polybody );
                poly.SetUserData( polyview );
                poly.CreateShape( polyshape );
                poly.SetMassFromShapes();

                anchor.Set( (initial_w + i*leftsize_w*resize_val)/NX_SCALE, polybody.position.y );
                joint.Initialize( prev, poly, anchor );
                this.world.CreateJoint( joint );

                prev = poly;
            }
            // 最後にもっかいグラウンドにくっつける
            anchor.Set( (initial_w + joint_num*leftsize_w*resize_val)/NX_SCALE, polybody.position.y );
            joint.Initialize( prev, ground, anchor );
            this.world.CreateJoint( joint );

            // つみあがっている箱たち
            // シェイプを作成
            var box_size:int = 20;
            var box_num:int = 10;

            polyshape.density = 1.0;
            polyshape.friction = 0.5;
            polyshape.restitution = 0.2;
            polyshape.SetAsBox( box_size/2/NX_SCALE, box_size/2/NX_SCALE );
            for( var w:int = 0; w < 3; ++w )
            {
                for( i = 0; i < box_num; ++i )
                {
                    // ボディを作成
                    polybody.position.Set( (w*100 + Math.random()+50)/NX_SCALE, (SCREEN_H-box_size*2 - i*(box_size+5))/NX_SCALE );
                    // 表示部分を作成
                    polyview = new Sprite();
                    polyview.graphics.beginFill( color, alpha );
                    polyview.graphics.drawRect( -box_size/2, -box_size/2, box_size, box_size );
                    polyview.graphics.endFill();
                    polyview.x = polybody.position.x * NX_SCALE;
                    polyview.y = polybody.position.y * NX_SCALE;
                    this.base.addChild( polyview );
                    // 実体を作成
                    poly = this.world.CreateBody( polybody );
                    poly.SetUserData( polyview );
                    poly.CreateShape( polyshape );
                    poly.SetMassFromShapes();
                }
            }
        }

        /**
         * @brief   実行と更新。
         */
        private function procFallBall( ev:Event ):void
        {
            if( this.is_start == false )
            {
                if( Input.mousePressed )
                {
                    this.base.removeChild( this.tf );
                    this.is_start = true;
                    initFallBall();
                    initFallWall();
                }
                return;
            }

            // 入力情報をもとに更新
            mousepos.Set( Input.mouseX, Input.mouseY );
            mousepos_scale.Set( Input.mouseX / NX_SCALE, Input.mouseY / NX_SCALE );

            // R が押された?
            if( Input.isKeyPressed( 82 ) )
            {
                // リセット
                this.balls = null;
                while( this.base.numChildren )
                {
                    this.base.removeChildAt( 0 );
                }
                this.world = null;
                this.mousejoint = null;
                initFallWorld();
                initFallBall();
                initFallWall();
            }

            // space が押された?
            if( Input.isKeyPressed( 32 ) )
            {
                // ボール追加
                initFallBall();
            }

            // 衝突判定更新
            var physStart:uint = getTimer();
            this.world.Step( NX_STEP, NX_ITER );
            fpsCounter.updatePhys( physStart );

            var it:b2Body;
            for( it = this.world.GetBodyList(); it != null; it = it.GetNext() )
            {
                // 表示物体を更新
                var usrdata:* = it.GetUserData();
                if( usrdata == null ) continue;
                usrdata.x = it.GetPosition().x * NX_SCALE;
                usrdata.y = it.GetPosition().y * NX_SCALE;
                usrdata.rotation = it.GetAngle() * 180 / Math.PI;
            }

            updateMouseDrag();

            // General update.
            Input.update();
            fpsCounter.update();
        }

        /**
         * @brief   マウスに引っ張られる処理
         */
        private function updateMouseDrag():void
        {
            var usrdata:Sprite;

            if( Input.mouseDown && !this.mousejoint )
            {
                var body:b2Body = getBodyAtMouse();

                if( body )
                {
                    var md:b2MouseJointDef = new b2MouseJointDef();
                    md.body1 = this.world.GetGroundBody();
                    md.body2 = body;
                    md.target.SetV( this.mousepos_scale );
                    md.maxForce = 18000.0 * body.GetMass();
                    md.timeStep = NX_STEP;
                    this.mousejoint = this.world.CreateJoint( md ) as b2MouseJoint;
                    var line:Sprite = new Sprite();
                    line.graphics.lineStyle( 2, 0xe0e090, 0.7 );
                    line.graphics.moveTo( this.mousepos.x, this.mousepos.y );
                    line.graphics.lineTo( body.GetUserData().x, body.GetUserData().y );
                    this.base.addChild( line );
                    this.mousejoint.SetUserData( line );
                    body.WakeUp(); // sleep から起こす。
                }
            }

            if( !Input.mouseDown )
            {
                if( this.mousejoint )
                {
                    // 引っ張るライン削除
                    usrdata = this.mousejoint.GetUserData() as Sprite;
                    usrdata.graphics.clear();
                    this.base.removeChild( usrdata );

                    // マウスジョイントも削除
                    this.world.DestroyJoint( this.mousejoint );
                    this.mousejoint = null;
                }
            }

            if( this.mousejoint )
            {
                // 引っ張るライン設定
                usrdata = this.mousejoint.GetUserData() as Sprite;
                usrdata.graphics.clear();
                usrdata.graphics.lineStyle( 2, 0xe0e090, 0.7 );
                usrdata.graphics.moveTo( this.mousepos.x, this.mousepos.y );
                usrdata.graphics.lineTo( this.mousejoint.GetBody2().GetUserData().x, this.mousejoint.GetBody2().GetUserData().y );

                this.mousejoint.SetTarget( this.mousepos_scale );
            }
        }

        /**
         * @brief   掴んだ物体を取得
         */
        private var mousePVec:b2Vec2 = new b2Vec2();
        private function getBodyAtMouse():b2Body
        {
            // ちっっさな領域を作成
            mousePVec.SetV( this.mousepos_scale );
            var aabb:b2AABB = new b2AABB();
            aabb.lowerBound.Set( this.mousepos_scale.x - 0.001, this.mousepos_scale.y - 0.001 );
            aabb.upperBound.Set( this.mousepos_scale.x + 0.001, this.mousepos_scale.y + 0.001 );

            // 上で作成した領域とぶつかっているやつを取得
            var k_maxCount:int = 10;
            var shapes:Array = new Array();
            var count:int = this.world.Query( aabb, shapes, k_maxCount );
            var body:b2Body = null;
            for( var i:int = 0; i < count; ++i )
            {
                if( shapes[i].GetBody().IsStatic() == false )
                {
                    var tShape:b2Shape = shapes[i] as b2Shape;
                    var inside:Boolean = tShape.TestPoint( tShape.GetBody().GetXForm(), mousePVec );
                    if( inside )
                    {
                        body = tShape.GetBody();
                        break;
                    }
                }
            }
            return body;
        }

    }
}