/*
 * GameScene.java
 *
 * The LibGDX scene class works a lot like the notion of a scene in other game
 * engines. It encapsulates the logic of a single player mode.  This particular
 * scene is the core class to our game.
 *
 * Based on original GameX Ship Demo by Rama C. Hoetzlein, 2002
 *
 * @author: Walker M. White
 * @date: 1/10/25
 */
package edu.cornell.cis3152.shipdemo;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.math.*;

import com.badlogic.gdx.utils.ScreenUtils;
import edu.cornell.gdiac.assets.AssetDirectory;
import edu.cornell.gdiac.audio.SoundEffect;
import edu.cornell.gdiac.graphics.SpriteBatch;
import edu.cornell.gdiac.graphics.SpriteSheet;

import com.badlogic.gdx.audio.*;
import com.badlogic.gdx.graphics.*;
import edu.cornell.gdiac.util.ScreenListener;

/**
 * The primary controller class for the game.
 *
 * While GDXRoot is the root class, it delegates all of the work to the player
 * mode classes (e.g. scenes). This is the player mode class for running the
 * game. In initializes all of the other classes in the game and hooks them
 * together.  It also provides the basic game loop (update-draw).
 */
public class GameScene implements Screen {
    /** The drawing camera for this scene (VIEW CLASS) */
    private OrthographicCamera camera;
    /** The 2d graphics pipeline (VIEW CLASS) */
    private SpriteBatch batch;

    /** Listener that will update the player mode when we are done */
    private ScreenListener listener;

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

    // Subcontrollers
    /** Read input for blue player from keyboard or game pad (CONTROLLER CLASS) */
    protected InputController blueController;
    /** Read input for red player from keyboard or game pad (CONTROLLER CLASS) */
    protected InputController redController;
    /** Handle collision and physics (CONTROLLER CLASS) */
    protected CollisionController physicsController;

    // Game assets
	/** The background image for the battle */
	private Texture background;
	/** The weapon fire sound for the blue player */
	private Sound blueSound;
	/** The weapon fire sound for the red player */
	private Sound redSound;

	/** Location and animation information for blue ship (MODEL CLASS) */
	protected Ship shipBlue;
	/** Location and animation information for red ship (MODEL CLASS) */
	protected Ship shipRed;
	/** Shared memory pool for photons. (MODEL CLASS) */
	protected PhotonQueue photons;

	/** The bounds to enforce the playing region */
	private Rectangle bounds;

    /** Whether this scene is still active */
    private boolean active;

	/**
	 * Creates a new game with a playing field of the given size.
	 *
	 * This constructor initializes the models and controllers for the game. The
	 * scene needs the asset directory and the graphics pipeline created by the
     * root class
	 *
	 * @param assets	The asset directory containing all the loaded assets
     * @param batch     The sprite batch to draw to
	 */
	public GameScene(AssetDirectory assets, SpriteBatch batch) {
        this.batch = batch;
        resize( Gdx.graphics.getWidth(),Gdx.graphics.getHeight());

		// Extract the assets from the asset directory.  All images are textures.
		background = assets.getEntry("background", Texture.class );

		// Initialize the photons.
		photons = new PhotonQueue();
		photons.setTexture(assets.getEntry( "photon", Texture.class ));

		// Load the sounds.  We need to use the subclass SoundBuffer because of our changes to audio.
		blueSound = assets.getEntry( "laser",  SoundEffect.class);
		redSound  = assets.getEntry( "fusion", SoundEffect.class);

        // Get the ship assets
        // NOTE: SpriteSheets are stateful. You cannot reuse the asset. Must copy it.
        SpriteSheet shipSprite = assets.getEntry( "ship.animation", SpriteSheet.class );
        Texture targetTexture = assets.getEntry( "target", Texture.class );

        // Create the two ships and place them across from each other.
        // RED PLAYER
		shipRed  = new Ship(width*(1.0f / 3.0f), height*(1.0f / 2.0f), 0);
		shipRed.setFilmStrip(new SpriteSheet(shipSprite));
		shipRed.setTargetTexture(targetTexture);
		shipRed.setColor(new Color(1.0f, 0.25f, 0.25f, 1.0f));  // Red, but makes texture easier to see

        // BLUE PLAYER
		shipBlue = new Ship(width*(2.0f / 3.0f), height*(1.0f / 2.0f), 180);
		shipBlue.setFilmStrip(new SpriteSheet(shipSprite));
		shipBlue.setTargetTexture(targetTexture);
		shipBlue.setColor(new Color(0.5f, 0.5f, 1.0f, 1.0f));   // Blue, but makes texture easier to see

		// Create the input controllers.
		redController  = new InputController(1);
		blueController = new InputController(0);
        physicsController = new CollisionController();
	}

