/*
 * Entity.java
 *
 * Base model class to support collisions.
 *
 * This model is different fro that in lab 4. In that lab we relied on Obstacles,
 * which hide a lot of details about box2d. In this model, we make all of the
 * box2d features explicit so that you see how they fit together.
 *
 * @author Walker M. White
 * @version 3/9/2025
 */
package edu.cornell.cis3152.box2d;

import com.badlogic.gdx.math.*;
import com.badlogic.gdx.graphics.*;
import com.badlogic.gdx.physics.box2d.*;
import com.badlogic.gdx.physics.box2d.BodyDef.BodyType;
import edu.cornell.gdiac.math.*;
import edu.cornell.gdiac.graphics.*;

/**
 * Instances represents a single physics object with no Joints.
 */
public abstract class Entity {
	// The Box2D information
	/** The physics body (MOVEMENT) */
	protected Body body;
	/** The physics geometry (collisions) */
	protected Fixture fixture;
	/** The size (width and height) of the bounding box */
	protected Vector2 size;
	/** Whether or not this is a static object */
	protected boolean isStatic;

	// The drawing information
	/** The polygon for drawing this shape */
	protected Poly2 poly;
	/** The drawing color */
	protected Color color;

	// Buffers for physical values
	/** The initial position of the physics object */
	protected Vector2 position;
    /** The affine transform for drawing */
    protected Affine2 transform;
    /** The physics units for drawing */
    protected float units;
	/** The initial density of the physics object */
	protected float density;
	/** The initial friction of the physics object */
	protected float friction;
	/** The initial restitution of the physics object */
	protected float restitution;

	/**
	 * Returns the position of this physics object
	 *
	 * If the physics object has been initialized (e.g. it has an allocated body),
	 * then it will return the current position. It is unsafe to change the returned
	 * object. Modifications to this object result in undefined behavior.
	 *
	 * @return the position of this physics object
	 */
	public Vector2 getPosition() {
		if (body == null) {
			return position;
		}
		return body.getPosition();
	}

	/**
	 * Sets the positition of the physics object
	 *
	 * If the object is already initialized (e.g. it has an allocated body),
	 * this will move the object to the given position. Otherwise, this just
	 * sets the initial object position.
	 *
	 * This is necessary because Box2D will not allow us to move static objects
	 * after creation.
	 *
	 * @param value  the positition of the physics object
	 */
	public void setPosition(Vector2 value) {
		position.set(value);
		if (body != null) {
			body.setTransform(value,body.getAngle());
		}
	}

	/**
	 * Returns the Box2D body for this object (MOVEMENT).
	 *
	 * This method returns a reference to the body, allowing you to apply forces
	 * directly to it.
	 *
	 * @return the Box2D body for this object (MOVEMENT).
	 */
	public Body getBody() {
		return body;
	}

	/**
	 * Returns the Box2D fixture for this object (COLLISIONS).
	 *
	 * This method returns a reference to the fixture, allowing you to change
	 * density, friction, or restitution. However, changes to the density
	 * require that you reset the mass data.
	 *
	 * @return the Box2D fixture for this object (COLLISIONS).
	 */
	public Fixture getFixture() {
		return fixture;
	}

	/**
	 * Returns the color of this shape
	 *
	 * This method returns a reference to the color object, allowing it
	 * to be changed.
	 *
	 * @return the color of this shape
	 */
	public Color getColor() {
		return color;
	}

	/**
	 * Returns the bounding box size of this object.
	 *
	 * This is fixed on creation. Changing the size means making a new fixture,
	 * which is complicated enough that we typically use an Obstable object in
	 * that case (because it is so complicated). We do not allow the fixture to
	 * be changed anymore, so changing this value has no effect.
	 *
	 * @return the bounding box size of this object.
	 */
	public Vector2 getSize() {
		return size;
	}

	/**
	 * Returns the width of the bounding box.
	 *
	 * This is fixed on creation. Changing the size means making a new fixture,
	 * which is complicated enough that we typically use an Obstable object in
	 * that case (because it is so complicated). We do not allow the fixture to
	 * be changed anymore, so changing this value has no effect.
	 *
	 * @return the width of the bounding box.
	 */
	public float getWidth() {
		return size.x;
	}

