import java.util.*;

import net.phys2d.math.*;
import net.phys2d.raw.*;
import net.phys2d.raw.shapes.*;
import net.phys2d.raw.strategies.*;

public World world;

ArrayList boxes;

final int SCREEN_WIDTH = 800;
final int SCREEN_HEIGHT = 400;

final int DROP_FREQUENCY = 2;
final float BOX_SIZE_MIN = 30.0f;
final float BOX_SIZE_MAX = 70.0f;
final float OBJECT_X_MIN = 100.0f;
final float OBJECT_X_MAX = SCREEN_WIDTH - 100.0f;
final float OBJECT_Y = -100.0f;
final float BOX_DEPTH = 50.0f;
final float FLOOR_DEPTH = 400.0f;
final float HALF_FLOOR_DEPTH = FLOOR_DEPTH / 2;
final float FRICTION = 0.3f;
final float MIN_FORCE_X = -10.0f;
final float MAX_FORCE_X = 10.0f;
final float ANGULAR_VELOCITY_MIN = -3.0f;
final float ANGULAR_VELOCITY_MAX = 3.0f;
final float GROUND_FILL = 250;
final float GROUND_X = SCREEN_WIDTH / 2;
final float GROUND_Y = SCREEN_HEIGHT - 10;
final color BOX_FILL = color(192, 156, 118);
final float EXPLOSION_FORCE = 1500000.0f;
final float MAX_BOUNDS_X = SCREEN_WIDTH + 100;
final float MIN_BOUNDS_X = -100;
final float MAX_BOUNDS_Y = SCREEN_HEIGHT + 100;

Body ground;

final float BOX_COLOUR_R = 180.0f;
final float BOX_COLOUR_G = 140.0f;
final float BOX_COLOUR_B = 100.0f;

final float BOX_COLOUR_DEVIATION = 0.2f;

int ticks = 0;

void setup()
{
    // Setup window
    size(SCREEN_WIDTH, SCREEN_HEIGHT, P3D);
    frameRate(24);

    boxes = new ArrayList();

    initialiseWorld();

    rectMode(CENTER);

    camera(
            SCREEN_WIDTH / 2, SCREEN_HEIGHT - 150, 270,
            SCREEN_WIDTH / 2, SCREEN_HEIGHT - 130, 0,
            0, 1.0, 0
          );
          
    //noLoop();
}

void initialiseWorld()
{
    // Initialise physics
    world = new World(new Vector2f(0.0f, 20.0f), 20, new QuadSpaceStrategy(20, 5));

    // Add ground

    ground = new StaticBody("Ground", new Box(width + 400, 10.0f));
    ground.setPosition(GROUND_X, GROUND_Y);
    ground.setFriction(0.7f);

    world.add(ground);
}

void draw()
{
    background(255);
    stroke(100);
    
    ticks++;

    if (ticks < 200 && ticks % DROP_FREQUENCY == 0)
    {
        addBox();

    }
    else if (ticks == 200)
    {
        explode();

    }

    removeOutOfBoundsBoxes();
    
    world.step();
    world.step();
    world.step();
    world.step();
    
    drawGround();
    Iterator boxIterator = boxes.iterator();
    while (boxIterator.hasNext())
    {
        PhysicalBox pbox = (PhysicalBox)boxIterator.next();
        pbox.render();
    }
    
    if (ticks == 600)
    {
        reset();
    }

}

void addBox()
{
    float boxWidth = random(BOX_SIZE_MIN, BOX_SIZE_MAX);
    float boxHeight = random(BOX_SIZE_MIN, BOX_SIZE_MAX);
    float colourDeviation = random(-BOX_COLOUR_DEVIATION, BOX_COLOUR_DEVIATION);
    color boxColour = color(
                              (int)(BOX_COLOUR_R + BOX_COLOUR_R * colourDeviation),
                              (int)(BOX_COLOUR_G + BOX_COLOUR_G * colourDeviation),
                              (int)(BOX_COLOUR_B + BOX_COLOUR_B * colourDeviation)
                           );

    PhysicalBox pbox = new PhysicalBox(boxWidth, boxHeight, BOX_DEPTH, boxColour);

    pbox.setPosition(random(OBJECT_X_MIN, OBJECT_X_MAX), OBJECT_Y);
    pbox.setRotation(random(0, TWO_PI));
    pbox.setForce(random(MIN_FORCE_X, MAX_FORCE_X), 20);
    pbox.setFriction(FRICTION);
    pbox.adjustAngularVelocity(random(ANGULAR_VELOCITY_MIN, ANGULAR_VELOCITY_MAX));
    boxes.add((Object)pbox);
    world.add(pbox);
}

void removeOutOfBoundsBoxes()
{
    Iterator boxIterator = boxes.iterator();
    while (boxIterator.hasNext())
    {
        PhysicalBox pbox = (PhysicalBox)boxIterator.next();
        ROVector2f p = pbox.getPosition();
        if (p.getX() > MAX_BOUNDS_X || p.getX() < MIN_BOUNDS_X ||
            p.getY() > MAX_BOUNDS_Y)
        {
            world.remove(pbox);
            boxIterator.remove();
        }
    }
}

void explode()
{
    Vector2f detonationPoint = new Vector2f(GROUND_X, GROUND_Y);

    Iterator boxIterator = boxes.iterator();
    while (boxIterator.hasNext())
    {
        PhysicalBox pbox = (PhysicalBox)boxIterator.next();
        pbox.explode(detonationPoint, EXPLOSION_FORCE);
    }
}

void reset()
{
    Iterator boxIterator = boxes.iterator();
    while (boxIterator.hasNext())
    {
        PhysicalBox pbox = (PhysicalBox)boxIterator.next();
        world.remove(pbox);
        boxIterator.remove();
    }
    ticks = 0;
}

void drawGround()
{
    fill(GROUND_FILL);
    
    Box b = (Box)ground.getShape();
    ROVector2f groundSize = b.getSize();
    ROVector2f p = ground.getPosition();
    float r = ground.getRotation();

    pushMatrix();
        translate(p.getX(), p.getY(), 0.0f);
        rotateZ(r);
        box(groundSize.getX(), groundSize.getY(), FLOOR_DEPTH);
    popMatrix();
}


class PhysicalBox extends Body
{
    private float boxWidth, boxHeight, boxDepth;
    private color c;

    PhysicalBox(float w, float h, float d, color c)
    {
        super(new Box(w, h), w * h);
        
        this.boxWidth = w;
        this.boxHeight = h;
        this.boxDepth = d;
        this.c = c;
    }

    public color getColor()
    {
        return this.c;
    }

    public void setColor(color c)
    {
        this.c = c;
    }
    
    public void explode(Vector2f detonationPoint, float magnitude)
    {
        ROVector2f p = this.getPosition();
        Vector2f explosionVector = MathUtil.sub(p, detonationPoint);
        magnitude /= explosionVector.lengthSquared();
        explosionVector = MathUtil.scale(explosionVector, magnitude * this.getMass());

        this.addForce(explosionVector);
    }

    public void render()
    {
        Box b = (Box)this.getShape();

        ROVector2f boxSize = b.getSize();
        ROVector2f p = this.getPosition();
        float r = this.getRotation();

        
        pushMatrix();
            fill(this.c);
            translate(p.getX(), p.getY(), 0.0f);
            rotateZ(r);
            box(boxSize.getX(), boxSize.getY(), boxDepth);
        popMatrix();

    }
}