	/**
	 * Reads user input, calculates physics, and updates the models.
	 *
	 * This method is HALF of the basic game loop. Every graphics frame calls
	 * the method update() and the method draw(). The method update() contains
	 * all the calculations for updating the world, such as checking for
	 * collisions, gathering input, and playing audio. It should not contain
	 * any calls for drawing to the screen.
     *
     * @param dt    The number of seconds since the last frame
	 */
	public void update(float dt) {
		// Read the keyboard for each controller.
		redController.sync();
		blueController.sync();

		// Move the photons forward, and add new ones if necessary.
		//photons.move (width,height);
		if (redController.didPressFire() && firePhoton(shipRed,photons)) {
            redSound.play();
		}
		if (blueController.didPressFire() && firePhoton(shipBlue,photons)) {
			blueSound.play();
		}

		// Move the ships forward (ignoring collisions)
		shipRed.move(redController.getForward(),   redController.getTurn());
		shipBlue.move(blueController.getForward(), blueController.getTurn());
		photons.move(bounds);

		// Change the target position.
		shipRed.acquireTarget(shipBlue);
		shipBlue.acquireTarget(shipRed);

		// This call handles BOTH ships.
		physicsController.checkForCollision(shipBlue, shipRed);
		physicsController.checkInBounds(shipBlue, bounds);
		physicsController.checkInBounds(shipRed, bounds);
	}

	/**
	 * Draw the game scene
	 *
	 * There should be no code in this method that alters the game state.  All
	 * assignments should be to local variables or cache fields only.
     *
     * @param dt    The number of seconds since the last frame
	 */
	public void draw(float dt) {
        // This erases the entire screen from the previous animation frame
        ScreenUtils.clear( Color.BLACK );

        // Drawing is broken into PASSES
        // We have to call begin to start the pass
        batch.begin(camera);
        batch.setBlendMode(SpriteBatch.BlendMode.ALPHA_BLEND);

        // Drawing happens back to front. Start with the background
        // Draw it to fill the entire screen
        batch.draw( background, 0, 0, width, height );

		// First drawing pass (ships + shadows)
		shipBlue.drawShip(batch);		// Draw Red and Blue ships
		shipRed.drawShip(batch);

		// Second drawing pass (photons)
        batch.setBlendMode( SpriteBatch.BlendMode.ADDITIVE );
		shipBlue.drawTarget(batch);  // Draw target
		shipRed.drawTarget(batch);   // Draw target
		photons.draw(batch);         // Draw Photons

        // We must end the drawing pass when drawing is complete
        batch.end();
	}

	/**
	 * Dispose of all (non-static) resources allocated to this mode.
	 */
	public void dispose() {
		// Garbage collection here is sufficient.  Nothing to do
	}

	/**
 	 * Fires a photon from the ship, adding it to the PhotonQueue.
 	 *
 	 * This is not inside either PhotonQueue or Ship because it is a relationship
 	 * between to objects. As we will see in class, we do not want to code binary
 	 * relationships that way (because it increases dependencies).
 	 *
 	 * @param ship  	Ship firing the photon
 	 * @param photons 	PhotonQueue for allocation
 	 */
	private boolean firePhoton(Ship ship, PhotonQueue photons) {
		// Only process if enough time has passed since last.
		if (ship.canFireWeapon()) {
			photons.addPhoton(ship.getPosition(),ship.getVelocity(),ship.getAngle());
			ship.reloadWeapon();
			return true;
		}
		return false;
	}

    /**
     * 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(). At the very least, it is important to
     * update the camera. If you have a non-trivial graphics pipeline (anything
     * other than a SpriteBatch), you may need to make changes there as well.
     *
     * @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 (bounds == null) {
            bounds = new Rectangle(0,0,width,height);
        } else {
            bounds.set( 0, 0, width, height );
        }

        if (camera == null) {
            camera = new OrthographicCamera(width,height);
        } else {
            camera.setToOrtho( false, width, height  );
        }
    }

    /**
     * Called when the Screen should render itself.
     *
     * We defer to the other methods update() and draw(). However, it is VERY
     * important that we only quit AFTER a draw.
     *
     * @param delta Number of seconds since last animation frame
     */
    public void render(float delta) {
        if (active) {
            update(delta);
            draw(delta);

            if (listener != null) {
                boolean exit = redController.didQuitGame();
                exit = exit || blueController.didQuitGame();
                if (exit) {
                    listener.exitScreen(this, 0);
                }
            }
        }
    }

    /**
     * Called when the Screen is paused.
     *
     * This is usually when it's not active or visible on screen. An Application is
     * also paused before it is destroyed.
     */
    public void pause() {
        // TODO Auto-generated method stub
    }

    /**
     * Called when the Screen is resumed from a paused state.
     *
     * This is usually when it regains focus.
     */
    public void resume() {
        // TODO Auto-generated method stub
    }

    /**
     * Called when this screen becomes the current screen for a Game.
     */
    public void show() {
        // Useless if called in outside animation loop
        active = true;
    }

    /**
     * Called when this screen is no longer the current screen for a Game.
     */
    public void hide() {
        // Useless if called in outside animation loop
        active = false;
    }

    /**
     * Sets the ScreenListener for this scene
     *
     * The ScreenListener will respond to requests to quit.
     */
    public void setScreenListener(ScreenListener listener) {
        this.listener = listener;
    }
}