	/**
	 * Returns the height of the bounding box.
	 *
	 * This is fixed on creation. Changing the size means making a new fixture,
	 * which is complicated enough that we typically use an Obstable object in
	 * that case (because it is so complicated). We do not allow the fixture to
	 * be changed anymore, so changing this value has no effect.
	 *
	 * @return the height of the bounding box.
	 */
	public float getHeight() {
		return size.y;
	}

	/**
	 * Returns the density for this object
	 *
	 * Together with the shape, the density determines the mass.
	 *
	 * @return the density for this object
	 */
	public float getDensity() {
		return density;
	}

	/**
	 * Sets the density for this object
	 *
	 * If the fixture already exists, this requires that we reset the mass data
	 * on the body.
	 *
	 * @param value the density for this object
	 */
	public void setDensity(float value) {
		density = value;
		if (fixture != null) {
			fixture.setDensity(density);
			body.resetMassData ();
		}
	}

	/**
	 * Returns the shape friction
	 *
	 * @return the shape friction
	 */
	public float getFriction() {
		return friction;
	}

	/**
	 * Sets the shape friction
	 *
	 * Information is passed through to the fixture if it exists.
	 *
	 * @param value the shape friction
	 */
	public void setFriction(float value) {
		friction = value;
		if (fixture != null) {
			fixture.setFriction(friction);
		}
	}

	/**
	 * Returns the shape restitution
	 *
	 * @return the shape restitution
	 */
	public float getRestitution() {
		return restitution;
	}

	/**
	 * Sets the shape restitution
	 *
	 * Information is passed through to the fixture if it exists.
	 *
	 * @param value the shape restitution
	 */
	public void setRestitution(float value) {
		restitution = value;
		if (fixture != null) {
			fixture.setRestitution(restitution);
		}
	}

	/**
	 * Returns true if the body type is static (not dynamic)
	 *
	 * There are actually three body types: Static, Kinematic, and Dynamic. But
	 * only Static and Dynamic are useful, so this is an easy way to go back and
	 * forth.
	 *
	 * @return true if the body type is static
	 */
	public boolean isStatic() {
		return isStatic;
	}

	/**
	 * Sets whether the body type is static (if true) or dynamic
	 *
	 * There are actually three body types: Static, Kinematic, and Dynamic. But
	 * only Static and Dynamic are useful, so this is an easy way to go back and
	 * forth.
	 *
	 * @param value true if the body type is static
	 */
	public void setStatic(boolean value) {
		isStatic = value;
		if (body != null) {
			body.setType(isStatic ? BodyType.StaticBody : BodyType.DynamicBody);
		}
	}


	/**
	 * Creates a new physics object for the given world
	 */
	protected Entity() {
		// No physics until we initialize
		body = null;
		fixture = null;

		// Set the default values
		isStatic = false;
		density  = 1.0f;
		friction = 0.1f;
		restitution = 0.0f;
		color = new Color(Color.WHITE);
		position = new Vector2();
        transform = new Affine2();
	}

	/**
	 * Initialize the object and create the physics bodies
	 *
 	 * Delaying (from the constructor) this allows us to take advantage of
 	 * reflection.
 	 *
 	 * @param world The physics world
 	 * @param size  The size of the bounding box
     * @param units The physics units for scaling
 	 */
	public void initialize(World world, Vector2 size, float units) {
		// Make a body, if possible
		BodyDef def = new BodyDef();
		def.type = (isStatic ? BodyType.StaticBody : BodyType.DynamicBody);
		def.position.set(position);

		body = world.createBody(def);
		body.setUserData(this);

        this.units = units;
		makeFixture(size);
		makeGraphics(size);
	}

	/**
     * Draws the physics object.
     *
     * @param batch The sprite batch
     */
	public void draw(SpriteBatch batch) {
        float px = body.getPosition().x*units;
        float py = body.getPosition().y*units;
        float rotate = body.getAngle()*180.0f/(float)Math.PI;


        SpriteBatch.computeTransform(transform, 0,0,
                                        px, py, rotate, 1, 1);

        batch.setColor( color );
        batch.fill( poly, transform );
	}

	/**
     * Creates the collision shape information
     *
     * @param size The object bounding box
     */
	protected abstract void makeFixture(Vector2 size);

	/**
     * Creates the drawing shape information
     *
     * @param size  The object bounding box
     */
	protected abstract void makeGraphics(Vector2 size);

}
