/*
 * Copyright 2019. Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.android.apps.gumball;

import java.util.ArrayList;
import java.util.List;
import org.jbox2d.collision.shapes.CircleShape;
import org.jbox2d.collision.shapes.EdgeShape;
import org.jbox2d.collision.shapes.PolygonShape;
import org.jbox2d.collision.shapes.Shape;
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.World;

/** Wraps the game world and physics simulation for the gumball game. */
public class PhysicsWorld {

    /** All {@link org.jbox2d.dynamics.Body} objects in the world. */
    private List<Body> mBodies = new ArrayList<Body>();
    /** The Physics world. */
    private World mWorld;
    /** Bodies that are to be removed from the scene. */
    public List<Body> mBodiesToBeRemoved = new ArrayList<Body>();
    /** Render refresh rate. */
    private static final float FRAME_RATE = 1.0f / 45.0f;
    /** Create the physics world and draws the boundries */
    public void create(Vec2 gravity) {

        // Create Physics World with Gravity
        mWorld = new World(gravity);
        mWorld.setAllowSleep(false);
        mWorld.setSleepingAllowed(false);
        mWorld.setAutoClearForces(true);

        BodyDef groundBodyDef = new BodyDef();

        // Create Ground Box
        groundBodyDef.position.set(new Vec2(5.0f, -2.0f));
        Body groundBody = mWorld.createBody(groundBodyDef);
        PolygonShape polygonShape = new PolygonShape();

        // Create top bound
        groundBodyDef.position.set(new Vec2(5.0f, 32.0f));
        groundBody = mWorld.createBody(groundBodyDef);
        groundBody.createFixture(polygonShape, 1.0f);

        polygonShape.setAsBox(2.0f, 18.0f);

        // Create left wall
        groundBodyDef.position.set(new Vec2(-2.0f, 16.0f));
        groundBody = mWorld.createBody(groundBodyDef);
        groundBody.createFixture(polygonShape, 1.0f);

        // Create right wall
        groundBodyDef.position.set(new Vec2(12.0f, 16.0f));
        groundBody = mWorld.createBody(groundBodyDef);
        groundBody.createFixture(polygonShape, 1.0f);
    }

    /** Adds a gumball to the scene. */
    public void addGumball(
            float x,
            float y,
            Gumball gumball,
            float density,
            float radius,
            float bounce,
            float friction,
            BodyType bodyType) {
        // Create Shape with Properties
        CircleShape circleShape = new CircleShape();
        circleShape.m_radius = radius;
        addItem(x, y, circleShape, bounce, gumball, density, friction, bodyType);
    }

    public void addPipeSides(
            float x,
            float y,
            int data,
            float density,
            float bounce,
            float friction,
            BodyType bodyType) {
        EdgeShape[] edgeShapes = new EdgeShape[2];
        edgeShapes[0] = new EdgeShape();
        edgeShapes[0].set(new Vec2(.23f, -1f), new Vec2(.01f, .48f));
        edgeShapes[1] = new EdgeShape();
        edgeShapes[1].set(new Vec2(1.4f, -1f), new Vec2(1.55f, .45f));
        addItem(x, y, edgeShapes, bounce, data, density, friction, bodyType);
    }

    public void addPipeBottom(
            float x,
            float y,
            int data,
            float density,
            float bounce,
            float friction,
            BodyType bodyType) {
        EdgeShape[] edgeShapes = new EdgeShape[1];
        edgeShapes[0] = new EdgeShape();
        edgeShapes[0].set(new Vec2(.83f, 0f), new Vec2(2.40f, 0f));
        addItem(x, y, edgeShapes, bounce, data, density, friction, bodyType);
    }

    public void addFloor(
            float x,
            float y,
            int data,
            float density,
            float bounce,
            float friction,
            BodyType bodyType) {
        EdgeShape[] edgeShapes = new EdgeShape[1];
        edgeShapes[0] = new EdgeShape();
        edgeShapes[0].set(new Vec2(-9f, -.8f), new Vec2(9f, -.8f));
        addItem(x, y, edgeShapes, bounce, data, density, friction, bodyType);
    }

    public void addItem(
            float x,
            float y,
            Shape[] shapes,
            float bounce,
            int data,
            float density,
            float friction,
            BodyType bodyType) {

        // Create Dynamic Body
        BodyDef bodyDef = new BodyDef();
        bodyDef.position.set(x, y);
        bodyDef.userData = data;
        bodyDef.type = bodyType;
        Body body = mWorld.createBody(bodyDef);
        mBodies.add(body);

        for (int i = 0; i < shapes.length; i++) {
            // Assign shape to Body
            FixtureDef fixtureDef = new FixtureDef();
            fixtureDef.shape = shapes[i];
            fixtureDef.density = density;
            fixtureDef.friction = friction;
            fixtureDef.restitution = bounce;

            body.createFixture(fixtureDef);
        }
    }

    public void addItem(
            float x,
            float y,
            Shape shape,
            float bounce,
            int data,
            float density,
            float friction,
            BodyType bodyType) {

        // Create Dynamic Body
        BodyDef bodyDef = new BodyDef();
        bodyDef.position.set(x, y);
        bodyDef.userData = data;
        bodyDef.type = bodyType;
        Body body = mWorld.createBody(bodyDef);
        mBodies.add(body);

        // Assign shape to Body
        FixtureDef fixtureDef = new FixtureDef();
        fixtureDef.shape = shape;
        fixtureDef.density = density;
        fixtureDef.friction = friction;
        fixtureDef.restitution = bounce;
        body.createFixture(fixtureDef);
    }

    public void addItem(
            float x,
            float y,
            Shape shape,
            float bounce,
            Gumball gumball,
            float density,
            float friction,
            BodyType bodyType) {

        // Create Dynamic Body
        BodyDef bodyDef = new BodyDef();
        bodyDef.position.set(x, y);
        bodyDef.userData = gumball;
        bodyDef.type = bodyType;
        Body body = mWorld.createBody(bodyDef);
        mBodies.add(body);

        // Assign shape to Body
        FixtureDef fixtureDef = new FixtureDef();
        fixtureDef.shape = shape;
        fixtureDef.density = density;
        fixtureDef.friction = friction;
        fixtureDef.restitution = bounce;
        body.createFixture(fixtureDef);
    }

    /** Updates the physics world by removing all pending bodies. */
    public void update() {
        // Update Physics World
        for (int i = 0; i < mBodiesToBeRemoved.size(); i++) {
            mWorld.destroyBody(mBodiesToBeRemoved.get(i));
        }
        mBodiesToBeRemoved.clear();
        mWorld.step(FRAME_RATE, 10, 10);
        mWorld.clearForces();
    }

    /** Gets a reference to the world. */
    public World getWorld() {
        return mWorld;
    }
}
