use strict;
use warnings;
use Box2D;
use SDL;
use SDL::Video;
use SDLx::App;
use SDL::Events ':all';

my $WIDTH  = 300;
my $HEIGHT = 300;

# record the mouse info
my %mouse = ( X => 0, Y => 0, left => 0 );

my $app = SDLx::App->new(
    dt     => 1.0 / 60,
    min_t  => 1.0 / 120,
    width  => $WIDTH,
    height => $HEIGHT,
    flags  => SDL_DOUBLEBUF | SDL_HWSURFACE,
    eoq    => 1
);

# default forces 0 x and -10 y
my $vec = Box2D::b2Vec2->new( 0, -10 );

# start the world
my $world = Box2D::b2World->new( $vec, 1 );

# update the app
$app->update();

# this will store the falling bodies
my @bodies   = ();
my $bodySize = 8.0;

sub makeBody {
    my ( $x, $y ) = @_;

    # make a new body definition
    # this is the structure that stores the physics info
    my $bodyDef = Box2D::b2BodyDef->new();
    $bodyDef->type(Box2D::b2_dynamicBody);

    # set position
    $bodyDef->position->Set( $x, $y );

    # create the body from that definition
    my $body = $world->CreateBody($bodyDef);

    # It's a circle with radius 8
    # this is the shape information that the fixture holds
    my $dynamicCircle = Box2D::b2CircleShape->new();
    $dynamicCircle->m_radius($bodySize);

    # make the fixture
    my $fixtureDef = Box2D::b2FixtureDef->new();

    # the shape
    $fixtureDef->shape($dynamicCircle);

    # the density
    $fixtureDef->density( 0.1 + 2 * rand() );

    # friction
    $fixtureDef->friction( 0.1 + 0.9 * rand() );

    # restitution
    $fixtureDef->restitution( 0.1 + 0.9 * rand() );

    # attach fixture to body to give it properties and shape
    $body->CreateFixtureDef($fixtureDef);

    # record the body
    push @bodies, $body;
    return $body;
}

# simulation timestep
my $timeStep = 1.0 / 60.0;

# iterate to solve velocity
my $velocityIterations = 6;

# position solver
my $positionIterations = 3;

# if a key is pressed down, make a body under the mouse!
$app->add_event_handler(
    sub {
        my ( $event, $app ) = @_;
        return 0 unless ( $event->type == SDL::Events::SDL_KEYDOWN );

        # note Y is flipped
        makeBody( $mouse{X}, $HEIGHT - $mouse{Y} );
    }
);

# radius of ground boxes (walls)
my $groundRad = 8.0;

# store the walls
my @walls = ();

sub makeGround {
    my ( $x, $y ) = @_;
    my $body_def = Box2D::b2BodyDef->new();

    my ( $grx, $gry ) = ( $x,         $y );
    my ( $grw, $grh ) = ( $groundRad, $groundRad );

    # position of ground
    $body_def->position->Set( $grx, $gry );

    # create body from definition
    my $groundBody = $world->CreateBody($body_def);

    # set the shape as a box
    my $groundBox = Box2D::b2PolygonShape->new();
    $groundBox->SetAsBox( $grw, $grh );

    # attach the fixture
    $groundBody->CreateFixture( $groundBox, 0.0 );

    # record the wall
    push @walls, $groundBody;
    return $groundBody;
}

$app->add_event_handler(
    sub {
        my ( $event, $app ) = @_;
        my $type = $event->type;
        return 0
            unless ( $type == SDL::Events::SDL_MOUSEMOTION
            || $type == SDL::Events::SDL_MOUSEBUTTONUP
            || $type == SDL::Events::SDL_MOUSEBUTTONDOWN );

        # update mouse state
        my ( $mask, $x, $y ) = @{ SDL::Events::get_mouse_state() };
        $mouse{X} = $x;
        $mouse{Y} = $y;
        my $left = ( $mask & SDL_BUTTON_LMASK );
        $mouse{left} = $left;

        # draw walls if dragging
        if ($left) {
            push @walls, makeGround( $x, $HEIGHT - $y );
        }
    }
);

$app->add_show_handler(
    sub {

        # simulate
        $world->Step( $timeStep, $velocityIterations, $positionIterations );
        $world->ClearForces();

        # draw walls
        $app->draw_rect( [ 0, 0, $WIDTH, $HEIGHT ], [ 0, 0, 0, 255 ] );
        foreach my $wall (@walls) {
            my $pos = $wall->GetPosition();
            my ( $x, $y ) = ( $pos->x(), $pos->y() );

            # draw around the middle of the object
            $app->draw_rect(
                [   $x - $groundRad,
                    $HEIGHT - $y - $groundRad,
                    $groundRad * 2,
                    $groundRad * 2
                ],
                [ 0, 255, 0, 255 ]
            );
        }

        # draw bodies
        my @nextbodies   = ();
        my @deletebodies = ();
        foreach my $body (@bodies) {
            my $pos = $body->GetPosition();
            my ( $x, $y ) = ( $pos->x(), $pos->y() );
            my $angle = $body->GetAngle();

            $app->draw_circle_filled( [ $x, $HEIGHT - $y ],
                $bodySize, [ 255, 255, 0, 255 ] );

            # arbitrary threshold before we delete an object;
            if ( $y > -200 ) {
                push @nextbodies, $body;
            }
        }
        @bodies = @nextbodies;

        # DestroyBody is dangerous, you want to destroy it in perl pretty
        # quickly too
        while (@deletebodies) {
            $world->DestroyBody( pop @deletebodies );
        }

        $app->update();
    }
);

# run the application
$app->run();