/*
 * BoxScene.java
 *
 * This combines the PhysicsScene with the mini-game specific RocketScene
 * (sans rocket) from the last lab. It represents a scene with falling boxes.
 * Our demo is designed to support multiple of these scenes, all running at
 * the same time.
 *
 * Based on the original PhysicsDemo Lab by Don Holden, 2007
 *
 * Author:  Walker M. White
 * Version: 3/1/2025
 */
package edu.cornell.cis3152.cube;

import java.util.Iterator;

import com.badlogic.gdx.*;
import com.badlogic.gdx.audio.*;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.math.*;
import com.badlogic.gdx.utils.*;
import com.badlogic.gdx.graphics.g2d.*;
import com.badlogic.gdx.physics.box2d.*;
import edu.cornell.gdiac.assets.*;
import edu.cornell.gdiac.audio.SoundEffect;
import edu.cornell.gdiac.audio.SoundEffectManager;
import edu.cornell.gdiac.graphics.SpriteBatch;
import edu.cornell.gdiac.util.*;
import edu.cornell.gdiac.physics2.*;

/**
 * The game scene for falling boxes.
 *
 * Each scene has its own physics world, as they are all distinct from each
 * other. The actual contents of the scene are defined by a JSON file.
 */
public class BoxScene {
	/** The asset directory for retrieving textures, atlases */
	private AssetDirectory directory;
	/** The drawing camera for this scene */
	private OrthographicCamera camera;

	/** The width of this scene */
	private int width;
	/** The height of this scene */
	private int height;

	/** The amount of time for a physics engine step. */
	public static final float WORLD_STEP = 1/60.0f;
	/** Number of velocity iterations for the constrain solvers */
	public static final int WORLD_VELOC = 6;
	/** Number of position iterations for the constrain solvers */
	public static final int WORLD_POSIT = 2;

	/** All the objects in the world. */
	protected PooledList<ObstacleSprite> sprites  = new PooledList<ObstacleSprite>();
	/** Listener that will update the player mode when we are done */
	private ScreenListener listener;

	/** The physics constants */
	private JsonValue constants;

	/** The Box2D world */
	protected World world;
	/** The boundary of the world */
	protected Rectangle bounds;

	/** The total amount of drift this animation frame */
	private float drift;
	/** The countdown to reset */
	private int countdown;

	/** Whether or not this is an active controller */
	private boolean active;

	/**
	 * Returns true if this is the active screen
	 *
	 * @return true if this is the active screen
	 */
	public boolean isActive( ) {
		return active;
	}

	/**
	 * Creates a new game world with the default values.
	 *
	 * The game world is scaled so that the screen coordinates do not agree
	 * with the Box2d coordinates.  The bounds are in terms of the Box2d
	 * world, not the screen.
	 */
	protected BoxScene(String key, AssetDirectory directory) {
		this.directory = directory;
		constants = directory.getEntry(key,JsonValue.class);
		resize(Gdx.graphics.getWidth(),Gdx.graphics.getHeight());
	}

	/**
	 * Dispose of all (non-static) resources allocated to this mode.
	 */
	public void dispose() {
		world.dispose();
		world = null;
	}

	/**
	 * Resets the status of the game so that we can play again.
	 *
	 * This method disposes of the world and creates a new one.
	 */
	public void reset() {
		// Compute the drawing scale
		world = new World(new Vector2(0,constants.getFloat("gravity")),false);

		bounds = new Rectangle(0,0,constants.get("world_size").getFloat( 0 ), constants.get("world_size").getFloat( 1 ));
		sprites.clear();

		// Height lock the physics
		float scale = height/bounds.height;

		Texture texture = directory.getEntry( "earth", Texture.class );

		// Create ground pieces
		Wall wall;
		JsonValue walls = constants.get("walls");
		wall = new Wall(walls.get(0).asFloatArray(), constants);
		wall.getObstacle().setName("wall1");
		wall.setTexture( texture );
		addSprite(wall);

		wall = new Wall(walls.get(1).asFloatArray(), constants);
		wall.getObstacle().setName("wall2");
		wall.setTexture( texture );
		addSprite(wall);

		// Create the pile of boxes
		JsonValue crates = constants.get("boxes");
		for (int ii = 0; ii < crates.size; ii++) {
			int id = crates.get(ii).getInt(0);
			texture = directory.getEntry( "crate0"+id, Texture.class );

			Crate box = new Crate(crates.get(ii).getFloat(1), crates.get(ii).getFloat(2), constants);
			box.getObstacle().setName("crate"+id);
			box.setTexture( texture );
			addSprite(box);
		}
	}

	/**
	 * Updates the core gameplay loop of this scene.
	 *
	 * This method contains the specific update code for this scene. In this
	 * case, that just means the simulation of dropping boxes.
	 *
	 * @param dt	Number of seconds since last animation frame
	 */
	public void update(float dt) {
		world.step(WORLD_STEP,WORLD_VELOC,WORLD_POSIT);

		if (countdown > 0) {
			countdown--;
			if (countdown == 0) {
				reset();
			}
		} else {
			countdown = constants.getInt("countdown",1);
		}
	}

	/**
	 * Immediately adds the object to the physics world
	 *
	 * param obj The object to add
	 */
	protected void addSprite(ObstacleSprite sprite) {
		assert inBounds(sprite) : "Sprite is not in bounds";
		sprites.add(sprite);
		sprite.getObstacle().activatePhysics(world);
	}

	/**
	 * Returns true if the object is in bounds.
	 *
	 * This assertion is useful for debugging the physics.
	 *
	 * @param obj The object to check.
	 *
	 * @return true if the object is in bounds.
	 */
	public boolean inBounds(ObstacleSprite sprite) {
		Obstacle obj = sprite.getObstacle();
		boolean horiz = (bounds.x <= obj.getX() && obj.getX() <= bounds.x+bounds.width);
		boolean vert  = (bounds.y <= obj.getY() && obj.getY() <= bounds.y+bounds.height);
		return horiz && vert;
	}

	/**
	 * Draw the physics objects to the canvas
	 *
	 * For simple worlds, this method is enough by itself.  It will need
	 * to be overriden if the world needs fancy backgrounds or the like.
	 *
	 * The method draws all objects in the order that they were added.
	 *
	 * @param dt	Number of seconds since last animation frame
	 */
	public void draw(SpriteBatch batch) {
		// Clear the screen (color is homage to the XNA years)
		ScreenUtils.clear(0.39f, 0.58f, 0.93f, 1.0f);

		batch.begin(camera);

		for(ObstacleSprite obj : sprites) {
			obj.draw(batch);
		}

		batch.end();
	}

	/**
	 * Called when the Screen is resized.
	 *
	 * This can happen at any point during a non-paused state but will never happen
	 * before a call to show().
	 *
	 * @param width  The new width in pixels
	 * @param height The new height in pixels
	 */
	public void resize(int width, int height) {
		this.width  = width;
		this.height = height;
		if (camera == null) {
			camera = new OrthographicCamera();
			camera.setToOrtho( false, width, height  );
 		} else {
			camera.setToOrtho( false, width, height  );
		}
		reset();
	}

}
