/*
 * GameLevel.java
 *
 * This stores all of the information to define a level in our simple platform
 * game. We have an avatar, some walls, some platforms, and an exit. This is a
 * refactoring of PhysicsScene in Lab 4 that separates the level data from the
 * level control.
 *
 * Note that we DO NOT embed the world in this model. This model is nothing more
 * that a container for all of our physics assets.
 *
 * Based on the original PhysicsDemo Lab by Don Holden, 2007
 *
 * Author:  Walker M. White
 * Version: 2/14/2025
 */
 package edu.cornell.cis3152.json;

import com.badlogic.gdx.math.*;
import com.badlogic.gdx.utils.*;
import com.badlogic.gdx.physics.box2d.*;

import edu.cornell.gdiac.assets.AssetDirectory;
import edu.cornell.gdiac.graphics.SpriteBatch;
import edu.cornell.gdiac.graphics.TextLayout;
import edu.cornell.gdiac.util.*;
import edu.cornell.gdiac.physics2.*;

/**
 * Represents a single level in our game
 *
 * Note that the constructor does very little.  The true initialization happens
 * by reading the JSON value.  To reset a level, dispose it and reread the JSON.
 *
 * The level contains its own Box2d World, as the World settings are defined by the
 * JSON file.  However, there is absolutely no controller code in this class, as
 * the majority of the methods are getters and setters.  The getters allow the
 * GameController class to modify the level elements.
 */
public class GameLevel {
	/** The boundary of the world */
	protected Rectangle bounds;
    /** The window width */
    protected float width;
    /** The window height */
    protected float height;
	/** The physics units for our game */
	protected float units;

	/** All the objects in the world. */
	protected PooledList<GameObject> objects = new PooledList<GameObject>();

	// Physics objects for the game
	/** Reference to the character avatar */
	private Traci avatar;
	/** Reference to the goalDoor (for collision detection) */
	private Door goalDoor;

	/** Whether or not the level is in debug mod(showing off physics) */
	private boolean debug;

	/**
	 * Returns the bounding rectangle for the physics world
	 *
	 * The size of the rectangle is in physics, coordinates, not screen coordinates
	 *
	 * @return the bounding rectangle for the physics world
	 */
	public Rectangle getBounds() {
		return bounds;
	}

	/**
	 * Returns the physics units for all of the sprites
	 *
	 * @return the physics units for all of the sprites
	 */
	public float getPhysicsUnits() {
		return units;
	}

	/**
	 * Returns a reference to the player avatar
	 *
	 * @return a reference to the player avatar
	 */
	public Traci getAvatar() {
		return avatar;
	}

	/**
	 * Returns a reference to the exit door
	 *
	 * @return a reference to the exit door
	 */
	public Door getDoor() {
		return goalDoor;
	}

	/**
	 * Returns whether this level is currently in debug node
	 *
	 * If the level is in debug mode, then the physics bodies will all be drawn
	 * as wireframes on screen
	 *
	 * @return whether this level is currently in debug node
	 */
	public boolean getDebug() {
		return debug;
	}

	/**
	 * Sets whether this level is currently in debug node
	 *
	 * If the level is in debug mode, then the physics bodies will all be drawn
	 * as wireframes on screen
	 *
	 * @param value	whether this level is currently in debug node
	 */
	public void setDebug(boolean value) {
		debug = value;
	}

	/**
	 * Creates a new GameLevel
	 *
	 * The game level will be instantly populated, but none of the physics
	 * will be activated.
	 *
	 * @param assets 	the asset manager
	 * @param data 	    the JSON file defining the level
	 */
	public GameLevel(AssetDirectory assets, JsonValue data, float w, float h) {
		bounds = new Rectangle();
		debug = false;
        width = w;
        height = h;
		reset(assets, data);
	}

	/**
	 * Lays out the game geography from the given JSON file
	 *
	 * @param assets 	the asset manager
	 * @param data 	    the JSON file defining the level
	 */
	public void reset(AssetDirectory assets, JsonValue data) {
        JsonValue defaults = data.get("world");

		float gravity = defaults.getFloat("gravity");
		float w = defaults.get("bounds").getFloat(0);
		float h = defaults.get("bounds").getFloat(1);

        // Height-lock is preferred over width-lock
        units = height/h;
		bounds.set(0,0,w,h);

		// Add level goal
        JsonValue exit = data.get("exit");
		goalDoor = new Door(assets,exit);
		goalDoor.setPhysicsUnits(units);
        objects.add(goalDoor);

	    JsonValue wall = data.get("walls").child();
	    while (wall != null) {
	    	Wall obj = new Wall(assets,wall);
		    obj.setPhysicsUnits(units);
	    	objects.add(obj);
	        wall = wall.next();
	    }

	    JsonValue floor = data.get("platforms").child();
	    while (floor != null) {
	    	Platform obj = new Platform(assets,floor);
		    obj.setPhysicsUnits(units);
	    	objects.add(obj);
	        floor = floor.next();
	    }

		// Create Traci
        JsonValue player = data.get("avatar");
	    avatar = new Traci(assets, player);
		avatar.setPhysicsUnits(units);
        objects.add(avatar);
	}

    /**
     * Disposes of this level
     *
     * This method does not clear the world. That has to be disposed separately.
     * This method should only be called after physics is deactivated.
     */
	public void dispose() {
	    // We assume the world clears its data elsewhere
		objects.clear();
	}

    /**
     * Resizes the level to fit the new window
     *
     * This will change the physics units of all the game objects, appropriately
     * resizing them for the screen
     *
     * @param width     The width in pixels
     * @param height    The height in pixels
     */
	public void resize(float width, float height) {
        // Height-lock is preferred over width-lock
        units = height/bounds.height;
        for(GameObject o : objects) {
            o.setPhysicsUnits(units);
        }

	}

    /**
     * Activates the physics bodies for all the game objects
     *
     * This method must be called every time the world is reset.
     *
     * @param world The box2d world
     */
    public void activate(World world) {
        for(GameObject o : objects) {
            Obstacle obs = o.getObstacle();
            obs.activatePhysics( world );
        }
        // TODO: Give a nicer way to do this
        avatar.createSensor();
    }

    /**
     * Dectivates the physics bodies for all the game objects
     *
     * This method should be called every time the world is destroyed.
     *
     * @param world The box2d world
     */
    public void deactivate(World world) {
        //avatar.destroySensor();
        for(GameObject o : objects) {
            Obstacle obs = o.getObstacle();
            obs.deactivatePhysics( 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.
	 */
	private boolean inBounds(Obstacle obj) {
		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;
	}

	/**
	 * Draws the level to the given game canvas
	 *
	 * If debug mode is true, it will outline all physics bodies as wireframes. Otherwise
	 * it will only draw the sprite representations.
	 *
	 * @param canvas	the drawing context
	 */
	public void draw(SpriteBatch batch) {
     // Draw the meshes (images)
        for(GameObject obj : objects) {
            obj.draw(batch);
        }

        if (debug) {
            // Draw the outlines
            for (GameObject obj : objects) {
                obj.drawDebug( batch );
            }
        }
	}
}
