/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package blok.plugin;

import blok.interfaces.ICore;
import blok.interfaces.IGameController;
import blok.interfaces.IPlugin;
import blok.interfaces.ISimulator;
import java.awt.Point;
import java.util.ArrayList;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.jbox2d.callbacks.ContactImpulse;
import org.jbox2d.callbacks.ContactListener;
import org.jbox2d.collision.Manifold;
import org.jbox2d.collision.shapes.PolygonShape;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.BodyDef;
import org.jbox2d.dynamics.BodyType;
import org.jbox2d.dynamics.FixtureDef;
import org.jbox2d.dynamics.contacts.Contact;
import org.jbox2d.dynamics.World;

/**
 *
 * @author aluno
 */
public class Box2DSimulator implements IPlugin, ISimulator, Runnable, ContactListener {

    @Override
    public boolean initialize() {
        m_bodies = new ArrayList<>();
        
        return true;
    }

    @Override
    public void start() {
        m_schedulerHandle = m_scheduler.scheduleAtFixedRate(this, 0, 3, TimeUnit.MILLISECONDS);
    }

    @Override
    public void stop() {
        m_schedulerHandle.cancel(true);
    }

    @Override
    public void init() {
        m_world = new World(new Vec2(0, -10f), true);
        m_world.setContactListener(this);
        m_bodies.clear();

        // Ground
        m_ground = createBody(0.0f, -260.0f, 900.0f, 20.0f, false, 1.0f, 0.3f, 0.5f);

        // Blocks
        int i, j;
        j = 0;
        for (i = 0; i < 10; ++i)
            for (j = 0; j < 11 - i; ++j)
                m_bodies.add(createBody(-150.0f+15*i+30*j, -236.0f+30*i, 28.0f, 28.0f, true, 1.0f, 0.3f, 0.5f));

        // Player
        j-=2;
        m_bodies.add(m_player = createBody(-150.0f+15*i+30*j, -236.0f+30*i+14, 56.0f, 56.0f, true, 1.0f, 0.3f, 0.5f));
        m_player.setUserData("player");

        ICore.getInstance().getUIController().bodiesCreated(m_bodies);
    }

    @Override
    public void removeBody(Point bodyPoint) {
        Body bodyToBeRemoved = null;
        for (Body body : m_bodies) {
            Vec2 position = body.getPosition();
            if (position.x == bodyPoint.x &&
                position.y == bodyPoint.y) {
                bodyToBeRemoved = body;
                break;
            }
        }
        m_world.destroyBody(bodyToBeRemoved);
        m_bodies.remove(bodyToBeRemoved);
        if (m_bodies.size() == 2)
        {
            stop();
            ICore.getInstance().getGameController().setGameState(IGameController.GameState.YOUWON);
        }
    }

    @Override
    public void run() {
        m_world.step(B2_TIMESTEP, B2_VELOCITY_ITERATIONS, B2_POSITION_ITERATIONS);
        ICore.getInstance().getUIController().bodiesUpdated(m_bodies);
    }

    @Override
    public void beginContact(Contact cntct) {
        if ((cntct.getFixtureA().getBody() == m_ground && cntct.getFixtureB().getBody() == m_player) ||
            (cntct.getFixtureB().getBody() == m_ground && cntct.getFixtureA().getBody() == m_player))
        {
            stop();
            ICore.getInstance().getGameController().setGameState(IGameController.GameState.YOULOST);
        }
    }

    @Override
    public void endContact(Contact cntct) {
    }

    @Override
    public void preSolve(Contact cntct, Manifold mnfld) {
    }

    @Override
    public void postSolve(Contact cntct, ContactImpulse ci) {
    }
    
    private Body createBody(float x, float y, float width, float height, boolean dynamic, float density, float friction, float restitution) {
        BodyDef bodyDef = new BodyDef();
        if (dynamic)
            bodyDef.type = BodyType.DYNAMIC;
        bodyDef.position.set(x, y);
        Body body = m_world.createBody(bodyDef);
        PolygonShape box = new PolygonShape();
        box.setAsBox(width/2, height/2);
        FixtureDef fixtureDef = new FixtureDef();
        fixtureDef.shape = box;
        fixtureDef.density = density;
        fixtureDef.friction = friction;
        fixtureDef.restitution = restitution;
        body.createFixture(fixtureDef);
        body.setSleepingAllowed(true);
        
        return body;
    }

    private static World m_world;

    private static final float PI = 3.14159265359f;
    private static final float B2_TIMESTEP = 1.0f / 30.0f;
    private static final int B2_VELOCITY_ITERATIONS = 8;
    private static final int B2_POSITION_ITERATIONS = 4;

    private final ScheduledExecutorService m_scheduler = Executors.newScheduledThreadPool(1);

    private ArrayList<Body> m_bodies;
    private ScheduledFuture<?> m_schedulerHandle = null;
    private Body m_ground = null;
    private Body m_player = null;
}
